@camstack/addon-post-analysis 1.2.347 → 1.2.349

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (15) hide show
  1. package/dist/{clip-model-registry-BftmbgcV.mjs → clip-model-registry-CC-BJYiK.mjs} +1786 -148
  2. package/dist/{clip-model-registry--UsI8Ud4.js → clip-model-registry-DI12tTgY.js} +1786 -148
  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-DuQPp3dT.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BYsMflvm.mjs} +3 -3
  7. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Du_tzgi2.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-CQOJKiwa.mjs +26 -0
  9. package/dist/pipeline-analytics/{hostInit-BDQ6oQ7b.mjs → hostInit-Cpi67Icm.mjs} +3 -3
  10. package/dist/pipeline-analytics/index.js +575 -259
  11. package/dist/pipeline-analytics/index.mjs +575 -259
  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-4Fu7jNn9.mjs +0 -26
  15. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-D5IzYeGQ.mjs +0 -26
@@ -1,4 +1,4 @@
1
- import { $ as NC_ALARM_DEFAULT_PROFILE_ID, $r as subKindsOf, $t as RECORDING_EXPORT_MAX_READ_BYTES, A as DetectionSourceSchema, Ai as boolean, An as applyBlockingEdit, At as NcConditionsSchema, B as FailureCounters, Bi as templateLiteral, Bn as consumableLowPercentOf, Br as resolveModeNotifyPolicy, Bt as NcSharedSequenceSchema, C as CoverStateSchema, Ci as isDeviceScopedCap, Cn as TrackRelationSchema, Cr as isVectorIndexDimConflict, Ct as NC_TEMPLATE_VARS, D as DETECTION_MACRO_CLASSES, Di as ZodObject, Dn as addonWidgetsSourceCapability, Dr as memoizePinnedFfmpeg, Dt as NcAlarmSensorsSchema, E as DEFAULT_TIMELAPSE_PREVIEW_TEXT, Ei as sleep$1, En as activateAlarmProfile, Er as mayWriteToLocation, Et as NcAlarmSensorsInputSchema, F as EVENT_KIND_BY_CAP, Fi as object, Fn as audioMetricsCapability, Fr as readTimelapseGeneratedAt, Ft as NcRulePatchSchema, G as MAX_ARCHIVED_DEBUG_NOTES, Gn as deriveDetailCropRect, Gr as ruleDeviceCompatCapQueries, Gt as NcSystemEventKindSchema, H as ListTrackRelationsInputSchema, Hi as union, Hn as cosineSimilarity$1, Hr as rootSupplyServes, Ht as NcSnoozeSchema, I as EVENT_OWNER_TYPES, Ii as partialRecord, In as audioModeOf, Ir as renameAlarmProfile, It as NcRuleSchema, J as MAX_TRACK_PURITIES, Jn as describeSystemEventDeliveryProblem, Jr as sceneMonitorCapability, Jt as NcTemplatePreviewSchema, K as MAX_BIRTH_DECISION_RECORDS, Kn as derivePostTriggeredAction, Kr as ruleGroupingApplies, Kt as NcTaxonomySchema, L as EXPORT_DENSE_MAX_RANGES, Li as record, Ln as buildEventKindDescriptor, Lr as resolveClipSearchMinScore, Lt as NcRuleTargetSchema, M as EMBEDDING_REQUIREMENTS_FAILED, Mi as literal, Mn as applyNonBlockingEdit, Mr as plateGalleryCapability, Mt as NcRuleActionSchema, N as EMBEDDING_REQUIREMENTS_UNAVAILABLE, Ni as looseObject, Nn as applySecureOnArmEdit, Nr as readDeviceStateFrom, Nt as NcRuleActionsSchema, O as DETECTION_PIPELINE_CAP_NAME, Oi as _enum, On as alarmDeviceSecureOnArm, Or as notificationRulesCapability, Ot as NcAlarmSettingsPatchSchema, P as EVENT_DENSITY_BATCH_MAX, Pi as number, Pn as assertTimelapseCadences, Pr as readMotionTriggeredBy, Pt as NcRuleInputSchema, Q as MotionTriggeredBySchema, Qn as embeddingRefusalOf, Qr as storageOccupancyCapability, R as FIRST_LEVEL_MACRO_CLASSES, Ri as strictObject, Rn as buildViewerLink, Rr as resolveLocationMode, Rt as NcRuleUpdateInputSchema, S as ClearTrackRelationResultSchema, Si as hydrateSchema, Sn as TrackRelationKindSchema, Sr as isSystemEventSummaryRule, St as NC_TAXONOMY, T as DEFAULT_EVENT_COLOR, Tn as TrackSourceSchema, Tr as linkedStepOf, Tt as NcAlarmPostTriggeredActionSchema, U as ListTrackRelationsResultSchema, Ui as unknown, Un as createAlarmProfile, Ur as ruleAutoTriggersAlarm, Ut as NcSnoozeSuppressedSchema, V as LabelAttributionSchema, Vi as tuple, Vn as coreBlockIdFromAddonId, Vt as NcSnoozeInputSchema, W as LockStateSchema, Wi as EventCategory, Wn as deleteAlarmProfile, Wr as ruleBelongsToAlarm$1, Wt as NcStateSummarySectionIdSchema, X as MOTION_CLOSE_AFTER_MS, Xr as sensorRaisedOf, Xt as OPS_LOG_DEFAULT_LIMIT, Y as MAX_TRACK_RELATIONS, Yn as describeSystemEventFamilyProblem, Yr as secureActuationFor, Yt as NcTemplateVarDescriptorSchema, Z as MediaFileKindEnum, Zr as servedTemplateVars, Zt as OpsLogEntrySchema, _ as BIRTH_DECISION_DEFAULT_LIMIT, _i as BaseAddon, _n as TRACK_RELATIONS_DEFAULT_LIMIT, _r as isMediaOwnerType, _t as NC_GROUPING_MIN_SEC, a as CLIP_EMBEDDING_STEP_ID, ai as systemEventSummarySplitOf, an as SCENE_DEFAULT_ANCHOR_THRESHOLD, ar as faceGalleryCapability, at as NC_ALARM_TRIGGERED_MAX_SEC, b as COCO_TO_MACRO, bi as customAction, bn as TimelapseRuleSchema, br as isScheduleActive, bt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, c as resolveClusterModelPin, ci as vectorDimFromBase64, cn as SCENE_DEFAULT_QUIET_SECONDS, cr as gifFromMp4Args, ct as NC_CLIP_POST_ROLL_DEFAULT_SEC, d as APPEARANCE_BLOCKS, di as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, dn as SCENE_RESET_RECAPTURES, dr as isAudioClassName, dt as NC_CONDITION_CATALOG, ei as syncActiveAlarmProfileSettings, en as RECORDING_SOURCE_CAMSTACK_ADDON, et as NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC, f as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, fi as canonicalHash, fn as SceneMonitorSchema, fr as isDefaultAlarmProfileId, ft as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, g as ArchivedDebugNoteSchema, gi as errMsg, gn as TIMELAPSE_DENSE_FLOOR_SEC, gr as isKnownTemplateVar, gt as NC_GROUPING_MAX_SEC, h as AlarmArmModeSchema, hi as DeviceType, hn as SetTrackRelationInputSchema, hr as isEventOwnerType, ht as NC_GROUPING_DEFAULT_SEC, ii as systemEventSummaryScheduleOf, in as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, ir as evictionPolicyOfLocation, it as NC_ALARM_SYSTEM_EVENT_KINDS, j as EMBEDDING_MODEL_FAILED, ji as discriminatedUnion, jn as applyModeDelaysEdits, jr as pipelineAnalyticsCapability, jt as NcLegacyRuleActionSequenceSchema, k as DeclaredDevices, ki as array, kn as alarmPanelCapability, kr as occupancyScope, kt as NcConditionDescriptorSchema, l as ALL_CAPABILITY_DEFINITIONS, li as viewerLinkBaseOf, ln as SCENE_DEFAULT_UNCOVERED_POLICY, lr as isAlarmControllableCap, lt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, m as AUDIO_MACRO_LABELS, mi as isSourceCap, mn as SetTrackPurityResultSchema, mr as isEventKindLabelKey, mt as NC_DEFAULT_SNOOZE_MINUTES, ni as systemEventFamilyProblems, nn as RetrainStatusSchema, nr as ensureAlarmProfiles, nt as NC_ALARM_DELAY_MAX_SEC, o as FACE_EMBEDDING_STEP_ID, oi as templateCarriesSummary, on as SCENE_DEFAULT_CHECK_INTERVAL_SEC, or as failureContributionCapability, ot as NC_AUDIO_CONFIRM_HITS_DEFAULT, p as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, pi as evaluateSensorEdge, pn as SetTrackPurityInputSchema, pr as isDetectionMacroClass, pt as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, q as MAX_BIRTH_LATENCY_PROXY_MS, qn as deriveRecordingMode, qr as ruleKindOf, qt as NcTemplatePreviewInputSchema, ri as systemEventFilterApplies, rn as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, rr as eventKindLabel, rt as NC_ALARM_NOTIFY_FIELDS, s as readClusterModelPinStrict, si as templateVarsFor, sn as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, sr as gifDeriveSpeed, st as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, t as BUILTIN_CLIP_MODELS, ti as systemEventDeliveryProblems, tn as RebuildObjectEmbeddingsRefusalSchema, tr as encodeVectorBase64, tt as NC_ALARM_DELAY_FIELDS, u as APPEARANCE_BIN_COUNT, ui as zoneAnalyticsCapability, un as SCENE_DIVERGED, ur as isAppLinkBase, ut as NC_CLIP_RETENTION_MAX_SEC, v as BaseDevice, vi as CamProfileSchema, vn as TimelapseRuleInputSchema, vr as isRetiredClearKey, vt as NC_SENSOR_TRANSITION_KINDS, w as DEFAULT_DETAIL_CROP_CONVENTION, wi as nodePin, wn as TrackSideSchema, wr as kebabToCamel, wt as NcAlarmConfigSchema, x as ClearTrackRelationInputSchema, xi as defineCustomActions, xn as TrackPuritySchema, xr as isSystemDelivery, xt as NC_TANK_SYSTEM_EVENT_KINDS, y as BirthDecisionRecordSchema, yi as createEvent, yn as TimelapseRulePatchSchema, yr as isRootSuppliableStepId, yt as NC_SNOOZE_MAX_MINUTES, z as FULL_IMAGE_BBOX, zi as string, zn as cloneAlarmSettings, zr as resolveModeDelays, zt as NcScheduleSchema } from "../clip-model-registry-BftmbgcV.mjs";
1
+ import { $ as NC_ALARM_DEFAULT_PROFILE_ID, $r as subKindsOf, $t as RECORDING_EXPORT_MAX_READ_BYTES, A as DetectionSourceSchema, Ai as boolean, An as applyBlockingEdit, At as NcConditionsSchema, B as FailureCounters, Bi as templateLiteral, Bn as consumableLowPercentOf, Br as resolveModeNotifyPolicy, Bt as NcSharedSequenceSchema, C as CoverStateSchema, Ci as isDeviceScopedCap, Cn as TrackRelationSchema, Cr as isVectorIndexDimConflict, Ct as NC_TEMPLATE_VARS, D as DETECTION_MACRO_CLASSES, Di as ZodObject, Dn as addonWidgetsSourceCapability, Dr as memoizePinnedFfmpeg, Dt as NcAlarmSensorsSchema, E as DEFAULT_TIMELAPSE_PREVIEW_TEXT, Ei as sleep$1, En as activateAlarmProfile, Er as mayWriteToLocation, Et as NcAlarmSensorsInputSchema, F as EVENT_KIND_BY_CAP, Fi as object, Fn as audioMetricsCapability, Fr as readTimelapseGeneratedAt, Ft as NcRulePatchSchema, G as MAX_ARCHIVED_DEBUG_NOTES, Gn as deriveDetailCropRect, Gr as ruleDeviceCompatCapQueries, Gt as NcSystemEventKindSchema, H as ListTrackRelationsInputSchema, Hi as union, Hn as cosineSimilarity$1, Hr as rootSupplyServes, Ht as NcSnoozeSchema, I as EVENT_OWNER_TYPES, Ii as partialRecord, In as audioModeOf, Ir as renameAlarmProfile, It as NcRuleSchema, J as MAX_TRACK_PURITIES, Jn as describeSystemEventDeliveryProblem, Jr as sceneMonitorCapability, Jt as NcTemplatePreviewSchema, K as MAX_BIRTH_DECISION_RECORDS, Kn as derivePostTriggeredAction, Kr as ruleGroupingApplies, Kt as NcTaxonomySchema, L as EXPORT_DENSE_MAX_RANGES, Li as record, Ln as buildEventKindDescriptor, Lr as resolveClipSearchMinScore, Lt as NcRuleTargetSchema, M as EMBEDDING_REQUIREMENTS_FAILED, Mi as literal, Mn as applyNonBlockingEdit, Mr as plateGalleryCapability, Mt as NcRuleActionSchema, N as EMBEDDING_REQUIREMENTS_UNAVAILABLE, Ni as looseObject, Nn as applySecureOnArmEdit, Nr as readDeviceStateFrom, Nt as NcRuleActionsSchema, O as DETECTION_PIPELINE_CAP_NAME, Oi as _enum, On as alarmDeviceSecureOnArm, Or as notificationRulesCapability, Ot as NcAlarmSettingsPatchSchema, P as EVENT_DENSITY_BATCH_MAX, Pi as number, Pn as assertTimelapseCadences, Pr as readMotionTriggeredBy, Pt as NcRuleInputSchema, Q as MotionTriggeredBySchema, Qn as embeddingRefusalOf, Qr as storageOccupancyCapability, R as FIRST_LEVEL_MACRO_CLASSES, Ri as strictObject, Rn as buildViewerLink, Rr as resolveLocationMode, Rt as NcRuleUpdateInputSchema, S as ClearTrackRelationResultSchema, Si as hydrateSchema, Sn as TrackRelationKindSchema, Sr as isSystemEventSummaryRule, St as NC_TAXONOMY, T as DEFAULT_EVENT_COLOR, Tn as TrackSourceSchema, Tr as linkedStepOf, Tt as NcAlarmPostTriggeredActionSchema, U as ListTrackRelationsResultSchema, Ui as unknown, Un as createAlarmProfile, Ur as ruleAutoTriggersAlarm, Ut as NcSnoozeSuppressedSchema, V as LabelAttributionSchema, Vi as tuple, Vn as coreBlockIdFromAddonId, Vt as NcSnoozeInputSchema, W as LockStateSchema, Wi as EventCategory, Wn as deleteAlarmProfile, Wr as ruleBelongsToAlarm$1, Wt as NcStateSummarySectionIdSchema, X as MOTION_CLOSE_AFTER_MS, Xr as sensorRaisedOf, Xt as OPS_LOG_DEFAULT_LIMIT, Y as MAX_TRACK_RELATIONS, Yn as describeSystemEventFamilyProblem, Yr as secureActuationFor, Yt as NcTemplateVarDescriptorSchema, Z as MediaFileKindEnum, Zr as servedTemplateVars, Zt as OpsLogEntrySchema, _ as BIRTH_DECISION_DEFAULT_LIMIT, _i as BaseAddon, _n as TRACK_RELATIONS_DEFAULT_LIMIT, _r as isMediaOwnerType, _t as NC_GROUPING_MIN_SEC, a as CLIP_EMBEDDING_STEP_ID, ai as systemEventSummarySplitOf, an as SCENE_DEFAULT_ANCHOR_THRESHOLD, ar as faceGalleryCapability, at as NC_ALARM_TRIGGERED_MAX_SEC, b as COCO_TO_MACRO, bi as customAction, bn as TimelapseRuleSchema, br as isScheduleActive, bt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, c as resolveClusterModelPin, ci as vectorDimFromBase64, cn as SCENE_DEFAULT_QUIET_SECONDS, cr as gifFromMp4Args, ct as NC_CLIP_POST_ROLL_DEFAULT_SEC, d as APPEARANCE_BLOCKS, di as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, dn as SCENE_RESET_RECAPTURES, dr as isAudioClassName, dt as NC_CONDITION_CATALOG, ei as syncActiveAlarmProfileSettings, en as RECORDING_SOURCE_CAMSTACK_ADDON, et as NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC, f as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, fi as canonicalHash, fn as SceneMonitorSchema, fr as isDefaultAlarmProfileId, ft as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, g as ArchivedDebugNoteSchema, gi as errMsg, gn as TIMELAPSE_DENSE_FLOOR_SEC, gr as isKnownTemplateVar, gt as NC_GROUPING_MAX_SEC, h as AlarmArmModeSchema, hi as DeviceType, hn as SetTrackRelationInputSchema, hr as isEventOwnerType, ht as NC_GROUPING_DEFAULT_SEC, ii as systemEventSummaryScheduleOf, in as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, ir as evictionPolicyOfLocation, it as NC_ALARM_SYSTEM_EVENT_KINDS, j as EMBEDDING_MODEL_FAILED, ji as discriminatedUnion, jn as applyModeDelaysEdits, jr as pipelineAnalyticsCapability, jt as NcLegacyRuleActionSequenceSchema, k as DeclaredDevices, ki as array, kn as alarmPanelCapability, kr as occupancyScope, kt as NcConditionDescriptorSchema, l as ALL_CAPABILITY_DEFINITIONS, li as viewerLinkBaseOf, ln as SCENE_DEFAULT_UNCOVERED_POLICY, lr as isAlarmControllableCap, lt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, m as AUDIO_MACRO_LABELS, mi as isSourceCap, mn as SetTrackPurityResultSchema, mr as isEventKindLabelKey, mt as NC_DEFAULT_SNOOZE_MINUTES, ni as systemEventFamilyProblems, nn as RetrainStatusSchema, nr as ensureAlarmProfiles, nt as NC_ALARM_DELAY_MAX_SEC, o as FACE_EMBEDDING_STEP_ID, oi as templateCarriesSummary, on as SCENE_DEFAULT_CHECK_INTERVAL_SEC, or as failureContributionCapability, ot as NC_AUDIO_CONFIRM_HITS_DEFAULT, p as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, pi as evaluateSensorEdge, pn as SetTrackPurityInputSchema, pr as isDetectionMacroClass, pt as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, q as MAX_BIRTH_LATENCY_PROXY_MS, qn as deriveRecordingMode, qr as ruleKindOf, qt as NcTemplatePreviewInputSchema, ri as systemEventFilterApplies, rn as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, rr as eventKindLabel, rt as NC_ALARM_NOTIFY_FIELDS, s as readClusterModelPinStrict, si as templateVarsFor, sn as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, sr as gifDeriveSpeed, st as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, t as BUILTIN_CLIP_MODELS, ti as systemEventDeliveryProblems, tn as RebuildObjectEmbeddingsRefusalSchema, tr as encodeVectorBase64, tt as NC_ALARM_DELAY_FIELDS, u as APPEARANCE_BIN_COUNT, ui as zoneAnalyticsCapability, un as SCENE_DIVERGED, ur as isAppLinkBase, ut as NC_CLIP_RETENTION_MAX_SEC, v as BaseDevice, vi as CamProfileSchema, vn as TimelapseRuleInputSchema, vr as isRetiredClearKey, vt as NC_SENSOR_TRANSITION_KINDS, w as DEFAULT_DETAIL_CROP_CONVENTION, wi as nodePin, wn as TrackSideSchema, wr as kebabToCamel, wt as NcAlarmConfigSchema, x as ClearTrackRelationInputSchema, xi as defineCustomActions, xn as TrackPuritySchema, xr as isSystemDelivery, xt as NC_TANK_SYSTEM_EVENT_KINDS, y as BirthDecisionRecordSchema, yi as createEvent, yn as TimelapseRulePatchSchema, yr as isRootSuppliableStepId, yt as NC_SNOOZE_MAX_MINUTES, z as FULL_IMAGE_BBOX, zi as string, zn as cloneAlarmSettings, zr as resolveModeDelays, zt as NcScheduleSchema } from "../clip-model-registry-CC-BJYiK.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";
@@ -13383,14 +13383,19 @@ function incomingFromSystemEvent(event, lookup) {
13383
13383
  case "agent.offline": {
13384
13384
  const nodeId = stringField(data, "agentId");
13385
13385
  if (nodeId === void 0) return null;
13386
- const kind = event.category.endsWith(".online") ? "node-online" : "node-offline";
13386
+ const online = event.category.endsWith(".online");
13387
+ const kind = online ? "node-online" : "node-offline";
13388
+ const reason = online ? void 0 : stringField(data, "reason");
13387
13389
  return systemSubject({
13388
13390
  facts,
13389
13391
  kind,
13390
13392
  subject: `node:${nodeId}`,
13391
13393
  titleKey: `system.${kind}.title`,
13392
- bodyKey: `system.${kind}.body.agent`,
13393
- textParams: { nodeId },
13394
+ bodyKey: reason === "connection-lost" || reason === "shutdown" ? `system.${kind}.body.agent.${reason}` : `system.${kind}.body.agent`,
13395
+ textParams: {
13396
+ nodeId,
13397
+ nodeName: stringField(data, "nodeName") ?? nodeId
13398
+ },
13394
13399
  nodeId
13395
13400
  });
13396
13401
  }
@@ -15476,10 +15481,13 @@ var en_default = {
15476
15481
  "system.stream-offline.title": "Stream offline — {{deviceName}}",
15477
15482
  "system.stream-offline.body": "Stream for camera {{deviceName}} is offline",
15478
15483
  "system.stream-offline.body.profiled": "{{profile}} stream for camera {{deviceName}} is offline",
15479
- "system.node-online.title": "Node online — {{nodeId}}",
15480
- "system.node-online.body.agent": "Agent {{nodeId}} is online",
15481
- "system.node-offline.title": "Node offline — {{nodeId}}",
15482
- "system.node-offline.body.agent": "Agent {{nodeId}} is offline",
15484
+ "system.node-online.title": "Node online — {{nodeName}}",
15485
+ "system.node-online.body.agent": "Agent {{nodeName}} is online",
15486
+ "system.node-online.body.agent.after": "Agent {{nodeName}} is back online after {{offlineFor}} offline",
15487
+ "system.node-offline.title": "Node offline — {{nodeName}}",
15488
+ "system.node-offline.body.agent": "Agent {{nodeName}} is offline",
15489
+ "system.node-offline.body.agent.connection-lost": "Agent {{nodeName}} lost its connection to the hub",
15490
+ "system.node-offline.body.agent.shutdown": "Agent {{nodeName}} shut down",
15483
15491
  "system.node-inference-unavailable.title": "Node cannot infer — {{nodeId}}",
15484
15492
  "system.node-inference-unavailable.body": "Node {{nodeId}} can no longer run inference — every enabled device is unavailable ({{devices}}). It will not receive cameras until one returns.",
15485
15493
  "system.addon-crash-loop.title": "Addon crash-loop — {{nodeId}}",
@@ -15838,10 +15846,13 @@ var it_default = {
15838
15846
  "system.stream-offline.title": "Stream offline — {{deviceName}}",
15839
15847
  "system.stream-offline.body": "Lo stream della camera {{deviceName}} è offline",
15840
15848
  "system.stream-offline.body.profiled": "Lo stream {{profile}} della camera {{deviceName}} è offline",
15841
- "system.node-online.title": "Nodo online — {{nodeId}}",
15842
- "system.node-online.body.agent": "L'agent {{nodeId}} è online",
15843
- "system.node-offline.title": "Nodo offline — {{nodeId}}",
15844
- "system.node-offline.body.agent": "L'agent {{nodeId}} è offline",
15849
+ "system.node-online.title": "Nodo online — {{nodeName}}",
15850
+ "system.node-online.body.agent": "L'agent {{nodeName}} è online",
15851
+ "system.node-online.body.agent.after": "L'agent {{nodeName}} è di nuovo online dopo {{offlineFor}} offline",
15852
+ "system.node-offline.title": "Nodo offline — {{nodeName}}",
15853
+ "system.node-offline.body.agent": "L'agent {{nodeName}} è offline",
15854
+ "system.node-offline.body.agent.connection-lost": "L'agent {{nodeName}} ha perso la connessione con l'hub",
15855
+ "system.node-offline.body.agent.shutdown": "L'agent {{nodeName}} si è spento",
15845
15856
  "system.node-inference-unavailable.title": "Nodo senza inferenza — {{nodeId}}",
15846
15857
  "system.node-inference-unavailable.body": "Il nodo {{nodeId}} non può più eseguire inferenza — tutti i dispositivi abilitati non sono disponibili ({{devices}}). Non riceverà camere finché uno non torna disponibile.",
15847
15858
  "system.addon-crash-loop.title": "Crash-loop addon — {{nodeId}}",
@@ -18470,6 +18481,7 @@ function planRuleTest(input) {
18470
18481
  deviceId: String(systemScope.deviceId ?? deviceId),
18471
18482
  deviceName: `camera ${String(systemScope.deviceId ?? deviceId)}`,
18472
18483
  nodeId: systemScope.nodeId ?? "hub",
18484
+ nodeName: systemScope.nodeId ?? "hub",
18473
18485
  packageName: systemScope.packageName ?? "@camstack/server",
18474
18486
  currentVersion: "1.0.0",
18475
18487
  latestVersion: "1.0.1",
@@ -26949,6 +26961,7 @@ function buildTemplateVars(texts, entry, deviceName, zoneLabels, timeZone) {
26949
26961
  currentVersion: typeof systemEvent?.data["currentVersion"] === "string" ? systemEvent.data["currentVersion"] : "",
26950
26962
  latestVersion: typeof systemEvent?.data["latestVersion"] === "string" ? systemEvent.data["latestVersion"] : "",
26951
26963
  ...systemEvent?.deviceId !== void 0 ? { deviceName: String(systemEvent.deviceId) } : {},
26964
+ ...systemEvent?.nodeId !== void 0 && (systemEvent.kind === "node-online" || systemEvent.kind === "node-offline") ? { nodeName: systemEvent.nodeId } : {},
26952
26965
  ...systemEvent?.textParams
26953
26966
  };
26954
26967
  }
@@ -28899,6 +28912,11 @@ var NcJudgedTracks = class {
28899
28912
  //#endregion
28900
28913
  //#region src/notification-center/liveness-holdoff.ts
28901
28914
  var DEVICE_LIVENESS_HOLDOFF_MS = 5 * 6e4;
28915
+ /** Grace before a node offline (or its recovery) is announced — see the docblock. */
28916
+ var NODE_LIVENESS_HOLDOFF_MS = 6e4;
28917
+ function isNodeLivenessKind(kind) {
28918
+ return kind === "node-online" || kind === "node-offline";
28919
+ }
28902
28920
  function isDeviceLivenessKind(kind) {
28903
28921
  return kind === "device-online" || kind === "device-offline" || kind === "camera-online" || kind === "camera-offline";
28904
28922
  }
@@ -28910,6 +28928,10 @@ var DeviceLivenessHoldoff = class {
28910
28928
  pending = /* @__PURE__ */ new Map();
28911
28929
  lastNotified = /* @__PURE__ */ new Map();
28912
28930
  timers = /* @__PURE__ */ new Map();
28931
+ /** First divergence from the last notified state, per subject (cleared on a bounce back). */
28932
+ departedAt = /* @__PURE__ */ new Map();
28933
+ /** When the last notified state began, per subject. */
28934
+ stateSince = /* @__PURE__ */ new Map();
28913
28935
  delayMs;
28914
28936
  schedule;
28915
28937
  cancel;
@@ -28922,6 +28944,11 @@ var DeviceLivenessHoldoff = class {
28922
28944
  /** The seeded (or last delivered) state — a bounce back to this is silence. */
28923
28945
  remember(subject, state) {
28924
28946
  this.lastNotified.set(subject, state);
28947
+ const pending = this.timers.get(subject);
28948
+ if (pending !== void 0) this.cancel(pending);
28949
+ this.timers.delete(subject);
28950
+ this.pending.delete(subject);
28951
+ this.departedAt.delete(subject);
28925
28952
  }
28926
28953
  /** Directory-seeded rows never pass through {@link remember}; first flip does. */
28927
28954
  rememberIfAbsent(subject, state) {
@@ -28930,6 +28957,7 @@ var DeviceLivenessHoldoff = class {
28930
28957
  /** A real ledger flip. Starts / resets the trailing timer. */
28931
28958
  arm(subject, incoming) {
28932
28959
  this.pending.set(subject, incoming);
28960
+ this.trackDeparture(subject, incoming);
28933
28961
  const existing = this.timers.get(subject);
28934
28962
  if (existing !== void 0) this.cancel(existing);
28935
28963
  if (this.delayMs <= 0) {
@@ -28947,6 +28975,16 @@ var DeviceLivenessHoldoff = class {
28947
28975
  for (const id of this.timers.values()) this.cancel(id);
28948
28976
  this.timers.clear();
28949
28977
  this.pending.clear();
28978
+ this.departedAt.clear();
28979
+ }
28980
+ trackDeparture(subject, incoming) {
28981
+ const kind = incoming.subject.systemEvent?.kind;
28982
+ if (kind === void 0) return;
28983
+ if (this.lastNotified.get(subject) === livenessStateOfKind(kind)) {
28984
+ this.departedAt.delete(subject);
28985
+ return;
28986
+ }
28987
+ if (!this.departedAt.has(subject)) this.departedAt.set(subject, incoming.subject.timestamp);
28950
28988
  }
28951
28989
  fire(subject) {
28952
28990
  const incoming = this.pending.get(subject);
@@ -28957,10 +28995,64 @@ var DeviceLivenessHoldoff = class {
28957
28995
  const state = livenessStateOfKind(kind);
28958
28996
  if (this.lastNotified.get(subject) === state) return;
28959
28997
  this.lastNotified.set(subject, state);
28960
- this.flush(incoming);
28998
+ const since = this.departedAt.get(subject) ?? incoming.subject.timestamp;
28999
+ const previousSince = this.stateSince.get(subject);
29000
+ this.departedAt.delete(subject);
29001
+ this.stateSince.set(subject, since);
29002
+ this.flush(incoming, previousSince !== void 0 ? {
29003
+ since,
29004
+ previousSince
29005
+ } : { since });
28961
29006
  }
28962
29007
  };
28963
29008
  //#endregion
29009
+ //#region src/notification-center/node-liveness-text.ts
29010
+ /** "45 s", "12 min", "2 h 5 min", "3 d 4 h" — unit symbols read the same in en and it. */
29011
+ function formatOutageDuration(ms) {
29012
+ const totalSec = Math.max(0, Math.round(ms / 1e3));
29013
+ if (totalSec < 60) return `${totalSec} s`;
29014
+ const totalMin = Math.floor(totalSec / 60);
29015
+ if (totalMin < 60) return `${totalMin} min`;
29016
+ const totalHours = Math.floor(totalMin / 60);
29017
+ const minutes = totalMin % 60;
29018
+ if (totalHours < 24) return minutes > 0 ? `${totalHours} h ${minutes} min` : `${totalHours} h`;
29019
+ const days = Math.floor(totalHours / 24);
29020
+ const hours = totalHours % 24;
29021
+ return hours > 0 ? `${days} d ${hours} h` : `${days} d`;
29022
+ }
29023
+ /**
29024
+ * Freeze the post-hold-off facts onto a node liveness event.
29025
+ *
29026
+ * - `nodeName`: the display name, when the directory knew one.
29027
+ * - `offlineFor` (recovery only, and only when the outage START is known — a
29028
+ * state that was merely seeded after a restart has none): the outage length,
29029
+ * with the `.after` body variant that says it.
29030
+ */
29031
+ function withNodeLivenessFacts(incoming, facts) {
29032
+ const system = incoming.subject.systemEvent;
29033
+ if (system === void 0) return incoming;
29034
+ const { since, previousSince } = facts.timing;
29035
+ const recovered = system.kind === "node-online" && previousSince !== void 0;
29036
+ const name = facts.nodeName !== void 0 && facts.nodeName.length > 0 ? facts.nodeName : void 0;
29037
+ if (!recovered && name === void 0) return incoming;
29038
+ const textParams = {
29039
+ ...system.textParams,
29040
+ ...name !== void 0 ? { nodeName: name } : {},
29041
+ ...recovered ? { offlineFor: formatOutageDuration(since - previousSince) } : {}
29042
+ };
29043
+ return {
29044
+ ...incoming,
29045
+ subject: {
29046
+ ...incoming.subject,
29047
+ systemEvent: {
29048
+ ...system,
29049
+ ...recovered ? { bodyKey: "system.node-online.body.agent.after" } : {},
29050
+ textParams
29051
+ }
29052
+ }
29053
+ };
29054
+ }
29055
+ //#endregion
28964
29056
  //#region src/notification-center/linked-camera-media.ts
28965
29057
  /** ADR 0632: absent = mosaic, applied HERE and never as a Zod default. */
28966
29058
  function linkedCameraMode(media) {
@@ -38280,6 +38372,8 @@ var ALARM_PANEL_CAP = alarmPanelCapability.name;
38280
38372
  /** Bound on remembered departure fallbacks (D691 review M3) — the shelf's own
38281
38373
  * capture bound. */
38282
38374
  var NC_DEPARTURE_FALLBACK_MAX = 64;
38375
+ /** Bound on the post-hold-off `nodes.directory` read that names a node. */
38376
+ var NODE_NAME_READ_TIMEOUT_MS = 3e3;
38283
38377
  /** Bound on the sampled "departed track alias resolution failed" memo
38284
38378
  * (D683 × D691) — the alias-resolution call sites' own shape. */
38285
38379
  var NC_DEPARTED_ALIAS_FAILURE_MEMO_MAX = 256;
@@ -39076,6 +39170,8 @@ var NotificationCenter = class {
39076
39170
  addonUpdates;
39077
39171
  /** Trailing 5 min hold-off for camera liveness flaps (plugin restart). */
39078
39172
  livenessHoldoff;
39173
+ /** Trailing 60 s hold-off for node liveness (reconnect storms). */
39174
+ nodeLivenessHoldoff;
39079
39175
  /**
39080
39176
  * Serialized evaluation chain. The persist hooks are fire-and-forget for
39081
39177
  * the frame path, but two concurrent evaluations of the same device
@@ -39777,6 +39873,7 @@ var NotificationCenter = class {
39777
39873
  if (incoming !== null) this.consumeEvent(incoming);
39778
39874
  });
39779
39875
  this.livenessHoldoff = new DeviceLivenessHoldoff((event) => this.consumeEvent(event), { delayMs: deps.livenessHoldoffMs ?? (process.env.VITEST ? 0 : DEVICE_LIVENESS_HOLDOFF_MS) });
39876
+ this.nodeLivenessHoldoff = new DeviceLivenessHoldoff((event, timing) => this.consumeNodeLiveness(event, timing), { delayMs: deps.nodeLivenessHoldoffMs ?? (process.env.VITEST ? 0 : NODE_LIVENESS_HOLDOFF_MS) });
39780
39877
  this.actionRunner = deps.actuate !== void 0 && deps.isCapDeviceScoped !== void 0 ? new NcRuleActionRunner({
39781
39878
  logger: this.logger.child("actions"),
39782
39879
  actuate: (input) => this.actuateWithAttribution(input),
@@ -40338,6 +40435,7 @@ var NotificationCenter = class {
40338
40435
  this.linkedStills?.clear();
40339
40436
  this.addonUpdates.dispose();
40340
40437
  this.livenessHoldoff.dispose();
40438
+ this.nodeLivenessHoldoff.dispose();
40341
40439
  this.evaluationActive = false;
40342
40440
  this.hydration = null;
40343
40441
  }
@@ -40410,6 +40508,42 @@ var NotificationCenter = class {
40410
40508
  this.scheduleEvaluation(event);
40411
40509
  }
40412
40510
  /**
40511
+ * A node liveness flip that survived its hold-off: freeze the node's display
40512
+ * name and (on a recovery) the outage length onto it, then consume it.
40513
+ *
40514
+ * The name read is bounded and best-effort — a slow or failed directory
40515
+ * costs the friendly name, never the notification (the intake already froze
40516
+ * the id as `nodeName`).
40517
+ */
40518
+ consumeNodeLiveness(event, timing) {
40519
+ const nodeId = event.subject.systemEvent?.nodeId;
40520
+ this.readNodeDisplayName(nodeId).then((nodeName) => {
40521
+ this.consumeEvent(withNodeLivenessFacts(event, nodeName !== void 0 ? {
40522
+ timing,
40523
+ nodeName
40524
+ } : { timing }));
40525
+ });
40526
+ }
40527
+ async readNodeDisplayName(nodeId) {
40528
+ const read = this.deps.readNodeDirectory;
40529
+ if (nodeId === void 0 || read === void 0) return void 0;
40530
+ let timer;
40531
+ try {
40532
+ const timeout = new Promise((resolve) => {
40533
+ timer = setTimeout(() => resolve(void 0), NODE_NAME_READ_TIMEOUT_MS);
40534
+ });
40535
+ return (await Promise.race([read(), timeout]))?.nodes.find((node) => node.id === nodeId)?.name;
40536
+ } catch (err) {
40537
+ this.logger.debug("node display name unavailable — notifying with the id", { meta: {
40538
+ nodeId,
40539
+ error: String(err)
40540
+ } });
40541
+ return;
40542
+ } finally {
40543
+ if (timer !== void 0) clearTimeout(timer);
40544
+ }
40545
+ }
40546
+ /**
40413
40547
  * The same consumption, awaited, with the verdict returned. Identical code
40414
40548
  * path — {@link consumeEvent} is this function with the report discarded — so
40415
40549
  * a producer that reads the report is testing what the live producers do, not
@@ -41342,8 +41476,9 @@ var NotificationCenter = class {
41342
41476
  if (observation !== null) {
41343
41477
  const previous = this.liveness.stateOf(observation.subject);
41344
41478
  const verdict = this.liveness.observe(observation, this.now());
41479
+ const holdoff = isNodeLivenessKind(observation.kind) ? this.nodeLivenessHoldoff : this.livenessHoldoff;
41345
41480
  if (verdict === "seeded") {
41346
- this.livenessHoldoff.remember(observation.subject, observation.state);
41481
+ holdoff.remember(observation.subject, observation.state);
41347
41482
  this.reportLivenessDrop(incoming, verdict);
41348
41483
  return;
41349
41484
  }
@@ -41351,9 +41486,9 @@ var NotificationCenter = class {
41351
41486
  this.reportLivenessDrop(incoming, verdict);
41352
41487
  return;
41353
41488
  }
41354
- if (previous !== void 0) this.livenessHoldoff.rememberIfAbsent(observation.subject, previous);
41489
+ if (previous !== void 0) holdoff.rememberIfAbsent(observation.subject, previous);
41355
41490
  if (observation.state === "online" && !this.deviceAlertSettings().notifyRecovery) {
41356
- this.livenessHoldoff.remember(observation.subject, observation.state);
41491
+ holdoff.remember(observation.subject, observation.state);
41357
41492
  this.logger.debug("device came back — the all-clear is switched off", {
41358
41493
  tags: incoming.log().tags,
41359
41494
  meta: {
@@ -41363,8 +41498,8 @@ var NotificationCenter = class {
41363
41498
  });
41364
41499
  return;
41365
41500
  }
41366
- if (isDeviceLivenessKind(observation.kind)) {
41367
- this.livenessHoldoff.arm(observation.subject, incoming);
41501
+ if (isDeviceLivenessKind(observation.kind) || isNodeLivenessKind(observation.kind)) {
41502
+ holdoff.arm(observation.subject, incoming);
41368
41503
  return;
41369
41504
  }
41370
41505
  } else if (system !== void 0 && (system.kind.startsWith("device-") || system.kind.startsWith("stream-"))) {
@@ -83412,6 +83547,260 @@ function withDatasetVersion(manifest, version) {
83412
83547
  };
83413
83548
  }
83414
83549
  //#endregion
83550
+ //#region src/pipeline-analytics/retrain/retrain-dataset-version-store.ts
83551
+ /**
83552
+ * @durable class=ledger owner=pipeline-analytics
83553
+ * write="one row per dataset version the operator creates: a frozen frameIds list,
83554
+ * the sha256 of the annotations those frames carried, the export parameters and
83555
+ * the counts at creation. Never updated after the insert — a different selection
83556
+ * is a new version."
83557
+ * retention="none — rows leave only when the operator deletes the version. The
83558
+ * frames themselves are owned by the retrain corpus; deleting a version deletes no
83559
+ * frame, and deleting a frame does not edit a version (verify reports it missing)."
83560
+ */
83561
+ var RETRAIN_DATASET_VERSIONS_COLLECTION = "pipeline-analytics:retrain-dataset-versions";
83562
+ var COLUMNS = [
83563
+ {
83564
+ name: "id",
83565
+ type: "TEXT",
83566
+ primaryKey: true,
83567
+ notNull: true
83568
+ },
83569
+ {
83570
+ name: "name",
83571
+ type: "TEXT",
83572
+ notNull: true
83573
+ },
83574
+ {
83575
+ name: "nameKey",
83576
+ type: "TEXT",
83577
+ notNull: true
83578
+ },
83579
+ {
83580
+ name: "createdAt",
83581
+ type: "INTEGER",
83582
+ notNull: true
83583
+ },
83584
+ {
83585
+ name: "selection",
83586
+ type: "JSON",
83587
+ notNull: true
83588
+ },
83589
+ {
83590
+ name: "layout",
83591
+ type: "TEXT",
83592
+ notNull: true
83593
+ },
83594
+ {
83595
+ name: "completeOnly",
83596
+ type: "BOOLEAN",
83597
+ notNull: true
83598
+ },
83599
+ {
83600
+ name: "frameIds",
83601
+ type: "JSON",
83602
+ notNull: true
83603
+ },
83604
+ {
83605
+ name: "annotationHash",
83606
+ type: "TEXT",
83607
+ notNull: true
83608
+ },
83609
+ {
83610
+ name: "manifestSchema",
83611
+ type: "TEXT",
83612
+ notNull: true
83613
+ },
83614
+ {
83615
+ name: "stats",
83616
+ type: "JSON",
83617
+ notNull: true
83618
+ }
83619
+ ];
83620
+ var INDEXES = [{
83621
+ name: "idx_retrain_dataset_versions_name_key",
83622
+ columns: ["nameKey"],
83623
+ unique: true
83624
+ }, {
83625
+ name: "idx_retrain_dataset_versions_created",
83626
+ columns: ["createdAt"]
83627
+ }];
83628
+ function summaryOfVersion(version) {
83629
+ const { frameIds, ...rest } = version;
83630
+ return {
83631
+ ...rest,
83632
+ frameCount: frameIds.length
83633
+ };
83634
+ }
83635
+ var nameKeyOf = (name) => name.trim().toLowerCase();
83636
+ function numberList(value) {
83637
+ return Array.isArray(value) ? value.filter((v) => typeof v === "number") : void 0;
83638
+ }
83639
+ function stringList(value) {
83640
+ return Array.isArray(value) ? value.filter((v) => typeof v === "string") : void 0;
83641
+ }
83642
+ function parseJson(value) {
83643
+ if (typeof value !== "string") return value;
83644
+ try {
83645
+ return JSON.parse(value);
83646
+ } catch {
83647
+ return;
83648
+ }
83649
+ }
83650
+ function asRecord(value) {
83651
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
83652
+ }
83653
+ var EMPTY_STATS = {
83654
+ frames: 0,
83655
+ completeFrames: 0,
83656
+ completeness: 0,
83657
+ subjects: 0,
83658
+ modelErrors: 0,
83659
+ hardNegatives: 0,
83660
+ cells: [],
83661
+ cameras: []
83662
+ };
83663
+ /** A stored stats object, trusted only as far as its shape. */
83664
+ function readStats(value) {
83665
+ const raw = asRecord(parseJson(value));
83666
+ const num = (k) => typeof raw[k] === "number" ? raw[k] : 0;
83667
+ const cells = Array.isArray(raw["cells"]) ? raw["cells"] : [];
83668
+ const cameras = Array.isArray(raw["cameras"]) ? raw["cameras"] : [];
83669
+ return {
83670
+ ...EMPTY_STATS,
83671
+ frames: num("frames"),
83672
+ completeFrames: num("completeFrames"),
83673
+ completeness: num("completeness"),
83674
+ subjects: num("subjects"),
83675
+ modelErrors: num("modelErrors"),
83676
+ hardNegatives: num("hardNegatives"),
83677
+ cells: cells.map((c) => {
83678
+ const r = asRecord(c);
83679
+ const lighting = r["lighting"] === "day" || r["lighting"] === "night" ? r["lighting"] : "unknown";
83680
+ return {
83681
+ macroClass: String(r["macroClass"] ?? ""),
83682
+ deviceId: Number(r["deviceId"] ?? 0),
83683
+ lighting,
83684
+ subjects: Number(r["subjects"] ?? 0),
83685
+ frames: Number(r["frames"] ?? 0)
83686
+ };
83687
+ }),
83688
+ cameras: cameras.map((c) => {
83689
+ const r = asRecord(c);
83690
+ return {
83691
+ deviceId: Number(r["deviceId"] ?? 0),
83692
+ frames: Number(r["frames"] ?? 0),
83693
+ completeFrames: Number(r["completeFrames"] ?? 0)
83694
+ };
83695
+ })
83696
+ };
83697
+ }
83698
+ var RetrainDatasetVersionStore = class {
83699
+ store;
83700
+ logger;
83701
+ constructor(deps) {
83702
+ this.store = deps.store;
83703
+ this.logger = deps.logger;
83704
+ }
83705
+ static async declare(store) {
83706
+ await store.declareCollection.mutate({
83707
+ collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
83708
+ columns: [...COLUMNS],
83709
+ indexes: [...INDEXES]
83710
+ });
83711
+ }
83712
+ insert = async (version) => {
83713
+ await this.store.set.mutate({
83714
+ collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
83715
+ key: version.id,
83716
+ value: {
83717
+ name: version.name,
83718
+ nameKey: nameKeyOf(version.name),
83719
+ createdAt: version.createdAt,
83720
+ selection: version.selection,
83721
+ layout: version.layout,
83722
+ completeOnly: version.completeOnly,
83723
+ frameIds: version.frameIds,
83724
+ annotationHash: version.annotationHash,
83725
+ manifestSchema: version.manifestSchema,
83726
+ stats: version.stats
83727
+ }
83728
+ });
83729
+ };
83730
+ get = async (id) => {
83731
+ try {
83732
+ const row = await this.store.get.query({
83733
+ collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
83734
+ key: id
83735
+ });
83736
+ return row === null || row === void 0 ? null : this.toVersion(id, asRecord(row));
83737
+ } catch (err) {
83738
+ this.logger.warn("retrain dataset version read failed", { meta: {
83739
+ versionId: id,
83740
+ error: String(err)
83741
+ } });
83742
+ return null;
83743
+ }
83744
+ };
83745
+ list = async () => {
83746
+ return (await this.listRows()).map(summaryOfVersion);
83747
+ };
83748
+ /** Every row WITH its frame list — the migration into `training` reads this. */
83749
+ listRows = async () => {
83750
+ try {
83751
+ return (await this.store.query.query({
83752
+ collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
83753
+ filter: { orderBy: {
83754
+ field: "createdAt",
83755
+ direction: "desc"
83756
+ } }
83757
+ })).map((r) => this.toVersion(r.id, asRecord(r.data)));
83758
+ } catch (err) {
83759
+ this.logger.warn("retrain dataset versions list failed", { meta: { error: String(err) } });
83760
+ return [];
83761
+ }
83762
+ };
83763
+ findIdByName = async (name) => {
83764
+ return (await this.store.query.query({
83765
+ collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
83766
+ filter: {
83767
+ where: { nameKey: nameKeyOf(name) },
83768
+ limit: 1
83769
+ }
83770
+ }))[0]?.id ?? null;
83771
+ };
83772
+ remove = async (id) => {
83773
+ if (await this.get(id) === null) return false;
83774
+ await this.store.delete.mutate({
83775
+ collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
83776
+ key: id
83777
+ });
83778
+ return true;
83779
+ };
83780
+ toVersion(id, data) {
83781
+ const selection = asRecord(parseJson(data["selection"]));
83782
+ const deviceIds = numberList(selection["deviceIds"]);
83783
+ const collectionIds = stringList(selection["collectionIds"]);
83784
+ const frameIdsSel = stringList(selection["frameIds"]);
83785
+ return {
83786
+ id,
83787
+ name: String(data["name"] ?? ""),
83788
+ createdAt: Number(data["createdAt"] ?? 0),
83789
+ selection: {
83790
+ ...deviceIds !== void 0 ? { deviceIds } : {},
83791
+ ...collectionIds !== void 0 ? { collectionIds } : {},
83792
+ ...frameIdsSel !== void 0 ? { frameIds: frameIdsSel } : {}
83793
+ },
83794
+ layout: data["layout"] === "crops" ? "crops" : "frames",
83795
+ completeOnly: data["completeOnly"] === true || data["completeOnly"] === 1,
83796
+ frameIds: stringList(parseJson(data["frameIds"])) ?? [],
83797
+ annotationHash: String(data["annotationHash"] ?? ""),
83798
+ manifestSchema: String(data["manifestSchema"] ?? ""),
83799
+ stats: readStats(data["stats"])
83800
+ };
83801
+ }
83802
+ };
83803
+ //#endregion
83415
83804
  //#region src/pipeline-analytics/retrain/retrain-geometry.ts
83416
83805
  function clamp01(v) {
83417
83806
  return v < 0 ? 0 : v > 1 ? 1 : v;
@@ -83700,13 +84089,6 @@ function filterOf(selection, completeOnly) {
83700
84089
  ...completeOnly ? { completeOnly: true } : {}
83701
84090
  };
83702
84091
  }
83703
- function summaryOf(version) {
83704
- const { frameIds, ...rest } = version;
83705
- return {
83706
- ...rest,
83707
- frameCount: frameIds.length
83708
- };
83709
- }
83710
84092
  var RetrainDatasetService = class {
83711
84093
  deps;
83712
84094
  constructor(deps) {
@@ -83759,11 +84141,11 @@ var RetrainDatasetService = class {
83759
84141
  } });
83760
84142
  return {
83761
84143
  kind: "created",
83762
- version: summaryOf(version)
84144
+ version: summaryOfVersion(version)
83763
84145
  };
83764
84146
  }
83765
84147
  async list() {
83766
- return (await this.deps.versions.list()).map(summaryOf);
84148
+ return this.deps.versions.list();
83767
84149
  }
83768
84150
  async delete(input) {
83769
84151
  const deleted = await this.deps.versions.remove(input.id);
@@ -83821,246 +84203,62 @@ var RetrainDatasetService = class {
83821
84203
  }
83822
84204
  };
83823
84205
  //#endregion
83824
- //#region src/pipeline-analytics/retrain/retrain-dataset-version-store.ts
83825
- /**
83826
- * @durable class=ledger owner=pipeline-analytics
83827
- * write="one row per dataset version the operator creates: a frozen frameIds list,
83828
- * the sha256 of the annotations those frames carried, the export parameters and
83829
- * the counts at creation. Never updated after the insert — a different selection
83830
- * is a new version."
83831
- * retention="none — rows leave only when the operator deletes the version. The
83832
- * frames themselves are owned by the retrain corpus; deleting a version deletes no
83833
- * frame, and deleting a frame does not edit a version (verify reports it missing)."
83834
- */
83835
- var RETRAIN_DATASET_VERSIONS_COLLECTION = "pipeline-analytics:retrain-dataset-versions";
83836
- var COLUMNS = [
83837
- {
83838
- name: "id",
83839
- type: "TEXT",
83840
- primaryKey: true,
83841
- notNull: true
83842
- },
83843
- {
83844
- name: "name",
83845
- type: "TEXT",
83846
- notNull: true
83847
- },
83848
- {
83849
- name: "nameKey",
83850
- type: "TEXT",
83851
- notNull: true
83852
- },
83853
- {
83854
- name: "createdAt",
83855
- type: "INTEGER",
83856
- notNull: true
83857
- },
83858
- {
83859
- name: "selection",
83860
- type: "JSON",
83861
- notNull: true
83862
- },
83863
- {
83864
- name: "layout",
83865
- type: "TEXT",
83866
- notNull: true
83867
- },
83868
- {
83869
- name: "completeOnly",
83870
- type: "BOOLEAN",
83871
- notNull: true
83872
- },
83873
- {
83874
- name: "frameIds",
83875
- type: "JSON",
83876
- notNull: true
83877
- },
83878
- {
83879
- name: "annotationHash",
83880
- type: "TEXT",
83881
- notNull: true
83882
- },
83883
- {
83884
- name: "manifestSchema",
83885
- type: "TEXT",
83886
- notNull: true
83887
- },
83888
- {
83889
- name: "stats",
83890
- type: "JSON",
83891
- notNull: true
83892
- }
83893
- ];
83894
- var INDEXES = [{
83895
- name: "idx_retrain_dataset_versions_name_key",
83896
- columns: ["nameKey"],
83897
- unique: true
83898
- }, {
83899
- name: "idx_retrain_dataset_versions_created",
83900
- columns: ["createdAt"]
83901
- }];
83902
- var nameKeyOf = (name) => name.trim().toLowerCase();
83903
- function numberList(value) {
83904
- return Array.isArray(value) ? value.filter((v) => typeof v === "number") : void 0;
83905
- }
83906
- function stringList(value) {
83907
- return Array.isArray(value) ? value.filter((v) => typeof v === "string") : void 0;
83908
- }
83909
- function parseJson(value) {
83910
- if (typeof value !== "string") return value;
83911
- try {
83912
- return JSON.parse(value);
83913
- } catch {
83914
- return;
83915
- }
83916
- }
83917
- function asRecord(value) {
83918
- return value !== null && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
83919
- }
83920
- var EMPTY_STATS = {
83921
- frames: 0,
83922
- completeFrames: 0,
83923
- completeness: 0,
83924
- subjects: 0,
83925
- modelErrors: 0,
83926
- hardNegatives: 0,
83927
- cells: [],
83928
- cameras: []
83929
- };
83930
- /** A stored stats object, trusted only as far as its shape. */
83931
- function readStats(value) {
83932
- const raw = asRecord(parseJson(value));
83933
- const num = (k) => typeof raw[k] === "number" ? raw[k] : 0;
83934
- const cells = Array.isArray(raw["cells"]) ? raw["cells"] : [];
83935
- const cameras = Array.isArray(raw["cameras"]) ? raw["cameras"] : [];
83936
- return {
83937
- ...EMPTY_STATS,
83938
- frames: num("frames"),
83939
- completeFrames: num("completeFrames"),
83940
- completeness: num("completeness"),
83941
- subjects: num("subjects"),
83942
- modelErrors: num("modelErrors"),
83943
- hardNegatives: num("hardNegatives"),
83944
- cells: cells.map((c) => {
83945
- const r = asRecord(c);
83946
- const lighting = r["lighting"] === "day" || r["lighting"] === "night" ? r["lighting"] : "unknown";
83947
- return {
83948
- macroClass: String(r["macroClass"] ?? ""),
83949
- deviceId: Number(r["deviceId"] ?? 0),
83950
- lighting,
83951
- subjects: Number(r["subjects"] ?? 0),
83952
- frames: Number(r["frames"] ?? 0)
83953
- };
83954
- }),
83955
- cameras: cameras.map((c) => {
83956
- const r = asRecord(c);
83957
- return {
83958
- deviceId: Number(r["deviceId"] ?? 0),
83959
- frames: Number(r["frames"] ?? 0),
83960
- completeFrames: Number(r["completeFrames"] ?? 0)
83961
- };
83962
- })
83963
- };
83964
- }
83965
- var RetrainDatasetVersionStore = class {
83966
- store;
83967
- logger;
84206
+ //#region src/pipeline-analytics/retrain/retrain-dataset-version-routing.ts
84207
+ var RoutingDatasetVersionRepository = class {
84208
+ deps;
84209
+ lastPresence = false;
84210
+ migrated = false;
84211
+ migration = null;
83968
84212
  constructor(deps) {
83969
- this.store = deps.store;
83970
- this.logger = deps.logger;
83971
- }
83972
- static async declare(store) {
83973
- await store.declareCollection.mutate({
83974
- collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
83975
- columns: [...COLUMNS],
83976
- indexes: [...INDEXES]
83977
- });
84213
+ this.deps = deps;
83978
84214
  }
83979
84215
  insert = async (version) => {
83980
- await this.store.set.mutate({
83981
- collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
83982
- key: version.id,
83983
- value: {
83984
- name: version.name,
83985
- nameKey: nameKeyOf(version.name),
83986
- createdAt: version.createdAt,
83987
- selection: version.selection,
83988
- layout: version.layout,
83989
- completeOnly: version.completeOnly,
83990
- frameIds: version.frameIds,
83991
- annotationHash: version.annotationHash,
83992
- manifestSchema: version.manifestSchema,
83993
- stats: version.stats
83994
- }
83995
- });
84216
+ if (await this.route() === "local") {
84217
+ await this.deps.local.insert(version);
84218
+ return;
84219
+ }
84220
+ await this.deps.training.put(version);
83996
84221
  };
83997
- get = async (id) => {
84222
+ get = async (id) => await this.route() === "local" ? this.deps.local.get(id) : this.deps.training.get(id);
84223
+ list = async () => await this.route() === "local" ? this.deps.local.list() : this.deps.training.list();
84224
+ findIdByName = async (name) => await this.route() === "local" ? this.deps.local.findIdByName(name) : this.deps.training.findIdByName(name);
84225
+ remove = async (id) => await this.route() === "local" ? this.deps.local.remove(id) : this.deps.training.remove(id);
84226
+ async route() {
84227
+ if (!await this.presence()) return "local";
84228
+ if (this.migrated) return "training";
84229
+ return await this.migrateOnce() ? "training" : "local";
84230
+ }
84231
+ async presence() {
84232
+ let answer;
83998
84233
  try {
83999
- const row = await this.store.get.query({
84000
- collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
84001
- key: id
84002
- });
84003
- return row === null || row === void 0 ? null : this.toVersion(id, asRecord(row));
84004
- } catch (err) {
84005
- this.logger.warn("retrain dataset version read failed", { meta: {
84006
- versionId: id,
84007
- error: String(err)
84008
- } });
84009
- return null;
84234
+ answer = await this.deps.trainingPresent();
84235
+ } catch {
84236
+ answer = null;
84010
84237
  }
84011
- };
84012
- list = async () => {
84238
+ if (answer !== null) this.lastPresence = answer;
84239
+ return this.lastPresence;
84240
+ }
84241
+ migrateOnce() {
84242
+ if (this.migration === null) this.migration = this.migrate().finally(() => {
84243
+ this.migration = null;
84244
+ });
84245
+ return this.migration;
84246
+ }
84247
+ async migrate() {
84248
+ const { local, training, logger } = this.deps;
84013
84249
  try {
84014
- return (await this.store.query.query({
84015
- collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
84016
- filter: { orderBy: {
84017
- field: "createdAt",
84018
- direction: "desc"
84019
- } }
84020
- })).map((r) => this.toVersion(r.id, asRecord(r.data)));
84250
+ const rows = await local.listRows();
84251
+ for (const row of rows) {
84252
+ await training.put(row);
84253
+ await local.remove(row.id);
84254
+ }
84255
+ this.migrated = true;
84256
+ if (rows.length > 0) logger.info("retrain dataset versions moved to the training capability", { meta: { versions: rows.length } });
84257
+ return true;
84021
84258
  } catch (err) {
84022
- this.logger.warn("retrain dataset versions list failed", { meta: { error: String(err) } });
84023
- return [];
84259
+ logger.warn("retrain dataset versions could not move to training yet — kept local", { meta: { error: err instanceof Error ? err.message : String(err) } });
84260
+ return false;
84024
84261
  }
84025
- };
84026
- findIdByName = async (name) => {
84027
- return (await this.store.query.query({
84028
- collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
84029
- filter: {
84030
- where: { nameKey: nameKeyOf(name) },
84031
- limit: 1
84032
- }
84033
- }))[0]?.id ?? null;
84034
- };
84035
- remove = async (id) => {
84036
- if (await this.get(id) === null) return false;
84037
- await this.store.delete.mutate({
84038
- collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
84039
- key: id
84040
- });
84041
- return true;
84042
- };
84043
- toVersion(id, data) {
84044
- const selection = asRecord(parseJson(data["selection"]));
84045
- const deviceIds = numberList(selection["deviceIds"]);
84046
- const collectionIds = stringList(selection["collectionIds"]);
84047
- const frameIdsSel = stringList(selection["frameIds"]);
84048
- return {
84049
- id,
84050
- name: String(data["name"] ?? ""),
84051
- createdAt: Number(data["createdAt"] ?? 0),
84052
- selection: {
84053
- ...deviceIds !== void 0 ? { deviceIds } : {},
84054
- ...collectionIds !== void 0 ? { collectionIds } : {},
84055
- ...frameIdsSel !== void 0 ? { frameIds: frameIdsSel } : {}
84056
- },
84057
- layout: data["layout"] === "crops" ? "crops" : "frames",
84058
- completeOnly: data["completeOnly"] === true || data["completeOnly"] === 1,
84059
- frameIds: stringList(parseJson(data["frameIds"])) ?? [],
84060
- annotationHash: String(data["annotationHash"] ?? ""),
84061
- manifestSchema: String(data["manifestSchema"] ?? ""),
84062
- stats: readStats(data["stats"])
84063
- };
84064
84262
  }
84065
84263
  };
84066
84264
  //#endregion
@@ -84730,6 +84928,61 @@ async function writeRetrainArchiveTo(deps) {
84730
84928
  return stats;
84731
84929
  }
84732
84930
  //#endregion
84931
+ //#region src/pipeline-analytics/retrain/retrain-archive-offer.ts
84932
+ /** A reader silent this long is gone; the body is cut. */
84933
+ var RETRAIN_OFFER_STALL_MS = 6e4;
84934
+ function responseArchiveSink(res, stallMs = RETRAIN_OFFER_STALL_MS) {
84935
+ let closed = false;
84936
+ res.once("close", () => {
84937
+ closed = true;
84938
+ });
84939
+ return { write: (chunk) => new Promise((resolve, reject) => {
84940
+ if (closed || res.destroyed) {
84941
+ reject(/* @__PURE__ */ new Error("retrain archive reader went away"));
84942
+ return;
84943
+ }
84944
+ if (res.write(chunk)) {
84945
+ resolve();
84946
+ return;
84947
+ }
84948
+ const onDrain = () => {
84949
+ cleanup();
84950
+ resolve();
84951
+ };
84952
+ const onClose = () => {
84953
+ cleanup();
84954
+ reject(/* @__PURE__ */ new Error("retrain archive reader went away"));
84955
+ };
84956
+ const timer = setTimeout(() => {
84957
+ cleanup();
84958
+ res.destroy(/* @__PURE__ */ new Error("retrain archive reader stalled"));
84959
+ reject(/* @__PURE__ */ new Error(`retrain archive reader stalled for ${stallMs} ms`));
84960
+ }, stallMs);
84961
+ const cleanup = () => {
84962
+ clearTimeout(timer);
84963
+ res.off("drain", onDrain);
84964
+ res.off("close", onClose);
84965
+ };
84966
+ res.once("drain", onDrain);
84967
+ res.once("close", onClose);
84968
+ }) };
84969
+ }
84970
+ /**
84971
+ * Serve one archive: headers, the writer's chunks, the end. A writer that
84972
+ * throws destroys the response (the reader sees a truncated body, never a
84973
+ * clean end) and the error propagates to the caller's log.
84974
+ */
84975
+ async function serveRetrainArchive(_req, res, writeArchive) {
84976
+ res.writeHead(200, { "content-type": "application/x-tar" });
84977
+ try {
84978
+ await writeArchive(responseArchiveSink(res));
84979
+ await new Promise((resolve) => res.end(resolve));
84980
+ } catch (err) {
84981
+ res.destroy(err instanceof Error ? err : new Error(String(err)));
84982
+ throw err;
84983
+ }
84984
+ }
84985
+ //#endregion
84733
84986
  //#region src/pipeline-analytics/retrain/retrain-crop-cut.ts
84734
84987
  /**
84735
84988
  * The `crops` layout's cutter: one subject crop from a retrain frame copy.
@@ -111026,8 +111279,26 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
111026
111279
  logger: logger.child("RetrainDatasets")
111027
111280
  });
111028
111281
  this.retrainDatasetVersions = retrainDatasetVersions;
111282
+ const routedDatasetVersions = new RoutingDatasetVersionRepository({
111283
+ local: retrainDatasetVersions,
111284
+ training: {
111285
+ put: (row) => api.training.putDatasetVersion.mutate({ ...row }),
111286
+ get: (id) => api.training.getDatasetVersion.query({ id }),
111287
+ list: async () => api.training.listDatasetVersions.query({}),
111288
+ findIdByName: async (name) => (await api.training.findDatasetVersionIdByName.query({ name })).id,
111289
+ remove: async (id) => (await api.training.removeDatasetVersion.mutate({ id })).removed
111290
+ },
111291
+ trainingPresent: async () => {
111292
+ try {
111293
+ return (await api.addons.listCapabilityProviders.query({ capName: "training" })).length > 0;
111294
+ } catch {
111295
+ return null;
111296
+ }
111297
+ },
111298
+ logger: logger.child("RetrainDatasets")
111299
+ });
111029
111300
  this.retrainDatasets = new RetrainDatasetService({
111030
- versions: retrainDatasetVersions,
111301
+ versions: routedDatasetVersions,
111031
111302
  buildPlan: (request) => this.buildRetrainExportPlan(request),
111032
111303
  now: () => Date.now(),
111033
111304
  newId: () => randomUUID(),
@@ -112392,6 +112663,51 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
112392
112663
  }
112393
112664
  }
112394
112665
  /**
112666
+ * The same archive behind a one-shot `ctx.peerBytes` ticket (D805): the
112667
+ * caller (a training provider, same host) dials it at once and streams it
112668
+ * into its cloud. The plan is built now, so an empty or unknown selection
112669
+ * fails here rather than as an empty body; the bytes and their sha256 are
112670
+ * the reader's to measure.
112671
+ */
112672
+ async offerRetrainArchive(input) {
112673
+ const frameStore = this.retrainFrames;
112674
+ if (frameStore === null) throw new Error("retrain export: analytics stores unavailable");
112675
+ const peerBytes = this.ctx.peerBytes;
112676
+ if (peerBytes === void 0) throw new Error("retrain archive offer: no peer-bytes facility");
112677
+ const plan = await this.buildRetrainExportPlan({
112678
+ filter: input.filter,
112679
+ layout: input.layout
112680
+ });
112681
+ if (plan.frames.length === 0) throw new Error("retrain archive offer: the selection holds no frame");
112682
+ const logger = this.ctx.logger;
112683
+ const offered = await peerBytes.offer({
112684
+ label: "retrain-archive",
112685
+ handler: (req, res) => serveRetrainArchive(req, res, async (sink) => {
112686
+ await writeRetrainArchiveTo({
112687
+ plan,
112688
+ readFrame: (frame) => frameStore.readFrame(frame),
112689
+ cutCrop: this.retrainCropCutter(),
112690
+ sink,
112691
+ markExported: (frameIds, batchId) => this.markRetrainExported(frameIds, batchId),
112692
+ logger,
112693
+ now: () => Date.now()
112694
+ });
112695
+ }).catch((err) => {
112696
+ logger.warn("retrain archive offer failed mid-body", { meta: { error: errMsg(err) } });
112697
+ })
112698
+ });
112699
+ if (offered.kind === "refused") throw new Error(`retrain archive offer refused: ${offered.code} — ${offered.detail}`);
112700
+ logger.info("retrain archive offered", { meta: {
112701
+ frames: plan.frames.length,
112702
+ layout: input.layout
112703
+ } });
112704
+ return {
112705
+ ticket: offered.ticket,
112706
+ frames: plan.frames.length,
112707
+ annotations: plan.manifest.annotationCount
112708
+ };
112709
+ }
112710
+ /**
112395
112711
  * The store seam the `debug.*` actions and the debug bundle both read through.
112396
112712
  *
112397
112713
  * Index reads, plus ONE existence probe per media row — never a blob. The