@camstack/addon-post-analysis 1.2.654 → 1.2.655

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.
@@ -1,4 +1,4 @@
1
- import { $ as MediaFileKindEnum, $i as BaseAddon, $n as cloneAlarmSettings, $r as paintHex, $t as OpsLogEntrySchema, A as DeclaredDevices, Aa as unknown, Ai as storageOccupancyCapability, An as TrackSideSchema, Ar as isAudioClassName, At as NcAlarmSensorsInputSchema, B as FULL_IMAGE_BBOX, Bi as systemEventSummarySplitOf, Bn as applyModeDelaysEdits, Br as isRootSuppliableStepId, Bt as NcRuleUpdateInputSchema, C as ClearTrackRelationResultSchema, Ca as partialRecord, Ci as ruleGroupingApplies, Cn as TimelapseRuleInputSchema, Cr as failureContributionCapability, Ct as NC_SNOOZE_MAX_MINUTES, D as DEFAULT_TIMELAPSE_PREVIEW_TEXT, Da as templateLiteral, Di as sensorRaisedOf, Dn as TrackPuritySchema, Dr as isAlertEpisodeOpen, Dt as NC_TEMPLATE_VARS, E as DEFAULT_EVENT_COLOR, Ea as string, Ei as secureActuationFor, En as TrackPlacementSchema, Er as isAlarmControllableCap, Et as NC_TAXONOMY, F as EVENT_DENSITY_BATCH_MAX, Fi as syncActiveAlarmProfileSettings, Fn as alarmPanelCapability, Fr as isEventOwnerType, Ft as NcRuleActionSchema, G as ListTrackRelationsResultSchema, Gi as withSystemEventClasses, Gn as audioMetricsCapability, Gr as kebabToCamel, Gt as NcSnoozeSuppressedSchema, H as LABEL_SUPPLY_MACRO, Hi as templateVarsFor, Hn as applySecureOnArmEdit, Hr as isSystemDelivery, Ht as NcSharedSequenceSchema, I as EVENT_KIND_BY_CAP, Ii as systemEventDeliveryProblems, In as alertEpisodeId, Ir as isKnownTemplateVar, It as NcRuleInputSchema, J as MAX_BIRTH_DECISION_RECORDS, Ji as canonicalHash, Jn as buildSubjectPath, Jr as memoizePinnedFfmpeg, Jt as NcTaxonomySchema, K as LockStateSchema, Ki as zoneAnalyticsCapability, Kn as audioModeOf, Kr as linkedStepOf, Kt as NcStateSummarySectionIdSchema, L as EVENT_OWNER_TYPES, Li as systemEventFamilyProblems, Ln as alertEpisodeKind, Lr as isLabelSupplyStepId, Lt as NcRulePatchSchema, M as EMBEDDING_MODEL_FAILED, Mi as subjectPathSampleFromBox, Mn as activateAlarmProfile, Mr as isDetectionMacroClass, Mt as NcAlarmSettingsPatchSchema, N as EMBEDDING_REQUIREMENTS_FAILED, Ni as subjectPathSvgPoints, Nn as addonWidgetsSourceCapability, Nr as isDrawableSubjectPath, Nt as NcConditionDescriptorSchema, O as DETECTION_MACRO_CLASSES, Oa as tuple, Oi as servedTemplateVars, On as TrackRelationKindSchema, Or as isAlertEpisodeSeverity, Ot as NcAlarmConfigSchema, P as EMBEDDING_REQUIREMENTS_UNAVAILABLE, Pi as summarizeReview, Pn as alarmDeviceSecureOnArm, Pr as isEventKindLabelKey, Pt as NcConditionsSchema, Q as MOTION_CLOSE_AFTER_MS, Qi as errMsg, Qn as classifyRetrainReview, Qr as occupancyScope, Qt as OPS_LOG_DEFAULT_LIMIT, R as EXPORT_DENSE_MAX_RANGES, Ri as systemEventFilterApplies, Rn as alertEpisodePayload, Rr as isMediaOwnerType, Rt as NcRuleSchema, S as ClearTrackRelationInputSchema, Sa as object, Si as ruleDeviceCompatCapQueries, Sn as TRACK_RELATIONS_DEFAULT_LIMIT, Sr as faceGalleryCapability, St as NC_SENSOR_TRANSITION_KINDS, T as DEFAULT_DETAIL_CROP_CONVENTION, Ta as strictObject, Ti as sceneMonitorCapability, Tn as TimelapseRuleSchema, Tr as gifFromMp4Args, Tt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, U as LabelAttributionSchema, Ui as vectorDimFromBase64, Un as armRemovalReconcile, Ur as isSystemEventSummaryRule, Ut as NcSnoozeInputSchema, V as FailureCounters, Vi as templateCarriesSummary, Vn as applyNonBlockingEdit, Vr as isScheduleActive, Vt as NcScheduleSchema, W as ListTrackRelationsInputSchema, Wi as viewerLinkBaseOf, Wn as assertTimelapseCadences, Wr as isVectorIndexDimConflict, Wt as NcSnoozeSchema, X as MAX_TRACK_PURITIES, Xi as isSourceCap, Xn as bundledCamstack, Xr as normalizeNcMediaPolicy, Xt as NcTemplatePreviewSchema, Y as MAX_BIRTH_LATENCY_PROXY_MS, Yi as evaluateSensorEdge, Yn as buildViewerLink, Yr as ncMediaPolicyError, Yt as NcTemplatePreviewInputSchema, Z as MAX_TRACK_RELATIONS, Zi as DeviceType, Zn as calendarEventLocalSpan, Zr as notificationRulesCapability, Zt as NcTemplateVarDescriptorSchema, _ as BIRTH_DECISION_DEFAULT_LIMIT, _a as boolean, _n as SceneMonitorSchema, _t as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, a as CLIP_EMBEDDING_STEP_ID, aa as createEvent, ai as plateGalleryCapability, an as RetrainDismissedSchema, ar as deleteAlarmProfile, at as NC_ALARM_NOTIFY_FIELDS, b as BucketAccumulator, ba as looseObject, bi as ruleAutoTriggersAlarm, bn as SetTrackRelationInputSchema, br as eventKindLabel, bt as NC_GROUPING_MAX_SEC, c as readClusterModelPinStrict, ca as defineRecordKinds, ci as readMotionTriggeredBy, cn as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, cr as deriveRecordingMode, ct as NC_AUDIO_CONFIRM_HITS_DEFAULT, d as APPEARANCE_BLOCKS, da as nodePin, di as resolveClipSearchMinScore, dn as SCENE_DEFAULT_CHECK_INTERVAL_SEC, dr as deviceAttentionKind, dt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, ea as CAP_ADVANCE_FIELDS, ei as paletteResultSchema, en as PackageLlmVerdictSchema, er as consumableFormOf, et as MotionTriggeredBySchema, f as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, fi as resolveDeliveryParams, fn as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, fr as deviceRemovalHookCapability, ft as NC_CLIP_RETENTION_MAX_SEC, g as ArchivedDebugNoteSchema, ga as array, gi as resolveNcMediaImage, gn as SCENE_RESET_RECAPTURES, gt as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, h as AlarmArmModeSchema, ha as _enum, hi as resolveModeNotifyPolicy, hn as SCENE_DIVERGED, hr as embeddingRefusalOf, ht as NC_CONFIRM_DEFAULT_TIMEOUT_MS, ia as RECORD_QUERY_MAX, ii as pipelineAnalyticsCapability, in as RebuildObjectEmbeddingsRefusalSchema, ir as createAlarmProfile, it as NC_ALARM_DELAY_MAX_SEC, j as DetectionSourceSchema, ja as EventCategory, ji as subKindsOf, jn as TrackSourceSchema, jr as isDefaultAlarmProfileId, jt as NcAlarmSensorsSchema, k as DETECTION_PIPELINE_CAP_NAME, ka as union, ki as startMoverUnderClearance, kn as TrackRelationSchema, kr as isAppLinkBase, kt as NcAlarmPostTriggeredActionSchema, l as resolveClusterModelPin, la as hydrateSchema, li as readTimelapseGeneratedAt, ln as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, lr as describeSystemEventDeliveryProblem, lt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, m as AUDIO_MACRO_LABELS, ma as ZodObject, mi as resolveModeDelays, mn as SCENE_DEFAULT_UNCOVERED_POLICY, mt as NC_CONFIRM_DEFAULT_MAX_IMAGE_PX, na as InvalidRecordError, nn as RECORDING_EXPORT_MAX_READ_BYTES, nr as coreBlockIdFromAddonId, nt as NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC, o as CLUSTER_MODEL_OWNER_ADDON_ID, oa as customAction, oi as projectSubjectPath, on as RetrainOriginSchema, or as deriveDetailCropRect, ot as NC_ALARM_SYSTEM_EVENT_KINDS, p as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, pa as sleep$1, pi as resolveLocationMode, pn as SCENE_DEFAULT_QUIET_SECONDS, pt as NC_CONDITION_CATALOG, q as MAX_ARCHIVED_DEBUG_NOTES, qi as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, qn as buildEventKindDescriptor, qr as mayWriteToLocation, qt as NcSystemEventKindSchema, ra as RECORD_MAX_DATA_BYTES, ri as pickFaceRescoreIntervalMinutes, rn as RECORDING_SOURCE_CAMSTACK_ADDON, rr as cosineSimilarity$1, rt as NC_ALARM_DELAY_FIELDS, s as FACE_EMBEDDING_STEP_ID, sa as defineCustomActions, si as readDeviceStateFrom, sn as RetrainStatusSchema, sr as derivePostTriggeredAction, st as NC_ALARM_TRIGGERED_MAX_SEC, t as BUILTIN_CLIP_MODELS, ta as CamProfileSchema, ti as parseAlertEpisodePayload, tr as consumableLowPercentOf, tt as NC_ALARM_DEFAULT_PROFILE_ID, u as APPEARANCE_BIN_COUNT, ua as isDeviceScopedCap, ui as renameAlarmProfile, un as SCENE_DEFAULT_ANCHOR_THRESHOLD, ur as describeSystemEventFamilyProblem, ut as NC_CLIP_POST_ROLL_DEFAULT_SEC, v as BaseDevice, va as discriminatedUnion, vi as retrainRevisionConflictMessage, vn as SetTrackPurityInputSchema, vr as encodeVectorBase64, vt as NC_DEFAULT_SNOOZE_MINUTES, w as CoverStateSchema, wa as record, wi as ruleKindOf, wn as TimelapseRulePatchSchema, wr as gifDeriveSpeed, wt as NC_STATE_SUMMARY_SECTION_IDS, x as COCO_TO_MACRO, xa as number, xi as ruleBelongsToAlarm$1, xn as TIMELAPSE_DENSE_FLOOR_SEC, xr as evictionPolicyOfLocation, xt as NC_GROUPING_MIN_SEC, y as BirthDecisionRecordSchema, ya as literal, yi as rootSupplyServes, yn as SetTrackPurityResultSchema, yr as ensureAlarmProfiles, yt as NC_GROUPING_DEFAULT_SEC, z as FIRST_LEVEL_MACRO_CLASSES, zi as systemEventSummaryScheduleOf, zn as applyBlockingEdit, zr as isRetiredClearKey, zt as NcRuleTargetSchema } from "../clip-model-registry-BX6j2lnF.mjs";
1
+ import { $ as MAX_TRACK_RELATIONS, $i as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, $n as bundledCamstack, $r as normalizeNcAction, $t as NcTemplateVarDescriptorSchema, A as DETECTION_MACRO_CLASSES, Aa as record, Ai as sceneMonitorCapability, An as TrackPuritySchema, Ar as isAlertEpisodeOpen, At as NcAlarmConfigSchema, B as EXPORT_DENSE_MAX_RANGES, Bi as sunTimes, Bn as alertEpisodeKind, Br as isLabelSupplyStepId, Bt as NcRuleSchema, C as COCO_TO_MACRO, Ca as boolean, Ci as retrainRevisionConflictMessage, Cn as SetTrackRelationInputSchema, Cr as eventKindLabel, Ct as NC_GROUPING_MIN_SEC, D as DEFAULT_DETAIL_CROP_CONVENTION, Da as number, Di as ruleDeviceCompatCapQueries, Dn as TimelapseRulePatchSchema, Dr as gifDeriveSpeed, Dt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, E as CoverStateSchema, Ea as looseObject, Ei as ruleBelongsToAlarm$1, En as TimelapseRuleInputSchema, Er as failureContributionCapability, Et as NC_STATE_SUMMARY_SECTION_IDS, F as EMBEDDING_REQUIREMENTS_FAILED, Fa as union, Fi as storageOccupancyCapability, Fn as activateAlarmProfile, Fr as isDetectionMacroClass, Ft as NcConditionDescriptorSchema, G as LabelAttributionSchema, Gi as systemEventSummaryScheduleOf, Gn as applySecureOnArmEdit, Gr as isSystemDelivery, Gt as NcSnoozeInputSchema, H as FULL_IMAGE_BBOX, Hi as systemEventDeliveryProblems, Hn as applyBlockingEdit, Hr as isRetiredClearKey, Ht as NcRuleUpdateInputSchema, I as EMBEDDING_REQUIREMENTS_UNAVAILABLE, Ia as unknown, Ii as subKindsOf, In as addonWidgetsSourceCapability, Ir as isDrawableSubjectPath, It as NcConditionsSchema, J as LockStateSchema, Ji as templateVarsFor, Jn as audioMetricsCapability, Jr as kebabToCamel, Jt as NcStateSummarySectionIdSchema, K as ListTrackRelationsInputSchema, Ki as systemEventSummarySplitOf, Kn as armRemovalReconcile, Kr as isSystemEventSummaryRule, Kt as NcSnoozeSchema, L as EVENT_DENSITY_BATCH_MAX, La as EventCategory, Li as subjectPathSampleFromBox, Ln as alarmDeviceSecureOnArm, Lr as isEventKindLabelKey, Lt as NcRuleActionSchema, M as DeclaredDevices, Ma as string, Mi as sensorRaisedOf, Mn as TrackRelationSchema, Mr as isAppLinkBase, Mt as NcAlarmSensorsInputSchema, N as DetectionSourceSchema, Na as templateLiteral, Ni as servedTemplateVars, Nn as TrackSideSchema, Nr as isAudioClassName, Nt as NcAlarmSensorsSchema, O as DEFAULT_EVENT_COLOR, Oa as object, Oi as ruleGroupingApplies, On as TimelapseRuleSchema, Or as gifFromMp4Args, Ot as NC_TAXONOMY, P as EMBEDDING_MODEL_FAILED, Pa as tuple, Pi as startMoverUnderClearance, Pn as TrackSourceSchema, Pr as isDefaultAlarmProfileId, Pt as NcAlarmSettingsPatchSchema, Q as MAX_TRACK_PURITIES, Qi as zoneAnalyticsCapability, Qn as buildViewerLink, Qr as ncMediaPolicyError, Qt as NcTemplatePreviewSchema, R as EVENT_KIND_BY_CAP, Ri as subjectPathSvgPoints, Rn as alarmPanelCapability, Rr as isEventOwnerType, Rt as NcRuleInputSchema, S as BucketAccumulator, Sa as array, Sn as SetTrackPurityResultSchema, Sr as ensureAlarmProfiles, St as NC_GROUPING_MAX_SEC, T as ClearTrackRelationResultSchema, Ta as literal, Ti as ruleAutoTriggersAlarm, Tn as TRACK_RELATIONS_DEFAULT_LIMIT, Tr as faceGalleryCapability, Tt as NC_SNOOZE_MAX_MINUTES, U as FailureCounters, Ui as systemEventFamilyProblems, Un as applyModeDelaysEdits, Ur as isRootSuppliableStepId, Ut as NcScheduleSchema, V as FIRST_LEVEL_MACRO_CLASSES, Vi as syncActiveAlarmProfileSettings, Vn as alertEpisodePayload, Vr as isMediaOwnerType, Vt as NcRuleTargetSchema, W as LABEL_SUPPLY_MACRO, Wi as systemEventFilterApplies, Wn as applyNonBlockingEdit, Wr as isScheduleActive, Wt as NcSharedSequenceSchema, X as MAX_BIRTH_DECISION_RECORDS, Xi as viewerLinkBaseOf, Xn as buildEventKindDescriptor, Xr as mayWriteToLocation, Xt as NcTaxonomySchema, Y as MAX_ARCHIVED_DEBUG_NOTES, Yi as vectorDimFromBase64, Yn as audioModeOf, Yr as linkedStepOf, Yt as NcSystemEventKindSchema, Z as MAX_BIRTH_LATENCY_PROXY_MS, Zi as withSystemEventClasses, Zn as buildSubjectPath, Zr as memoizePinnedFfmpeg, Zt as NcTemplatePreviewInputSchema, _ as AlarmArmModeSchema, _a as nodePin, _i as resolveDeliveryParams, _n as SCENE_DEFAULT_UNCOVERED_POLICY, _t as NC_CONFIRM_DEFAULT_TIMEOUT_MS, a as CLIP_EMBEDDING_STEP_ID, aa as BaseAddon, ai as paletteResultSchema, an as RECORDING_SOURCE_CAMSTACK_ADDON, ar as coreBlockIdFromAddonId, at as NC_ALARM_DELAY_FIELDS, b as BaseDevice, ba as ZodObject, bi as resolveModeNotifyPolicy, bn as SceneMonitorSchema, bt as NC_DEFAULT_SNOOZE_MINUTES, c as readClusterModelPinStrict, ca as InvalidRecordError, ci as pickFaceRescoreIntervalMinutes, cn as RetrainOriginSchema, cr as deleteAlarmProfile, ct as NC_ALARM_SYSTEM_EVENT_KINDS, d as APPEARANCE_BIN_COUNT, da as createEvent, di as projectSubjectPath, dn as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, dr as deriveRecordingMode, dt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, ea as canonicalHash, ei as normalizeNcCapTarget, en as OPS_LOG_DEFAULT_LIMIT, er as calendarEventLocalSpan, et as MOTION_CLOSE_AFTER_MS, f as APPEARANCE_BLOCKS, fa as customAction, fi as readDeviceStateFrom, fn as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, fr as describeSystemEventDeliveryProblem, ft as NC_CLIP_POST_ROLL_DEFAULT_SEC, g as ActionTargetResolver, ga as isDeviceScopedCap, gi as resolveClipSearchMinScore, gn as SCENE_DEFAULT_QUIET_SECONDS, gt as NC_CONFIRM_DEFAULT_MAX_IMAGE_PX, h as AUDIO_MACRO_LABELS, ha as hydrateSchema, hi as renameAlarmProfile, hn as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, hr as deviceRemovalHookCapability, ht as NC_CONDITION_CATALOG, ia as errMsg, ii as paintHex, in as RECORDING_EXPORT_MAX_READ_BYTES, ir as consumableLowPercentOf, it as NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC, j as DETECTION_PIPELINE_CAP_NAME, ja as strictObject, ji as secureActuationFor, jn as TrackRelationKindSchema, jr as isAlertEpisodeSeverity, jt as NcAlarmPostTriggeredActionSchema, k as DEFAULT_TIMELAPSE_PREVIEW_TEXT, ka as partialRecord, ki as ruleKindOf, kn as TrackPlacementSchema, kr as isAlarmControllableCap, kt as NC_TEMPLATE_VARS, l as resolveClusterModelPin, la as RECORD_MAX_DATA_BYTES, li as pipelineAnalyticsCapability, ln as RetrainStatusSchema, lr as deriveDetailCropRect, lt as NC_ALARM_TRIGGERED_MAX_SEC, m as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, ma as defineRecordKinds, mi as readTimelapseGeneratedAt, mn as SCENE_DEFAULT_CHECK_INTERVAL_SEC, mr as deviceAttentionKind, mt as NC_CLIP_RETENTION_MAX_SEC, na as isSourceCap, ni as notificationRulesCapability, nn as PackageLlmVerdictSchema, nr as cloneAlarmSettings, nt as MotionTriggeredBySchema, o as CLUSTER_MODEL_OWNER_ADDON_ID, oa as CAP_ADVANCE_FIELDS, oi as parseAlertEpisodePayload, on as RebuildObjectEmbeddingsRefusalSchema, or as cosineSimilarity$1, ot as NC_ALARM_DELAY_MAX_SEC, p as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, pa as defineCustomActions, pi as readMotionTriggeredBy, pn as SCENE_DEFAULT_ANCHOR_THRESHOLD, pr as describeSystemEventFamilyProblem, pt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, q as ListTrackRelationsResultSchema, qi as templateCarriesSummary, qn as assertTimelapseCadences, qr as isVectorIndexDimConflict, qt as NcSnoozeSuppressedSchema, ra as DeviceType, ri as occupancyScope, rr as consumableFormOf, rt as NC_ALARM_DEFAULT_PROFILE_ID, s as FACE_EMBEDDING_STEP_ID, sa as CamProfileSchema, sn as RetrainDismissedSchema, sr as createAlarmProfile, st as NC_ALARM_NOTIFY_FIELDS, t as BUILTIN_CLIP_MODELS, ta as evaluateSensorEdge, ti as normalizeNcMediaPolicy, tn as OpsLogEntrySchema, tr as classifyRetrainReview, tt as MediaFileKindEnum, u as ACTION_TARGET_INVALIDATION_EVENTS, ua as RECORD_QUERY_MAX, ui as plateGalleryCapability, un as RetrainVerdictSubjectSchema, ur as derivePostTriggeredAction, ut as NC_AUDIO_CONFIRM_HITS_DEFAULT, v as ArchivedDebugNoteSchema, vi as resolveLocationMode, vn as SCENE_DIVERGED, vr as embeddingRefusalOf, vt as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, w as ClearTrackRelationInputSchema, wa as discriminatedUnion, wi as rootSupplyServes, wn as TIMELAPSE_DENSE_FLOOR_SEC, wr as evictionPolicyOfLocation, wt as NC_SENSOR_TRANSITION_KINDS, x as BirthDecisionRecordSchema, xa as _enum, xi as resolveNcMediaImage, xn as SetTrackPurityInputSchema, xr as encodeVectorBase64, xt as NC_GROUPING_DEFAULT_SEC, y as BIRTH_DECISION_DEFAULT_LIMIT, ya as sleep$1, yi as resolveModeDelays, yn as SCENE_RESET_RECAPTURES, yt as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, z as EVENT_OWNER_TYPES, zi as summarizeReview, zn as alertEpisodeId, zr as isKnownTemplateVar, zt as NcRulePatchSchema } from "../clip-model-registry-_b3KHYFm.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";
@@ -888,7 +888,9 @@ function describeGrant(grant) {
888
888
  return grant.action === "disarm" ? "Alarm disarmed" : `Alarm armed${grant.mode !== void 0 ? ` (${grant.mode})` : ""}`;
889
889
  case "addon-update": return `${grant.packages.length} addon update(s) on ${grant.nodeId}`;
890
890
  case "server-update": return `Server update on ${grant.nodeId}`;
891
- case "detection-verdict": return grant.verdict === "correct" ? "Marked correct — kept for retraining" : grant.suggestedClass === void 0 ? "Marked wrong — kept for retraining" : `Marked wrong, it was a ${grant.suggestedClass} — kept for retraining`;
891
+ case "detection-verdict":
892
+ if (grant.verdict === "wrong-label") return "Marked label wrong, class right — kept for retraining";
893
+ return grant.verdict === "correct" ? "Marked correct — kept for retraining" : grant.suggestedClass === void 0 ? "Marked wrong — kept for retraining" : `Marked wrong, it was a ${grant.suggestedClass} — kept for retraining`;
892
894
  }
893
895
  }
894
896
  /** The audit line's `meta`, per family. A snooze that logged a `sequence` it
@@ -962,7 +964,7 @@ var FAILURE_COPY = {
962
964
  /** The question a link-only channel lands on, answered by a POST form. */
963
965
  function confirmPage(url, grant) {
964
966
  const action = `${url.pathname}?exp=${encodeURIComponent(url.searchParams.get("exp") ?? "")}&sig=${encodeURIComponent(url.searchParams.get("sig") ?? "")}&confirm=1`;
965
- return `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Confirm</title><style>body{font:16px/1.5 system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#111;color:#eee}main{max-width:24rem;padding:2rem;text-align:center}h1{font-size:1.25rem;margin:0 0 1rem}button{font:inherit;padding:.6rem 1.4rem;border-radius:.5rem;border:0;background:#3b82f6;color:#fff}</style><main><h1>Confirm</h1><p>${grant.verdict === "correct" ? "Mark this detection as correct?" : grant.suggestedClass === void 0 ? "Mark this detection as wrong?" : `Mark this detection as wrong, it was a ${grant.suggestedClass}?`}</p><form method="post" action="${action}"><button type="submit">Confirm</button></form></main>`;
967
+ return `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Confirm</title><style>body{font:16px/1.5 system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#111;color:#eee}main{max-width:24rem;padding:2rem;text-align:center}h1{font-size:1.25rem;margin:0 0 1rem}button{font:inherit;padding:.6rem 1.4rem;border-radius:.5rem;border:0;background:#3b82f6;color:#fff}</style><main><h1>Confirm</h1><p>${grant.verdict === "wrong-label" ? "Mark the label of this detection as wrong (the class is right)?" : grant.verdict === "correct" ? "Mark this detection as correct?" : grant.suggestedClass === void 0 ? "Mark this detection as wrong?" : `Mark this detection as wrong, it was a ${grant.suggestedClass}?`}</p><form method="post" action="${action}"><button type="submit">Confirm</button></form></main>`;
966
968
  }
967
969
  function page(title, detail) {
968
970
  return `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${title}</title><style>body{font:16px/1.5 system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#111;color:#eee}main{max-width:24rem;padding:2rem;text-align:center}h1{font-size:1.25rem;margin:0 0 .5rem}p{margin:0;color:#aaa}</style><main><h1>${title}</h1><p>${detail}</p></main>`;
@@ -2945,7 +2947,6 @@ var NIGHT = "night";
2945
2947
  var IR = "ir";
2946
2948
  var DAWN = "dawn";
2947
2949
  var DUSK = "dusk";
2948
- var DEG = Math.PI / 180;
2949
2950
  /**
2950
2951
  * Mean channel spread below which a frame is judged infrared.
2951
2952
  *
@@ -2967,38 +2968,6 @@ var IR_CHROMA_THRESHOLD = 6;
2967
2968
  * scene reports `unknown` — the honest answer.
2968
2969
  */
2969
2970
  var TWILIGHT_BAND_MS = 2400 * 1e3;
2970
- /** Julian day number for a JS Date (UTC). */
2971
- function toJulian(date) {
2972
- return date.getTime() / 864e5 + 2440587.5;
2973
- }
2974
- /** Julian day → epoch ms (UTC). */
2975
- function fromJulian(j) {
2976
- return (j - 2440587.5) * 864e5;
2977
- }
2978
- /**
2979
- * Sunrise/sunset (UTC epoch ms) for a location and date via the standard NOAA
2980
- * sunrise equation. Returns `null` for the polar edge cases (sun never
2981
- * rises/sets that day) so the caller can degrade to the clock split.
2982
- */
2983
- function sunTimes(lat, lng, date) {
2984
- const jDate = toJulian(date);
2985
- const jStar = Math.round(jDate - 2451545 + 8e-4) - lng / 360;
2986
- const m = (357.5291 + .98560028 * jStar) % 360;
2987
- const mRad = m * DEG;
2988
- const lambda = (m + (1.9148 * Math.sin(mRad) + .02 * Math.sin(2 * mRad) + 3e-4 * Math.sin(3 * mRad)) + 180 + 102.9372) % 360 * DEG;
2989
- const jTransit = 2451545 + jStar + .0053 * Math.sin(mRad) - .0069 * Math.sin(2 * lambda);
2990
- const declination = Math.asin(Math.sin(lambda) * Math.sin(23.44 * DEG));
2991
- const latRad = lat * DEG;
2992
- const cosOmega = (Math.sin(-.833 * DEG) - Math.sin(latRad) * Math.sin(declination)) / (Math.cos(latRad) * Math.cos(declination));
2993
- if (cosOmega > 1 || cosOmega < -1) return null;
2994
- const omega = Math.acos(cosOmega) / DEG;
2995
- const jRise = jTransit - omega / 360;
2996
- const jSet = jTransit + omega / 360;
2997
- return {
2998
- rise: fromJulian(jRise),
2999
- set: fromJulian(jSet)
3000
- };
3001
- }
3002
2971
  /** Coarse UTC clock split — day between 06:00 and 20:00 UTC. Deterministic
3003
2972
  * last resort when no coordinates let us compute real sun-times. */
3004
2973
  function clockCondition(now) {
@@ -9281,6 +9250,7 @@ var en_default = {
9281
9250
  "button.verdict.correct": "Correct",
9282
9251
  "button.verdict.wrong": "Wrong",
9283
9252
  "button.verdict.wrongAs": "Wrong: it is {{class}}",
9253
+ "button.verdict.wrongLabel": "Wrong label, class OK",
9284
9254
  "action.server-update.title": "Server update",
9285
9255
  "action.server-update.started": "Updating server on {{nodeId}}…",
9286
9256
  "action.server-update.restarting": "Server update staged on {{nodeId}} — the node is restarting. You will hear when it comes back.",
@@ -9686,6 +9656,7 @@ var it_default = {
9686
9656
  "button.verdict.correct": "Corretto",
9687
9657
  "button.verdict.wrong": "Sbagliato",
9688
9658
  "button.verdict.wrongAs": "Sbagliato: è {{class}}",
9659
+ "button.verdict.wrongLabel": "Etichetta sbagliata, classe giusta",
9689
9660
  "action.server-update.title": "Aggiornamento server",
9690
9661
  "action.server-update.started": "Aggiornamento server su {{nodeId}}…",
9691
9662
  "action.server-update.restarting": "Aggiornamento server predisposto su {{nodeId}} — il nodo si riavvia. Ti avviseremo quando torna.",
@@ -39035,6 +39006,11 @@ function rowIsDurableRetrainCopy(data) {
39035
39006
  * audit resolves ownership from INDEX rows, but a future filesystem-side tool
39036
39007
  * must be able to tell the two corpora apart by path alone. */
39037
39008
  var RETRAIN_PATH_PREFIX = "retrain";
39009
+ /** The stored verdict subject, validated; anything unreadable reads as absent. */
39010
+ function narrowVerdictSubject(value) {
39011
+ const parsed = RetrainVerdictSubjectSchema.safeParse(value);
39012
+ return parsed.success ? { verdictSubject: parsed.data } : {};
39013
+ }
39038
39014
  /** Ids per `IN (…)` page in {@link RetrainFrameStore.getMany}. */
39039
39015
  var GET_MANY_PAGE = 500;
39040
39016
  /**
@@ -39118,7 +39094,8 @@ var RetrainFrameStore = class {
39118
39094
  complete: data["complete"] === true || data["complete"] === 1,
39119
39095
  ...finiteNumber(data["epochMs"]) !== void 0 ? { epochMs: finiteNumber(data["epochMs"]) } : {},
39120
39096
  revision: finiteNumber(data["revision"]) ?? 0,
39121
- dismissed: narrowDismissed(data["dismissed"])
39097
+ dismissed: narrowDismissed(data["dismissed"]),
39098
+ ...narrowVerdictSubject(data["verdictSubject"])
39122
39099
  };
39123
39100
  }
39124
39101
  /** The copies with these ids, in the order given; unknown ids are skipped. */
@@ -39471,7 +39448,8 @@ var RetrainFrameStore = class {
39471
39448
  capturedAt: input.epochMs,
39472
39449
  ...chromaSpread !== void 0 ? { chromaSpread } : {},
39473
39450
  complete: false,
39474
- epochMs: input.epochMs
39451
+ epochMs: input.epochMs,
39452
+ ...input.verdictSubject !== void 0 ? { verdictSubject: input.verdictSubject } : {}
39475
39453
  };
39476
39454
  const path = framePath(frame);
39477
39455
  const persist = async () => {
@@ -39495,7 +39473,8 @@ var RetrainFrameStore = class {
39495
39473
  chromaSpread: frame.chromaSpread ?? null,
39496
39474
  complete: false,
39497
39475
  origin: "recording",
39498
- epochMs: input.epochMs
39476
+ epochMs: input.epochMs,
39477
+ ...input.verdictSubject !== void 0 ? { verdictSubject: input.verdictSubject } : {}
39499
39478
  });
39500
39479
  await this.ledger.transact([row, ...withRow]);
39501
39480
  };
@@ -39532,6 +39511,22 @@ var RetrainFrameStore = class {
39532
39511
  };
39533
39512
  }
39534
39513
  /**
39514
+ * Record the verdict subject on a still that already existed (the moment was
39515
+ * in the dataset before the operator tapped). The latest answer wins, like
39516
+ * the verdict ledger.
39517
+ *
39518
+ * @returns the updated frame, or `null` when no such copy exists.
39519
+ */
39520
+ async setVerdictSubject(frameId, verdictSubject) {
39521
+ const existing = await this.get(frameId);
39522
+ if (existing === null) return null;
39523
+ await this.ledger.update(frameId, { verdictSubject });
39524
+ return {
39525
+ ...existing,
39526
+ verdictSubject
39527
+ };
39528
+ }
39529
+ /**
39535
39530
  * The operator's "every subject in this frame is boxed" (D785 context: an
39536
39531
  * unboxed subject is a false positive to a detector evaluation). Only the
39537
39532
  * operator sets it; nothing derives it from the annotation count, because a
@@ -68420,6 +68415,7 @@ function wireSubscriptions(deps) {
68420
68415
  deps.onClusterTopology(nodes);
68421
68416
  }));
68422
68417
  for (const category of NC_SYSTEM_EVENT_CATEGORIES) unsubs.push(deps.eventBus.subscribe({ category }, (ev) => deps.onSystemEvent(ev)));
68418
+ for (const category of ACTION_TARGET_INVALIDATION_EVENTS) unsubs.push(deps.eventBus.subscribe({ category }, () => deps.onDeviceRegistryChange()));
68423
68419
  }
68424
68420
  unsubs.push(deps.eventBus.subscribe({ category: EventCategory.DeviceBindingsChanged }, (ev) => {
68425
68421
  deps.onBindingsChanged(ev.data);
@@ -68981,6 +68977,12 @@ async function composeEventKindsForDevices(deviceIds, composeOne, onError) {
68981
68977
  * nearest one is normally the same detection; the delta is reported so a
68982
68978
  * reviewer can see how far it was.
68983
68979
  *
68980
+ * 0. **The subject a notification verdict froze** (a recording still landed by
68981
+ * a verdict whose track was already gone). It is the one subject the
68982
+ * operator answered about, so it comes FIRST and is the own box; the
68983
+ * neighbours below stay secondary. Its box is normalised against the full
68984
+ * frame already, so it is used as is — never re-scaled.
68985
+ *
68984
68986
  * Every OTHER track alive on the camera at that instant contributes its own
68985
68987
  * nearest position the same way — a frame with three people is three boxes,
68986
68988
  * and a recording still (no track of its own) gets everything the pipeline
@@ -69035,6 +69037,18 @@ function toRetrainClassTiers(className, label) {
69035
69037
  label: kept
69036
69038
  } : { macroClass };
69037
69039
  }
69040
+ /** A verdict on the wire: the operator's class split into tiers once, here. */
69041
+ function toRetrainVerdict(input) {
69042
+ const tiers = input.suggestedClass !== void 0 ? toRetrainClassTiers(input.suggestedClass, void 0) : null;
69043
+ return {
69044
+ verdict: input.verdict,
69045
+ trackId: input.trackId,
69046
+ ...input.suggestedClass !== void 0 ? { suggestedClass: input.suggestedClass } : {},
69047
+ ...tiers !== null ? { suggestedMacroClass: tiers.macroClass } : {},
69048
+ ...tiers?.label !== void 0 ? { suggestedLabel: tiers.label } : {},
69049
+ ...input.at !== void 0 ? { at: input.at } : {}
69050
+ };
69051
+ }
69038
69052
  /** A pixel box as a clamped normalised one, or `null` when nothing is left. */
69039
69053
  function normalise(box, frameWidth, frameHeight) {
69040
69054
  if (frameWidth === void 0 || frameHeight === void 0) return null;
@@ -69092,6 +69106,22 @@ function boxFor(track, bbox, basis, deltaMs, score) {
69092
69106
  deltaMs
69093
69107
  };
69094
69108
  }
69109
+ /** The box a notification verdict froze, or `null` when the event carried none. */
69110
+ function boxOfVerdict(subject) {
69111
+ const box = subject.bbox;
69112
+ if (box === void 0) return null;
69113
+ const clamped = normalise(box, 1, 1);
69114
+ if (clamped === null) return null;
69115
+ const tiers = toRetrainClassTiers(subject.className, subject.label);
69116
+ return {
69117
+ bbox: clamped,
69118
+ macroClass: tiers.macroClass,
69119
+ ...tiers.label !== void 0 ? { label: tiers.label } : {},
69120
+ trackId: subject.trackId,
69121
+ basis: "verdict",
69122
+ deltaMs: 0
69123
+ };
69124
+ }
69095
69125
  /** The own track's box: the recorded subject box first, the nearest position second. */
69096
69126
  function ownBox(input, tolerance) {
69097
69127
  const track = input.ownTrack;
@@ -69114,10 +69144,13 @@ function pipelineBoxesForFrame(input) {
69114
69144
  const out = [];
69115
69145
  const own = ownBox(input, tolerance);
69116
69146
  if (own !== null) out.push(own);
69147
+ const verdictBox = input.verdictSubject !== void 0 ? boxOfVerdict(input.verdictSubject) : null;
69148
+ if (verdictBox !== null) out.push(verdictBox);
69117
69149
  const at = input.frame.capturedAt;
69118
69150
  if (at === void 0) return out;
69119
69151
  const ownId = input.frame.trackId;
69120
69152
  const seen = new Set(ownId !== void 0 ? [ownId] : []);
69153
+ if (input.verdictSubject !== void 0) seen.add(input.verdictSubject.trackId);
69121
69154
  for (const track of input.neighbours) {
69122
69155
  if (seen.has(track.trackId) || track.deviceId !== input.frame.deviceId) continue;
69123
69156
  seen.add(track.trackId);
@@ -82367,10 +82400,18 @@ function parseActions(raw) {
82367
82400
  for (const entry of raw) {
82368
82401
  const parsed = NcRuleActionSchema.safeParse(entry);
82369
82402
  if (!parsed.success) return null;
82370
- out.push(parsed.data);
82403
+ out.push(normalizeNcAction(parsed.data));
82371
82404
  }
82372
82405
  return out;
82373
82406
  }
82407
+ /** Does a stored row still carry a pre-D983 cap step (`deviceId`, no `target`)? */
82408
+ function hasLegacyTarget(raw) {
82409
+ if (!Array.isArray(raw)) return false;
82410
+ return raw.some((entry) => {
82411
+ const parsed = NcRuleActionSchema.safeParse(entry);
82412
+ return parsed.success && parsed.data.kind === "cap" && parsed.data.target === void 0;
82413
+ });
82414
+ }
82374
82415
  function rowToSequence(id, body) {
82375
82416
  const actions = parseActions(body.actions);
82376
82417
  if (actions === null) return null;
@@ -82409,6 +82450,7 @@ var NcSequenceStore = class NcSequenceStore {
82409
82450
  try {
82410
82451
  const { records } = await this.kind.query({ limit: 1e4 });
82411
82452
  const next = /* @__PURE__ */ new Map();
82453
+ const legacyRows = [];
82412
82454
  let skipped = 0;
82413
82455
  for (const row of records) {
82414
82456
  const sequence = rowToSequence(row.id, row.data);
@@ -82417,9 +82459,11 @@ var NcSequenceStore = class NcSequenceStore {
82417
82459
  continue;
82418
82460
  }
82419
82461
  next.set(sequence.id, sequence);
82462
+ if (hasLegacyTarget(row.data.actions)) legacyRows.push(row.id);
82420
82463
  }
82421
82464
  this.byId.clear();
82422
82465
  for (const [id, seq] of next) this.byId.set(id, seq);
82466
+ await this.rewriteLegacyRows(legacyRows, records);
82423
82467
  if (skipped > 0) this.logger.warn("sequence rows skipped as malformed — bindings to them will miss", { meta: { skipped } });
82424
82468
  } catch (err) {
82425
82469
  this.logger.warn("sequence load failed — keeping the sequences already in memory", { meta: {
@@ -82428,6 +82472,28 @@ var NcSequenceStore = class NcSequenceStore {
82428
82472
  } });
82429
82473
  }
82430
82474
  }
82475
+ /**
82476
+ * One-shot, idempotent (D983): a row whose steps still carry `deviceId` is
82477
+ * stored again in the target shape. A failed write leaves the row as it was —
82478
+ * it is still READ as the new shape — and is on the record.
82479
+ */
82480
+ async rewriteLegacyRows(ids, records) {
82481
+ for (const id of ids) {
82482
+ const sequence = this.byId.get(id) ?? null;
82483
+ const row = records.find((r) => r.id === id);
82484
+ if (sequence === null || row === void 0) continue;
82485
+ try {
82486
+ const createdAt = row.data.createdAt;
82487
+ await this.persist(sequence, typeof createdAt === "number" && Number.isFinite(createdAt) ? createdAt : this.now(), this.now());
82488
+ this.logger.info("notification sequence rewritten to the target shape", { meta: { sequenceId: id } });
82489
+ } catch (err) {
82490
+ this.logger.warn("notification sequence could not be rewritten — it is still read as the target shape", { meta: {
82491
+ sequenceId: id,
82492
+ error: String(err)
82493
+ } });
82494
+ }
82495
+ }
82496
+ }
82431
82497
  list() {
82432
82498
  return [...this.byId.values()].toSorted((a, b) => a.name.localeCompare(b.name));
82433
82499
  }
@@ -82447,7 +82513,7 @@ var NcSequenceStore = class NcSequenceStore {
82447
82513
  id: randomUUID(),
82448
82514
  name: input.name,
82449
82515
  ...input.minDelaySec !== void 0 ? { minDelaySec: input.minDelaySec } : {},
82450
- actions: [...input.actions]
82516
+ actions: input.actions.map(normalizeNcAction)
82451
82517
  };
82452
82518
  await this.persist(sequence, now, now);
82453
82519
  this.byId.set(sequence.id, sequence);
@@ -82463,7 +82529,7 @@ var NcSequenceStore = class NcSequenceStore {
82463
82529
  id: sequence.id,
82464
82530
  name: sequence.name,
82465
82531
  ...sequence.minDelaySec !== void 0 ? { minDelaySec: sequence.minDelaySec } : {},
82466
- actions: [...sequence.actions]
82532
+ actions: sequence.actions.map(normalizeNcAction)
82467
82533
  };
82468
82534
  await this.persist(stored, now, now);
82469
82535
  this.byId.set(stored.id, stored);
@@ -82476,7 +82542,7 @@ var NcSequenceStore = class NcSequenceStore {
82476
82542
  id: sequenceId,
82477
82543
  name: input.name,
82478
82544
  ...input.minDelaySec !== void 0 ? { minDelaySec: input.minDelaySec } : {},
82479
- actions: [...input.actions]
82545
+ actions: input.actions.map(normalizeNcAction)
82480
82546
  };
82481
82547
  const createdAt = await this.readCreatedAt(sequenceId, now);
82482
82548
  await this.persist(sequence, createdAt, now);
@@ -82544,7 +82610,11 @@ var NC_DETECTION_VERDICTS_COLUMNS = [
82544
82610
  var NcDetectionVerdictRecordSchema = object({
82545
82611
  trackId: string().min(1),
82546
82612
  deviceId: number().int(),
82547
- verdict: _enum(["correct", "wrong"]),
82613
+ verdict: _enum([
82614
+ "correct",
82615
+ "wrong",
82616
+ "wrong-label"
82617
+ ]),
82548
82618
  /** The class the operator said it really was. Only with `wrong`. */
82549
82619
  suggestedClass: string().min(1).optional(),
82550
82620
  /** Whose notification it was (the token's frozen owner), NOT who tapped. */
@@ -82579,6 +82649,16 @@ var NcDetectionVerdictLedger = class {
82579
82649
  logger: deps.logger
82580
82650
  });
82581
82651
  }
82652
+ /** The answers recorded for these tracks (a track nobody answered is absent).
82653
+ * Reads the in-memory mirror: no I/O, so a listing never waits on it. */
82654
+ forTracks(trackIds) {
82655
+ const out = /* @__PURE__ */ new Map();
82656
+ for (const id of trackIds) {
82657
+ const row = this.ledger.get(id);
82658
+ if (row !== void 0) out.set(id, row);
82659
+ }
82660
+ return out;
82661
+ }
82582
82662
  /** Write-through; throws on a failed write so the tap reports it. */
82583
82663
  async record(row) {
82584
82664
  await this.ledger.put(NcDetectionVerdictRecordSchema.parse(row));
@@ -82712,10 +82792,33 @@ function pruneHookBindings(bindings, field, isDead, notes) {
82712
82792
  kept.push(binding);
82713
82793
  continue;
82714
82794
  }
82715
- if (binding.action.kind === "cap" && isDead(binding.action.deviceId)) {
82716
- changed = true;
82717
- notes.removed(field, deviceSubjects([binding.action.deviceId]), "binding-removed");
82718
- continue;
82795
+ if (binding.action.kind === "cap") {
82796
+ const { target } = normalizeNcCapTarget(binding.action);
82797
+ if (target.kind === "devices") {
82798
+ const dead = target.ids.filter(isDead);
82799
+ if (dead.length === target.ids.length) {
82800
+ changed = true;
82801
+ notes.removed(field, deviceSubjects(dead), "binding-removed");
82802
+ continue;
82803
+ }
82804
+ if (dead.length > 0) {
82805
+ changed = true;
82806
+ notes.removed(field, deviceSubjects(dead), "binding-removed");
82807
+ const alive = target.ids.filter((id) => !isDead(id));
82808
+ kept.push({
82809
+ ...binding,
82810
+ action: normalizeNcAction({
82811
+ ...binding.action,
82812
+ deviceId: void 0,
82813
+ target: {
82814
+ kind: "devices",
82815
+ ids: alive
82816
+ }
82817
+ })
82818
+ });
82819
+ continue;
82820
+ }
82821
+ }
82719
82822
  }
82720
82823
  kept.push(binding);
82721
82824
  }
@@ -82871,7 +82974,11 @@ function referencedDeviceIds(snapshot) {
82871
82974
  add(c.systemEvent?.deviceIds);
82872
82975
  if (c.deviceState !== void 0) ids.add(c.deviceState.deviceId);
82873
82976
  if (c.scene?.deviceId !== void 0) ids.add(c.scene.deviceId);
82874
- for (const binding of [...rule.actions?.onTrigger ?? [], ...rule.actions?.onTriggerOff ?? []]) if (binding.kind === "do" && binding.action.kind === "cap") ids.add(binding.action.deviceId);
82977
+ for (const binding of [...rule.actions?.onTrigger ?? [], ...rule.actions?.onTriggerOff ?? []]) {
82978
+ if (binding.kind !== "do" || binding.action.kind !== "cap") continue;
82979
+ const { target } = normalizeNcCapTarget(binding.action);
82980
+ if (target.kind === "devices") add(target.ids);
82981
+ }
82875
82982
  }
82876
82983
  for (const rule of snapshot.timelapse) add(rule.deviceIds);
82877
82984
  for (const rule of snapshot.summary) add(rule.deviceIds);
@@ -83542,6 +83649,8 @@ var NcDeviceStateCache = class {
83542
83649
  //#region src/notification-center/detection-verdict-buttons.ts
83543
83650
  var VERDICT_CORRECT_ID = "detection-verdict-correct";
83544
83651
  var VERDICT_WRONG_ID = "detection-verdict-wrong";
83652
+ /** Wrong LABEL, right class — only for a subject that has a label. */
83653
+ var VERDICT_WRONG_LABEL_ID = "detection-verdict-wrong-label";
83545
83654
  /** `detection-verdict-wrong:<class>` — Wrong, and what it really was. */
83546
83655
  var VERDICT_WRONG_AS_PREFIX = "detection-verdict-wrong:";
83547
83656
  /** The macro classes offered as "it was really…". Short on purpose. */
@@ -83558,6 +83667,15 @@ function verdictButtonRefusal(subject) {
83558
83667
  if (subject.ownerUserId === void 0) return "unowned-target";
83559
83668
  return null;
83560
83669
  }
83670
+ /**
83671
+ * Does the notified subject carry a label below its macro class — a sub-class
83672
+ * (`dog` under `animal`), a recognised name, a plate? The same tier split the
83673
+ * retrain annotations use, so "has a label" means one thing in both places.
83674
+ */
83675
+ function subjectHasLabel(className, label) {
83676
+ if (className === void 0) return false;
83677
+ return toRetrainClassTiers(className, label).label !== void 0;
83678
+ }
83561
83679
  /** The classes worth offering for a subject of `className`. */
83562
83680
  function verdictClassChoices(className) {
83563
83681
  return VERDICT_CLASS_CHOICES.filter((c) => c !== className).slice(0, VERDICT_MAX_CLASS_CHOICES);
@@ -83569,13 +83687,17 @@ function verdictClassChoices(className) {
83569
83687
  * when the channel declared room for a fourth button.
83570
83688
  */
83571
83689
  function verdictButtonSpecs(input) {
83572
- const base = [{
83690
+ const plain = [{
83573
83691
  actionId: VERDICT_CORRECT_ID,
83574
83692
  verdict: "correct"
83575
83693
  }, {
83576
83694
  actionId: VERDICT_WRONG_ID,
83577
83695
  verdict: "wrong"
83578
83696
  }];
83697
+ const base = subjectHasLabel(input.className, input.label) ? [...plain, {
83698
+ actionId: VERDICT_WRONG_LABEL_ID,
83699
+ verdict: "wrong-label"
83700
+ }] : plain;
83579
83701
  if (input.maxActions === void 0 || input.maxActions <= 3) return base;
83580
83702
  const room = input.maxActions - base.length;
83581
83703
  const extra = verdictClassChoices(input.className).slice(0, room).map((suggestedClass) => ({
@@ -87306,6 +87428,8 @@ var NcDispatcher = class {
87306
87428
  ...target.ownerUserId !== void 0 ? { ownerUserId: target.ownerUserId } : {},
87307
87429
  ...entry.payload.subject.className !== void 0 ? { className: entry.payload.subject.className } : {},
87308
87430
  ...entry.payload.subject.trackId !== void 0 ? { trackId: entry.payload.subject.trackId } : {},
87431
+ ...entry.payload.subject.label !== void 0 ? { label: entry.payload.subject.label } : {},
87432
+ ...entry.payload.subject.bbox !== void 0 ? { bbox: entry.payload.subject.bbox } : {},
87309
87433
  eventAtMs: entry.payload.subject.timestamp,
87310
87434
  ...target.maxActions !== void 0 ? { maxActions: target.maxActions } : {},
87311
87435
  ...entry.payload.subject.systemEvent !== void 0 ? { systemEvent: entry.payload.subject.systemEvent } : {}
@@ -91669,7 +91793,7 @@ var NcRuleActionRunner = class {
91669
91793
  if (sequence.throttle) this.lastRunAt.set(sequence.id, now);
91670
91794
  let stepsRun = 0;
91671
91795
  for (const [index, action] of sequence.actions.entries()) try {
91672
- await this.step(action, input.origin);
91796
+ await this.step(action, input.origin, sequence.id);
91673
91797
  stepsRun += 1;
91674
91798
  } catch (err) {
91675
91799
  this.deps.logger.warn("rule action failed — stopping the sequence", {
@@ -91709,20 +91833,28 @@ var NcRuleActionRunner = class {
91709
91833
  stepsRun
91710
91834
  };
91711
91835
  }
91712
- async step(action, origin) {
91836
+ async step(action, origin, owner) {
91713
91837
  if (action.kind === "wait") {
91714
91838
  await this.sleep(action.seconds * 1e3);
91715
91839
  return;
91716
91840
  }
91717
91841
  if (!this.deps.isDeviceScoped(action.cap)) throw new Error(`rule action refused: "${action.cap}" is not a device capability — only devices can be actuated`);
91718
- await this.deps.actuate({
91719
- deviceId: action.deviceId,
91842
+ const { target, actionId } = normalizeNcCapTarget(action);
91843
+ const deviceIds = await this.devicesOf(target, actionId, owner);
91844
+ for (const deviceId of deviceIds) await this.deps.actuate({
91845
+ deviceId,
91720
91846
  cap: action.cap,
91721
91847
  method: action.method,
91722
91848
  args: action.args ?? {},
91723
91849
  ...origin !== void 0 ? { origin } : {}
91724
91850
  });
91725
91851
  }
91852
+ async devicesOf(target, actionId, owner) {
91853
+ if (target.kind === "devices") return target.ids;
91854
+ if (target.kind === "var") throw new Error("rule action refused: a sequence has no variables to resolve a target from");
91855
+ if (this.deps.resolveTarget === void 0) throw new Error("rule action refused: this node cannot resolve a domain target");
91856
+ return (await this.deps.resolveTarget(target, actionId, owner)).deviceIds;
91857
+ }
91726
91858
  };
91727
91859
  /** Force-enable a shared sequence for a button tap (always runs, still throttled). */
91728
91860
  function asForcedSequence(sequence) {
@@ -100110,6 +100242,12 @@ function rowRefusalId(systemEvent) {
100110
100242
  const value = systemEvent?.data["refusalId"];
100111
100243
  return typeof value === "string" && value.length > 0 ? value : void 0;
100112
100244
  }
100245
+ /** The text-catalog key of each verdict button. */
100246
+ var VERDICT_LABEL_KEY = {
100247
+ correct: "button.verdict.correct",
100248
+ wrong: "button.verdict.wrong",
100249
+ "wrong-label": "button.verdict.wrongLabel"
100250
+ };
100113
100251
  /**
100114
100252
  * The tap-through buttons for ONE delivery, each with its own single-use
100115
100253
  * token.
@@ -100146,6 +100284,7 @@ async function mintButtons(host, input) {
100146
100284
  const verdictOwner = input.ownerUserId;
100147
100285
  const verdictButtons = verdictRefusal !== null || verdictTrackId === void 0 || verdictOwner === void 0 ? [] : verdictButtonSpecs({
100148
100286
  className: input.className,
100287
+ label: input.label,
100149
100288
  maxActions: input.maxActions
100150
100289
  }).map((spec) => {
100151
100290
  const id = randomUUID();
@@ -100163,6 +100302,11 @@ async function mintButtons(host, input) {
100163
100302
  ...spec.suggestedClass !== void 0 ? { suggestedClass: spec.suggestedClass } : {},
100164
100303
  ...input.maxActions === 0 ? { viaLink: true } : {},
100165
100304
  ...input.eventAtMs !== void 0 ? { eventAtMs: input.eventAtMs } : {},
100305
+ ...input.className !== void 0 ? { subject: {
100306
+ className: input.className,
100307
+ ...input.label !== void 0 ? { label: input.label } : {},
100308
+ ...input.bbox !== void 0 ? { bbox: input.bbox } : {}
100309
+ } } : {},
100166
100310
  deviceId: input.deviceId,
100167
100311
  actionId: spec.actionId,
100168
100312
  targetId: input.targetId,
@@ -100171,7 +100315,7 @@ async function mintButtons(host, input) {
100171
100315
  });
100172
100316
  return {
100173
100317
  id: spec.actionId,
100174
- label: spec.suggestedClass === void 0 ? host.texts.text({ key: spec.verdict === "correct" ? "button.verdict.correct" : "button.verdict.wrong" }) : host.texts.text({
100318
+ label: spec.suggestedClass === void 0 ? host.texts.text({ key: VERDICT_LABEL_KEY[spec.verdict] }) : host.texts.text({
100175
100319
  key: "button.verdict.wrongAs",
100176
100320
  vars: { class: spec.suggestedClass }
100177
100321
  }),
@@ -100630,6 +100774,18 @@ var VERDICT_FRAME_REFUSAL_COPY = {
100630
100774
  "still-busy": "The track is gone and the recorder is busy — nothing was changed, try again later",
100631
100775
  failed: "The track is gone and its frame could not be saved — nothing was changed"
100632
100776
  };
100777
+ /** The grant's frozen subject, in the shape the retrain still stores it in. */
100778
+ function verdictSubjectOf(grant, subject, at) {
100779
+ return {
100780
+ trackId: grant.trackId,
100781
+ className: subject.className,
100782
+ ...subject.label !== void 0 ? { label: subject.label } : {},
100783
+ ...subject.bbox !== void 0 ? { bbox: subject.bbox } : {},
100784
+ verdict: grant.verdict,
100785
+ ...grant.suggestedClass !== void 0 ? { suggestedClass: grant.suggestedClass } : {},
100786
+ at
100787
+ };
100788
+ }
100633
100789
  /**
100634
100790
  * The track is gone (aged out, or discarded as a still object): the camera's
100635
100791
  * RECORDING still covers the instant, so the verdict lands a frame there
@@ -100652,7 +100808,8 @@ async function landFrameFromRecording(host, grant) {
100652
100808
  }
100653
100809
  const landed = await addFrame({
100654
100810
  deviceId: grant.deviceId,
100655
- epochMs: grant.eventAtMs
100811
+ epochMs: grant.eventAtMs,
100812
+ ...grant.subject !== void 0 ? { subject: verdictSubjectOf(grant, grant.subject, host.now()) } : {}
100656
100813
  });
100657
100814
  if (!landed.ok) {
100658
100815
  host.logger.warn("detection verdict dropped — the track is gone and the recording refused", {
@@ -101613,6 +101770,7 @@ function buildEntries(host, rule, subject, kind, matchedOn, userTargets = [], or
101613
101770
  ...alarmSource?.eventId !== void 0 ? { eventId: alarmSource.eventId } : hasEventMedia ? { eventId: subject.recordId } : {},
101614
101771
  className: subject.classNames[0] ?? subject.sensorKind ?? "event",
101615
101772
  ...subject.label !== void 0 ? { label: subject.label } : {},
101773
+ ...subject.bbox !== void 0 ? { bbox: subject.bbox } : {},
101616
101774
  ...subject.labelKind !== void 0 ? { labelKind: subject.labelKind } : {},
101617
101775
  ...subject.confidence !== void 0 ? { confidence: subject.confidence } : {},
101618
101776
  zones: renderedZones,
@@ -105933,6 +106091,8 @@ var NotificationCenter = class {
105933
106091
  zoneOwners;
105934
106092
  /** Runs a matched rule's action sequences. Null when no actuator is wired. */
105935
106093
  actionRunner;
106094
+ /** Domain action targets read from the registry (D983); null without a directory. */
106095
+ actionTargets;
105936
106096
  /** Grants behind the tap-through buttons. In-process and single-use — read
105937
106097
  * `action-token.ts` before assuming anything about what a tap proves. */
105938
106098
  actionTokens = new NcActionTokenRegistry();
@@ -106340,9 +106500,15 @@ var NotificationCenter = class {
106340
106500
  });
106341
106501
  this.livenessHoldoff = new DeviceLivenessHoldoff((event) => this.consumeEvent(event), { delayMs: deps.livenessHoldoffMs ?? (process.env.VITEST ? 0 : DEVICE_LIVENESS_HOLDOFF_MS) });
106342
106502
  this.nodeLivenessHoldoff = new DeviceLivenessHoldoff((event, timing) => this.consumeNodeLiveness(event, timing), { delayMs: deps.nodeLivenessHoldoffMs ?? (process.env.VITEST ? 0 : NODE_LIVENESS_HOLDOFF_MS) });
106503
+ const targets = deps.actionTargetDirectory !== void 0 ? new ActionTargetResolver(deps.actionTargetDirectory, this.logger.child("actions")) : null;
106504
+ this.actionTargets = targets;
106343
106505
  this.actionRunner = deps.actuate !== void 0 && deps.isCapDeviceScoped !== void 0 ? new NcRuleActionRunner({
106344
106506
  logger: this.logger.child("actions"),
106345
106507
  actuate: (input) => this.actuateWithAttribution(input),
106508
+ ...targets !== null ? { resolveTarget: (target, actionId, owner) => targets.resolve(target, actionId, {}, {
106509
+ actionId,
106510
+ owner
106511
+ }) } : {},
106346
106512
  isDeviceScoped: deps.isCapDeviceScoped,
106347
106513
  ...deps.now !== void 0 ? { now: deps.now } : {}
106348
106514
  }) : null;
@@ -107132,6 +107298,21 @@ var NotificationCenter = class {
107132
107298
  }
107133
107299
  };
107134
107300
  }
107301
+ /**
107302
+ * What the operator answered to the "was this detection right?" buttons, for
107303
+ * the retrain page: by track, the latest answer wins. A track nobody answered
107304
+ * is absent.
107305
+ */
107306
+ detectionVerdictsOf(trackIds) {
107307
+ const out = /* @__PURE__ */ new Map();
107308
+ for (const [trackId, row] of this.verdicts.forTracks(trackIds)) out.set(trackId, toRetrainVerdict({
107309
+ verdict: row.verdict,
107310
+ trackId,
107311
+ at: row.at,
107312
+ suggestedClass: row.suggestedClass
107313
+ }));
107314
+ return out;
107315
+ }
107135
107316
  observeOccupancy(deviceId, snapshot, evidence = "scene") {
107136
107317
  return observeOccupancy(this.occupancyWatchHost(), deviceId, snapshot, evidence);
107137
107318
  }
@@ -107307,6 +107488,10 @@ var NotificationCenter = class {
107307
107488
  }
107308
107489
  };
107309
107490
  }
107491
+ /** The device registry changed: a cached domain action target is stale (D983). */
107492
+ invalidateActionTargets() {
107493
+ this.actionTargets?.invalidate();
107494
+ }
107310
107495
  onSystemEvent(event) {
107311
107496
  return onSystemEvent(this.systemEventsHost(), event);
107312
107497
  }
@@ -108780,7 +108965,9 @@ async function addVerdictFrameFromRecording(api, logger, input) {
108780
108965
  }
108781
108966
  const result = await api.addRetrainFrameFromRecording({
108782
108967
  collectionId,
108783
- ...input
108968
+ deviceId: input.deviceId,
108969
+ epochMs: input.epochMs,
108970
+ ...input.subject !== void 0 ? { verdictSubject: input.subject } : {}
108784
108971
  });
108785
108972
  if (result.kind === "added") return { ok: true };
108786
108973
  return {
@@ -109067,6 +109254,20 @@ var BuildNotificationCenter = class extends BuildNotificationCenterBase {
109067
109254
  });
109068
109255
  },
109069
109256
  isCapDeviceScoped: isDeviceScopedCap,
109257
+ actionTargetDirectory: {
109258
+ listDeviceIdsByCaps: (capNames) => api.deviceManager.listDeviceIdsByCaps.query({ capNames: [...capNames] }),
109259
+ listDevices: async (deviceIds) => {
109260
+ return (await api.deviceManager.listAll.query({
109261
+ deviceIds: [...deviceIds],
109262
+ projection: "slim"
109263
+ })).map((d) => ({
109264
+ id: d.id,
109265
+ type: d.type,
109266
+ location: d.location,
109267
+ disabled: d.disabled
109268
+ }));
109269
+ }
109270
+ },
109070
109271
  generateVision: async (request) => {
109071
109272
  const result = await api.llm.generateVision.mutate({
109072
109273
  ...request.profileId !== void 0 ? { profileId: request.profileId } : {},
@@ -113692,7 +113893,7 @@ var RetrainCollectionService = class {
113692
113893
  frameId: recordingFrameId(input.deviceId, input.epochMs),
113693
113894
  deviceId: input.deviceId
113694
113895
  });
113695
- const stored = await this.storeStill(input.deviceId, input.epochMs, membershipOp === null ? [] : [membershipOp]);
113896
+ const stored = await this.storeStill(input.deviceId, input.epochMs, membershipOp === null ? [] : [membershipOp], input.verdictSubject);
113696
113897
  if (stored.kind === "refused") {
113697
113898
  this.deps.logger.info("retrain collection add refused", {
113698
113899
  tags,
@@ -113704,11 +113905,15 @@ var RetrainCollectionService = class {
113704
113905
  });
113705
113906
  return stored;
113706
113907
  }
113707
- if (stored.reused) await this.deps.collections.addFrame({
113708
- collectionId: input.collectionId,
113709
- frameId: stored.frame.frameId,
113710
- deviceId: input.deviceId
113711
- });
113908
+ let storedFrame = stored.frame;
113909
+ if (stored.reused) {
113910
+ await this.deps.collections.addFrame({
113911
+ collectionId: input.collectionId,
113912
+ frameId: stored.frame.frameId,
113913
+ deviceId: input.deviceId
113914
+ });
113915
+ if (input.verdictSubject !== void 0) storedFrame = await this.deps.frames.setVerdictSubject(stored.frame.frameId, input.verdictSubject) ?? stored.frame;
113916
+ }
113712
113917
  this.deps.logger.info("retrain frame added to a collection from the recording", {
113713
113918
  tags,
113714
113919
  meta: {
@@ -113718,7 +113923,7 @@ var RetrainCollectionService = class {
113718
113923
  reused: stored.reused
113719
113924
  }
113720
113925
  });
113721
- const [wire] = await this.deps.retrain.toWireFrames([stored.frame]);
113926
+ const [wire] = await this.deps.retrain.toWireFrames([storedFrame]);
113722
113927
  if (wire === void 0) throw new Error("retrain: a stored still produced no wire frame");
113723
113928
  return {
113724
113929
  kind: "added",
@@ -113769,7 +113974,7 @@ var RetrainCollectionService = class {
113769
113974
  const ids = await this.deps.collections.listFrameIds(input.collectionId);
113770
113975
  return this.deps.retrain.toWireFrames(await this.deps.frames.getMany(ids));
113771
113976
  }
113772
- async storeStill(deviceId, epochMs, withRow) {
113977
+ async storeStill(deviceId, epochMs, withRow, verdictSubject) {
113773
113978
  const existing = await this.deps.frames.get(recordingFrameId(deviceId, epochMs));
113774
113979
  if (existing !== null) return {
113775
113980
  kind: "stored",
@@ -113788,7 +113993,8 @@ var RetrainCollectionService = class {
113788
113993
  const stored = await this.deps.frames.storeRecordingStill({
113789
113994
  deviceId,
113790
113995
  epochMs,
113791
- bytes: still.jpeg
113996
+ bytes: still.jpeg,
113997
+ ...verdictSubject !== void 0 ? { verdictSubject } : {}
113792
113998
  }, withRow);
113793
113999
  return stored.kind === "copied" ? {
113794
114000
  kind: "stored",
@@ -114574,6 +114780,14 @@ function toWireFrame(f) {
114574
114780
  dismissed: f.dismissed ?? []
114575
114781
  };
114576
114782
  }
114783
+ /** The answer on a frame: a track copy asks the ledger, a still carries the
114784
+ * subject the notification froze. */
114785
+ function verdictOfFrame(copy, verdicts) {
114786
+ if (copy.origin === "track") return verdicts.get(copy.trackId);
114787
+ const s = copy.verdictSubject;
114788
+ if (s === void 0) return void 0;
114789
+ return toRetrainVerdict(s);
114790
+ }
114577
114791
  /**
114578
114792
  * What the operator did with the proposals on a frame (`retrain-review.ts`).
114579
114793
  *
@@ -114634,7 +114848,11 @@ var RetrainService = class {
114634
114848
  const tracks = [];
114635
114849
  for (const deviceId of devices) tracks.push(...await this.deps.tracks.listStaging(deviceId, limit));
114636
114850
  const ids = tracks.map((t) => t.trackId);
114637
- const [frameCounts, annotationCounts] = await Promise.all([this.deps.frames.countsByTrack(ids), this.deps.annotations.countsByTrack(ids)]);
114851
+ const [frameCounts, annotationCounts, verdicts] = await Promise.all([
114852
+ this.deps.frames.countsByTrack(ids),
114853
+ this.deps.annotations.countsByTrack(ids),
114854
+ this.verdictsOfTracks(ids)
114855
+ ]);
114638
114856
  return tracks.map((t) => ({
114639
114857
  trackId: t.trackId,
114640
114858
  deviceId: t.deviceId,
@@ -114643,7 +114861,8 @@ var RetrainService = class {
114643
114861
  firstSeen: t.firstSeen,
114644
114862
  lastSeen: t.lastSeen,
114645
114863
  frameCount: frameCounts.get(t.trackId) ?? 0,
114646
- annotationCount: annotationCounts.get(t.trackId) ?? 0
114864
+ annotationCount: annotationCounts.get(t.trackId) ?? 0,
114865
+ ...verdicts.has(t.trackId) ? { verdict: verdicts.get(t.trackId) } : {}
114647
114866
  }));
114648
114867
  }
114649
114868
  /**
@@ -114693,9 +114912,14 @@ var RetrainService = class {
114693
114912
  const ownTracks = /* @__PURE__ */ new Map();
114694
114913
  const subjectBoxes = /* @__PURE__ */ new Map();
114695
114914
  const annotationsByFrame = await this.annotationsByFrame(copies);
114915
+ const verdicts = await this.verdictsOf(copies);
114696
114916
  const out = [];
114697
114917
  for (const copy of copies) {
114698
- const wire = toWireFrame(copy);
114918
+ const verdict = verdictOfFrame(copy, verdicts);
114919
+ const wire = {
114920
+ ...toWireFrame(copy),
114921
+ ...verdict !== void 0 ? { verdict } : {}
114922
+ };
114699
114923
  const collectionIds = collections?.get(copy.frameId);
114700
114924
  const withCollections = {
114701
114925
  ...collections !== null ? {
@@ -114726,7 +114950,8 @@ var RetrainService = class {
114726
114950
  },
114727
114951
  ownTrack: trackId !== "" ? ownTracks.get(trackId) ?? null : null,
114728
114952
  ownSubjectBox: trackId !== "" ? subjectBoxes.get(trackId)?.get(copy.sourceMediaId) ?? null : null,
114729
- neighbours
114953
+ neighbours,
114954
+ ...copy.verdictSubject !== void 0 ? { verdictSubject: copy.verdictSubject } : {}
114730
114955
  });
114731
114956
  out.push({
114732
114957
  ...withCollections,
@@ -114736,6 +114961,22 @@ var RetrainService = class {
114736
114961
  }
114737
114962
  return out;
114738
114963
  }
114964
+ /** The verdicts of the tracks these frames were copied from. */
114965
+ async verdictsOf(copies) {
114966
+ return this.verdictsOfTracks(copies.flatMap((c) => c.origin === "track" ? [c.trackId] : []));
114967
+ }
114968
+ async verdictsOfTracks(trackIds) {
114969
+ const port = this.deps.verdicts;
114970
+ const ids = [...new Set(trackIds)];
114971
+ if (port === void 0 || ids.length === 0) return /* @__PURE__ */ new Map();
114972
+ return port.forTracks(ids).catch((err) => {
114973
+ this.deps.logger.warn("retrain frames: verdicts unreadable", { meta: {
114974
+ tracks: ids.length,
114975
+ error: String(err)
114976
+ } });
114977
+ return /* @__PURE__ */ new Map();
114978
+ });
114979
+ }
114739
114980
  /** The URLs of a frame's pixels, when a data plane is served for them. */
114740
114981
  frameUrls(frameId) {
114741
114982
  const frameUrl = this.deps.frameUrl;
@@ -116852,6 +117093,7 @@ var BuildStores = class extends BuildStoresBase {
116852
117093
  subjectBoxes: (trackId, deviceId) => mediaStore.listSubjectBoxesByOwner("track", trackId, deviceId)
116853
117094
  },
116854
117095
  memberships: retrainCollectionStore,
117096
+ verdicts: { forTracks: async (trackIds) => this.notificationCenter?.detectionVerdictsOf(trackIds) ?? /* @__PURE__ */ new Map() },
116855
117097
  frameUrl: (frameId, size) => retrainFramePath(this.ctx.id, frameId, size)
116856
117098
  });
116857
117099
  this.retrain = retrain;
@@ -122563,7 +122805,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
122563
122805
  onBindingsChanged: (data) => this.handleBindingsChangedEvent(data),
122564
122806
  onDeviceUnregistered: (deviceId) => this.handleDeviceUnregisteredEvent(deviceId),
122565
122807
  onClusterTopology: (nodes) => this.nodeLiveness.applySnapshot(nodes, Date.now()),
122566
- onSystemEvent: (event) => this.notificationCenter?.onSystemEvent(event)
122808
+ onSystemEvent: (event) => this.notificationCenter?.onSystemEvent(event),
122809
+ onDeviceRegistryChange: () => this.notificationCenter?.invalidateActionTargets()
122567
122810
  });
122568
122811
  await step("startTrackingTapes", () => this.startTrackingTapes(this.isPostProcessingNode));
122569
122812
  if (this.isPostProcessingNode) await step("startEmbeddingDispatcher", () => this.startEmbeddingDispatcher(logger, transport));