@camstack/addon-post-analysis 1.2.641 → 1.2.643

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (14) hide show
  1. package/dist/{clip-model-registry-Cw7Iy7Qi.mjs → clip-model-registry-DdgT3gdf.mjs} +301 -25
  2. package/dist/{clip-model-registry-Qr0XipkX.js → clip-model-registry-lKeBMKlT.js} +301 -25
  3. package/dist/embedding-encoder/index.js +1 -1
  4. package/dist/embedding-encoder/index.mjs +1 -1
  5. package/dist/pipeline-analytics/_stub.js +2 -2
  6. package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Cct0TMzC.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-B6X8TAQP.mjs} +2 -2
  7. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BIjXlrFH.mjs +26 -0
  8. package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CuNXxDlT.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js--XkysBjS.mjs} +1 -1
  9. package/dist/pipeline-analytics/{hostInit-B9c2FowI.mjs → hostInit-CDPfOUWf.mjs} +2 -2
  10. package/dist/pipeline-analytics/index.js +411 -15
  11. package/dist/pipeline-analytics/index.mjs +411 -15
  12. package/dist/pipeline-analytics/remoteEntry.js +1 -1
  13. package/package.json +1 -1
  14. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-_TUthUZh.mjs +0 -26
@@ -1,4 +1,4 @@
1
- import { $ as MediaFileKindEnum, $i as CAP_ADVANCE_FIELDS, $n as cloneAlarmSettings, $r as paintHex, $t as OpsLogEntrySchema, A as DeclaredDevices, Aa as EventCategory, Ai as subKindsOf, An as TrackSideSchema, Ar as isAudioClassName, At as NcAlarmSensorsInputSchema, B as FULL_IMAGE_BBOX, Bi as templateCarriesSummary, Bn as applyModeDelaysEdits, Br as isRootSuppliableStepId, Bt as NcRuleUpdateInputSchema, C as ClearTrackRelationResultSchema, Ca as record, Ci as ruleKindOf, Cn as TimelapseRuleInputSchema, Cr as failureContributionCapability, Ct as NC_SNOOZE_MAX_MINUTES, D as DEFAULT_TIMELAPSE_PREVIEW_TEXT, Da as tuple, Di as servedTemplateVars, Dn as TrackPuritySchema, Dr as isAlertEpisodeOpen, Dt as NC_TEMPLATE_VARS, E as DEFAULT_EVENT_COLOR, Ea as templateLiteral, Ei as sensorRaisedOf, En as TrackPlacementSchema, Er as isAlarmControllableCap, Et as NC_TAXONOMY, F as EVENT_DENSITY_BATCH_MAX, Fi as systemEventDeliveryProblems, Fn as alarmPanelCapability, Fr as isEventOwnerType, Ft as NcRuleActionSchema, G as ListTrackRelationsResultSchema, Gi as zoneAnalyticsCapability, Gn as audioMetricsCapability, Gr as kebabToCamel, Gt as NcSnoozeSuppressedSchema, H as LABEL_SUPPLY_MACRO, Hi as vectorDimFromBase64, Hn as applySecureOnArmEdit, Hr as isSystemDelivery, Ht as NcSharedSequenceSchema, I as EVENT_KIND_BY_CAP, Ii as systemEventFamilyProblems, In as alertEpisodeId, Ir as isKnownTemplateVar, It as NcRuleInputSchema, J as MAX_BIRTH_DECISION_RECORDS, Ji as evaluateSensorEdge, Jn as buildSubjectPath, Jr as memoizePinnedFfmpeg, Jt as NcTaxonomySchema, K as LockStateSchema, Ki as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, Kn as audioModeOf, Kr as linkedStepOf, Kt as NcStateSummarySectionIdSchema, L as EVENT_OWNER_TYPES, Li as systemEventFilterApplies, Ln as alertEpisodeKind, Lr as isLabelSupplyStepId, Lt as NcRulePatchSchema, M as EMBEDDING_MODEL_FAILED, Mi as subjectPathSvgPoints, Mn as activateAlarmProfile, Mr as isDetectionMacroClass, Mt as NcAlarmSettingsPatchSchema, N as EMBEDDING_REQUIREMENTS_FAILED, Ni as summarizeReview, Nn as addonWidgetsSourceCapability, Nr as isDrawableSubjectPath, Nt as NcConditionDescriptorSchema, O as DETECTION_MACRO_CLASSES, Oa as union, Oi as startMoverUnderClearance, On as TrackRelationKindSchema, Or as isAlertEpisodeSeverity, Ot as NcAlarmConfigSchema, P as EMBEDDING_REQUIREMENTS_UNAVAILABLE, Pi as syncActiveAlarmProfileSettings, Pn as alarmDeviceSecureOnArm, Pr as isEventKindLabelKey, Pt as NcConditionsSchema, Q as MOTION_CLOSE_AFTER_MS, Qi as BaseAddon, Qn as classifyRetrainReview, Qr as occupancyScope, Qt as OPS_LOG_DEFAULT_LIMIT, R as EXPORT_DENSE_MAX_RANGES, Ri as systemEventSummaryScheduleOf, Rn as alertEpisodePayload, Rr as isMediaOwnerType, Rt as NcRuleSchema, S as ClearTrackRelationInputSchema, Sa as partialRecord, Si as ruleGroupingApplies, Sn as TRACK_RELATIONS_DEFAULT_LIMIT, Sr as faceGalleryCapability, St as NC_SENSOR_TRANSITION_KINDS, T as DEFAULT_DETAIL_CROP_CONVENTION, Ta as string, Ti as secureActuationFor, Tn as TimelapseRuleSchema, Tr as gifFromMp4Args, Tt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, U as LabelAttributionSchema, Ui as viewerLinkBaseOf, Un as armRemovalReconcile, Ur as isSystemEventSummaryRule, Ut as NcSnoozeInputSchema, V as FailureCounters, Vi as templateVarsFor, Vn as applyNonBlockingEdit, Vr as isScheduleActive, Vt as NcScheduleSchema, W as ListTrackRelationsInputSchema, Wi as withSystemEventClasses, Wn as assertTimelapseCadences, Wr as isVectorIndexDimConflict, Wt as NcSnoozeSchema, X as MAX_TRACK_PURITIES, Xi as DeviceType, Xn as bundledCamstack, Xr as normalizeNcMediaPolicy, Xt as NcTemplatePreviewSchema, Y as MAX_BIRTH_LATENCY_PROXY_MS, Yi as isSourceCap, Yn as buildViewerLink, Yr as ncMediaPolicyError, Yt as NcTemplatePreviewInputSchema, Z as MAX_TRACK_RELATIONS, Zi as errMsg, Zn as calendarEventLocalSpan, Zr as notificationRulesCapability, Zt as NcTemplateVarDescriptorSchema, _ as BIRTH_DECISION_DEFAULT_LIMIT, _a as discriminatedUnion, _i as retrainRevisionConflictMessage, _n as SceneMonitorSchema, _t as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, a as CLIP_EMBEDDING_STEP_ID, aa as customAction, ai as plateGalleryCapability, an as RetrainDismissedSchema, ar as deleteAlarmProfile, at as NC_ALARM_NOTIFY_FIELDS, b as BucketAccumulator, ba as number, bi as ruleBelongsToAlarm$1, bn as SetTrackRelationInputSchema, br as eventKindLabel, bt as NC_GROUPING_MAX_SEC, c as readClusterModelPinStrict, ca as hydrateSchema, 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, di as resolveClipSearchMinScore, dn as SCENE_DEFAULT_CHECK_INTERVAL_SEC, dr as deviceAttentionKind, dt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, ea as CamProfileSchema, ei as paletteResultSchema, en as PackageLlmVerdictSchema, er as consumableFormOf, et as MotionTriggeredBySchema, f as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, fa as sleep$1, fi as resolveLocationMode, fn as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, fr as deviceRemovalHookCapability, ft as NC_CLIP_RETENTION_MAX_SEC, g as ArchivedDebugNoteSchema, ga as boolean, gn as SCENE_RESET_RECAPTURES, gt as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, h as AlarmArmModeSchema, ha as array, hi as resolveNcMediaImage, hn as SCENE_DIVERGED, hr as embeddingRefusalOf, ht as NC_CONFIRM_DEFAULT_TIMEOUT_MS, ia as createEvent, ii as pipelineAnalyticsCapability, in as RebuildObjectEmbeddingsRefusalSchema, ir as createAlarmProfile, it as NC_ALARM_DELAY_MAX_SEC, j as DetectionSourceSchema, ji as subjectPathSampleFromBox, jn as TrackSourceSchema, jr as isDefaultAlarmProfileId, jt as NcAlarmSensorsSchema, k as DETECTION_PIPELINE_CAP_NAME, ka as unknown, ki as storageOccupancyCapability, kn as TrackRelationSchema, kr as isAppLinkBase, kt as NcAlarmPostTriggeredActionSchema, l as resolveClusterModelPin, la as isDeviceScopedCap, 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 _enum, mi as resolveModeNotifyPolicy, mn as SCENE_DEFAULT_UNCOVERED_POLICY, mt as NC_CONFIRM_DEFAULT_MAX_IMAGE_PX, na as RECORD_MAX_DATA_BYTES, 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 defineCustomActions, 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 ZodObject, pi as resolveModeDelays, pn as SCENE_DEFAULT_QUIET_SECONDS, pt as NC_CONDITION_CATALOG, q as MAX_ARCHIVED_DEBUG_NOTES, qi as canonicalHash, qn as buildEventKindDescriptor, qr as mayWriteToLocation, qt as NcSystemEventKindSchema, ra as RECORD_QUERY_MAX, 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 defineRecordKinds, si as readDeviceStateFrom, sn as RetrainStatusSchema, sr as derivePostTriggeredAction, st as NC_ALARM_TRIGGERED_MAX_SEC, t as BUILTIN_CLIP_MODELS, ta as InvalidRecordError, ti as parseAlertEpisodePayload, tr as consumableLowPercentOf, tt as NC_ALARM_DEFAULT_PROFILE_ID, u as APPEARANCE_BIN_COUNT, ua as nodePin, 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 literal, vi as rootSupplyServes, vn as SetTrackPurityInputSchema, vr as encodeVectorBase64, vt as NC_DEFAULT_SNOOZE_MINUTES, w as CoverStateSchema, wa as strictObject, wi as sceneMonitorCapability, wn as TimelapseRulePatchSchema, wr as gifDeriveSpeed, wt as NC_STATE_SUMMARY_SECTION_IDS, x as COCO_TO_MACRO, xa as object, xi as ruleDeviceCompatCapQueries, xn as TIMELAPSE_DENSE_FLOOR_SEC, xr as evictionPolicyOfLocation, xt as NC_GROUPING_MIN_SEC, y as BirthDecisionRecordSchema, ya as looseObject, yi as ruleAutoTriggersAlarm, yn as SetTrackPurityResultSchema, yr as ensureAlarmProfiles, yt as NC_GROUPING_DEFAULT_SEC, z as FIRST_LEVEL_MACRO_CLASSES, zi as systemEventSummarySplitOf, zn as applyBlockingEdit, zr as isRetiredClearKey, zt as NcRuleTargetSchema } from "../clip-model-registry-Cw7Iy7Qi.mjs";
1
+ import { $ as MediaFileKindEnum, $i as CAP_ADVANCE_FIELDS, $n as cloneAlarmSettings, $r as paintHex, $t as OpsLogEntrySchema, A as DeclaredDevices, Aa as EventCategory, Ai as subKindsOf, An as TrackSideSchema, Ar as isAudioClassName, At as NcAlarmSensorsInputSchema, B as FULL_IMAGE_BBOX, Bi as templateCarriesSummary, Bn as applyModeDelaysEdits, Br as isRootSuppliableStepId, Bt as NcRuleUpdateInputSchema, C as ClearTrackRelationResultSchema, Ca as record, Ci as ruleKindOf, Cn as TimelapseRuleInputSchema, Cr as failureContributionCapability, Ct as NC_SNOOZE_MAX_MINUTES, D as DEFAULT_TIMELAPSE_PREVIEW_TEXT, Da as tuple, Di as servedTemplateVars, Dn as TrackPuritySchema, Dr as isAlertEpisodeOpen, Dt as NC_TEMPLATE_VARS, E as DEFAULT_EVENT_COLOR, Ea as templateLiteral, Ei as sensorRaisedOf, En as TrackPlacementSchema, Er as isAlarmControllableCap, Et as NC_TAXONOMY, F as EVENT_DENSITY_BATCH_MAX, Fi as systemEventDeliveryProblems, Fn as alarmPanelCapability, Fr as isEventOwnerType, Ft as NcRuleActionSchema, G as ListTrackRelationsResultSchema, Gi as zoneAnalyticsCapability, Gn as audioMetricsCapability, Gr as kebabToCamel, Gt as NcSnoozeSuppressedSchema, H as LABEL_SUPPLY_MACRO, Hi as vectorDimFromBase64, Hn as applySecureOnArmEdit, Hr as isSystemDelivery, Ht as NcSharedSequenceSchema, I as EVENT_KIND_BY_CAP, Ii as systemEventFamilyProblems, In as alertEpisodeId, Ir as isKnownTemplateVar, It as NcRuleInputSchema, J as MAX_BIRTH_DECISION_RECORDS, Ji as evaluateSensorEdge, Jn as buildSubjectPath, Jr as memoizePinnedFfmpeg, Jt as NcTaxonomySchema, K as LockStateSchema, Ki as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, Kn as audioModeOf, Kr as linkedStepOf, Kt as NcStateSummarySectionIdSchema, L as EVENT_OWNER_TYPES, Li as systemEventFilterApplies, Ln as alertEpisodeKind, Lr as isLabelSupplyStepId, Lt as NcRulePatchSchema, M as EMBEDDING_MODEL_FAILED, Mi as subjectPathSvgPoints, Mn as activateAlarmProfile, Mr as isDetectionMacroClass, Mt as NcAlarmSettingsPatchSchema, N as EMBEDDING_REQUIREMENTS_FAILED, Ni as summarizeReview, Nn as addonWidgetsSourceCapability, Nr as isDrawableSubjectPath, Nt as NcConditionDescriptorSchema, O as DETECTION_MACRO_CLASSES, Oa as union, Oi as startMoverUnderClearance, On as TrackRelationKindSchema, Or as isAlertEpisodeSeverity, Ot as NcAlarmConfigSchema, P as EMBEDDING_REQUIREMENTS_UNAVAILABLE, Pi as syncActiveAlarmProfileSettings, Pn as alarmDeviceSecureOnArm, Pr as isEventKindLabelKey, Pt as NcConditionsSchema, Q as MOTION_CLOSE_AFTER_MS, Qi as BaseAddon, Qn as classifyRetrainReview, Qr as occupancyScope, Qt as OPS_LOG_DEFAULT_LIMIT, R as EXPORT_DENSE_MAX_RANGES, Ri as systemEventSummaryScheduleOf, Rn as alertEpisodePayload, Rr as isMediaOwnerType, Rt as NcRuleSchema, S as ClearTrackRelationInputSchema, Sa as partialRecord, Si as ruleGroupingApplies, Sn as TRACK_RELATIONS_DEFAULT_LIMIT, Sr as faceGalleryCapability, St as NC_SENSOR_TRANSITION_KINDS, T as DEFAULT_DETAIL_CROP_CONVENTION, Ta as string, Ti as secureActuationFor, Tn as TimelapseRuleSchema, Tr as gifFromMp4Args, Tt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, U as LabelAttributionSchema, Ui as viewerLinkBaseOf, Un as armRemovalReconcile, Ur as isSystemEventSummaryRule, Ut as NcSnoozeInputSchema, V as FailureCounters, Vi as templateVarsFor, Vn as applyNonBlockingEdit, Vr as isScheduleActive, Vt as NcScheduleSchema, W as ListTrackRelationsInputSchema, Wi as withSystemEventClasses, Wn as assertTimelapseCadences, Wr as isVectorIndexDimConflict, Wt as NcSnoozeSchema, X as MAX_TRACK_PURITIES, Xi as DeviceType, Xn as bundledCamstack, Xr as normalizeNcMediaPolicy, Xt as NcTemplatePreviewSchema, Y as MAX_BIRTH_LATENCY_PROXY_MS, Yi as isSourceCap, Yn as buildViewerLink, Yr as ncMediaPolicyError, Yt as NcTemplatePreviewInputSchema, Z as MAX_TRACK_RELATIONS, Zi as errMsg, Zn as calendarEventLocalSpan, Zr as notificationRulesCapability, Zt as NcTemplateVarDescriptorSchema, _ as BIRTH_DECISION_DEFAULT_LIMIT, _a as discriminatedUnion, _i as retrainRevisionConflictMessage, _n as SceneMonitorSchema, _t as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, a as CLIP_EMBEDDING_STEP_ID, aa as customAction, ai as plateGalleryCapability, an as RetrainDismissedSchema, ar as deleteAlarmProfile, at as NC_ALARM_NOTIFY_FIELDS, b as BucketAccumulator, ba as number, bi as ruleBelongsToAlarm$1, bn as SetTrackRelationInputSchema, br as eventKindLabel, bt as NC_GROUPING_MAX_SEC, c as readClusterModelPinStrict, ca as hydrateSchema, 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, di as resolveClipSearchMinScore, dn as SCENE_DEFAULT_CHECK_INTERVAL_SEC, dr as deviceAttentionKind, dt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, ea as CamProfileSchema, ei as paletteResultSchema, en as PackageLlmVerdictSchema, er as consumableFormOf, et as MotionTriggeredBySchema, f as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, fa as sleep$1, fi as resolveLocationMode, fn as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, fr as deviceRemovalHookCapability, ft as NC_CLIP_RETENTION_MAX_SEC, g as ArchivedDebugNoteSchema, ga as boolean, gn as SCENE_RESET_RECAPTURES, gt as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, h as AlarmArmModeSchema, ha as array, hi as resolveNcMediaImage, hn as SCENE_DIVERGED, hr as embeddingRefusalOf, ht as NC_CONFIRM_DEFAULT_TIMEOUT_MS, ia as createEvent, ii as pipelineAnalyticsCapability, in as RebuildObjectEmbeddingsRefusalSchema, ir as createAlarmProfile, it as NC_ALARM_DELAY_MAX_SEC, j as DetectionSourceSchema, ji as subjectPathSampleFromBox, jn as TrackSourceSchema, jr as isDefaultAlarmProfileId, jt as NcAlarmSensorsSchema, k as DETECTION_PIPELINE_CAP_NAME, ka as unknown, ki as storageOccupancyCapability, kn as TrackRelationSchema, kr as isAppLinkBase, kt as NcAlarmPostTriggeredActionSchema, l as resolveClusterModelPin, la as isDeviceScopedCap, 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 _enum, mi as resolveModeNotifyPolicy, mn as SCENE_DEFAULT_UNCOVERED_POLICY, mt as NC_CONFIRM_DEFAULT_MAX_IMAGE_PX, na as RECORD_MAX_DATA_BYTES, 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 defineCustomActions, 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 ZodObject, pi as resolveModeDelays, pn as SCENE_DEFAULT_QUIET_SECONDS, pt as NC_CONDITION_CATALOG, q as MAX_ARCHIVED_DEBUG_NOTES, qi as canonicalHash, qn as buildEventKindDescriptor, qr as mayWriteToLocation, qt as NcSystemEventKindSchema, ra as RECORD_QUERY_MAX, 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 defineRecordKinds, si as readDeviceStateFrom, sn as RetrainStatusSchema, sr as derivePostTriggeredAction, st as NC_ALARM_TRIGGERED_MAX_SEC, t as BUILTIN_CLIP_MODELS, ta as InvalidRecordError, ti as parseAlertEpisodePayload, tr as consumableLowPercentOf, tt as NC_ALARM_DEFAULT_PROFILE_ID, u as APPEARANCE_BIN_COUNT, ua as nodePin, 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 literal, vi as rootSupplyServes, vn as SetTrackPurityInputSchema, vr as encodeVectorBase64, vt as NC_DEFAULT_SNOOZE_MINUTES, w as CoverStateSchema, wa as strictObject, wi as sceneMonitorCapability, wn as TimelapseRulePatchSchema, wr as gifDeriveSpeed, wt as NC_STATE_SUMMARY_SECTION_IDS, x as COCO_TO_MACRO, xa as object, xi as ruleDeviceCompatCapQueries, xn as TIMELAPSE_DENSE_FLOOR_SEC, xr as evictionPolicyOfLocation, xt as NC_GROUPING_MIN_SEC, y as BirthDecisionRecordSchema, ya as looseObject, yi as ruleAutoTriggersAlarm, yn as SetTrackPurityResultSchema, yr as ensureAlarmProfiles, yt as NC_GROUPING_DEFAULT_SEC, z as FIRST_LEVEL_MACRO_CLASSES, zi as systemEventSummarySplitOf, zn as applyBlockingEdit, zr as isRetiredClearKey, zt as NcRuleTargetSchema } from "../clip-model-registry-DdgT3gdf.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";
@@ -750,6 +750,8 @@ var DEFAULT_ACTION_TTL_MS = 15 * 6e4;
750
750
  * notification that is not fresh.
751
751
  */
752
752
  var SNOOZE_ACTION_TTL_MS = 60 * 6e4;
753
+ /** How long a Correct / Wrong button stays live: one day. */
754
+ var VERDICT_ACTION_TTL_MS = 1440 * 6e4;
753
755
  /**
754
756
  * How long a DISARM button stays live: five minutes.
755
757
  *
@@ -777,6 +779,7 @@ function actionTtlMs(subject) {
777
779
  case "sequence": return DEFAULT_ACTION_TTL_MS;
778
780
  case "addon-update": return DEFAULT_ACTION_TTL_MS;
779
781
  case "server-update": return DEFAULT_ACTION_TTL_MS;
782
+ case "detection-verdict": return VERDICT_ACTION_TTL_MS;
780
783
  }
781
784
  }
782
785
  var NcActionTokenRegistry = class {
@@ -820,6 +823,30 @@ var NcActionTokenRegistry = class {
820
823
  grant: found.grant
821
824
  };
822
825
  }
826
+ /**
827
+ * Read the grant without spending it. ONLY the confirm page of a link-only
828
+ * channel may call this: it exists so a link preview that opens the URL
829
+ * cannot spend the token, and it never runs anything.
830
+ */
831
+ peek(id) {
832
+ const found = this.grants.get(id);
833
+ if (found === void 0) return {
834
+ ok: false,
835
+ reason: "unknown"
836
+ };
837
+ if (found.used) return {
838
+ ok: false,
839
+ reason: "already-used"
840
+ };
841
+ if (found.grant.expiresAt <= this.now()) return {
842
+ ok: false,
843
+ reason: "expired"
844
+ };
845
+ return {
846
+ ok: true,
847
+ grant: found.grant
848
+ };
849
+ }
823
850
  /** Drop records that can no longer be claimed. Memory hygiene only — an
824
851
  * expired record is already refused by {@link claim}, so a sweep that never
825
852
  * ran would cost bytes, never safety. */
@@ -861,6 +888,7 @@ function describeGrant(grant) {
861
888
  return grant.action === "disarm" ? "Alarm disarmed" : `Alarm armed${grant.mode !== void 0 ? ` (${grant.mode})` : ""}`;
862
889
  case "addon-update": return `${grant.packages.length} addon update(s) on ${grant.nodeId}`;
863
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`;
864
892
  }
865
893
  }
866
894
  /** The audit line's `meta`, per family. A snooze that logged a `sequence` it
@@ -914,6 +942,15 @@ function grantMeta(grant) {
914
942
  ...common,
915
943
  nodeId: grant.nodeId
916
944
  };
945
+ case "detection-verdict": return {
946
+ ...common,
947
+ ruleId: grant.ruleId,
948
+ rule: grant.ruleName,
949
+ userId: grant.userId,
950
+ trackId: grant.trackId,
951
+ verdict: grant.verdict,
952
+ suggestedClass: grant.suggestedClass ?? null
953
+ };
917
954
  }
918
955
  }
919
956
  /** What the human standing in front of the page is told. */
@@ -922,6 +959,11 @@ var FAILURE_COPY = {
922
959
  expired: "This button has expired. Open CamStack to do it there.",
923
960
  "already-used": "Already done — this button had been used."
924
961
  };
962
+ /** The question a link-only channel lands on, answered by a POST form. */
963
+ function confirmPage(url, grant) {
964
+ const action = `${url.pathname}?exp=${encodeURIComponent(url.searchParams.get("exp") ?? "")}&amp;sig=${encodeURIComponent(url.searchParams.get("sig") ?? "")}&amp;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>`;
966
+ }
925
967
  function page(title, detail) {
926
968
  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>`;
927
969
  }
@@ -935,7 +977,8 @@ var NcActionPlane = class {
935
977
  handler = async (req, res) => {
936
978
  const url = new URL(req.url ?? "/", "http://placeholder");
937
979
  const id = decodeURIComponent(url.pathname.split("/").filter(Boolean).pop() ?? "");
938
- const background = (req.method ?? "GET").toUpperCase() === "POST";
980
+ const posted = (req.method ?? "GET").toUpperCase() === "POST";
981
+ const background = posted && url.searchParams.get("confirm") !== "1";
939
982
  if (!verifyActionSignature({
940
983
  secret: this.deps.secret,
941
984
  id,
@@ -947,6 +990,13 @@ var NcActionPlane = class {
947
990
  this.reply(res, background, 404, "Not found", "This link is not valid.");
948
991
  return;
949
992
  }
993
+ if (!posted) {
994
+ const peeked = this.deps.registry.peek(id);
995
+ if (peeked.ok && peeked.grant.kind === "detection-verdict" && peeked.grant.viaLink === true) {
996
+ this.sendBody(res, 200, "text/html; charset=utf-8", confirmPage(url, peeked.grant));
997
+ return;
998
+ }
999
+ }
950
1000
  const claim = this.deps.registry.claim(id);
951
1001
  if (!claim.ok) {
952
1002
  this.deps.logger.info("nc action callback not claimable", { meta: {
@@ -1092,6 +1142,7 @@ var ncDurableRecordKinds = defineRecordKinds({
1092
1142
  "state-summary-stamps": ledger("digest delivery stamps per rule kind"),
1093
1143
  meta: ledger("outbox boot-reconcile watermark and other single-key meta rows", "settings", { none: "one fixed key (watermark), overwritten in place, never deleted" }),
1094
1144
  snoozes: ledger("operator opens or closes a snooze window (write-through)"),
1145
+ "detection-verdicts": ledger("operator taps Correct or Wrong on a detection notification; one row per track"),
1095
1146
  "snooze-suppressed": {
1096
1147
  ...QUERIED,
1097
1148
  class: "ledger",
@@ -9226,6 +9277,9 @@ var en_default = {
9226
9277
  "action.addon-update.uptodate": "Already up to date on {{nodeId}} — nothing to do",
9227
9278
  "action.addon-update.unknown": "Could not read what needs updating on {{nodeId}} — nothing was changed. Open CamStack.",
9228
9279
  "button.server-update.apply": "Update server on {{nodeId}}",
9280
+ "button.verdict.correct": "Correct",
9281
+ "button.verdict.wrong": "Wrong",
9282
+ "button.verdict.wrongAs": "Wrong: it is {{class}}",
9229
9283
  "action.server-update.title": "Server update",
9230
9284
  "action.server-update.started": "Updating server on {{nodeId}}…",
9231
9285
  "action.server-update.restarting": "Server update staged on {{nodeId}} — the node is restarting. You will hear when it comes back.",
@@ -9627,6 +9681,9 @@ var it_default = {
9627
9681
  "action.addon-update.uptodate": "Già aggiornato su {{nodeId}} — niente da fare",
9628
9682
  "action.addon-update.unknown": "Non è stato possibile leggere cosa aggiornare su {{nodeId}} — non è stato cambiato nulla. Apri CamStack.",
9629
9683
  "button.server-update.apply": "Aggiorna server su {{nodeId}}",
9684
+ "button.verdict.correct": "Corretto",
9685
+ "button.verdict.wrong": "Sbagliato",
9686
+ "button.verdict.wrongAs": "Sbagliato: è {{class}}",
9630
9687
  "action.server-update.title": "Aggiornamento server",
9631
9688
  "action.server-update.started": "Aggiornamento server su {{nodeId}}…",
9632
9689
  "action.server-update.restarting": "Aggiornamento server predisposto su {{nodeId}} — il nodo si riavvia. Ti avviseremo quando torna.",
@@ -53035,6 +53092,15 @@ var SESSION_STILLNESS_MIN_POINTS = 4;
53035
53092
  * long (object removed while unobserved, or a long occlusion) → retire it. */
53036
53093
  var ENTRY_TTL_MS = 5 * 6e4;
53037
53094
  /**
53095
+ * The most one gap between two consecutive processed frames counts toward an
53096
+ * entry's WATCHED unconfirmed time. Frames inside a motion session arrive every
53097
+ * 0.2-2 s; the silence between on-motion sessions is tens of seconds to hours.
53098
+ * 10 s sits above any real in-session cadence (a slower frame still counts
53099
+ * nearly in full) and far below a session gap, so a gap contributes at most
53100
+ * one bound, never its length. Under-counting only delays a retirement.
53101
+ */
53102
+ var WATCHED_GAP_CAP_MS = 1e4;
53103
+ /**
53038
53104
  * How far apart the two reads of a departure must be before the second one
53039
53105
  * counts as CONFIRMING the first.
53040
53106
  *
@@ -59702,6 +59768,25 @@ var TrackAlreadyTrainedError = class extends Error {
59702
59768
  }
59703
59769
  };
59704
59770
  /**
59771
+ * Classify a failed `setTrackFlags` as one of the lifecycle's DELIBERATE
59772
+ * refusals, or `null` when it is a failure of the call itself.
59773
+ *
59774
+ * Needed because the caller that matters (a notification button) reaches the
59775
+ * method over `ctx.api`, where the error class does not survive the hop and
59776
+ * only the message does. The class check comes first; the message check is the
59777
+ * transported form, and `track-flags.spec.ts` pins it against the real
59778
+ * messages so a reworded refusal cannot silently turn into a generic failure.
59779
+ */
59780
+ function retrainRefusalReason(err) {
59781
+ if (err instanceof TrackAlreadyTrainedError) return "trained";
59782
+ if (err instanceof StagingBudgetExceededError) return "budget";
59783
+ const message = err instanceof Error ? err.message : "";
59784
+ if (message.includes("is already trained")) return "trained";
59785
+ if (message.includes("in retrain staging")) return "budget";
59786
+ if (message.includes("unknown track")) return "unknown-track";
59787
+ return null;
59788
+ }
59789
+ /**
59705
59790
  * The `debugNote` half of the patch — `{}` when nothing should be written.
59706
59791
  *
59707
59792
  * The note is not an independent column: it is the operator's sentence about
@@ -62265,6 +62350,19 @@ var StationaryObjectRegistry = class {
62265
62350
  * so quiet minutes must not age the entries (see {@link sweep}). */
62266
62351
  lastFrameAtByDevice = /* @__PURE__ */ new Map();
62267
62352
  /**
62353
+ * WATCHED time each entry has gone unconfirmed, in ms. Entry expiry is judged
62354
+ * on this, never on the span between two frame timestamps: a session-dispatch
62355
+ * camera's silence between motion sessions would otherwise be counted in full
62356
+ * the moment the next session's first frame arrives (measured: a car parked
62357
+ * 3.3 h was retired after seconds of watching, 316 s after its last
62358
+ * confirmation). Each frame adds its delta from the previous one, capped at
62359
+ * {@link WATCHED_GAP_CAP_MS}; a confirmation resets it. `confirmedAt` is the
62360
+ * `lastConfirmedAt` the counter was last reset for, so a changed value (any of
62361
+ * the places that confirm an entry) restarts the count without a hook at each.
62362
+ * An entry that was just hydrated has no record here and starts at zero.
62363
+ */
62364
+ unconfirmedWatched = /* @__PURE__ */ new Map();
62365
+ /**
62268
62366
  * Entries carrying ONE unconfirmed departure read, per device (D49, D174,
62269
62367
  * D279).
62270
62368
  *
@@ -62378,7 +62476,27 @@ var StationaryObjectRegistry = class {
62378
62476
  /** Record that a frame was processed for a device — advances the OBSERVED
62379
62477
  * clock that drives entry expiry in {@link sweep}. */
62380
62478
  noteFrame(deviceId, timestamp) {
62381
- if (timestamp > (this.lastFrameAtByDevice.get(deviceId) ?? 0)) this.lastFrameAtByDevice.set(deviceId, timestamp);
62479
+ const known = this.lastFrameAtByDevice.get(deviceId);
62480
+ if (timestamp <= (known ?? 0)) return;
62481
+ this.lastFrameAtByDevice.set(deviceId, timestamp);
62482
+ const ids = this.idsByDevice.get(deviceId);
62483
+ if (!ids) return;
62484
+ const delta = known === void 0 ? 0 : Math.min(timestamp - known, WATCHED_GAP_CAP_MS);
62485
+ for (const id of ids) {
62486
+ const e = this.ledger.get(id);
62487
+ if (!e) continue;
62488
+ const rec = this.unconfirmedWatched.get(id);
62489
+ if (!rec || rec.confirmedAt !== e.lastConfirmedAt) this.unconfirmedWatched.set(id, {
62490
+ confirmedAt: e.lastConfirmedAt,
62491
+ ms: 0
62492
+ });
62493
+ else rec.ms += delta;
62494
+ }
62495
+ }
62496
+ /** Watched ms an entry has gone unconfirmed (0 when not yet tracked). */
62497
+ watchedUnconfirmedMs(e) {
62498
+ const rec = this.unconfirmedWatched.get(e.id);
62499
+ return rec && rec.confirmedAt === e.lastConfirmedAt ? rec.ms : 0;
62382
62500
  }
62383
62501
  /**
62384
62502
  * Per-frame gate: partition this frame's detections against the device's
@@ -62623,12 +62741,14 @@ var StationaryObjectRegistry = class {
62623
62741
  * logged by `MacMiniAgent.localdomain` and by `agent-a`, the same
62624
62742
  * rows, seven seconds apart.
62625
62743
  *
62626
- * Expiry is measured against the device's latest processed-frame timestamp
62627
- * ({@link noteFrame}), NOT the wall clock: a session-dispatch camera emits
62628
- * no frames between motion sessions, and that silence says nothing about
62629
- * the object. Only when the camera has actually been WATCHING for `ttl`
62744
+ * Expiry is measured in WATCHED time ({@link noteFrame}): the sum of the
62745
+ * gaps between consecutive processed frames, each capped at
62746
+ * {@link WATCHED_GAP_CAP_MS}, since the entry's last confirmation. Neither the
62747
+ * wall clock nor the span between frame timestamps: a session-dispatch
62748
+ * camera emits no frames between motion sessions, and a silent gap must not
62749
+ * age the object. Only when the camera has actually been WATCHING for `ttl`
62630
62750
  * beyond the last confirmation (frames flowed, object never matched) is the
62631
- * object considered removed. A device with no recorded frame yet never
62751
+ * object considered removed. A freshly hydrated entry starts at zero. A device with no recorded frame yet never
62632
62752
  * expires its entries. `now` only stamps the departed telemetry.
62633
62753
  */
62634
62754
  async sweep(now) {
@@ -62647,9 +62767,10 @@ var StationaryObjectRegistry = class {
62647
62767
  ids.delete(id);
62648
62768
  continue;
62649
62769
  }
62650
- const overdue = observedAt - e.lastConfirmedAt > ttl;
62770
+ const watchedMs = this.watchedUnconfirmedMs(e);
62771
+ const overdue = watchedMs > ttl;
62651
62772
  const proven = this.detectorProvedItself(e);
62652
- const unprovenBackstop = observedAt - e.lastConfirmedAt > ttl * 12;
62773
+ const unprovenBackstop = watchedMs > ttl * 12;
62653
62774
  if (overdue && !proven && !unprovenBackstop) {
62654
62775
  this.bumpStrikeCounters(deviceId, { ttlHeldUnproven: 1 });
62655
62776
  continue;
@@ -62665,7 +62786,8 @@ var StationaryObjectRegistry = class {
62665
62786
  className: e.className,
62666
62787
  reason: proven ? "ttl" : "ttl-unproven",
62667
62788
  ttlMs: ttl,
62668
- unconfirmedForMs: observedAt - e.lastConfirmedAt,
62789
+ unconfirmedForMs: watchedMs,
62790
+ wallGapMs: observedAt - e.lastConfirmedAt,
62669
62791
  parkedForMs: observedAt - e.becameStationaryAt
62670
62792
  }
62671
62793
  });
@@ -62749,6 +62871,7 @@ var StationaryObjectRegistry = class {
62749
62871
  for (const id of ids) {
62750
62872
  this.dirty.delete(id);
62751
62873
  this.weakRuns.delete(id);
62874
+ this.unconfirmedWatched.delete(id);
62752
62875
  this.ledger.evict(id);
62753
62876
  }
62754
62877
  this.idsByDevice.delete(deviceId);
@@ -62936,6 +63059,7 @@ var StationaryObjectRegistry = class {
62936
63059
  return at !== void 0 && at > entry.lastConfirmedAt;
62937
63060
  }
62938
63061
  dropEntry(deviceId, id) {
63062
+ this.unconfirmedWatched.delete(id);
62939
63063
  const dropped = this.ledger.get(id);
62940
63064
  this.idsByDevice.get(deviceId)?.delete(id);
62941
63065
  this.dirty.delete(id);
@@ -81843,6 +81967,10 @@ var NC_DEVICE_ROW_KINDS = [
81843
81967
  {
81844
81968
  key: "nc-artifacts",
81845
81969
  kind: ncDurableRecordKinds.artifacts
81970
+ },
81971
+ {
81972
+ key: "nc-detection-verdicts",
81973
+ kind: ncDurableRecordKinds["detection-verdicts"]
81846
81974
  }
81847
81975
  ];
81848
81976
  /**
@@ -82262,6 +82390,82 @@ var NcSequenceStore = class NcSequenceStore {
82262
82390
  }
82263
82391
  };
82264
82392
  //#endregion
82393
+ //#region src/notification-center/detection-verdict-ledger.ts
82394
+ /**
82395
+ * @durable class=ledger owner=notification-center
82396
+ * write="the operator taps Correct / Wrong on a detection notification; one row per track, the latest answer replaces the earlier one"
82397
+ * retention="none — bounded by the tracks a rule asked about; deleted with the camera by the device-removal hook"
82398
+ */
82399
+ var NC_DETECTION_VERDICTS_COLLECTION = "notification-center:detection-verdicts";
82400
+ var NC_DETECTION_VERDICTS_COLUMNS = [
82401
+ {
82402
+ name: "id",
82403
+ type: "TEXT",
82404
+ primaryKey: true,
82405
+ notNull: true
82406
+ },
82407
+ {
82408
+ name: "ruleId",
82409
+ type: "TEXT",
82410
+ notNull: true
82411
+ },
82412
+ {
82413
+ name: "at",
82414
+ type: "INTEGER",
82415
+ notNull: true
82416
+ },
82417
+ (
82418
+ /** The FULL row (Zod-validated on read). */
82419
+ {
82420
+ name: "row",
82421
+ type: "JSON",
82422
+ notNull: true
82423
+ })
82424
+ ];
82425
+ var NcDetectionVerdictRecordSchema = object({
82426
+ trackId: string().min(1),
82427
+ deviceId: number().int(),
82428
+ verdict: _enum(["correct", "wrong"]),
82429
+ /** The class the operator said it really was. Only with `wrong`. */
82430
+ suggestedClass: string().min(1).optional(),
82431
+ /** Whose notification it was (the token's frozen owner), NOT who tapped. */
82432
+ userId: string().min(1),
82433
+ ruleId: string().min(1),
82434
+ ruleName: string(),
82435
+ at: number()
82436
+ });
82437
+ var NC_DETECTION_VERDICTS_SPEC = {
82438
+ collection: NC_DETECTION_VERDICTS_COLLECTION,
82439
+ columns: [...NC_DETECTION_VERDICTS_COLUMNS],
82440
+ writeMode: "write-through",
82441
+ keyOf: (row) => row.trackId,
82442
+ toValue: (row) => ({
82443
+ ruleId: row.ruleId,
82444
+ at: row.at,
82445
+ row
82446
+ }),
82447
+ fromRecord: (_key, data) => {
82448
+ const parsed = NcDetectionVerdictRecordSchema.safeParse(data["row"]);
82449
+ return parsed.success ? parsed.data : null;
82450
+ },
82451
+ deviceIdOf: (row) => row.deviceId,
82452
+ loadLimit: 1e4
82453
+ };
82454
+ var NcDetectionVerdictLedger = class {
82455
+ ledger;
82456
+ constructor(deps) {
82457
+ this.ledger = new DurableLedger({
82458
+ spec: NC_DETECTION_VERDICTS_SPEC,
82459
+ kind: deps.records.kind(ncDurableRecordKinds["detection-verdicts"]),
82460
+ logger: deps.logger
82461
+ });
82462
+ }
82463
+ /** Write-through; throws on a failed write so the tap reports it. */
82464
+ async record(row) {
82465
+ await this.ledger.put(NcDetectionVerdictRecordSchema.parse(row));
82466
+ }
82467
+ };
82468
+ //#endregion
82265
82469
  //#region src/notification-center/device-prune.ts
82266
82470
  function judgeList(list, isDead) {
82267
82471
  const dead = list.filter(isDead);
@@ -83216,6 +83420,64 @@ var NcDeviceStateCache = class {
83216
83420
  }
83217
83421
  };
83218
83422
  //#endregion
83423
+ //#region src/notification-center/detection-verdict-buttons.ts
83424
+ var VERDICT_CORRECT_ID = "detection-verdict-correct";
83425
+ var VERDICT_WRONG_ID = "detection-verdict-wrong";
83426
+ /** `detection-verdict-wrong:<class>` — Wrong, and what it really was. */
83427
+ var VERDICT_WRONG_AS_PREFIX = "detection-verdict-wrong:";
83428
+ /** The macro classes offered as "it was really…". Short on purpose. */
83429
+ var VERDICT_CLASS_CHOICES = [
83430
+ "person",
83431
+ "vehicle",
83432
+ "animal"
83433
+ ];
83434
+ /** At most this many class buttons, and only when the channel has room. */
83435
+ var VERDICT_MAX_CLASS_CHOICES = 2;
83436
+ function verdictButtonRefusal(subject) {
83437
+ if (!subject.askVerdict) return "rule-off";
83438
+ if (subject.trackId === void 0) return "no-track";
83439
+ if (subject.ownerUserId === void 0) return "unowned-target";
83440
+ return null;
83441
+ }
83442
+ /** The classes worth offering for a subject of `className`. */
83443
+ function verdictClassChoices(className) {
83444
+ return VERDICT_CLASS_CHOICES.filter((c) => c !== className).slice(0, VERDICT_MAX_CLASS_CHOICES);
83445
+ }
83446
+ /**
83447
+ * Which buttons a channel with `maxActions` slots gets. `maxActions` absent
83448
+ * (the kind catalog did not answer) is treated as the common floor of three:
83449
+ * the two plain choices, never the class ones. The class choices appear only
83450
+ * when the channel declared room for a fourth button.
83451
+ */
83452
+ function verdictButtonSpecs(input) {
83453
+ const base = [{
83454
+ actionId: VERDICT_CORRECT_ID,
83455
+ verdict: "correct"
83456
+ }, {
83457
+ actionId: VERDICT_WRONG_ID,
83458
+ verdict: "wrong"
83459
+ }];
83460
+ if (input.maxActions === void 0 || input.maxActions <= 3) return base;
83461
+ const room = input.maxActions - base.length;
83462
+ const extra = verdictClassChoices(input.className).slice(0, room).map((suggestedClass) => ({
83463
+ actionId: `${VERDICT_WRONG_AS_PREFIX}${suggestedClass}`,
83464
+ verdict: "wrong",
83465
+ suggestedClass
83466
+ }));
83467
+ return [...base, ...extra];
83468
+ }
83469
+ /** Is this one of our buttons (so a link-only channel can lift it into the body)? */
83470
+ function isVerdictAction(action) {
83471
+ return action.id === VERDICT_CORRECT_ID || action.id.startsWith(VERDICT_WRONG_ID);
83472
+ }
83473
+ /**
83474
+ * The body lines that carry the verdict choices on a channel with no buttons.
83475
+ * A bare URL per line: every chat and mail client linkifies it.
83476
+ */
83477
+ function verdictLinkLines(actions) {
83478
+ return actions.filter(isVerdictAction).map((a) => `${a.label}: ${a.url ?? ""}`).join("\n");
83479
+ }
83480
+ //#endregion
83219
83481
  //#region src/notification-center/state-summary/state-model.ts
83220
83482
  /**
83221
83483
  * The STATE SUMMARY's vocabulary (D647): its sections, and what one reported
@@ -86924,6 +87186,8 @@ var NcDispatcher = class {
86924
87186
  targetId: target.id,
86925
87187
  ...target.ownerUserId !== void 0 ? { ownerUserId: target.ownerUserId } : {},
86926
87188
  ...entry.payload.subject.className !== void 0 ? { className: entry.payload.subject.className } : {},
87189
+ ...entry.payload.subject.trackId !== void 0 ? { trackId: entry.payload.subject.trackId } : {},
87190
+ ...target.maxActions !== void 0 ? { maxActions: target.maxActions } : {},
86927
87191
  ...entry.payload.subject.systemEvent !== void 0 ? { systemEvent: entry.payload.subject.systemEvent } : {}
86928
87192
  });
86929
87193
  } catch (err) {
@@ -87019,9 +87283,13 @@ var NcDispatcher = class {
87019
87283
  ...summarySectionOf(systemEvent.data)
87020
87284
  } } : {}
87021
87285
  }) : null;
87022
- const actions = await this.mintActions(entry, target);
87286
+ const minted = await this.mintActions(entry, target);
87287
+ const linkOnly = target.maxActions === 0;
87288
+ const verdictLinks = linkOnly ? verdictLinkLines(minted) : "";
87289
+ const actions = linkOnly ? minted.filter((a) => !isVerdictAction(a)) : minted;
87290
+ const withLinks = (text) => verdictLinks === "" ? text : `${text}\n\n${verdictLinks}`;
87023
87291
  const notification = {
87024
- body,
87292
+ body: withLinks(body),
87025
87293
  title,
87026
87294
  priority: clampPriority(paramNumber(entry.payload.params, "priority") ?? entry.payload.priority),
87027
87295
  ...params,
@@ -87043,7 +87311,7 @@ var NcDispatcher = class {
87043
87311
  grouped: resolved.grouped,
87044
87312
  withAiText: (aiText) => ({
87045
87313
  ...notification,
87046
- body: renderBody(aiText)
87314
+ body: withLinks(renderBody(aiText))
87047
87315
  })
87048
87316
  };
87049
87317
  }
@@ -99656,6 +99924,55 @@ async function mintButtons(host, input) {
99656
99924
  const announcement = stored === null && input.ruleId === NC_ALARM_ANNOUNCE_RULE_ID;
99657
99925
  if (stored === null && !announcement) return [];
99658
99926
  const drafts = [];
99927
+ const verdictRefusal = verdictButtonRefusal({
99928
+ askVerdict: stored?.askVerdict === true,
99929
+ ...input.trackId !== void 0 ? { trackId: input.trackId } : {},
99930
+ ...input.ownerUserId !== void 0 ? { ownerUserId: input.ownerUserId } : {}
99931
+ });
99932
+ if (stored?.askVerdict === true && verdictRefusal !== null) host.logger.info("detection notification carries no verdict buttons", {
99933
+ tags: { deviceId: input.deviceId },
99934
+ meta: {
99935
+ ruleId: input.ruleId,
99936
+ reason: verdictRefusal,
99937
+ targetId: input.targetId
99938
+ }
99939
+ });
99940
+ const verdictTrackId = input.trackId;
99941
+ const verdictOwner = input.ownerUserId;
99942
+ const verdictButtons = verdictRefusal !== null || verdictTrackId === void 0 || verdictOwner === void 0 ? [] : verdictButtonSpecs({
99943
+ className: input.className,
99944
+ maxActions: input.maxActions
99945
+ }).map((spec) => {
99946
+ const id = randomUUID();
99947
+ drafts.push({
99948
+ id,
99949
+ ttlMs: actionTtlMs({ kind: "detection-verdict" }),
99950
+ grant: (expiresAt) => ({
99951
+ kind: "detection-verdict",
99952
+ id,
99953
+ verdict: spec.verdict,
99954
+ trackId: verdictTrackId,
99955
+ ruleId: input.ruleId,
99956
+ ruleName: input.ruleName,
99957
+ userId: verdictOwner,
99958
+ ...spec.suggestedClass !== void 0 ? { suggestedClass: spec.suggestedClass } : {},
99959
+ ...input.maxActions === 0 ? { viaLink: true } : {},
99960
+ deviceId: input.deviceId,
99961
+ actionId: spec.actionId,
99962
+ targetId: input.targetId,
99963
+ expiresAt
99964
+ })
99965
+ });
99966
+ return {
99967
+ id: spec.actionId,
99968
+ label: spec.suggestedClass === void 0 ? host.texts.text({ key: spec.verdict === "correct" ? "button.verdict.correct" : "button.verdict.wrong" }) : host.texts.text({
99969
+ key: "button.verdict.wrongAs",
99970
+ vars: { class: spec.suggestedClass }
99971
+ }),
99972
+ url: id,
99973
+ mode: "background"
99974
+ };
99975
+ });
99659
99976
  const sequenceButtons = host.actionRunner === null || stored === null ? [] : buildActionButtons({
99660
99977
  actions: stored.actions,
99661
99978
  knownSequenceIds: host.sequences.knownIds(),
@@ -99868,6 +100185,7 @@ async function mintButtons(host, input) {
99868
100185
  }
99869
100186
  });
99870
100187
  const out = [
100188
+ ...verdictButtons,
99871
100189
  ...sequenceButtons,
99872
100190
  ...snoozeButtons,
99873
100191
  ...alarmButtons,
@@ -100091,8 +100409,53 @@ async function runGrantedAction(host, grant) {
100091
100409
  case "sequence": return host.runGrantedSequence(grant);
100092
100410
  case "addon-update": return host.applyGrantedAddonUpdate(grant);
100093
100411
  case "server-update": return host.applyGrantedServerUpdate(grant);
100412
+ case "detection-verdict": return host.applyGrantedDetectionVerdict(grant);
100094
100413
  }
100095
100414
  }
100415
+ /** What a person is told when the retrain lifecycle refuses the mark. */
100416
+ var VERDICT_REFUSAL_COPY = {
100417
+ trained: "This track was already used for training — nothing was changed",
100418
+ budget: "This camera already holds as many tracks for retraining as it may — nothing was changed",
100419
+ "unknown-track": "This track is no longer available — nothing was changed"
100420
+ };
100421
+ /**
100422
+ * Mark the track one notification reported for retraining, and record who was
100423
+ * asked and when.
100424
+ *
100425
+ * Order matters. The MARK goes first and is the authority: a refusal ("already
100426
+ * trained", "budget full") means the operator's answer changed nothing, so no
100427
+ * ledger row is written for an answer that was not applied. The token is
100428
+ * already spent (D47) — the refusal is shown as the reason, not a retry.
100429
+ */
100430
+ async function applyGrantedDetectionVerdict(host, grant) {
100431
+ const mark = host.deps.markTrackForRetrain;
100432
+ if (mark === void 0) throw new Error("no retrain mark is wired on this node");
100433
+ const outcome = await mark({
100434
+ deviceId: grant.deviceId,
100435
+ trackId: grant.trackId
100436
+ });
100437
+ if (!outcome.ok) {
100438
+ host.logger.info("detection verdict changed nothing — the retrain flow refused the mark", {
100439
+ tags: { deviceId: grant.deviceId },
100440
+ meta: {
100441
+ trackId: grant.trackId,
100442
+ reason: outcome.reason,
100443
+ verdict: grant.verdict
100444
+ }
100445
+ });
100446
+ throw new NcActionRefusedError(VERDICT_REFUSAL_COPY[outcome.reason]);
100447
+ }
100448
+ await host.verdicts.record({
100449
+ trackId: grant.trackId,
100450
+ deviceId: grant.deviceId,
100451
+ verdict: grant.verdict,
100452
+ ...grant.suggestedClass !== void 0 ? { suggestedClass: grant.suggestedClass } : {},
100453
+ userId: grant.userId,
100454
+ ruleId: grant.ruleId,
100455
+ ruleName: grant.ruleName,
100456
+ at: host.now()
100457
+ });
100458
+ }
100096
100459
  /**
100097
100460
  * Apply the addon updates one notification was about.
100098
100461
  *
@@ -105269,6 +105632,7 @@ var NotificationCenter = class {
105269
105632
  * evaluating one.
105270
105633
  */
105271
105634
  pruneLedger;
105635
+ verdicts;
105272
105636
  /** D905: which rules cannot work, announced once per distinct reason. */
105273
105637
  ruleHealth = new NcRuleHealthMonitor();
105274
105638
  /** The prune itself — see `device-prune-runner.ts`. */
@@ -105497,6 +105861,11 @@ var NotificationCenter = class {
105497
105861
  applyGrantedArmAnyway: (panel, grant) => self.applyGrantedArmAnyway(panel, grant),
105498
105862
  applyGrantedBypassInput: (panel, grant) => self.applyGrantedBypassInput(panel, grant),
105499
105863
  applyGrantedServerUpdate: (grant) => self.applyGrantedServerUpdate(grant),
105864
+ applyGrantedDetectionVerdict: (grant) => self.applyGrantedDetectionVerdict(grant),
105865
+ now: self.now,
105866
+ get verdicts() {
105867
+ return self.verdicts;
105868
+ },
105500
105869
  applyGrantedSnooze: (grant) => self.applyGrantedSnooze(grant),
105501
105870
  get deps() {
105502
105871
  return self.deps;
@@ -105547,6 +105916,9 @@ var NotificationCenter = class {
105547
105916
  async applyGrantedAddonUpdate(grant) {
105548
105917
  return applyGrantedAddonUpdate(this.grantedActionsHost(), grant);
105549
105918
  }
105919
+ async applyGrantedDetectionVerdict(grant) {
105920
+ return applyGrantedDetectionVerdict(this.grantedActionsHost(), grant);
105921
+ }
105550
105922
  async applyGrantedServerUpdate(grant) {
105551
105923
  return applyGrantedServerUpdate(this.grantedActionsHost(), grant);
105552
105924
  }
@@ -105786,6 +106158,10 @@ var NotificationCenter = class {
105786
106158
  }
105787
106159
  this.summaryRules = NcSummaryStore.of(deps.records, this.logger.child("summary"), deps.now);
105788
106160
  this.calendarEventsRules = NcCalendarEventsStore.of(deps.records, this.logger.child("calendar-events"), deps.now);
106161
+ this.verdicts = new NcDetectionVerdictLedger({
106162
+ ...durableStore,
106163
+ logger: this.logger.child("detection-verdicts")
106164
+ });
105789
106165
  this.pruneLedger = new NcPruneLedger({
105790
106166
  ...durableStore,
105791
106167
  logger: this.logger.child("device-prune")
@@ -108393,6 +108769,23 @@ var BuildNotificationCenter = class extends BuildNotificationCenterBase {
108393
108769
  };
108394
108770
  }
108395
108771
  },
108772
+ markTrackForRetrain: async ({ deviceId, trackId }) => {
108773
+ try {
108774
+ await this.ctx.api.pipelineAnalytics.setTrackFlags.mutate({
108775
+ deviceId,
108776
+ trackId,
108777
+ flags: { markForTrain: true }
108778
+ });
108779
+ return { ok: true };
108780
+ } catch (err) {
108781
+ const reason = retrainRefusalReason(err);
108782
+ if (reason === null) throw err;
108783
+ return {
108784
+ ok: false,
108785
+ reason
108786
+ };
108787
+ }
108788
+ },
108396
108789
  readDeviceStates: async (ids) => {
108397
108790
  const out = /* @__PURE__ */ new Map();
108398
108791
  for (const id of ids) try {
@@ -108464,9 +108857,11 @@ var BuildNotificationCenter = class extends BuildNotificationCenterBase {
108464
108857
  const targets = await api.notificationOutput.listTargets.query({});
108465
108858
  const kinds = await api.notificationOutput.listTargetKinds.query({}).catch(() => []);
108466
108859
  const bodyMaxLenByKind = new Map(kinds.map((k) => [k.kind, k.caps.bodyMaxLen]));
108860
+ const maxActionsByKind = new Map(kinds.map((k) => [k.kind, k.caps.actions]));
108467
108861
  return targets.map((t) => {
108468
108862
  const owner = t.config["ownerUserId"];
108469
108863
  const bodyMaxLen = bodyMaxLenByKind.get(t.kind);
108864
+ const maxActions = maxActionsByKind.get(t.kind);
108470
108865
  return {
108471
108866
  id: t.id,
108472
108867
  addonId: t.addonId,
@@ -108474,7 +108869,8 @@ var BuildNotificationCenter = class extends BuildNotificationCenterBase {
108474
108869
  kind: t.kind,
108475
108870
  enabled: t.enabled,
108476
108871
  ...typeof owner === "string" && owner.length > 0 ? { ownerUserId: owner } : {},
108477
- ...typeof bodyMaxLen === "number" ? { bodyMaxLen } : {}
108872
+ ...typeof bodyMaxLen === "number" ? { bodyMaxLen } : {},
108873
+ ...typeof maxActions === "number" ? { maxActions } : {}
108478
108874
  };
108479
108875
  });
108480
108876
  },