@camstack/addon-post-analysis 1.2.613 → 1.2.615
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{clip-model-registry-D6tdLEFB.mjs → clip-model-registry-BtK1fPgi.mjs} +21 -52
- package/dist/{clip-model-registry-CM-m6e1l.js → clip-model-registry-ycbGgfo_.js} +21 -52
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-PyXxg2Wg.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CnHfiisq.mjs} +3 -3
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CSQBGkvs.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-RCMuxWxC.mjs → hostInit-BGsx8mDn.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +899 -386
- package/dist/pipeline-analytics/index.mjs +898 -386
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-mydSlNUz.mjs +0 -26
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as MediaFileKindEnum, $i as sleep$1, $n as coreBlockIdFromAddonId, $r as readDeviceStateFrom, $t as NcTemplateVarDescriptorSchema, A as DeclaredDevices, Ai as templateCarriesSummary, An as TrackSideSchema, Ar as isEventOwnerType, At as NcAlarmSensorsInputSchema, B as FULL_IMAGE_BBOX, Bi as errMsg, Bn as applyModeDelaysEdits, Br as kebabToCamel, Bt as NcRuleSchema, C as ClearTrackRelationResultSchema, Ci as summarizeReview, Cn as TRACK_RELATIONS_DEFAULT_LIMIT, Cr as isAlertEpisodeOpen, Ct as NC_SNOOZE_MAX_MINUTES, D as DEFAULT_TIMELAPSE_PREVIEW_TEXT, Di as systemEventFilterApplies, Dn as TrackPuritySchema, Dr as isDefaultAlarmProfileId, Dt as NC_TEMPLATE_VARS, E as DEFAULT_EVENT_COLOR, Ei as systemEventFamilyProblems, En as TimelapseRuleSchema, Er as isAudioClassName, Et as NC_TAXONOMY, F as EVENT_DENSITY_BATCH_MAX, Fi as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, Fn as alarmPanelCapability, Fr as isRootSuppliableStepId, Ft as NcLegacyRuleActionSequenceSchema, G as ListTrackRelationsResultSchema, Gi as createEvent, Gn as audioMetricsCapability, Gr as normalizeNcMediaPolicy, Gt as NcSnoozeInputSchema, H as LABEL_SUPPLY_MACRO, Hi as CamProfileSchema, Hn as applySecureOnArmEdit, Hr as mayWriteToLocation, Ht as NcRuleUpdateInputSchema, I as EVENT_KIND_BY_CAP, Ii as canonicalHash, In as alertEpisodeId, Ir as isScheduleActive, It as NcRuleActionSchema, J as MAX_BIRTH_DECISION_RECORDS, Ji as defineRecordKinds, Jn as buildViewerLink, Jr as parseAlertEpisodePayload, Jt as NcStateSummarySectionIdSchema, K as LockStateSchema, Ki as customAction, Kn as audioModeOf, Kr as notificationRulesCapability, Kt as NcSnoozeSchema, L as EVENT_OWNER_TYPES, Li as evaluateSensorEdge, Ln as alertEpisodeKind, Lr as isSystemDelivery, Lt as NcRuleActionsSchema, M as EMBEDDING_MODEL_FAILED, Mi as vectorDimFromBase64, Mn as activateAlarmProfile, Mr as isLabelSupplyStepId, Mt as NcAlarmSettingsPatchSchema, N as EMBEDDING_REQUIREMENTS_FAILED, Ni as viewerLinkBaseOf, Nn as addonWidgetsSourceCapability, Nr as isMediaOwnerType, Nt as NcConditionDescriptorSchema, O as DETECTION_MACRO_CLASSES, Oi as systemEventSummaryScheduleOf, On as TrackRelationKindSchema, Or as isDetectionMacroClass, Ot as NcAlarmConfigSchema, P as EMBEDDING_REQUIREMENTS_UNAVAILABLE, Pi as zoneAnalyticsCapability, Pn as alarmDeviceSecureOnArm, Pr as isRetiredClearKey, Pt as NcConditionsSchema, Q as MOTION_CLOSE_AFTER_MS, Qn as consumableLowPercentOf, Qr as plateGalleryCapability, Qt as NcTemplatePreviewSchema, R as EXPORT_DENSE_MAX_RANGES, Ri as isSourceCap, Rn as alertEpisodePayload, Rr as isSystemEventSummaryRule, Rt as NcRuleInputSchema, S as ClearTrackRelationInputSchema, Si as subKindsOf, Sn as TIMELAPSE_DENSE_FLOOR_SEC, Sr as isAlarmControllableCap, St as NC_SENSOR_TRANSITION_KINDS, T as DEFAULT_DETAIL_CROP_CONVENTION, Ti as systemEventDeliveryProblems, Tn as TimelapseRulePatchSchema, Tr as isAppLinkBase, Tt as NC_TANK_SYSTEM_EVENT_KINDS, U as LabelAttributionSchema, Ui as RECORD_MAX_DATA_BYTES, Un as armRemovalReconcile, Ur as memoizePinnedFfmpeg, Ut as NcScheduleSchema, V as FailureCounters, Vi as BaseAddon, Vn as applyNonBlockingEdit, Vr as linkedStepOf, Vt as NcRuleTargetSchema, W as ListTrackRelationsInputSchema, Wi as RECORD_QUERY_MAX, Wn as assertTimelapseCadences, Wr as ncMediaPolicyError, Wt as NcSharedSequenceSchema, X as MAX_TRACK_PURITIES, Xi as isDeviceScopedCap, Xn as classifyRetrainReview, Xr as pickFaceRescoreIntervalMinutes, Xt as NcTaxonomySchema, Y as MAX_BIRTH_LATENCY_PROXY_MS, Yi as hydrateSchema, Yn as calendarEventLocalSpan, Yt as NcSystemEventKindSchema, Z as MAX_TRACK_RELATIONS, Zi as nodePin, Zn as cloneAlarmSettings, Zr as pipelineAnalyticsCapability, Zt as NcTemplatePreviewInputSchema, _ as BIRTH_DECISION_DEFAULT_LIMIT, _a as EventCategory, _i as secureActuationFor, _n as SCENE_RESET_RECAPTURES, _r as evictionPolicyOfLocation, _t as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, a as CLIP_EMBEDDING_STEP_ID, aa as literal, ai as resolveModeDelays, an as RebuildObjectEmbeddingsRefusalSchema, ar as deriveRecordingMode, at as NC_ALARM_NOTIFY_FIELDS, b as BucketAccumulator, bi as startMoverUnderClearance, bn as SetTrackPurityResultSchema, br as gifDeriveSpeed, bt as NC_GROUPING_MAX_SEC, c as readClusterModelPinStrict, ca as object, cn as RetrainStatusSchema, cr as deviceRemovalHookCapability, ct as NC_AUDIO_CONFIRM_HITS_DEFAULT, d as APPEARANCE_BLOCKS, da as strictObject, di as ruleAutoTriggersAlarm, dn as SCENE_DEFAULT_ANCHOR_THRESHOLD, dr as embeddingRefusalOf, dt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, ea as ZodObject, ei as readMotionTriggeredBy, en as OPS_LOG_DEFAULT_LIMIT, er as cosineSimilarity$1, et as MotionTriggeredBySchema, f as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, fa as string, fi as ruleBelongsToAlarm$1, fn as SCENE_DEFAULT_CHECK_INTERVAL_SEC, ft as NC_CLIP_RETENTION_MAX_SEC, g as ArchivedDebugNoteSchema, ga as unknown, gi as sceneMonitorCapability, gn as SCENE_DIVERGED, gr as eventKindLabel, gt as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, h as AlarmArmModeSchema, ha as union, hi as ruleKindOf, hn as SCENE_DEFAULT_UNCOVERED_POLICY, hr as ensureAlarmProfiles, ht as NC_CONFIRM_DEFAULT_TIMEOUT_MS, ia as discriminatedUnion, ii as resolveLocationMode, in as RECORDING_SOURCE_CAMSTACK_ADDON, ir as derivePostTriggeredAction, it as NC_ALARM_DELAY_MAX_SEC, j as DetectionSourceSchema, ji as templateVarsFor, jn as TrackSourceSchema, jr as isKnownTemplateVar, jt as NcAlarmSensorsSchema, k as DETECTION_PIPELINE_CAP_NAME, ki as systemEventSummarySplitOf, kn as TrackRelationSchema, kr as isEventKindLabelKey, kt as NcAlarmPostTriggeredActionSchema, l as resolveClusterModelPin, la as partialRecord, li as retrainRevisionConflictMessage, ln as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, lt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, m as AUDIO_MACRO_LABELS, ma as tuple, mi as ruleGroupingApplies, mn as SCENE_DEFAULT_QUIET_SECONDS, mr as encodeVectorBase64, mt as NC_CONFIRM_DEFAULT_MAX_IMAGE_PX, na as array, ni as renameAlarmProfile, nr as deleteAlarmProfile, nt as NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC, o as CLUSTER_MODEL_OWNER_ADDON_ID, oa as looseObject, oi as resolveModeNotifyPolicy, on as RetrainDismissedSchema, or as describeSystemEventDeliveryProblem, ot as NC_ALARM_SYSTEM_EVENT_KINDS, p as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, pa as templateLiteral, pi as ruleDeviceCompatCapQueries, pn as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, pt as NC_CONDITION_CATALOG, q as MAX_ARCHIVED_DEBUG_NOTES, qi as defineCustomActions, qn as buildEventKindDescriptor, qr as occupancyScope, qt as NcSnoozeSuppressedSchema, ra as boolean, ri as resolveClipSearchMinScore, rn as RECORDING_EXPORT_MAX_READ_BYTES, rr as deriveDetailCropRect, rt as NC_ALARM_DELAY_FIELDS, s as FACE_EMBEDDING_STEP_ID, sa as number, si as resolveNcMediaImage, sn as RetrainOriginSchema, sr as describeSystemEventFamilyProblem, st as NC_ALARM_TRIGGERED_MAX_SEC, t as BUILTIN_CLIP_MODELS, ta as _enum, ti as readTimelapseGeneratedAt, tn as OpsLogEntrySchema, tr as createAlarmProfile, tt as NC_ALARM_DEFAULT_PROFILE_ID, u as APPEARANCE_BIN_COUNT, ua as record, ui as rootSupplyServes, un as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, ut as NC_CLIP_POST_ROLL_DEFAULT_SEC, v as BaseDevice, vi as sensorRaisedOf, vn as SceneMonitorSchema, vr as faceGalleryCapability, vt as NC_DEFAULT_SNOOZE_MINUTES, w as CoverStateSchema, wi as syncActiveAlarmProfileSettings, wn as TimelapseRuleInputSchema, wr as isAlertEpisodeSeverity, wt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, x as COCO_TO_MACRO, xi as storageOccupancyCapability, xn as SetTrackRelationInputSchema, xr as gifFromMp4Args, xt as NC_GROUPING_MIN_SEC, y as BirthDecisionRecordSchema, yi as servedTemplateVars, yn as SetTrackPurityInputSchema, yr as failureContributionCapability, yt as NC_GROUPING_DEFAULT_SEC, z as FIRST_LEVEL_MACRO_CLASSES, zi as DeviceType, zn as applyBlockingEdit, zr as isVectorIndexDimConflict, zt as NcRulePatchSchema } from "../clip-model-registry-
|
|
1
|
+
import { $ as MediaFileKindEnum, $i as sleep$1, $n as coreBlockIdFromAddonId, $r as readDeviceStateFrom, $t as NcTemplateVarDescriptorSchema, A as DeclaredDevices, Ai as templateCarriesSummary, An as TrackSideSchema, Ar as isEventOwnerType, At as NcAlarmSensorsInputSchema, B as FULL_IMAGE_BBOX, Bi as errMsg, Bn as applyModeDelaysEdits, Br as kebabToCamel, Bt as NcRuleSchema, C as ClearTrackRelationResultSchema, Ci as summarizeReview, Cn as TRACK_RELATIONS_DEFAULT_LIMIT, Cr as isAlertEpisodeOpen, Ct as NC_SNOOZE_MAX_MINUTES, D as DEFAULT_TIMELAPSE_PREVIEW_TEXT, Di as systemEventFilterApplies, Dn as TrackPuritySchema, Dr as isDefaultAlarmProfileId, Dt as NC_TEMPLATE_VARS, E as DEFAULT_EVENT_COLOR, Ei as systemEventFamilyProblems, En as TimelapseRuleSchema, Er as isAudioClassName, Et as NC_TAXONOMY, F as EVENT_DENSITY_BATCH_MAX, Fi as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, Fn as alarmPanelCapability, Fr as isRootSuppliableStepId, Ft as NcLegacyRuleActionSequenceSchema, G as ListTrackRelationsResultSchema, Gi as createEvent, Gn as audioMetricsCapability, Gr as normalizeNcMediaPolicy, Gt as NcSnoozeInputSchema, H as LABEL_SUPPLY_MACRO, Hi as CamProfileSchema, Hn as applySecureOnArmEdit, Hr as mayWriteToLocation, Ht as NcRuleUpdateInputSchema, I as EVENT_KIND_BY_CAP, Ii as canonicalHash, In as alertEpisodeId, Ir as isScheduleActive, It as NcRuleActionSchema, J as MAX_BIRTH_DECISION_RECORDS, Ji as defineRecordKinds, Jn as buildViewerLink, Jr as parseAlertEpisodePayload, Jt as NcStateSummarySectionIdSchema, K as LockStateSchema, Ki as customAction, Kn as audioModeOf, Kr as notificationRulesCapability, Kt as NcSnoozeSchema, L as EVENT_OWNER_TYPES, Li as evaluateSensorEdge, Ln as alertEpisodeKind, Lr as isSystemDelivery, Lt as NcRuleActionsSchema, M as EMBEDDING_MODEL_FAILED, Mi as vectorDimFromBase64, Mn as activateAlarmProfile, Mr as isLabelSupplyStepId, Mt as NcAlarmSettingsPatchSchema, N as EMBEDDING_REQUIREMENTS_FAILED, Ni as viewerLinkBaseOf, Nn as addonWidgetsSourceCapability, Nr as isMediaOwnerType, Nt as NcConditionDescriptorSchema, O as DETECTION_MACRO_CLASSES, Oi as systemEventSummaryScheduleOf, On as TrackRelationKindSchema, Or as isDetectionMacroClass, Ot as NcAlarmConfigSchema, P as EMBEDDING_REQUIREMENTS_UNAVAILABLE, Pi as zoneAnalyticsCapability, Pn as alarmDeviceSecureOnArm, Pr as isRetiredClearKey, Pt as NcConditionsSchema, Q as MOTION_CLOSE_AFTER_MS, Qn as consumableLowPercentOf, Qr as plateGalleryCapability, Qt as NcTemplatePreviewSchema, R as EXPORT_DENSE_MAX_RANGES, Ri as isSourceCap, Rn as alertEpisodePayload, Rr as isSystemEventSummaryRule, Rt as NcRuleInputSchema, S as ClearTrackRelationInputSchema, Si as subKindsOf, Sn as TIMELAPSE_DENSE_FLOOR_SEC, Sr as isAlarmControllableCap, St as NC_SENSOR_TRANSITION_KINDS, T as DEFAULT_DETAIL_CROP_CONVENTION, Ti as systemEventDeliveryProblems, Tn as TimelapseRulePatchSchema, Tr as isAppLinkBase, Tt as NC_TANK_SYSTEM_EVENT_KINDS, U as LabelAttributionSchema, Ui as RECORD_MAX_DATA_BYTES, Un as armRemovalReconcile, Ur as memoizePinnedFfmpeg, Ut as NcScheduleSchema, V as FailureCounters, Vi as BaseAddon, Vn as applyNonBlockingEdit, Vr as linkedStepOf, Vt as NcRuleTargetSchema, W as ListTrackRelationsInputSchema, Wi as RECORD_QUERY_MAX, Wn as assertTimelapseCadences, Wr as ncMediaPolicyError, Wt as NcSharedSequenceSchema, X as MAX_TRACK_PURITIES, Xi as isDeviceScopedCap, Xn as classifyRetrainReview, Xr as pickFaceRescoreIntervalMinutes, Xt as NcTaxonomySchema, Y as MAX_BIRTH_LATENCY_PROXY_MS, Yi as hydrateSchema, Yn as calendarEventLocalSpan, Yt as NcSystemEventKindSchema, Z as MAX_TRACK_RELATIONS, Zi as nodePin, Zn as cloneAlarmSettings, Zr as pipelineAnalyticsCapability, Zt as NcTemplatePreviewInputSchema, _ as BIRTH_DECISION_DEFAULT_LIMIT, _a as EventCategory, _i as secureActuationFor, _n as SCENE_RESET_RECAPTURES, _r as evictionPolicyOfLocation, _t as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, a as CLIP_EMBEDDING_STEP_ID, aa as literal, ai as resolveModeDelays, an as RebuildObjectEmbeddingsRefusalSchema, ar as deriveRecordingMode, at as NC_ALARM_NOTIFY_FIELDS, b as BucketAccumulator, bi as startMoverUnderClearance, bn as SetTrackPurityResultSchema, br as gifDeriveSpeed, bt as NC_GROUPING_MAX_SEC, c as readClusterModelPinStrict, ca as object, cn as RetrainStatusSchema, cr as deviceRemovalHookCapability, ct as NC_AUDIO_CONFIRM_HITS_DEFAULT, d as APPEARANCE_BLOCKS, da as strictObject, di as ruleAutoTriggersAlarm, dn as SCENE_DEFAULT_ANCHOR_THRESHOLD, dr as embeddingRefusalOf, dt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, ea as ZodObject, ei as readMotionTriggeredBy, en as OPS_LOG_DEFAULT_LIMIT, er as cosineSimilarity$1, et as MotionTriggeredBySchema, f as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, fa as string, fi as ruleBelongsToAlarm$1, fn as SCENE_DEFAULT_CHECK_INTERVAL_SEC, ft as NC_CLIP_RETENTION_MAX_SEC, g as ArchivedDebugNoteSchema, ga as unknown, gi as sceneMonitorCapability, gn as SCENE_DIVERGED, gr as eventKindLabel, gt as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, h as AlarmArmModeSchema, ha as union, hi as ruleKindOf, hn as SCENE_DEFAULT_UNCOVERED_POLICY, hr as ensureAlarmProfiles, ht as NC_CONFIRM_DEFAULT_TIMEOUT_MS, ia as discriminatedUnion, ii as resolveLocationMode, in as RECORDING_SOURCE_CAMSTACK_ADDON, ir as derivePostTriggeredAction, it as NC_ALARM_DELAY_MAX_SEC, j as DetectionSourceSchema, ji as templateVarsFor, jn as TrackSourceSchema, jr as isKnownTemplateVar, jt as NcAlarmSensorsSchema, k as DETECTION_PIPELINE_CAP_NAME, ki as systemEventSummarySplitOf, kn as TrackRelationSchema, kr as isEventKindLabelKey, kt as NcAlarmPostTriggeredActionSchema, l as resolveClusterModelPin, la as partialRecord, li as retrainRevisionConflictMessage, ln as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, lt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, m as AUDIO_MACRO_LABELS, ma as tuple, mi as ruleGroupingApplies, mn as SCENE_DEFAULT_QUIET_SECONDS, mr as encodeVectorBase64, mt as NC_CONFIRM_DEFAULT_MAX_IMAGE_PX, na as array, ni as renameAlarmProfile, nr as deleteAlarmProfile, nt as NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC, o as CLUSTER_MODEL_OWNER_ADDON_ID, oa as looseObject, oi as resolveModeNotifyPolicy, on as RetrainDismissedSchema, or as describeSystemEventDeliveryProblem, ot as NC_ALARM_SYSTEM_EVENT_KINDS, p as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, pa as templateLiteral, pi as ruleDeviceCompatCapQueries, pn as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, pt as NC_CONDITION_CATALOG, q as MAX_ARCHIVED_DEBUG_NOTES, qi as defineCustomActions, qn as buildEventKindDescriptor, qr as occupancyScope, qt as NcSnoozeSuppressedSchema, ra as boolean, ri as resolveClipSearchMinScore, rn as RECORDING_EXPORT_MAX_READ_BYTES, rr as deriveDetailCropRect, rt as NC_ALARM_DELAY_FIELDS, s as FACE_EMBEDDING_STEP_ID, sa as number, si as resolveNcMediaImage, sn as RetrainOriginSchema, sr as describeSystemEventFamilyProblem, st as NC_ALARM_TRIGGERED_MAX_SEC, t as BUILTIN_CLIP_MODELS, ta as _enum, ti as readTimelapseGeneratedAt, tn as OpsLogEntrySchema, tr as createAlarmProfile, tt as NC_ALARM_DEFAULT_PROFILE_ID, u as APPEARANCE_BIN_COUNT, ua as record, ui as rootSupplyServes, un as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, ut as NC_CLIP_POST_ROLL_DEFAULT_SEC, v as BaseDevice, vi as sensorRaisedOf, vn as SceneMonitorSchema, vr as faceGalleryCapability, vt as NC_DEFAULT_SNOOZE_MINUTES, w as CoverStateSchema, wi as syncActiveAlarmProfileSettings, wn as TimelapseRuleInputSchema, wr as isAlertEpisodeSeverity, wt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, x as COCO_TO_MACRO, xi as storageOccupancyCapability, xn as SetTrackRelationInputSchema, xr as gifFromMp4Args, xt as NC_GROUPING_MIN_SEC, y as BirthDecisionRecordSchema, yi as servedTemplateVars, yn as SetTrackPurityInputSchema, yr as failureContributionCapability, yt as NC_GROUPING_DEFAULT_SEC, z as FIRST_LEVEL_MACRO_CLASSES, zi as DeviceType, zn as applyBlockingEdit, zr as isVectorIndexDimConflict, zt as NcRulePatchSchema } from "../clip-model-registry-BtK1fPgi.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";
|
|
@@ -4071,6 +4071,224 @@ function ncAlarmPanelPort(panel) {
|
|
|
4071
4071
|
};
|
|
4072
4072
|
}
|
|
4073
4073
|
//#endregion
|
|
4074
|
+
//#region src/durable/durable-record-body.ts
|
|
4075
|
+
/**
|
|
4076
|
+
* Zod body for phase-3 durable ledger record kinds. The engine validates on
|
|
4077
|
+
* write; each owner's `fromRecord` / `toValue` remain authoritative on read.
|
|
4078
|
+
*/
|
|
4079
|
+
var DurableLedgerBodySchema = record(string(), unknown());
|
|
4080
|
+
//#endregion
|
|
4081
|
+
//#region src/notification-center/nc-durable-record-kinds.ts
|
|
4082
|
+
/**
|
|
4083
|
+
* Record kinds for NC live-state ledgers (records plan phase 3, steps 25–36).
|
|
4084
|
+
*
|
|
4085
|
+
* Hosted in the pipeline-analytics addon (`pipeline-analytics` owner stamp).
|
|
4086
|
+
* Declared by the hub; every node reads and writes them through `ctx.records`.
|
|
4087
|
+
* The legacy settings-store handles (`durableLegacyKind`, `legacyCollectionKind`)
|
|
4088
|
+
* serve only a centre built WITHOUT `records` — the specs.
|
|
4089
|
+
*/
|
|
4090
|
+
/** The settings-store tables these kinds replaced; read only WITHOUT `ctx.records` (the specs). */
|
|
4091
|
+
var NC_LIVENESS_LEGACY = "notification-center:liveness";
|
|
4092
|
+
var NC_DEVICE_ALERTS_LEGACY = "notification-center:device-alerts";
|
|
4093
|
+
var NC_SYSTEM_STATE_LEGACY = "notification-center:system-state";
|
|
4094
|
+
var NC_OCCUPANCY_LEGACY = "notification-center:occupancy";
|
|
4095
|
+
var NC_DEVICE_PRUNES_LEGACY = "notification-center:device-prunes";
|
|
4096
|
+
var NC_STATE_SUMMARY_STAMPS_LEGACY = "notification-center:state-summary-stamps";
|
|
4097
|
+
var NC_ALERT_EPISODES_LEGACY = "notification-center:alert-episodes";
|
|
4098
|
+
var NC_META_LEGACY = "notification-center:meta";
|
|
4099
|
+
var NC_SNOOZES_LEGACY = "notification-center:snoozes";
|
|
4100
|
+
var NC_SNOOZE_SUPPRESSED_LEGACY = "notification-center:snooze-suppressed";
|
|
4101
|
+
var NC_TRACK_GROUPS_LEGACY = "notification-center:track-groups";
|
|
4102
|
+
var NC_OUTBOX_LEGACY = "notification-center:outbox";
|
|
4103
|
+
var NC_ARTIFACTS_LEGACY = "notification-center:artifacts";
|
|
4104
|
+
var ledger = (writeTrigger, database = "settings", retention = { none: "mirror load-all at boot; rows drop only via owner prune or device-removal hook" }) => ({
|
|
4105
|
+
schema: DurableLedgerBodySchema,
|
|
4106
|
+
class: "ledger",
|
|
4107
|
+
database,
|
|
4108
|
+
writeTrigger,
|
|
4109
|
+
device: "optional",
|
|
4110
|
+
at: "none",
|
|
4111
|
+
retention
|
|
4112
|
+
});
|
|
4113
|
+
/**
|
|
4114
|
+
* The shape the four QUERIED kinds share with the mirrors. Each spells its own
|
|
4115
|
+
* file, trigger, indexes and retention as literals (the records guards read them).
|
|
4116
|
+
* `device` and `at` stay what the first (empty) declaration said — the engine
|
|
4117
|
+
* refuses to change either — so every read is served by a DATA-field index, and
|
|
4118
|
+
* the camera rides the record envelope (`deviceId`) for the D865 removal.
|
|
4119
|
+
*/
|
|
4120
|
+
var QUERIED = {
|
|
4121
|
+
schema: DurableLedgerBodySchema,
|
|
4122
|
+
device: "optional",
|
|
4123
|
+
at: "none"
|
|
4124
|
+
};
|
|
4125
|
+
var ncDurableRecordKinds = defineRecordKinds({
|
|
4126
|
+
liveness: ledger("a liveness state TRANSITION the centre accepted (seed or flip); a no-flip writes nothing"),
|
|
4127
|
+
"device-alerts": ledger("a sustained device alert episode the centre confirmed or cleared"),
|
|
4128
|
+
"system-state": ledger("a system-state subject flip the state summary accepted"),
|
|
4129
|
+
"alert-episodes": ledger("an alert episode the centre notified (critical or error); its resolution deletes the row"),
|
|
4130
|
+
occupancy: ledger("a confirmed occupancy level write-through after the watcher decided an edge"),
|
|
4131
|
+
"device-prunes": ledger("one row recording what a deleted-device prune removed"),
|
|
4132
|
+
"state-summary-stamps": ledger("digest delivery stamps per rule kind"),
|
|
4133
|
+
meta: ledger("outbox boot-reconcile watermark and other single-key meta rows", "settings", { none: "one fixed key (watermark), overwritten in place, never deleted" }),
|
|
4134
|
+
snoozes: ledger("operator opens or closes a snooze window (write-through)"),
|
|
4135
|
+
"snooze-suppressed": {
|
|
4136
|
+
...QUERIED,
|
|
4137
|
+
class: "ledger",
|
|
4138
|
+
database: "events",
|
|
4139
|
+
writeTrigger: "aggregate count per (window, target, rule, camera) for suppressed notifications",
|
|
4140
|
+
indexes: [{
|
|
4141
|
+
name: "snooze",
|
|
4142
|
+
fields: ["snoozeId"]
|
|
4143
|
+
}],
|
|
4144
|
+
retention: {
|
|
4145
|
+
owner: "notification-center",
|
|
4146
|
+
reason: "deleted with its snooze window, 6 h after that window closes"
|
|
4147
|
+
}
|
|
4148
|
+
},
|
|
4149
|
+
"track-groups": {
|
|
4150
|
+
...QUERIED,
|
|
4151
|
+
class: "ledger",
|
|
4152
|
+
database: "events",
|
|
4153
|
+
writeTrigger: "one row per grouped delivery window opened at admission time",
|
|
4154
|
+
indexes: [
|
|
4155
|
+
{
|
|
4156
|
+
name: "open",
|
|
4157
|
+
fields: ["sealed"]
|
|
4158
|
+
},
|
|
4159
|
+
{
|
|
4160
|
+
name: "opened",
|
|
4161
|
+
fields: ["openedAt"]
|
|
4162
|
+
},
|
|
4163
|
+
{
|
|
4164
|
+
name: "device_opened",
|
|
4165
|
+
fields: ["deviceId", "openedAt"]
|
|
4166
|
+
},
|
|
4167
|
+
{
|
|
4168
|
+
name: "rule_opened",
|
|
4169
|
+
fields: ["ruleId", "openedAt"]
|
|
4170
|
+
},
|
|
4171
|
+
{
|
|
4172
|
+
name: "rule_device_opened",
|
|
4173
|
+
fields: [
|
|
4174
|
+
"ruleId",
|
|
4175
|
+
"deviceId",
|
|
4176
|
+
"openedAt"
|
|
4177
|
+
]
|
|
4178
|
+
}
|
|
4179
|
+
],
|
|
4180
|
+
retention: {
|
|
4181
|
+
owner: "notification-center",
|
|
4182
|
+
reason: "ring: the newest NC_GROUP_MAX_ROWS_PER_DEVICE rows per camera, trimmed on write"
|
|
4183
|
+
}
|
|
4184
|
+
},
|
|
4185
|
+
outbox: {
|
|
4186
|
+
...QUERIED,
|
|
4187
|
+
class: "ledger",
|
|
4188
|
+
database: "events",
|
|
4189
|
+
writeTrigger: "one row per (rule, dedupRef, target) enqueued for delivery",
|
|
4190
|
+
indexes: [
|
|
4191
|
+
{
|
|
4192
|
+
name: "created",
|
|
4193
|
+
fields: ["createdAt"]
|
|
4194
|
+
},
|
|
4195
|
+
{
|
|
4196
|
+
name: "rule_created",
|
|
4197
|
+
fields: ["ruleId", "createdAt"]
|
|
4198
|
+
},
|
|
4199
|
+
{
|
|
4200
|
+
name: "status_created",
|
|
4201
|
+
fields: ["status", "createdAt"]
|
|
4202
|
+
},
|
|
4203
|
+
{
|
|
4204
|
+
name: "device_created",
|
|
4205
|
+
fields: ["deviceId", "createdAt"]
|
|
4206
|
+
},
|
|
4207
|
+
{
|
|
4208
|
+
name: "rule_status_created",
|
|
4209
|
+
fields: [
|
|
4210
|
+
"ruleId",
|
|
4211
|
+
"status",
|
|
4212
|
+
"createdAt"
|
|
4213
|
+
]
|
|
4214
|
+
},
|
|
4215
|
+
{
|
|
4216
|
+
name: "rule_device_created",
|
|
4217
|
+
fields: [
|
|
4218
|
+
"ruleId",
|
|
4219
|
+
"deviceId",
|
|
4220
|
+
"createdAt"
|
|
4221
|
+
]
|
|
4222
|
+
},
|
|
4223
|
+
{
|
|
4224
|
+
name: "status_device_created",
|
|
4225
|
+
fields: [
|
|
4226
|
+
"status",
|
|
4227
|
+
"deviceId",
|
|
4228
|
+
"createdAt"
|
|
4229
|
+
]
|
|
4230
|
+
},
|
|
4231
|
+
{
|
|
4232
|
+
name: "rule_status_device_created",
|
|
4233
|
+
fields: [
|
|
4234
|
+
"ruleId",
|
|
4235
|
+
"status",
|
|
4236
|
+
"deviceId",
|
|
4237
|
+
"createdAt"
|
|
4238
|
+
]
|
|
4239
|
+
}
|
|
4240
|
+
],
|
|
4241
|
+
retention: {
|
|
4242
|
+
owner: "notification-center",
|
|
4243
|
+
reason: "terminal (sent/dead) rows older than 7 days pruned at boot; a pending row retries to dead first"
|
|
4244
|
+
},
|
|
4245
|
+
maxDataBytes: RECORD_MAX_DATA_BYTES
|
|
4246
|
+
},
|
|
4247
|
+
artifacts: {
|
|
4248
|
+
...QUERIED,
|
|
4249
|
+
class: "ledger",
|
|
4250
|
+
database: "events",
|
|
4251
|
+
writeTrigger: "retained notification artefact metadata indexed for lookup and expiry",
|
|
4252
|
+
indexes: [{
|
|
4253
|
+
name: "captured",
|
|
4254
|
+
fields: ["capturedAt"]
|
|
4255
|
+
}, {
|
|
4256
|
+
name: "expires",
|
|
4257
|
+
fields: ["expiresAt"]
|
|
4258
|
+
}],
|
|
4259
|
+
retention: {
|
|
4260
|
+
owner: "notification-center",
|
|
4261
|
+
reason: "falls with its bytes: expired rows on every node, orphans against the shelf on the producer node"
|
|
4262
|
+
}
|
|
4263
|
+
}
|
|
4264
|
+
});
|
|
4265
|
+
/**
|
|
4266
|
+
* The settings-store table each kind stands in for when a centre is built
|
|
4267
|
+
* WITHOUT `ctx.records` - the specs' fake store (`legacyCollectionKind`,
|
|
4268
|
+
* `durableLegacyKind`). Production never reads it: every node has
|
|
4269
|
+
* `ctx.records`, and the operator dropped these tables offline (D900).
|
|
4270
|
+
*/
|
|
4271
|
+
var NC_DURABLE_LEGACY_TABLES = {
|
|
4272
|
+
liveness: NC_LIVENESS_LEGACY,
|
|
4273
|
+
"device-alerts": NC_DEVICE_ALERTS_LEGACY,
|
|
4274
|
+
"system-state": NC_SYSTEM_STATE_LEGACY,
|
|
4275
|
+
"alert-episodes": NC_ALERT_EPISODES_LEGACY,
|
|
4276
|
+
occupancy: NC_OCCUPANCY_LEGACY,
|
|
4277
|
+
"device-prunes": NC_DEVICE_PRUNES_LEGACY,
|
|
4278
|
+
"state-summary-stamps": NC_STATE_SUMMARY_STAMPS_LEGACY,
|
|
4279
|
+
meta: NC_META_LEGACY,
|
|
4280
|
+
snoozes: NC_SNOOZES_LEGACY,
|
|
4281
|
+
"snooze-suppressed": NC_SNOOZE_SUPPRESSED_LEGACY,
|
|
4282
|
+
"track-groups": NC_TRACK_GROUPS_LEGACY,
|
|
4283
|
+
outbox: NC_OUTBOX_LEGACY,
|
|
4284
|
+
artifacts: NC_ARTIFACTS_LEGACY
|
|
4285
|
+
};
|
|
4286
|
+
/** The legacy table of an NC durable kind by name, or `undefined` for any other kind. */
|
|
4287
|
+
function ncDurableLegacyTableOf(kind) {
|
|
4288
|
+
for (const [name, table] of Object.entries(NC_DURABLE_LEGACY_TABLES)) if (name === kind) return table;
|
|
4289
|
+
}
|
|
4290
|
+
Object.values(NC_DURABLE_LEGACY_TABLES);
|
|
4291
|
+
//#endregion
|
|
4074
4292
|
//#region src/durable/legacy-collection-kind.ts
|
|
4075
4293
|
/** Bounds of a half-open range on the legacy filter (the store has no `>=`). */
|
|
4076
4294
|
var RANGE_MIN = -864e13;
|
|
@@ -4216,8 +4434,8 @@ var LegacyCollectionKind = class {
|
|
|
4216
4434
|
/** The handle an NC owner reads and writes through: records when present, else the legacy table. */
|
|
4217
4435
|
function queriedKind(definition, deps) {
|
|
4218
4436
|
if (deps.records !== void 0) return deps.records.kind(definition);
|
|
4219
|
-
const collection = definition.
|
|
4220
|
-
if (collection === void 0) throw new Error(`kind "${definition.name}"
|
|
4437
|
+
const collection = ncDurableLegacyTableOf(definition.name);
|
|
4438
|
+
if (collection === void 0) throw new Error(`kind "${definition.name}" has no legacy table to stand in for it`);
|
|
4221
4439
|
return new LegacyCollectionKind({
|
|
4222
4440
|
definition,
|
|
4223
4441
|
collection,
|
|
@@ -4262,203 +4480,6 @@ async function collectRecordPages(kind, q, max) {
|
|
|
4262
4480
|
}
|
|
4263
4481
|
}
|
|
4264
4482
|
//#endregion
|
|
4265
|
-
//#region src/durable/durable-record-body.ts
|
|
4266
|
-
/**
|
|
4267
|
-
* Zod body for phase-3 durable ledger record kinds. The engine validates on
|
|
4268
|
-
* write; each owner's `fromRecord` / `toValue` remain authoritative on read.
|
|
4269
|
-
*/
|
|
4270
|
-
var DurableLedgerBodySchema = record(string(), unknown());
|
|
4271
|
-
//#endregion
|
|
4272
|
-
//#region src/notification-center/nc-durable-record-kinds.ts
|
|
4273
|
-
/**
|
|
4274
|
-
* Record kinds for NC live-state ledgers (records plan phase 3, steps 25–36).
|
|
4275
|
-
*
|
|
4276
|
-
* Hosted in the pipeline-analytics addon (`pipeline-analytics` owner stamp).
|
|
4277
|
-
* Declared by the hub; every node reads and writes them through `ctx.records`.
|
|
4278
|
-
* The legacy settings-store handles (`durableLegacyKind`, `legacyCollectionKind`)
|
|
4279
|
-
* serve only a centre built WITHOUT `records` — the specs.
|
|
4280
|
-
*/
|
|
4281
|
-
/** Legacy collection names — keep in sync with owner constants. */
|
|
4282
|
-
var NC_LIVENESS_LEGACY = "notification-center:liveness";
|
|
4283
|
-
var NC_DEVICE_ALERTS_LEGACY = "notification-center:device-alerts";
|
|
4284
|
-
var NC_SYSTEM_STATE_LEGACY = "notification-center:system-state";
|
|
4285
|
-
var NC_OCCUPANCY_LEGACY = "notification-center:occupancy";
|
|
4286
|
-
var NC_DEVICE_PRUNES_LEGACY = "notification-center:device-prunes";
|
|
4287
|
-
var NC_STATE_SUMMARY_STAMPS_LEGACY = "notification-center:state-summary-stamps";
|
|
4288
|
-
var NC_ALERT_EPISODES_LEGACY = "notification-center:alert-episodes";
|
|
4289
|
-
var NC_META_LEGACY = "notification-center:meta";
|
|
4290
|
-
var NC_SNOOZES_LEGACY = "notification-center:snoozes";
|
|
4291
|
-
var NC_SNOOZE_SUPPRESSED_LEGACY = "notification-center:snooze-suppressed";
|
|
4292
|
-
var NC_TRACK_GROUPS_LEGACY = "notification-center:track-groups";
|
|
4293
|
-
var NC_OUTBOX_LEGACY = "notification-center:outbox";
|
|
4294
|
-
var NC_ARTIFACTS_LEGACY = "notification-center:artifacts";
|
|
4295
|
-
var ledger = (adoptsFrom, writeTrigger, database = "settings", retention = { none: "mirror load-all at boot; rows drop only via owner prune or device-removal hook" }) => ({
|
|
4296
|
-
schema: DurableLedgerBodySchema,
|
|
4297
|
-
class: "ledger",
|
|
4298
|
-
database,
|
|
4299
|
-
writeTrigger,
|
|
4300
|
-
device: "optional",
|
|
4301
|
-
at: "none",
|
|
4302
|
-
retention,
|
|
4303
|
-
adoptsFrom
|
|
4304
|
-
});
|
|
4305
|
-
/**
|
|
4306
|
-
* The shape the four QUERIED kinds share with the mirrors. Each spells its own
|
|
4307
|
-
* file, trigger, indexes and retention as literals (the records guards read them).
|
|
4308
|
-
* `device` and `at` stay what the first (empty) declaration said — the engine
|
|
4309
|
-
* refuses to change either — so every read is served by a DATA-field index, and
|
|
4310
|
-
* the camera rides the record envelope (`deviceId`) for the D865 removal.
|
|
4311
|
-
*/
|
|
4312
|
-
var QUERIED = {
|
|
4313
|
-
schema: DurableLedgerBodySchema,
|
|
4314
|
-
device: "optional",
|
|
4315
|
-
at: "none"
|
|
4316
|
-
};
|
|
4317
|
-
var ncDurableRecordKinds = defineRecordKinds({
|
|
4318
|
-
liveness: ledger(NC_LIVENESS_LEGACY, "a liveness state TRANSITION the centre accepted (seed or flip); a no-flip writes nothing"),
|
|
4319
|
-
"device-alerts": ledger(NC_DEVICE_ALERTS_LEGACY, "a sustained device alert episode the centre confirmed or cleared"),
|
|
4320
|
-
"system-state": ledger(NC_SYSTEM_STATE_LEGACY, "a system-state subject flip the state summary accepted"),
|
|
4321
|
-
"alert-episodes": ledger(NC_ALERT_EPISODES_LEGACY, "an alert episode the centre notified (critical or error); its resolution deletes the row"),
|
|
4322
|
-
occupancy: ledger(NC_OCCUPANCY_LEGACY, "a confirmed occupancy level write-through after the watcher decided an edge"),
|
|
4323
|
-
"device-prunes": ledger(NC_DEVICE_PRUNES_LEGACY, "one row recording what a deleted-device prune removed"),
|
|
4324
|
-
"state-summary-stamps": ledger(NC_STATE_SUMMARY_STAMPS_LEGACY, "digest delivery stamps per rule kind"),
|
|
4325
|
-
meta: ledger(NC_META_LEGACY, "outbox boot-reconcile watermark and other single-key meta rows", "settings", { none: "one fixed key (watermark), overwritten in place, never deleted" }),
|
|
4326
|
-
snoozes: ledger(NC_SNOOZES_LEGACY, "operator opens or closes a snooze window (write-through)"),
|
|
4327
|
-
"snooze-suppressed": {
|
|
4328
|
-
...QUERIED,
|
|
4329
|
-
class: "ledger",
|
|
4330
|
-
database: "events",
|
|
4331
|
-
writeTrigger: "aggregate count per (window, target, rule, camera) for suppressed notifications",
|
|
4332
|
-
indexes: [{
|
|
4333
|
-
name: "snooze",
|
|
4334
|
-
fields: ["snoozeId"]
|
|
4335
|
-
}],
|
|
4336
|
-
retention: {
|
|
4337
|
-
owner: "notification-center",
|
|
4338
|
-
reason: "deleted with its snooze window, 6 h after that window closes"
|
|
4339
|
-
},
|
|
4340
|
-
adoptsFrom: NC_SNOOZE_SUPPRESSED_LEGACY
|
|
4341
|
-
},
|
|
4342
|
-
"track-groups": {
|
|
4343
|
-
...QUERIED,
|
|
4344
|
-
class: "ledger",
|
|
4345
|
-
database: "events",
|
|
4346
|
-
writeTrigger: "one row per grouped delivery window opened at admission time",
|
|
4347
|
-
indexes: [
|
|
4348
|
-
{
|
|
4349
|
-
name: "open",
|
|
4350
|
-
fields: ["sealed"]
|
|
4351
|
-
},
|
|
4352
|
-
{
|
|
4353
|
-
name: "opened",
|
|
4354
|
-
fields: ["openedAt"]
|
|
4355
|
-
},
|
|
4356
|
-
{
|
|
4357
|
-
name: "device_opened",
|
|
4358
|
-
fields: ["deviceId", "openedAt"]
|
|
4359
|
-
},
|
|
4360
|
-
{
|
|
4361
|
-
name: "rule_opened",
|
|
4362
|
-
fields: ["ruleId", "openedAt"]
|
|
4363
|
-
},
|
|
4364
|
-
{
|
|
4365
|
-
name: "rule_device_opened",
|
|
4366
|
-
fields: [
|
|
4367
|
-
"ruleId",
|
|
4368
|
-
"deviceId",
|
|
4369
|
-
"openedAt"
|
|
4370
|
-
]
|
|
4371
|
-
}
|
|
4372
|
-
],
|
|
4373
|
-
retention: {
|
|
4374
|
-
owner: "notification-center",
|
|
4375
|
-
reason: "ring: the newest NC_GROUP_MAX_ROWS_PER_DEVICE rows per camera, trimmed on write"
|
|
4376
|
-
},
|
|
4377
|
-
adoptsFrom: NC_TRACK_GROUPS_LEGACY
|
|
4378
|
-
},
|
|
4379
|
-
outbox: {
|
|
4380
|
-
...QUERIED,
|
|
4381
|
-
class: "ledger",
|
|
4382
|
-
database: "events",
|
|
4383
|
-
writeTrigger: "one row per (rule, dedupRef, target) enqueued for delivery",
|
|
4384
|
-
indexes: [
|
|
4385
|
-
{
|
|
4386
|
-
name: "created",
|
|
4387
|
-
fields: ["createdAt"]
|
|
4388
|
-
},
|
|
4389
|
-
{
|
|
4390
|
-
name: "rule_created",
|
|
4391
|
-
fields: ["ruleId", "createdAt"]
|
|
4392
|
-
},
|
|
4393
|
-
{
|
|
4394
|
-
name: "status_created",
|
|
4395
|
-
fields: ["status", "createdAt"]
|
|
4396
|
-
},
|
|
4397
|
-
{
|
|
4398
|
-
name: "device_created",
|
|
4399
|
-
fields: ["deviceId", "createdAt"]
|
|
4400
|
-
},
|
|
4401
|
-
{
|
|
4402
|
-
name: "rule_status_created",
|
|
4403
|
-
fields: [
|
|
4404
|
-
"ruleId",
|
|
4405
|
-
"status",
|
|
4406
|
-
"createdAt"
|
|
4407
|
-
]
|
|
4408
|
-
},
|
|
4409
|
-
{
|
|
4410
|
-
name: "rule_device_created",
|
|
4411
|
-
fields: [
|
|
4412
|
-
"ruleId",
|
|
4413
|
-
"deviceId",
|
|
4414
|
-
"createdAt"
|
|
4415
|
-
]
|
|
4416
|
-
},
|
|
4417
|
-
{
|
|
4418
|
-
name: "status_device_created",
|
|
4419
|
-
fields: [
|
|
4420
|
-
"status",
|
|
4421
|
-
"deviceId",
|
|
4422
|
-
"createdAt"
|
|
4423
|
-
]
|
|
4424
|
-
},
|
|
4425
|
-
{
|
|
4426
|
-
name: "rule_status_device_created",
|
|
4427
|
-
fields: [
|
|
4428
|
-
"ruleId",
|
|
4429
|
-
"status",
|
|
4430
|
-
"deviceId",
|
|
4431
|
-
"createdAt"
|
|
4432
|
-
]
|
|
4433
|
-
}
|
|
4434
|
-
],
|
|
4435
|
-
retention: {
|
|
4436
|
-
owner: "notification-center",
|
|
4437
|
-
reason: "terminal (sent/dead) rows older than 7 days pruned at boot; a pending row retries to dead first"
|
|
4438
|
-
},
|
|
4439
|
-
maxDataBytes: RECORD_MAX_DATA_BYTES,
|
|
4440
|
-
adoptsFrom: NC_OUTBOX_LEGACY
|
|
4441
|
-
},
|
|
4442
|
-
artifacts: {
|
|
4443
|
-
...QUERIED,
|
|
4444
|
-
class: "ledger",
|
|
4445
|
-
database: "events",
|
|
4446
|
-
writeTrigger: "retained notification artefact metadata indexed for lookup and expiry",
|
|
4447
|
-
indexes: [{
|
|
4448
|
-
name: "captured",
|
|
4449
|
-
fields: ["capturedAt"]
|
|
4450
|
-
}, {
|
|
4451
|
-
name: "expires",
|
|
4452
|
-
fields: ["expiresAt"]
|
|
4453
|
-
}],
|
|
4454
|
-
retention: {
|
|
4455
|
-
owner: "notification-center",
|
|
4456
|
-
reason: "falls with its bytes: expired rows on every node, orphans against the shelf on the producer node"
|
|
4457
|
-
},
|
|
4458
|
-
adoptsFrom: NC_ARTIFACTS_LEGACY
|
|
4459
|
-
}
|
|
4460
|
-
});
|
|
4461
|
-
//#endregion
|
|
4462
4483
|
//#region src/notification-center/artifact-index.ts
|
|
4463
4484
|
/**
|
|
4464
4485
|
* @durable class=ledger owner=notification-center database=events
|
|
@@ -9433,38 +9454,12 @@ var FaceRescoreJobStatusSchema = object({
|
|
|
9433
9454
|
* Record kinds owned by the pipeline-analytics addon (records plan phase 2,
|
|
9434
9455
|
* steps 7-11).
|
|
9435
9456
|
*
|
|
9436
|
-
* Each kind
|
|
9437
|
-
* name
|
|
9438
|
-
*
|
|
9439
|
-
*
|
|
9457
|
+
* Each kind replaced the legacy `pipeline-analytics:*` collection of the same
|
|
9458
|
+
* name; the records migration that copied them is closed (D900) and the tables
|
|
9459
|
+
* are gone (`retired-legacy-tables.ts`). The addon declares no
|
|
9460
|
+
* `execution.group`, so the hub owner stamp is the addon id
|
|
9440
9461
|
* (`pipeline-analytics`).
|
|
9441
9462
|
*/
|
|
9442
|
-
/** Legacy names for phase-3 analytics ledgers (keep in sync with owner constants). */
|
|
9443
|
-
var PA_PHANTOM_CELLS = "pipeline-analytics:phantom-cells";
|
|
9444
|
-
var PA_PACKAGE_BACKGROUND = "pipeline-analytics:package-background";
|
|
9445
|
-
var PA_STATIONARY_EVIDENCE = "pipeline-analytics:stationary-evidence";
|
|
9446
|
-
var PA_STATIONARY_OBJECTS = "pipeline-analytics:stationary-objects";
|
|
9447
|
-
var PA_SCENE_LATCH = "pipeline-analytics:scene-latch";
|
|
9448
|
-
var PA_MOTION_EVENTS = "pipeline-analytics:motion-events";
|
|
9449
|
-
var PA_AUDIO_EVENTS = "pipeline-analytics:audio-events";
|
|
9450
|
-
var PA_SENSOR_EVENTS = "pipeline-analytics:sensor-events";
|
|
9451
|
-
var PA_FACES = "pipeline-analytics:faces";
|
|
9452
|
-
var PA_PLATES = "pipeline-analytics:plates";
|
|
9453
|
-
var PA_TRACK_ALIASES = "pipeline-analytics:track-aliases";
|
|
9454
|
-
var PA_TRACK_PURITY = "pipeline-analytics:track-purity";
|
|
9455
|
-
var PA_TRACK_RELATIONS = "pipeline-analytics:track-relations";
|
|
9456
|
-
var PA_TRACKING_TAPES = "pipeline-analytics:tracking-tapes";
|
|
9457
|
-
var PA_BIRTH_DECISION_LEDGER = "pipeline-analytics:birth-decision-ledger";
|
|
9458
|
-
var PA_DEBUG_NOTE_ARCHIVE = "pipeline-analytics:debug-note-archive";
|
|
9459
|
-
var PA_OPS_LOG = "pipeline-analytics:ops-log";
|
|
9460
|
-
var PA_RETRAIN_FRAMES = "pipeline-analytics:retrain-frames";
|
|
9461
|
-
var PA_RETRAIN_ANNOTATIONS = "pipeline-analytics:retrain-annotations";
|
|
9462
|
-
var PA_RETRAIN_COLLECTIONS = "pipeline-analytics:retrain-collections";
|
|
9463
|
-
var PA_RETRAIN_COLLECTION_FRAMES = "pipeline-analytics:retrain-collection-frames";
|
|
9464
|
-
var PA_IDENTITIES = "pipeline-analytics:identities";
|
|
9465
|
-
var PA_IDENTITY_SAMPLES = "pipeline-analytics:identity-samples";
|
|
9466
|
-
var PA_VEHICLES = "pipeline-analytics:vehicles";
|
|
9467
|
-
var PA_VEHICLE_SAMPLES = "pipeline-analytics:vehicle-samples";
|
|
9468
9463
|
/**
|
|
9469
9464
|
* Kinds that must agree through ONE `transact` sit in one file under one family
|
|
9470
9465
|
* name, and the engine refuses a family split across files (records plan risk 8).
|
|
@@ -9491,7 +9486,9 @@ var MigrationMarkerBodySchema = object({
|
|
|
9491
9486
|
});
|
|
9492
9487
|
/** The orphan audit's resume position: one row, the whole map in one value on purpose. */
|
|
9493
9488
|
var SweepCursorBodySchema = object({
|
|
9494
|
-
|
|
9489
|
+
/** A number is an offset (a settings-store scope); a string is the records
|
|
9490
|
+
* engine's opaque keyset cursor (a scope walked through its kind). */
|
|
9491
|
+
scopes: record(string(), union([number(), string()])),
|
|
9495
9492
|
/** Absent on a row written before the walk existed — absent means "from the top". */
|
|
9496
9493
|
faceVectorCursor: number().optional(),
|
|
9497
9494
|
/** One cursor per CLIP index, keyed by index name. Absent on an older row. */
|
|
@@ -9516,8 +9513,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9516
9513
|
fields: ["deviceId"]
|
|
9517
9514
|
}],
|
|
9518
9515
|
retention: { none: "operator intent: a scene goes only through deleteScene, never by age" },
|
|
9519
|
-
maxDataBytes: RECORD_MAX_DATA_BYTES
|
|
9520
|
-
adoptsFrom: "pipeline-analytics:scenes"
|
|
9516
|
+
maxDataBytes: RECORD_MAX_DATA_BYTES
|
|
9521
9517
|
},
|
|
9522
9518
|
migrations: {
|
|
9523
9519
|
schema: MigrationMarkerBodySchema,
|
|
@@ -9526,8 +9522,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9526
9522
|
writeTrigger: "one marker row when a boot migration (label-tier-4g, audio-track-class-d189) completes without truncating",
|
|
9527
9523
|
device: "none",
|
|
9528
9524
|
at: "none",
|
|
9529
|
-
retention: { none: "its PRESENCE stops the migration re-scanning every track, face and event row on the next boot; bounded by the number of migrations ever written (two today)" }
|
|
9530
|
-
adoptsFrom: "pipeline-analytics:migrations"
|
|
9525
|
+
retention: { none: "its PRESENCE stops the migration re-scanning every track, face and event row on the next boot; bounded by the number of migrations ever written (two today)" }
|
|
9531
9526
|
},
|
|
9532
9527
|
"sweep-cursors": {
|
|
9533
9528
|
schema: SweepCursorBodySchema,
|
|
@@ -9536,8 +9531,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9536
9531
|
writeTrigger: "the end of every orphan-audit pass rewrites the one row holding every scope cursor",
|
|
9537
9532
|
device: "none",
|
|
9538
9533
|
at: "none",
|
|
9539
|
-
retention: { none: "one row (id orphan-audit); losing it costs a re-walk from offset 0, never a wrong delete" }
|
|
9540
|
-
adoptsFrom: "pipeline-analytics:sweep-cursors"
|
|
9534
|
+
retention: { none: "one row (id orphan-audit); losing it costs a re-walk from offset 0, never a wrong delete" }
|
|
9541
9535
|
},
|
|
9542
9536
|
"face-rescore-state": {
|
|
9543
9537
|
schema: FaceRescoreStateBodySchema,
|
|
@@ -9545,8 +9539,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9545
9539
|
writeTrigger: "a whole-archive face rescore (manual or scheduled) completes",
|
|
9546
9540
|
device: "none",
|
|
9547
9541
|
at: "none",
|
|
9548
|
-
retention: { none: "one row (id last-run); losing it costs one idempotent rescore on the next scheduler tick" }
|
|
9549
|
-
adoptsFrom: "pipeline-analytics:face-rescore-state"
|
|
9542
|
+
retention: { none: "one row (id last-run); losing it costs one idempotent rescore on the next scheduler tick" }
|
|
9550
9543
|
},
|
|
9551
9544
|
"phantom-cells": {
|
|
9552
9545
|
schema: DurableLedgerBodySchema,
|
|
@@ -9554,8 +9547,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9554
9547
|
writeTrigger: "a phantom cell closes and the short-motionless bar records it",
|
|
9555
9548
|
device: "required",
|
|
9556
9549
|
at: "none",
|
|
9557
|
-
retention: { none: "bounded by per-device cap in application code" }
|
|
9558
|
-
adoptsFrom: PA_PHANTOM_CELLS
|
|
9550
|
+
retention: { none: "bounded by per-device cap in application code" }
|
|
9559
9551
|
},
|
|
9560
9552
|
"package-background": {
|
|
9561
9553
|
schema: DurableLedgerBodySchema,
|
|
@@ -9563,8 +9555,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9563
9555
|
writeTrigger: "package background state accepted for a device",
|
|
9564
9556
|
device: "required",
|
|
9565
9557
|
at: "none",
|
|
9566
|
-
retention: { none: "one row per device key the owner tracks" }
|
|
9567
|
-
adoptsFrom: PA_PACKAGE_BACKGROUND
|
|
9558
|
+
retention: { none: "one row per device key the owner tracks" }
|
|
9568
9559
|
},
|
|
9569
9560
|
"stationary-evidence": {
|
|
9570
9561
|
schema: DurableLedgerBodySchema,
|
|
@@ -9572,8 +9563,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9572
9563
|
writeTrigger: "stationary evidence row written on the registry sweep",
|
|
9573
9564
|
device: "required",
|
|
9574
9565
|
at: "none",
|
|
9575
|
-
retention: { none: "owner sweep drops idle evidence; maxRowsPerDevice in kind metadata" }
|
|
9576
|
-
adoptsFrom: PA_STATIONARY_EVIDENCE
|
|
9566
|
+
retention: { none: "owner sweep drops idle evidence; maxRowsPerDevice in kind metadata" }
|
|
9577
9567
|
},
|
|
9578
9568
|
"stationary-objects": {
|
|
9579
9569
|
schema: DurableLedgerBodySchema,
|
|
@@ -9581,8 +9571,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9581
9571
|
writeTrigger: "parked object registry upsert or flush on the sweep interval",
|
|
9582
9572
|
device: "required",
|
|
9583
9573
|
at: "none",
|
|
9584
|
-
retention: { none: "rows drop when the object moves or TTL expires" }
|
|
9585
|
-
adoptsFrom: PA_STATIONARY_OBJECTS
|
|
9574
|
+
retention: { none: "rows drop when the object moves or TTL expires" }
|
|
9586
9575
|
},
|
|
9587
9576
|
"scene-latch": {
|
|
9588
9577
|
schema: DurableLedgerBodySchema,
|
|
@@ -9590,8 +9579,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9590
9579
|
writeTrigger: "scene monitor latch state write-through after a verdict",
|
|
9591
9580
|
device: "required",
|
|
9592
9581
|
at: "none",
|
|
9593
|
-
retention: { none: "one row per (deviceId, monitorId) scene latch" }
|
|
9594
|
-
adoptsFrom: PA_SCENE_LATCH
|
|
9582
|
+
retention: { none: "one row per (deviceId, monitorId) scene latch" }
|
|
9595
9583
|
},
|
|
9596
9584
|
"motion-events": {
|
|
9597
9585
|
schema: MotionEventBodySchema,
|
|
@@ -9608,8 +9596,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9608
9596
|
retention: {
|
|
9609
9597
|
owner: "device-window",
|
|
9610
9598
|
reason: "terminal age-keyed root swept with the device retention window (D61/D62)"
|
|
9611
|
-
}
|
|
9612
|
-
adoptsFrom: PA_MOTION_EVENTS
|
|
9599
|
+
}
|
|
9613
9600
|
},
|
|
9614
9601
|
"audio-events": {
|
|
9615
9602
|
schema: AudioEventBodySchema,
|
|
@@ -9626,8 +9613,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9626
9613
|
retention: {
|
|
9627
9614
|
owner: "device-window",
|
|
9628
9615
|
reason: "terminal age-keyed root on the device window, same as motion-events"
|
|
9629
|
-
}
|
|
9630
|
-
adoptsFrom: PA_AUDIO_EVENTS
|
|
9616
|
+
}
|
|
9631
9617
|
},
|
|
9632
9618
|
"sensor-events": {
|
|
9633
9619
|
schema: SensorEventBodySchema,
|
|
@@ -9647,8 +9633,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9647
9633
|
retention: {
|
|
9648
9634
|
owner: "device-window",
|
|
9649
9635
|
reason: "terminal age-keyed root on the camera retention window (D61)"
|
|
9650
|
-
}
|
|
9651
|
-
adoptsFrom: PA_SENSOR_EVENTS
|
|
9636
|
+
}
|
|
9652
9637
|
},
|
|
9653
9638
|
faces: {
|
|
9654
9639
|
schema: FaceLedgerBodySchema,
|
|
@@ -9675,8 +9660,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9675
9660
|
fields: ["trackId"]
|
|
9676
9661
|
}
|
|
9677
9662
|
],
|
|
9678
|
-
retention: { none: "track-owned (D61): rows leave with their track; enrolled faces exempt from capacity sweeps" }
|
|
9679
|
-
adoptsFrom: PA_FACES
|
|
9663
|
+
retention: { none: "track-owned (D61): rows leave with their track; enrolled faces exempt from capacity sweeps" }
|
|
9680
9664
|
},
|
|
9681
9665
|
identities: {
|
|
9682
9666
|
schema: IdentityConfigBodySchema,
|
|
@@ -9690,8 +9674,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9690
9674
|
name: "name",
|
|
9691
9675
|
fields: ["name"]
|
|
9692
9676
|
}],
|
|
9693
|
-
retention: { none: "operator delete only — losing the gallery un-enrols every person" }
|
|
9694
|
-
adoptsFrom: PA_IDENTITIES
|
|
9677
|
+
retention: { none: "operator delete only — losing the gallery un-enrols every person" }
|
|
9695
9678
|
},
|
|
9696
9679
|
"identity-samples": {
|
|
9697
9680
|
schema: IdentitySampleLedgerBodySchema,
|
|
@@ -9705,7 +9688,6 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9705
9688
|
fields: ["identityId", "addedAt"]
|
|
9706
9689
|
}],
|
|
9707
9690
|
retention: { none: "enrollment exempt — rows leave only on removeSample or deleteIdentity" },
|
|
9708
|
-
adoptsFrom: PA_IDENTITY_SAMPLES,
|
|
9709
9691
|
transactFamily: IDENTITY_GALLERY
|
|
9710
9692
|
},
|
|
9711
9693
|
vehicles: {
|
|
@@ -9720,8 +9702,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9720
9702
|
name: "name",
|
|
9721
9703
|
fields: ["name"]
|
|
9722
9704
|
}],
|
|
9723
|
-
retention: { none: "operator delete only — losing the gallery un-enrols every vehicle" }
|
|
9724
|
-
adoptsFrom: PA_VEHICLES
|
|
9705
|
+
retention: { none: "operator delete only — losing the gallery un-enrols every vehicle" }
|
|
9725
9706
|
},
|
|
9726
9707
|
"vehicle-samples": {
|
|
9727
9708
|
schema: VehicleSampleLedgerBodySchema,
|
|
@@ -9735,7 +9716,6 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9735
9716
|
fields: ["vehicleId", "addedAt"]
|
|
9736
9717
|
}],
|
|
9737
9718
|
retention: { none: "enrollment exempt — rows leave only on removeSample or deleteVehicle" },
|
|
9738
|
-
adoptsFrom: PA_VEHICLE_SAMPLES,
|
|
9739
9719
|
transactFamily: VEHICLE_GALLERY
|
|
9740
9720
|
},
|
|
9741
9721
|
plates: {
|
|
@@ -9759,8 +9739,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9759
9739
|
fields: ["recognizedVehicleId", "timestamp"]
|
|
9760
9740
|
}
|
|
9761
9741
|
],
|
|
9762
|
-
retention: { none: "track-owned (D61): rows leave with their track; corrected and enrolled reads exempt" }
|
|
9763
|
-
adoptsFrom: PA_PLATES
|
|
9742
|
+
retention: { none: "track-owned (D61): rows leave with their track; corrected and enrolled reads exempt" }
|
|
9764
9743
|
},
|
|
9765
9744
|
"track-aliases": {
|
|
9766
9745
|
schema: PaEventsLedgerBodySchema,
|
|
@@ -9776,8 +9755,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9776
9755
|
name: "device",
|
|
9777
9756
|
fields: ["deviceId"]
|
|
9778
9757
|
}],
|
|
9779
|
-
retention: { none: "track-owned via survivor `trackId`; widens retention cascade (D683)" }
|
|
9780
|
-
adoptsFrom: PA_TRACK_ALIASES
|
|
9758
|
+
retention: { none: "track-owned via survivor `trackId`; widens retention cascade (D683)" }
|
|
9781
9759
|
},
|
|
9782
9760
|
"track-purity": {
|
|
9783
9761
|
schema: PaEventsLedgerBodySchema,
|
|
@@ -9793,8 +9771,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9793
9771
|
name: "updated",
|
|
9794
9772
|
fields: ["updatedAt"]
|
|
9795
9773
|
}],
|
|
9796
|
-
retention: { none: "row-count ceiling MAX_TRACK_PURITIES in application code (D681)" }
|
|
9797
|
-
adoptsFrom: PA_TRACK_PURITY
|
|
9774
|
+
retention: { none: "row-count ceiling MAX_TRACK_PURITIES in application code (D681)" }
|
|
9798
9775
|
},
|
|
9799
9776
|
"track-relations": {
|
|
9800
9777
|
schema: PaEventsLedgerBodySchema,
|
|
@@ -9810,8 +9787,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9810
9787
|
name: "updated",
|
|
9811
9788
|
fields: ["updatedAt"]
|
|
9812
9789
|
}],
|
|
9813
|
-
retention: { none: "row-count ceiling MAX_TRACK_RELATIONS in application code (D681)" }
|
|
9814
|
-
adoptsFrom: PA_TRACK_RELATIONS
|
|
9790
|
+
retention: { none: "row-count ceiling MAX_TRACK_RELATIONS in application code (D681)" }
|
|
9815
9791
|
},
|
|
9816
9792
|
"tracking-tapes": {
|
|
9817
9793
|
schema: PaEventsLedgerBodySchema,
|
|
@@ -9824,8 +9800,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9824
9800
|
name: "device_created",
|
|
9825
9801
|
fields: ["deviceId", "createdAt"]
|
|
9826
9802
|
}],
|
|
9827
|
-
retention: { none: "promotion budget MAX_PROMOTED_TAPES — refused, never evicted by age" }
|
|
9828
|
-
adoptsFrom: PA_TRACKING_TAPES
|
|
9803
|
+
retention: { none: "promotion budget MAX_PROMOTED_TAPES — refused, never evicted by age" }
|
|
9829
9804
|
},
|
|
9830
9805
|
"birth-decision-ledger": {
|
|
9831
9806
|
schema: PaEventsLedgerBodySchema,
|
|
@@ -9841,8 +9816,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9841
9816
|
name: "device_at",
|
|
9842
9817
|
fields: ["deviceId", "at"]
|
|
9843
9818
|
}],
|
|
9844
|
-
retention: { none: "row-count ceiling MAX_BIRTH_DECISION_RECORDS in application code" }
|
|
9845
|
-
adoptsFrom: PA_BIRTH_DECISION_LEDGER
|
|
9819
|
+
retention: { none: "row-count ceiling MAX_BIRTH_DECISION_RECORDS in application code" }
|
|
9846
9820
|
},
|
|
9847
9821
|
"debug-note-archive": {
|
|
9848
9822
|
schema: PaEventsLedgerBodySchema,
|
|
@@ -9858,8 +9832,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9858
9832
|
name: "device_archived",
|
|
9859
9833
|
fields: ["deviceId", "archivedAt"]
|
|
9860
9834
|
}],
|
|
9861
|
-
retention: { none: "row-count ceiling MAX_ARCHIVED_DEBUG_NOTES in application code" }
|
|
9862
|
-
adoptsFrom: PA_DEBUG_NOTE_ARCHIVE
|
|
9835
|
+
retention: { none: "row-count ceiling MAX_ARCHIVED_DEBUG_NOTES in application code" }
|
|
9863
9836
|
},
|
|
9864
9837
|
"ops-log": {
|
|
9865
9838
|
schema: PaEventsLedgerBodySchema,
|
|
@@ -9878,8 +9851,7 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9878
9851
|
retention: {
|
|
9879
9852
|
ttlMs: 720 * 60 * 60 * 1e3,
|
|
9880
9853
|
from: "at"
|
|
9881
|
-
}
|
|
9882
|
-
adoptsFrom: PA_OPS_LOG
|
|
9854
|
+
}
|
|
9883
9855
|
},
|
|
9884
9856
|
"retrain-frames": {
|
|
9885
9857
|
schema: PaEventsLedgerBodySchema,
|
|
@@ -9896,7 +9868,6 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9896
9868
|
fields: ["deviceId", "complete"]
|
|
9897
9869
|
}],
|
|
9898
9870
|
retention: { none: "operator curation only; blobs on artifacts tenant (D781)" },
|
|
9899
|
-
adoptsFrom: PA_RETRAIN_FRAMES,
|
|
9900
9871
|
transactFamily: RETRAIN
|
|
9901
9872
|
},
|
|
9902
9873
|
"retrain-annotations": {
|
|
@@ -9921,7 +9892,6 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9921
9892
|
}
|
|
9922
9893
|
],
|
|
9923
9894
|
retention: { none: "leaves with frame deselect / operator delete" },
|
|
9924
|
-
adoptsFrom: PA_RETRAIN_ANNOTATIONS,
|
|
9925
9895
|
transactFamily: RETRAIN
|
|
9926
9896
|
},
|
|
9927
9897
|
"retrain-collections": {
|
|
@@ -9940,7 +9910,6 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9940
9910
|
fields: ["createdAt"]
|
|
9941
9911
|
}],
|
|
9942
9912
|
retention: { none: "deleted with operator deleteCollection" },
|
|
9943
|
-
adoptsFrom: PA_RETRAIN_COLLECTIONS,
|
|
9944
9913
|
transactFamily: RETRAIN
|
|
9945
9914
|
},
|
|
9946
9915
|
"retrain-collection-frames": {
|
|
@@ -9958,14 +9927,13 @@ var pipelineAnalyticsRecordKinds = defineRecordKinds({
|
|
|
9958
9927
|
fields: ["frameId"]
|
|
9959
9928
|
}],
|
|
9960
9929
|
retention: { none: "membership only; frame pixels live in retrain-frames" },
|
|
9961
|
-
adoptsFrom: PA_RETRAIN_COLLECTION_FRAMES,
|
|
9962
9930
|
transactFamily: RETRAIN
|
|
9963
9931
|
}
|
|
9964
9932
|
});
|
|
9965
9933
|
//#endregion
|
|
9966
9934
|
//#region src/pipeline-analytics/scene/scene-config-ledger.ts
|
|
9967
9935
|
/** The engine's page cap for one query; a reseed pages by it, bounded by the ledger limit. */
|
|
9968
|
-
var PAGE_SIZE$
|
|
9936
|
+
var PAGE_SIZE$2 = 1e3;
|
|
9969
9937
|
/**
|
|
9970
9938
|
* A row whose monitor JSON no longer validates is SKIPPED, not repaired: a
|
|
9971
9939
|
* half-parsed scene would report a verdict about a region it cannot describe.
|
|
@@ -10003,7 +9971,7 @@ var SceneConfigLedger = class {
|
|
|
10003
9971
|
let seen = 0;
|
|
10004
9972
|
let cursor;
|
|
10005
9973
|
for (;;) {
|
|
10006
|
-
const limit = Math.min(PAGE_SIZE$
|
|
9974
|
+
const limit = Math.min(PAGE_SIZE$2, this.loadLimit - seen);
|
|
10007
9975
|
if (limit <= 0) break;
|
|
10008
9976
|
const page = await this.kind.query({
|
|
10009
9977
|
limit,
|
|
@@ -12050,6 +12018,28 @@ var EventLedgerCollection = class EventLedgerCollection {
|
|
|
12050
12018
|
if (mapped.limit !== void 0 && out.length > mapped.limit) return out.slice(0, mapped.limit);
|
|
12051
12019
|
return out;
|
|
12052
12020
|
}
|
|
12021
|
+
/**
|
|
12022
|
+
* One page of the whole kind in `id` order, resumed from the records
|
|
12023
|
+
* engine's OPAQUE keyset cursor (`null` = from the top). The answer's
|
|
12024
|
+
* `nextCursor` is `null` at the end. A keyset cursor names the last row
|
|
12025
|
+
* read, so deleting rows behind it never shifts what comes next — the
|
|
12026
|
+
* reason a bulk walk that reclaims as it goes needs this, not an offset.
|
|
12027
|
+
*
|
|
12028
|
+
* Records only: without `ctx.records` the caller pages the settings store
|
|
12029
|
+
* itself, and asking here throws rather than inventing a cursor.
|
|
12030
|
+
*/
|
|
12031
|
+
async pageById(cursor, limit) {
|
|
12032
|
+
if (this.kind === null) throw new Error(`event ledger ${this.collection}: pageById needs the records kind`);
|
|
12033
|
+
const page = await this.kind.query({
|
|
12034
|
+
order: "id",
|
|
12035
|
+
limit,
|
|
12036
|
+
...cursor === null ? {} : { cursor }
|
|
12037
|
+
});
|
|
12038
|
+
return {
|
|
12039
|
+
rows: page.records.map((row) => recordsRowToLegacy(row, this.legacyAtField)),
|
|
12040
|
+
nextCursor: page.nextCursor
|
|
12041
|
+
};
|
|
12042
|
+
}
|
|
12053
12043
|
async getManyByIds(ids) {
|
|
12054
12044
|
if (ids.length === 0) return [];
|
|
12055
12045
|
if (this.kind === null) return (await this.store.query.query({
|
|
@@ -13176,11 +13166,35 @@ var FaceStore = class {
|
|
|
13176
13166
|
* carries no `embeddingModelId`, and those rows were all `arcface-r100`, so
|
|
13177
13167
|
* that is what they are stamped with — a KNOWN historical fact, not a
|
|
13178
13168
|
* fallback guess. A row that names some other model keeps its own.
|
|
13169
|
+
*
|
|
13170
|
+
* On a records hub the rows are paged through the faces LEDGER: the legacy
|
|
13171
|
+
* table is dropped there, and the adoption copied each row's body whole —
|
|
13172
|
+
* an `embedding` the pass had not drained yet included — so a records-era
|
|
13173
|
+
* row can still need converting (the same shape as `IdentityStore`).
|
|
13179
13174
|
*/
|
|
13180
13175
|
conversionSpec() {
|
|
13176
|
+
const ledger = this.ledger;
|
|
13177
|
+
const recordsPaging = ledger.usesRecords ? {
|
|
13178
|
+
pageRows: (limit, offset) => ledger.query({
|
|
13179
|
+
orderBy: {
|
|
13180
|
+
field: "id",
|
|
13181
|
+
direction: "asc"
|
|
13182
|
+
},
|
|
13183
|
+
limit,
|
|
13184
|
+
offset
|
|
13185
|
+
}, [
|
|
13186
|
+
"deviceId",
|
|
13187
|
+
"timestamp",
|
|
13188
|
+
"trackId",
|
|
13189
|
+
"embeddingModelId",
|
|
13190
|
+
"embedding"
|
|
13191
|
+
]),
|
|
13192
|
+
drainEmbedding: (id) => ledger.update(id, { embedding: [] })
|
|
13193
|
+
} : {};
|
|
13181
13194
|
return {
|
|
13182
13195
|
collection: FACES_COLLECTION,
|
|
13183
13196
|
vectors: this.vectors,
|
|
13197
|
+
...recordsPaging,
|
|
13184
13198
|
write: (id, embedding, data) => {
|
|
13185
13199
|
const deviceId = data["deviceId"];
|
|
13186
13200
|
const timestamp = data["timestamp"];
|
|
@@ -38396,22 +38410,8 @@ async function migrateLegacyRuleActions(actions, createSequence) {
|
|
|
38396
38410
|
* The notification-center module is hosted inside the pipeline-analytics addon.
|
|
38397
38411
|
* That addon declares no `execution.group`, so the UDS owner stamp is the addon
|
|
38398
38412
|
* id: `pipeline-analytics`. Declared hub-only (agents have no `ctx.records`).
|
|
38399
|
-
* One deploy covers all 7 kinds
|
|
38400
|
-
*/
|
|
38401
|
-
/** Legacy collection for the text-catalog document. */
|
|
38402
|
-
var NC_TEXTS_COLLECTION$1 = "notification-center:texts";
|
|
38403
|
-
/** Legacy collection for shared action sequences. */
|
|
38404
|
-
var NC_SEQUENCES_COLLECTION$1 = "notification-center:sequences";
|
|
38405
|
-
/** Legacy collection for per-rule AI sections. */
|
|
38406
|
-
var NC_RULE_AI_COLLECTION$1 = "notification-center:rule-ai";
|
|
38407
|
-
/** Legacy collection for calendar-events rules. */
|
|
38408
|
-
var NC_CALENDAR_EVENTS_RULES_COLLECTION$1 = "notification-center:calendar-events-rules";
|
|
38409
|
-
/** Legacy collection for multi-camera digest rules. */
|
|
38410
|
-
var NC_SUMMARY_RULES_COLLECTION$1 = "notification-center:summary-rules";
|
|
38411
|
-
/** Legacy collection for timelapse rules. */
|
|
38412
|
-
var NC_TIMELAPSE_RULES_COLLECTION$1 = "notification-center:timelapse-rules";
|
|
38413
|
-
/** Legacy collection for notification rules. */
|
|
38414
|
-
var NC_RULES_COLLECTION$1 = "notification-center:rules";
|
|
38413
|
+
* One deploy covers all 7 kinds.
|
|
38414
|
+
*/
|
|
38415
38415
|
/**
|
|
38416
38416
|
* The hub language and operator-authored per-key overrides.
|
|
38417
38417
|
* One row, id `texts`. Losing it degrades to the shipped English catalog.
|
|
@@ -38453,8 +38453,7 @@ var ncRecordKinds = defineRecordKinds({
|
|
|
38453
38453
|
writeTrigger: "an operator changes the hub language (setLanguage) or replaces the per-key override set (setOverrides)",
|
|
38454
38454
|
device: "none",
|
|
38455
38455
|
at: "none",
|
|
38456
|
-
retention: { none: "one row (id `texts`); losing it costs the chosen language and household wording, never a notification — every read degrades to the shipped English catalog" }
|
|
38457
|
-
adoptsFrom: NC_TEXTS_COLLECTION$1
|
|
38456
|
+
retention: { none: "one row (id `texts`); losing it costs the chosen language and household wording, never a notification — every read degrades to the shipped English catalog" }
|
|
38458
38457
|
},
|
|
38459
38458
|
/**
|
|
38460
38459
|
* Shared action sequences — named, ordered runs of steps with their own throttle.
|
|
@@ -38466,8 +38465,7 @@ var ncRecordKinds = defineRecordKinds({
|
|
|
38466
38465
|
writeTrigger: "an admin creates/edits/deletes a shared sequence via notificationRules.createSequence / updateSequence / deleteSequence, or the one-shot boot migration that materialises legacy inline sequences",
|
|
38467
38466
|
device: "none",
|
|
38468
38467
|
at: "none",
|
|
38469
|
-
retention: { none: "a row goes only when an admin deletes it; bounded by the sequence set a human is willing to author" }
|
|
38470
|
-
adoptsFrom: NC_SEQUENCES_COLLECTION$1
|
|
38468
|
+
retention: { none: "a row goes only when an admin deletes it; bounded by the sequence set a human is willing to author" }
|
|
38471
38469
|
},
|
|
38472
38470
|
/**
|
|
38473
38471
|
* Per-rule AI section: whether and how a rule narrates itself.
|
|
@@ -38479,8 +38477,7 @@ var ncRecordKinds = defineRecordKinds({
|
|
|
38479
38477
|
writeTrigger: "an operator switches the AI section of a notification rule on or off, or edits its prompt / word cap / language",
|
|
38480
38478
|
device: "none",
|
|
38481
38479
|
at: "none",
|
|
38482
|
-
retention: { none: "a row goes when the operator clears the section or deletes the rule; bounded by the rule set a human is willing to author" }
|
|
38483
|
-
adoptsFrom: NC_RULE_AI_COLLECTION$1
|
|
38480
|
+
retention: { none: "a row goes when the operator clears the section or deletes the rule; bounded by the rule set a human is willing to author" }
|
|
38484
38481
|
},
|
|
38485
38482
|
/**
|
|
38486
38483
|
* Calendar-events rules. One row per rule, id = rule UUID.
|
|
@@ -38495,8 +38492,7 @@ var ncRecordKinds = defineRecordKinds({
|
|
|
38495
38492
|
name: "enabled",
|
|
38496
38493
|
fields: ["enabled"]
|
|
38497
38494
|
}],
|
|
38498
|
-
retention: { none: "a row goes only when the operator deletes the rule; bounded by the rule set a human is willing to author" }
|
|
38499
|
-
adoptsFrom: NC_CALENDAR_EVENTS_RULES_COLLECTION$1
|
|
38495
|
+
retention: { none: "a row goes only when the operator deletes the rule; bounded by the rule set a human is willing to author" }
|
|
38500
38496
|
},
|
|
38501
38497
|
/**
|
|
38502
38498
|
* Multi-camera digest rules. One row per rule, id = rule UUID.
|
|
@@ -38511,8 +38507,7 @@ var ncRecordKinds = defineRecordKinds({
|
|
|
38511
38507
|
name: "enabled",
|
|
38512
38508
|
fields: ["enabled"]
|
|
38513
38509
|
}],
|
|
38514
|
-
retention: { none: "a row goes only when the operator deletes the rule; bounded by the rule set a human is willing to author" }
|
|
38515
|
-
adoptsFrom: NC_SUMMARY_RULES_COLLECTION$1
|
|
38510
|
+
retention: { none: "a row goes only when the operator deletes the rule; bounded by the rule set a human is willing to author" }
|
|
38516
38511
|
},
|
|
38517
38512
|
/**
|
|
38518
38513
|
* Timelapse rules. One row per rule, id = rule UUID.
|
|
@@ -38528,8 +38523,7 @@ var ncRecordKinds = defineRecordKinds({
|
|
|
38528
38523
|
name: "enabled",
|
|
38529
38524
|
fields: ["enabled"]
|
|
38530
38525
|
}],
|
|
38531
|
-
retention: { none: "a row goes only when the operator deletes the rule; bounded by the rule set a human is willing to author" }
|
|
38532
|
-
adoptsFrom: NC_TIMELAPSE_RULES_COLLECTION$1
|
|
38526
|
+
retention: { none: "a row goes only when the operator deletes the rule; bounded by the rule set a human is willing to author" }
|
|
38533
38527
|
},
|
|
38534
38528
|
/**
|
|
38535
38529
|
* Notification rules — the main rule set. One row per rule, id = rule UUID.
|
|
@@ -38545,8 +38539,7 @@ var ncRecordKinds = defineRecordKinds({
|
|
|
38545
38539
|
name: "delivery",
|
|
38546
38540
|
fields: ["delivery"]
|
|
38547
38541
|
}],
|
|
38548
|
-
retention: { none: "a row goes only when the operator deletes the rule; bounded by the rule set a human is willing to author" }
|
|
38549
|
-
adoptsFrom: NC_RULES_COLLECTION$1
|
|
38542
|
+
retention: { none: "a row goes only when the operator deletes the rule; bounded by the rule set a human is willing to author" }
|
|
38550
38543
|
}
|
|
38551
38544
|
});
|
|
38552
38545
|
//#endregion
|
|
@@ -67432,7 +67425,7 @@ async function settleReplaced(deps, sample, replacement, dryRun) {
|
|
|
67432
67425
|
});
|
|
67433
67426
|
return "old-removal-retried";
|
|
67434
67427
|
}
|
|
67435
|
-
function tally$
|
|
67428
|
+
function tally$2(samples, outcomes, dryRun) {
|
|
67436
67429
|
const count = (kind) => outcomes.filter((o) => o.outcome === kind).length;
|
|
67437
67430
|
const refusals = [];
|
|
67438
67431
|
for (const o of outcomes) if (o.outcome === "refused" && o.reason !== void 0) refusals.push({
|
|
@@ -67504,7 +67497,7 @@ async function reenrolPhotoSamples(deps, options) {
|
|
|
67504
67497
|
outcomes.push(outcomeOf(sample, verdict));
|
|
67505
67498
|
await deps.pause(pacingMs);
|
|
67506
67499
|
}
|
|
67507
|
-
const result = tally$
|
|
67500
|
+
const result = tally$2(samples, outcomes, dryRun);
|
|
67508
67501
|
if (!dryRun && (result.reenrolled > 0 || result.oldRemovalRetried > 0)) await deps.refreshGallery();
|
|
67509
67502
|
const { outcomes: _perSample, ...summary } = result;
|
|
67510
67503
|
deps.logger.info("face photo re-enrol finished", { meta: { ...summary } });
|
|
@@ -78919,24 +78912,11 @@ function createDiscardJob(deps) {
|
|
|
78919
78912
|
});
|
|
78920
78913
|
await sleep(0);
|
|
78921
78914
|
}
|
|
78922
|
-
|
|
78915
|
+
const retrainIds = (await deps.retrainFrameLedger.query({})).filter((row) => row.data["locationId"] === locationId).map((row) => row.id);
|
|
78916
|
+
for (let i = 0; i < retrainIds.length; i += MEDIA_PAGE) {
|
|
78923
78917
|
if (cancelRequested) return owners;
|
|
78924
|
-
const
|
|
78925
|
-
|
|
78926
|
-
columns: ["id"],
|
|
78927
|
-
filter: {
|
|
78928
|
-
where: { locationId },
|
|
78929
|
-
limit: MEDIA_PAGE
|
|
78930
|
-
}
|
|
78931
|
-
});
|
|
78932
|
-
if (rows.length === 0) break;
|
|
78933
|
-
for (const row of rows) {
|
|
78934
|
-
await deps.store.delete.mutate({
|
|
78935
|
-
collection: RETRAIN_FRAMES_COLLECTION,
|
|
78936
|
-
key: row.id
|
|
78937
|
-
});
|
|
78938
|
-
deleted += 1;
|
|
78939
|
-
}
|
|
78918
|
+
const page = retrainIds.slice(i, i + MEDIA_PAGE);
|
|
78919
|
+
deleted += await deps.retrainFrameLedger.deleteByIds(page);
|
|
78940
78920
|
report({
|
|
78941
78921
|
dbRowsDeleted: deleted,
|
|
78942
78922
|
detail: `deleted ${deleted} media/retrain rows`
|
|
@@ -78988,6 +78968,382 @@ function createDiscardJob(deps) {
|
|
|
78988
78968
|
};
|
|
78989
78969
|
}
|
|
78990
78970
|
//#endregion
|
|
78971
|
+
//#region src/pipeline-analytics/retention/media-forget-actions.ts
|
|
78972
|
+
/**
|
|
78973
|
+
* The operator door for the NARROW media-row forget (`media-row-forget.ts`).
|
|
78974
|
+
*
|
|
78975
|
+
* It forgets `pipeline-analytics:media` rows stamped with ONE location whose
|
|
78976
|
+
* file is provably gone, and nothing else: no event, track, object-event,
|
|
78977
|
+
* face, plate or identity row is read for deletion, ever. Contrast
|
|
78978
|
+
* `storage.discardLocation`, which wipes the disk AND cascades owners.
|
|
78979
|
+
*
|
|
78980
|
+
* `dryRun` defaults to `true`: an unstated intent destroys nothing. Applying
|
|
78981
|
+
* also requires `expectAbsent` — the `absent` count a dry run just reported —
|
|
78982
|
+
* so the forget only ever deletes a population a human has already read.
|
|
78983
|
+
*/
|
|
78984
|
+
/** Hard ceiling on rows one call examines — the call is synchronous. */
|
|
78985
|
+
var MEDIA_FORGET_MAX_ROWS = 5e4;
|
|
78986
|
+
var MediaForgetInputSchema = object({
|
|
78987
|
+
locationId: string().min(1),
|
|
78988
|
+
/** Restrict to these cameras. Omitted = every row stamped with the location. */
|
|
78989
|
+
deviceIds: array(number().int()).min(1).optional(),
|
|
78990
|
+
dryRun: boolean().default(true),
|
|
78991
|
+
/**
|
|
78992
|
+
* Required when `dryRun` is false: the `totals.absent` of the dry run. A
|
|
78993
|
+
* different count at apply time refuses the whole call before any delete.
|
|
78994
|
+
*/
|
|
78995
|
+
expectAbsent: number().int().nonnegative().optional(),
|
|
78996
|
+
/** Rows examined in this call. Default and max {@link MEDIA_FORGET_MAX_ROWS}. */
|
|
78997
|
+
maxRows: number().int().min(1).max(MEDIA_FORGET_MAX_ROWS).optional(),
|
|
78998
|
+
/** Detailed rows returned per verdict. Default 20. */
|
|
78999
|
+
sampleSize: number().int().min(0).max(500).optional()
|
|
79000
|
+
});
|
|
79001
|
+
var VerdictCountsSchema = object({
|
|
79002
|
+
rows: number(),
|
|
79003
|
+
/** File answered ENOENT — the only verdict that may be forgotten. */
|
|
79004
|
+
absent: number(),
|
|
79005
|
+
/** File is there: relocation is incomplete. Never forgotten. */
|
|
79006
|
+
present: number(),
|
|
79007
|
+
/** The stat failed with any errno other than ENOENT. Never forgotten. */
|
|
79008
|
+
error: number(),
|
|
79009
|
+
/** No path, a path escaping the root, or a retention-exempt owner. Never forgotten. */
|
|
79010
|
+
kept: number(),
|
|
79011
|
+
/** Rows actually deleted (0 on a dry run). */
|
|
79012
|
+
deleted: number()
|
|
79013
|
+
});
|
|
79014
|
+
var MediaForgetSampleSchema = object({
|
|
79015
|
+
mediaId: number(),
|
|
79016
|
+
deviceId: number(),
|
|
79017
|
+
kind: string(),
|
|
79018
|
+
ownerKind: string(),
|
|
79019
|
+
ownerId: string(),
|
|
79020
|
+
path: string(),
|
|
79021
|
+
/** The errno (error) or the reason (kept); absent otherwise. */
|
|
79022
|
+
reason: string().optional()
|
|
79023
|
+
});
|
|
79024
|
+
var mediaForgetActions = defineCustomActions({ "storage.forgetMissingMedia": customAction(MediaForgetInputSchema, object({
|
|
79025
|
+
locationId: string(),
|
|
79026
|
+
dryRun: boolean(),
|
|
79027
|
+
root: string(),
|
|
79028
|
+
/** Rows of this location that were not examined because `maxRows` was hit. */
|
|
79029
|
+
truncated: boolean(),
|
|
79030
|
+
totals: VerdictCountsSchema,
|
|
79031
|
+
byDevice: array(VerdictCountsSchema.extend({ deviceId: number() })),
|
|
79032
|
+
byKind: array(VerdictCountsSchema.extend({ kind: string() })),
|
|
79033
|
+
/** Every id the call deletes (dry run: would delete). The exact list. */
|
|
79034
|
+
absentIds: array(number()),
|
|
79035
|
+
samples: object({
|
|
79036
|
+
absent: array(MediaForgetSampleSchema),
|
|
79037
|
+
present: array(MediaForgetSampleSchema),
|
|
79038
|
+
error: array(MediaForgetSampleSchema),
|
|
79039
|
+
kept: array(MediaForgetSampleSchema)
|
|
79040
|
+
}),
|
|
79041
|
+
/** Absent at classification, no longer ENOENT at delete time: kept. */
|
|
79042
|
+
changedBeforeDelete: number()
|
|
79043
|
+
}), {
|
|
79044
|
+
kind: "mutation",
|
|
79045
|
+
auth: "admin"
|
|
79046
|
+
}) });
|
|
79047
|
+
//#endregion
|
|
79048
|
+
//#region src/pipeline-analytics/retention/media-row-forget.ts
|
|
79049
|
+
/**
|
|
79050
|
+
* The NARROW media-row forget: delete `pipeline-analytics:media` rows stamped
|
|
79051
|
+
* with ONE storage location whose file is provably gone — and nothing else.
|
|
79052
|
+
*
|
|
79053
|
+
* Why it exists: a location whose bytes were lost keeps rows naming it, and a
|
|
79054
|
+
* cascading cleanup is not an acceptable way to clear them. `reconcileFromDisk`
|
|
79055
|
+
* once cascaded ~132 k events away from a read it did not double-check. Here:
|
|
79056
|
+
*
|
|
79057
|
+
* - **Only media rows.** No event, track, object-event, face, plate or
|
|
79058
|
+
* identity row is deleted. A parent keeps existing with that media kind
|
|
79059
|
+
* missing — which is exactly what it already serves (`getBytesById` answers
|
|
79060
|
+
* `null` for a missing row as it does for a missing blob).
|
|
79061
|
+
* - **Only `ENOENT` is absence (D49).** Each row's file is `stat`ed under the
|
|
79062
|
+
* location's own root on THIS node. Any other errno keeps the row and is
|
|
79063
|
+
* reported; a present file keeps the row and is reported (relocation is
|
|
79064
|
+
* incomplete). A row with no path, a path that escapes the root, or a
|
|
79065
|
+
* retention-exempt owner (the enrolled gallery) is kept.
|
|
79066
|
+
* - **Only rows stamped with the id.** `locationId IS NULL` rows mean
|
|
79067
|
+
* "wherever eventMedia points now" and are never selected.
|
|
79068
|
+
* - **Only a location nothing writes to.** An active (writable) location is
|
|
79069
|
+
* refused: new rows could land while the walk runs.
|
|
79070
|
+
* - **Dry run first, then the same count.** Applying requires
|
|
79071
|
+
* `expectAbsent`; the whole population is classified before any delete,
|
|
79072
|
+
* and a different absent count refuses the call with nothing written.
|
|
79073
|
+
* Each id is re-`stat`ed right before its batch is deleted, and the delete
|
|
79074
|
+
* predicate repeats `locationId`, so a row repointed in between survives.
|
|
79075
|
+
*
|
|
79076
|
+
* Occupancy needs no separate bookkeeping: `storage-occupancy` sums
|
|
79077
|
+
* `sizeBytes WHERE locationId = ?` on demand, so the row IS the accounting.
|
|
79078
|
+
*/
|
|
79079
|
+
var PAGE_SIZE$1 = 200;
|
|
79080
|
+
var DELETE_BATCH = 200;
|
|
79081
|
+
var DEFAULT_SAMPLE_SIZE = 20;
|
|
79082
|
+
function errnoOf(err) {
|
|
79083
|
+
if (err instanceof Error && "code" in err && typeof err.code === "string") return err.code;
|
|
79084
|
+
return err instanceof Error ? err.message : String(err);
|
|
79085
|
+
}
|
|
79086
|
+
function emptyCounts() {
|
|
79087
|
+
return {
|
|
79088
|
+
rows: 0,
|
|
79089
|
+
absent: 0,
|
|
79090
|
+
present: 0,
|
|
79091
|
+
error: 0,
|
|
79092
|
+
kept: 0,
|
|
79093
|
+
deleted: 0
|
|
79094
|
+
};
|
|
79095
|
+
}
|
|
79096
|
+
function tally$1(counts, verdict) {
|
|
79097
|
+
const rows = counts.rows + 1;
|
|
79098
|
+
switch (verdict) {
|
|
79099
|
+
case "absent": return {
|
|
79100
|
+
...counts,
|
|
79101
|
+
rows,
|
|
79102
|
+
absent: counts.absent + 1
|
|
79103
|
+
};
|
|
79104
|
+
case "present": return {
|
|
79105
|
+
...counts,
|
|
79106
|
+
rows,
|
|
79107
|
+
present: counts.present + 1
|
|
79108
|
+
};
|
|
79109
|
+
case "error": return {
|
|
79110
|
+
...counts,
|
|
79111
|
+
rows,
|
|
79112
|
+
error: counts.error + 1
|
|
79113
|
+
};
|
|
79114
|
+
case "kept": return {
|
|
79115
|
+
...counts,
|
|
79116
|
+
rows,
|
|
79117
|
+
kept: counts.kept + 1
|
|
79118
|
+
};
|
|
79119
|
+
}
|
|
79120
|
+
}
|
|
79121
|
+
/** The file's absolute path under `root`, or `null` when it would escape it. */
|
|
79122
|
+
function resolveUnder(root, relativePath) {
|
|
79123
|
+
const base = path.resolve(root);
|
|
79124
|
+
const full = path.resolve(base, relativePath);
|
|
79125
|
+
return full.startsWith(base + path.sep) ? full : null;
|
|
79126
|
+
}
|
|
79127
|
+
function toSample(id, data) {
|
|
79128
|
+
return {
|
|
79129
|
+
mediaId: Number(id),
|
|
79130
|
+
deviceId: Number(data["deviceId"] ?? 0),
|
|
79131
|
+
kind: String(data["kind"] ?? ""),
|
|
79132
|
+
ownerKind: String(data["ownerKind"] ?? ""),
|
|
79133
|
+
ownerId: String(data["ownerId"] ?? ""),
|
|
79134
|
+
path: String(data["path"] ?? "")
|
|
79135
|
+
};
|
|
79136
|
+
}
|
|
79137
|
+
async function probe(stat, absolutePath) {
|
|
79138
|
+
try {
|
|
79139
|
+
await stat(absolutePath);
|
|
79140
|
+
return { verdict: "present" };
|
|
79141
|
+
} catch (err) {
|
|
79142
|
+
const code = errnoOf(err);
|
|
79143
|
+
return code === "ENOENT" ? { verdict: "absent" } : {
|
|
79144
|
+
verdict: "error",
|
|
79145
|
+
reason: code
|
|
79146
|
+
};
|
|
79147
|
+
}
|
|
79148
|
+
}
|
|
79149
|
+
async function classify(stat, root, id, data) {
|
|
79150
|
+
const sample = toSample(id, data);
|
|
79151
|
+
const ownerKind = sample.ownerKind;
|
|
79152
|
+
if (isMediaOwnerKind(ownerKind) && isRetentionExemptOwnerKind(ownerKind)) return {
|
|
79153
|
+
key: id,
|
|
79154
|
+
sample: {
|
|
79155
|
+
...sample,
|
|
79156
|
+
reason: "retention-exempt owner"
|
|
79157
|
+
},
|
|
79158
|
+
verdict: "kept",
|
|
79159
|
+
absolutePath: null
|
|
79160
|
+
};
|
|
79161
|
+
if (sample.path.length === 0) return {
|
|
79162
|
+
key: id,
|
|
79163
|
+
sample: {
|
|
79164
|
+
...sample,
|
|
79165
|
+
reason: "no path"
|
|
79166
|
+
},
|
|
79167
|
+
verdict: "kept",
|
|
79168
|
+
absolutePath: null
|
|
79169
|
+
};
|
|
79170
|
+
const absolutePath = resolveUnder(root, sample.path);
|
|
79171
|
+
if (absolutePath === null) return {
|
|
79172
|
+
key: id,
|
|
79173
|
+
sample: {
|
|
79174
|
+
...sample,
|
|
79175
|
+
reason: "path escapes the root"
|
|
79176
|
+
},
|
|
79177
|
+
verdict: "kept",
|
|
79178
|
+
absolutePath
|
|
79179
|
+
};
|
|
79180
|
+
const result = await probe(stat, absolutePath);
|
|
79181
|
+
return {
|
|
79182
|
+
key: id,
|
|
79183
|
+
sample: result.reason === void 0 ? sample : {
|
|
79184
|
+
...sample,
|
|
79185
|
+
reason: result.reason
|
|
79186
|
+
},
|
|
79187
|
+
verdict: result.verdict,
|
|
79188
|
+
absolutePath
|
|
79189
|
+
};
|
|
79190
|
+
}
|
|
79191
|
+
function resolveTarget(locations, locationId, localNodeId) {
|
|
79192
|
+
const location = locations.find((row) => row.id === locationId);
|
|
79193
|
+
if (!location) throw new Error(`forgetMissingMedia refused: unknown location ${locationId}`);
|
|
79194
|
+
if (location.type !== "eventMedia") throw new Error(`forgetMissingMedia refused: ${locationId} is ${location.type}, pipeline-analytics owns eventMedia rows only`);
|
|
79195
|
+
if (mayWriteToLocation(location)) throw new Error(`forgetMissingMedia refused: ${locationId} is still a write target — set it to drain, readonly or disabled first`);
|
|
79196
|
+
const nodeId = location.nodeId === void 0 || location.nodeId === "" ? "hub" : location.nodeId;
|
|
79197
|
+
if (nodeId !== localNodeId) throw new Error(`forgetMissingMedia refused: ${locationId} lives on node ${nodeId}, this process is ${localNodeId}`);
|
|
79198
|
+
return {
|
|
79199
|
+
location,
|
|
79200
|
+
root: locationBasePath(location)
|
|
79201
|
+
};
|
|
79202
|
+
}
|
|
79203
|
+
/**
|
|
79204
|
+
* Classify every row stamped with `input.locationId` (bounded by `maxRows`),
|
|
79205
|
+
* and — only when `dryRun` is false and the absent count equals
|
|
79206
|
+
* `expectAbsent` — delete the absent ones, per camera, in batches.
|
|
79207
|
+
*/
|
|
79208
|
+
async function forgetMissingMediaRows(deps, rawInput) {
|
|
79209
|
+
const input = MediaForgetInputSchema.parse(rawInput);
|
|
79210
|
+
if (!input.dryRun && input.expectAbsent === void 0) throw new Error("forgetMissingMedia refused: an apply needs expectAbsent (the dry run’s totals.absent)");
|
|
79211
|
+
const stat = deps.stat ?? ((p) => promises.stat(p));
|
|
79212
|
+
const pace = deps.pace ?? (() => new Promise((resolve) => setImmediate(resolve)));
|
|
79213
|
+
const maxRows = input.maxRows ?? MEDIA_FORGET_MAX_ROWS;
|
|
79214
|
+
const sampleSize = input.sampleSize ?? DEFAULT_SAMPLE_SIZE;
|
|
79215
|
+
const { root } = resolveTarget(await deps.listLocations(), input.locationId, deps.localNodeId);
|
|
79216
|
+
const classified = [];
|
|
79217
|
+
let cursor = 0;
|
|
79218
|
+
let truncated = false;
|
|
79219
|
+
for (;;) {
|
|
79220
|
+
const rows = await deps.store.query.query({
|
|
79221
|
+
collection: MEDIA_COLLECTION,
|
|
79222
|
+
columns: [
|
|
79223
|
+
"id",
|
|
79224
|
+
"deviceId",
|
|
79225
|
+
"ownerKind",
|
|
79226
|
+
"ownerId",
|
|
79227
|
+
"kind",
|
|
79228
|
+
"path"
|
|
79229
|
+
],
|
|
79230
|
+
filter: {
|
|
79231
|
+
where: { locationId: input.locationId },
|
|
79232
|
+
...input.deviceIds ? { whereIn: { deviceId: [...input.deviceIds] } } : {},
|
|
79233
|
+
whereBetween: { id: [cursor + 1, Number.MAX_SAFE_INTEGER] },
|
|
79234
|
+
orderBy: {
|
|
79235
|
+
field: "id",
|
|
79236
|
+
direction: "asc"
|
|
79237
|
+
},
|
|
79238
|
+
limit: PAGE_SIZE$1
|
|
79239
|
+
}
|
|
79240
|
+
});
|
|
79241
|
+
if (rows.length === 0) break;
|
|
79242
|
+
for (const row of rows) {
|
|
79243
|
+
if (classified.length >= maxRows) {
|
|
79244
|
+
truncated = true;
|
|
79245
|
+
break;
|
|
79246
|
+
}
|
|
79247
|
+
classified.push(await classify(stat, root, row.id, row.data));
|
|
79248
|
+
cursor = Math.max(cursor, Number(row.id));
|
|
79249
|
+
}
|
|
79250
|
+
if (truncated || rows.length < PAGE_SIZE$1) break;
|
|
79251
|
+
await pace();
|
|
79252
|
+
}
|
|
79253
|
+
const absentRows = classified.filter((c) => c.verdict === "absent");
|
|
79254
|
+
const deletedIds = /* @__PURE__ */ new Set();
|
|
79255
|
+
let changedBeforeDelete = 0;
|
|
79256
|
+
if (!input.dryRun) {
|
|
79257
|
+
if (absentRows.length !== input.expectAbsent) throw new Error(`forgetMissingMedia refused: ${String(absentRows.length)} rows are absent now, expectAbsent was ${String(input.expectAbsent)} — run the dry run again; nothing was deleted`);
|
|
79258
|
+
const byDevice = /* @__PURE__ */ new Map();
|
|
79259
|
+
for (const row of absentRows) byDevice.set(row.sample.deviceId, [...byDevice.get(row.sample.deviceId) ?? [], row]);
|
|
79260
|
+
for (const [deviceId, rows] of byDevice) for (let at = 0; at < rows.length; at += DELETE_BATCH) {
|
|
79261
|
+
const batch = [];
|
|
79262
|
+
for (const row of rows.slice(at, at + DELETE_BATCH)) if ((row.absolutePath === null ? null : await probe(stat, row.absolutePath))?.verdict === "absent") batch.push(row);
|
|
79263
|
+
else changedBeforeDelete += 1;
|
|
79264
|
+
if (batch.length === 0) continue;
|
|
79265
|
+
const { deleted } = await deps.store.deleteWhere.mutate({
|
|
79266
|
+
collection: MEDIA_COLLECTION,
|
|
79267
|
+
filter: {
|
|
79268
|
+
where: {
|
|
79269
|
+
locationId: input.locationId,
|
|
79270
|
+
deviceId
|
|
79271
|
+
},
|
|
79272
|
+
whereIn: { id: batch.map((row) => row.key) }
|
|
79273
|
+
}
|
|
79274
|
+
});
|
|
79275
|
+
for (const row of batch) deletedIds.add(row.sample.mediaId);
|
|
79276
|
+
deps.logger.info("forgetMissingMedia: forgot media rows whose file is gone", {
|
|
79277
|
+
tags: { deviceId },
|
|
79278
|
+
meta: {
|
|
79279
|
+
locationId: input.locationId,
|
|
79280
|
+
batch: batch.length,
|
|
79281
|
+
deleted,
|
|
79282
|
+
firstId: batch[0]?.sample.mediaId,
|
|
79283
|
+
lastId: batch[batch.length - 1]?.sample.mediaId
|
|
79284
|
+
}
|
|
79285
|
+
});
|
|
79286
|
+
if (deleted !== batch.length) deps.logger.warn("forgetMissingMedia: a batch deleted fewer rows than it named", {
|
|
79287
|
+
tags: { deviceId },
|
|
79288
|
+
meta: {
|
|
79289
|
+
locationId: input.locationId,
|
|
79290
|
+
batch: batch.length,
|
|
79291
|
+
deleted
|
|
79292
|
+
}
|
|
79293
|
+
});
|
|
79294
|
+
await pace();
|
|
79295
|
+
}
|
|
79296
|
+
}
|
|
79297
|
+
let totals = emptyCounts();
|
|
79298
|
+
const perDevice = /* @__PURE__ */ new Map();
|
|
79299
|
+
const perKind = /* @__PURE__ */ new Map();
|
|
79300
|
+
for (const row of classified) {
|
|
79301
|
+
const isDeleted = deletedIds.has(row.sample.mediaId);
|
|
79302
|
+
const bump = (c) => {
|
|
79303
|
+
const next = tally$1(c, row.verdict);
|
|
79304
|
+
return isDeleted ? {
|
|
79305
|
+
...next,
|
|
79306
|
+
deleted: next.deleted + 1
|
|
79307
|
+
} : next;
|
|
79308
|
+
};
|
|
79309
|
+
totals = bump(totals);
|
|
79310
|
+
perDevice.set(row.sample.deviceId, bump(perDevice.get(row.sample.deviceId) ?? emptyCounts()));
|
|
79311
|
+
perKind.set(row.sample.kind, bump(perKind.get(row.sample.kind) ?? emptyCounts()));
|
|
79312
|
+
}
|
|
79313
|
+
const samplesOf = (verdict) => classified.filter((c) => c.verdict === verdict).slice(0, sampleSize).map((c) => c.sample);
|
|
79314
|
+
for (const [deviceId, counts] of perDevice) deps.logger.info("forgetMissingMedia: per-camera verdict", {
|
|
79315
|
+
tags: { deviceId },
|
|
79316
|
+
meta: {
|
|
79317
|
+
locationId: input.locationId,
|
|
79318
|
+
dryRun: input.dryRun,
|
|
79319
|
+
...counts
|
|
79320
|
+
}
|
|
79321
|
+
});
|
|
79322
|
+
return {
|
|
79323
|
+
locationId: input.locationId,
|
|
79324
|
+
dryRun: input.dryRun,
|
|
79325
|
+
root,
|
|
79326
|
+
truncated,
|
|
79327
|
+
totals,
|
|
79328
|
+
byDevice: [...perDevice].map(([deviceId, c]) => ({
|
|
79329
|
+
deviceId,
|
|
79330
|
+
...c
|
|
79331
|
+
})),
|
|
79332
|
+
byKind: [...perKind].map(([kind, c]) => ({
|
|
79333
|
+
kind,
|
|
79334
|
+
...c
|
|
79335
|
+
})),
|
|
79336
|
+
absentIds: absentRows.map((r) => r.sample.mediaId),
|
|
79337
|
+
samples: {
|
|
79338
|
+
absent: samplesOf("absent"),
|
|
79339
|
+
present: samplesOf("present"),
|
|
79340
|
+
error: samplesOf("error"),
|
|
79341
|
+
kept: samplesOf("kept")
|
|
79342
|
+
},
|
|
79343
|
+
changedBeforeDelete
|
|
79344
|
+
};
|
|
79345
|
+
}
|
|
79346
|
+
//#endregion
|
|
78991
79347
|
//#region src/pipeline-analytics/retention/media-reclaim.ts
|
|
78992
79348
|
var MEDIA_RECLAIM_SCOPES = ["motion-still", "track-filmstrip"];
|
|
78993
79349
|
var DEFAULT_MEDIA_RECLAIM_LIMITS = {
|
|
@@ -93265,17 +93621,8 @@ function rowIsGallery(data) {
|
|
|
93265
93621
|
* D295 calls "a guard nobody believes".
|
|
93266
93622
|
*/
|
|
93267
93623
|
function collectionsFor(mode) {
|
|
93268
|
-
if (mode === "gallery") return [{
|
|
93269
|
-
|
|
93270
|
-
order: "timestamp"
|
|
93271
|
-
}];
|
|
93272
|
-
return [{
|
|
93273
|
-
name: MEDIA_COLLECTION,
|
|
93274
|
-
order: "timestamp"
|
|
93275
|
-
}, {
|
|
93276
|
-
name: RETRAIN_FRAMES_COLLECTION,
|
|
93277
|
-
order: "copiedAt"
|
|
93278
|
-
}];
|
|
93624
|
+
if (mode === "gallery") return [{ lane: "media" }];
|
|
93625
|
+
return [{ lane: "media" }, { lane: "retrainFrames" }];
|
|
93279
93626
|
}
|
|
93280
93627
|
/**
|
|
93281
93628
|
* Does this pass act on this row at all?
|
|
@@ -93360,7 +93707,7 @@ var MediaRelocateEngine = class {
|
|
|
93360
93707
|
async countUnstamped() {
|
|
93361
93708
|
try {
|
|
93362
93709
|
const media = await this.countUnstampedIn(MEDIA_COLLECTION);
|
|
93363
|
-
const retrainFrames = await this.
|
|
93710
|
+
const retrainFrames = await this.countUnstampedRetrainFrames();
|
|
93364
93711
|
return {
|
|
93365
93712
|
media,
|
|
93366
93713
|
retrainFrames,
|
|
@@ -93412,6 +93759,28 @@ var MediaRelocateEngine = class {
|
|
|
93412
93759
|
};
|
|
93413
93760
|
}
|
|
93414
93761
|
}
|
|
93762
|
+
/**
|
|
93763
|
+
* The retrain lane's unstamped population. The ledger's records `where` is
|
|
93764
|
+
* equality-only (a `null` predicate is dropped, which would count the whole
|
|
93765
|
+
* dataset), so the rows are read and judged by {@link rowIsUnstamped} — the
|
|
93766
|
+
* dataset is operator-selected and small.
|
|
93767
|
+
*/
|
|
93768
|
+
async countUnstampedRetrainFrames() {
|
|
93769
|
+
const rows = (await this.deps.retrainFrameLedger.query({})).filter((row) => rowIsUnstamped(row.data)).length;
|
|
93770
|
+
return {
|
|
93771
|
+
present: rows > 0,
|
|
93772
|
+
rows
|
|
93773
|
+
};
|
|
93774
|
+
}
|
|
93775
|
+
/** Every row of one lane, oldest-first. */
|
|
93776
|
+
rowsOf(collection) {
|
|
93777
|
+
return collection.lane === "media" ? this.pageRows(MEDIA_COLLECTION, "timestamp") : this.retrainFrameRows();
|
|
93778
|
+
}
|
|
93779
|
+
/** The retrain-frame ledger, read whole and yielded oldest copy first. */
|
|
93780
|
+
async *retrainFrameRows() {
|
|
93781
|
+
const ordered = (await this.deps.retrainFrameLedger.query({})).toSorted((a, b) => Number(a.data["copiedAt"] ?? 0) - Number(b.data["copiedAt"] ?? 0));
|
|
93782
|
+
for (const row of ordered) yield row;
|
|
93783
|
+
}
|
|
93415
93784
|
start(input) {
|
|
93416
93785
|
for (const j of this.jobs.values()) if (j.state === "running") throw new Error(`a media relocation is already running (${j.jobId})`);
|
|
93417
93786
|
this.cancelCensuses("a relocate pass started");
|
|
@@ -93515,22 +93884,22 @@ var MediaRelocateEngine = class {
|
|
|
93515
93884
|
let sealable = 0;
|
|
93516
93885
|
for (const collection of collectionsFor(mode)) {
|
|
93517
93886
|
if (stop()) return null;
|
|
93518
|
-
sealable += await this.deps.store.count.query({
|
|
93519
|
-
collection:
|
|
93887
|
+
sealable += collection.lane === "media" ? await this.deps.store.count.query({
|
|
93888
|
+
collection: MEDIA_COLLECTION,
|
|
93520
93889
|
filter: { where: { [LOCATION_ID_FIELD]: null } }
|
|
93521
|
-
});
|
|
93890
|
+
}) : (await this.countUnstampedRetrainFrames()).rows ?? 0;
|
|
93522
93891
|
}
|
|
93523
93892
|
return { rows: sealable };
|
|
93524
93893
|
}
|
|
93525
93894
|
let rows = 0;
|
|
93526
93895
|
for (const collection of collectionsFor(mode)) {
|
|
93527
|
-
const isRetrain = collection.
|
|
93528
|
-
for await (const row of this.
|
|
93896
|
+
const isRetrain = collection.lane === "retrainFrames";
|
|
93897
|
+
for await (const row of this.rowsOf(collection)) {
|
|
93529
93898
|
if (stop()) {
|
|
93530
93899
|
this.deps.logger.warn("media relocate: census stopped before it finished — reporting the residue as unknown, not as empty", { meta: {
|
|
93531
93900
|
mode,
|
|
93532
93901
|
toLocationId,
|
|
93533
|
-
collection: collection.
|
|
93902
|
+
collection: collection.lane
|
|
93534
93903
|
} });
|
|
93535
93904
|
return null;
|
|
93536
93905
|
}
|
|
@@ -93538,7 +93907,7 @@ var MediaRelocateEngine = class {
|
|
|
93538
93907
|
this.deps.logger.warn("media relocate: census exceeded its budget — this collection is larger than a walk can answer", { meta: {
|
|
93539
93908
|
mode,
|
|
93540
93909
|
toLocationId,
|
|
93541
|
-
collection: collection.
|
|
93910
|
+
collection: collection.lane,
|
|
93542
93911
|
rowsVisited: budget.rowsVisited,
|
|
93543
93912
|
rowBudget: MEDIA_CENSUS_ROW_BUDGET,
|
|
93544
93913
|
deadlineMs: MEDIA_CENSUS_DEADLINE_MS
|
|
@@ -93574,15 +93943,15 @@ var MediaRelocateEngine = class {
|
|
|
93574
93943
|
if (job.mode === "seal") {
|
|
93575
93944
|
for (const collection of collectionsFor(job.mode)) {
|
|
93576
93945
|
if (job.cancelRequested) break;
|
|
93577
|
-
await this.sealCollection(collection
|
|
93946
|
+
await this.sealCollection(collection, job);
|
|
93578
93947
|
}
|
|
93579
93948
|
job.state = job.cancelRequested ? "cancelled" : "done";
|
|
93580
93949
|
return;
|
|
93581
93950
|
}
|
|
93582
93951
|
for (const collection of collectionsFor(job.mode)) {
|
|
93583
93952
|
if (job.cancelRequested) break;
|
|
93584
|
-
const isRetrain = collection.
|
|
93585
|
-
for await (const row of this.
|
|
93953
|
+
const isRetrain = collection.lane === "retrainFrames";
|
|
93954
|
+
for await (const row of this.rowsOf(collection)) {
|
|
93586
93955
|
if (job.cancelRequested) break;
|
|
93587
93956
|
const data = row.data;
|
|
93588
93957
|
if (!selectsRow(job.mode, job.toLocationId, data, isRetrain, job.fromLocationId)) continue;
|
|
@@ -93594,7 +93963,7 @@ var MediaRelocateEngine = class {
|
|
|
93594
93963
|
}
|
|
93595
93964
|
try {
|
|
93596
93965
|
if (await this.samePhysicalLocation(from, job.toLocationId)) {
|
|
93597
|
-
await this.stamp(collection
|
|
93966
|
+
await this.stamp(collection, row.id, data, job.toLocationId);
|
|
93598
93967
|
continue;
|
|
93599
93968
|
}
|
|
93600
93969
|
const bytes = await this.deps.storage.read({
|
|
@@ -93610,7 +93979,7 @@ var MediaRelocateEngine = class {
|
|
|
93610
93979
|
location: job.toLocationId,
|
|
93611
93980
|
relativePath
|
|
93612
93981
|
})).length !== bytes.length) throw new Error(`relocated media size mismatch for ${relativePath}`);
|
|
93613
|
-
await this.stamp(collection
|
|
93982
|
+
await this.stamp(collection, row.id, data, job.toLocationId);
|
|
93614
93983
|
await this.deps.storage.delete({
|
|
93615
93984
|
location: from,
|
|
93616
93985
|
relativePath
|
|
@@ -93621,7 +93990,7 @@ var MediaRelocateEngine = class {
|
|
|
93621
93990
|
} catch (err) {
|
|
93622
93991
|
failedRows++;
|
|
93623
93992
|
this.deps.logger.debug("media relocate row failed", { meta: {
|
|
93624
|
-
collection: collection.
|
|
93993
|
+
collection: collection.lane,
|
|
93625
93994
|
key: row.id,
|
|
93626
93995
|
error: String(err)
|
|
93627
93996
|
} });
|
|
@@ -93694,7 +94063,17 @@ var MediaRelocateEngine = class {
|
|
|
93694
94063
|
* reports a completed seal having stamped nothing. Zero unstamped rows is the
|
|
93695
94064
|
* single value the migration gate reads as "safe to cut over".
|
|
93696
94065
|
*/
|
|
93697
|
-
async sealCollection(
|
|
94066
|
+
async sealCollection(lane, job) {
|
|
94067
|
+
if (lane.lane === "retrainFrames") {
|
|
94068
|
+
for (const row of await this.deps.retrainFrameLedger.query({})) {
|
|
94069
|
+
if (job.cancelRequested) return;
|
|
94070
|
+
if (!rowIsUnstamped(row.data)) continue;
|
|
94071
|
+
await this.deps.retrainFrameLedger.update(row.id, { [LOCATION_ID_FIELD]: job.toLocationId });
|
|
94072
|
+
job.filesMoved += 1;
|
|
94073
|
+
}
|
|
94074
|
+
return;
|
|
94075
|
+
}
|
|
94076
|
+
const collection = MEDIA_COLLECTION;
|
|
93698
94077
|
for (;;) {
|
|
93699
94078
|
if (job.cancelRequested) return;
|
|
93700
94079
|
const batch = await this.deps.store.query.query({
|
|
@@ -93715,9 +94094,13 @@ var MediaRelocateEngine = class {
|
|
|
93715
94094
|
job.filesMoved += updated;
|
|
93716
94095
|
}
|
|
93717
94096
|
}
|
|
93718
|
-
async stamp(
|
|
94097
|
+
async stamp(lane, key, data, locationId) {
|
|
94098
|
+
if (lane.lane === "retrainFrames") {
|
|
94099
|
+
await this.deps.retrainFrameLedger.update(key, { [LOCATION_ID_FIELD]: locationId });
|
|
94100
|
+
return;
|
|
94101
|
+
}
|
|
93719
94102
|
await this.deps.store.set.mutate({
|
|
93720
|
-
collection,
|
|
94103
|
+
collection: MEDIA_COLLECTION,
|
|
93721
94104
|
key,
|
|
93722
94105
|
value: {
|
|
93723
94106
|
...data,
|
|
@@ -102164,6 +102547,18 @@ function decodeCursorMap(raw) {
|
|
|
102164
102547
|
function nonNegativeInt(value) {
|
|
102165
102548
|
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : null;
|
|
102166
102549
|
}
|
|
102550
|
+
function decodeScopeCursors(raw) {
|
|
102551
|
+
const out = {};
|
|
102552
|
+
if (raw !== null && typeof raw === "object") for (const [key, cursor] of Object.entries({ ...raw })) {
|
|
102553
|
+
if (typeof cursor === "string" && cursor.length > 0) {
|
|
102554
|
+
out[key] = cursor;
|
|
102555
|
+
continue;
|
|
102556
|
+
}
|
|
102557
|
+
const parsed = nonNegativeInt(cursor);
|
|
102558
|
+
if (parsed !== null) out[key] = parsed;
|
|
102559
|
+
}
|
|
102560
|
+
return out;
|
|
102561
|
+
}
|
|
102167
102562
|
/** Read a stored row back into a cursor map, ignoring anything unreadable. */
|
|
102168
102563
|
function decodeOrphanAuditCursors(value) {
|
|
102169
102564
|
const empty = {
|
|
@@ -102175,7 +102570,7 @@ function decodeOrphanAuditCursors(value) {
|
|
|
102175
102570
|
if (value === null || typeof value !== "object") return empty;
|
|
102176
102571
|
const row = { ...value };
|
|
102177
102572
|
return {
|
|
102178
|
-
scopes:
|
|
102573
|
+
scopes: decodeScopeCursors(row["scopes"]),
|
|
102179
102574
|
faceVectorCursor: nonNegativeInt(row["faceVectorCursor"]) ?? 0,
|
|
102180
102575
|
clipIndexCursors: decodeCursorMap(row["clipIndexCursors"]),
|
|
102181
102576
|
updatedAtMs: nonNegativeInt(row["updatedAtMs"]) ?? 0
|
|
@@ -103748,7 +104143,9 @@ async function auditScope(scope, deps, limits, cursor) {
|
|
|
103748
104143
|
};
|
|
103749
104144
|
const deviceIds = /* @__PURE__ */ new Set();
|
|
103750
104145
|
const graceFloor = deps.now() - limits.graceMs;
|
|
103751
|
-
let offset = Math.max(0, cursor);
|
|
104146
|
+
let offset = typeof cursor === "number" ? Math.max(0, cursor) : 0;
|
|
104147
|
+
let keysetCursor = typeof cursor === "string" ? cursor : null;
|
|
104148
|
+
let keyed = keysetCursor !== null;
|
|
103752
104149
|
let stoppedBy = "exhausted";
|
|
103753
104150
|
let collectionSize = null;
|
|
103754
104151
|
try {
|
|
@@ -103765,9 +104162,16 @@ async function auditScope(scope, deps, limits, cursor) {
|
|
|
103765
104162
|
stoppedBy = "row-ceiling";
|
|
103766
104163
|
break;
|
|
103767
104164
|
}
|
|
103768
|
-
|
|
104165
|
+
if (deps.stop?.() === true) {
|
|
104166
|
+
stoppedBy = "stopped";
|
|
104167
|
+
break;
|
|
104168
|
+
}
|
|
104169
|
+
const keyedPage = deps.pageKeyset === void 0 ? null : await deps.pageKeyset(scope.collection, keysetCursor, limits.pageSize);
|
|
104170
|
+
keyed = keyedPage !== null;
|
|
104171
|
+
const rows = keyedPage !== null ? keyedPage.rows : await deps.page(scope.collection, scope.shard, offset, limits.pageSize);
|
|
103769
104172
|
if (rows.length === 0) {
|
|
103770
104173
|
offset = 0;
|
|
104174
|
+
keysetCursor = null;
|
|
103771
104175
|
break;
|
|
103772
104176
|
}
|
|
103773
104177
|
const refs = rows.map((row) => scope.resolve(row));
|
|
@@ -103854,9 +104258,17 @@ async function auditScope(scope, deps, limits, cursor) {
|
|
|
103854
104258
|
if (deps.breathe) await deps.breathe();
|
|
103855
104259
|
if (ceilingHit) {
|
|
103856
104260
|
stoppedBy = "reclaim-ceiling";
|
|
103857
|
-
offset += rows.length - reclaimedInPage;
|
|
104261
|
+
if (!keyed) offset += rows.length - reclaimedInPage;
|
|
103858
104262
|
break;
|
|
103859
104263
|
}
|
|
104264
|
+
if (keyedPage !== null) {
|
|
104265
|
+
if (keyedPage.nextCursor === null) {
|
|
104266
|
+
keysetCursor = null;
|
|
104267
|
+
break;
|
|
104268
|
+
}
|
|
104269
|
+
keysetCursor = keyedPage.nextCursor;
|
|
104270
|
+
continue;
|
|
104271
|
+
}
|
|
103860
104272
|
const advanced = rows.length - reclaimedInPage;
|
|
103861
104273
|
if (advanced === 0 && rows.length < limits.pageSize) {
|
|
103862
104274
|
offset = 0;
|
|
@@ -103872,7 +104284,7 @@ async function auditScope(scope, deps, limits, cursor) {
|
|
|
103872
104284
|
collectionSize,
|
|
103873
104285
|
coveragePercent: Math.round(tally.examined / collectionSize * 1e3) / 10,
|
|
103874
104286
|
stoppedBy,
|
|
103875
|
-
nextCursor: offset
|
|
104287
|
+
nextCursor: keyed ? keysetCursor ?? 0 : offset
|
|
103876
104288
|
} });
|
|
103877
104289
|
const freedNothing = scope.sizeField === null ? tally.reclaimed === 0 : tally.bytesReclaimed === 0;
|
|
103878
104290
|
if (limits.mode === "reclaim" && tally.orphans > 0 && freedNothing) deps.logger.warn("orphan audit: reclaim pass freed no bytes against a standing backlog", { meta: {
|
|
@@ -103899,7 +104311,7 @@ async function auditScope(scope, deps, limits, cursor) {
|
|
|
103899
104311
|
unresolvable: tally.unresolvable,
|
|
103900
104312
|
deviceIds: [...deviceIds],
|
|
103901
104313
|
stoppedBy,
|
|
103902
|
-
nextCursor: offset
|
|
104314
|
+
nextCursor: keyed ? keysetCursor ?? 0 : offset
|
|
103903
104315
|
};
|
|
103904
104316
|
}
|
|
103905
104317
|
/**
|
|
@@ -103991,7 +104403,8 @@ var ORPHAN_AUDIT_DEFAULTS = {
|
|
|
103991
104403
|
//#endregion
|
|
103992
104404
|
//#region src/pipeline-analytics/retention/orphan-reclaimers.ts
|
|
103993
104405
|
var OBJECT_EVENTS_COLLECTION = "pipeline-analytics:object-events";
|
|
103994
|
-
var
|
|
104406
|
+
var PLATES_COLLECTION = "pipeline-analytics:plates";
|
|
104407
|
+
var RAW_DELETE_COLLECTIONS = new Set([OBJECT_EVENTS_COLLECTION]);
|
|
103995
104408
|
/**
|
|
103996
104409
|
* The `deleteRow` port: ONE row by its primary key `id` (what every
|
|
103997
104410
|
* raw-delete collection above declares), answering whether a row went.
|
|
@@ -104065,6 +104478,13 @@ function buildOrphanReclaimer(ports) {
|
|
|
104065
104478
|
rows: await ports.deleteTrackAlias(key) ? 1 : 0,
|
|
104066
104479
|
bytes: null
|
|
104067
104480
|
};
|
|
104481
|
+
if (collection === PLATES_COLLECTION) {
|
|
104482
|
+
const assert = trackIdAssertOf(data);
|
|
104483
|
+
return {
|
|
104484
|
+
rows: await ports.deletePlate(key, assert === null ? void 0 : assert) ? 1 : 0,
|
|
104485
|
+
bytes: null
|
|
104486
|
+
};
|
|
104487
|
+
}
|
|
104068
104488
|
if (RAW_DELETE_COLLECTIONS.has(collection)) {
|
|
104069
104489
|
const assert = trackIdAssertOf(data);
|
|
104070
104490
|
if (collection === OBJECT_EVENTS_COLLECTION && assert === null) {
|
|
@@ -104086,6 +104506,87 @@ function buildOrphanReclaimer(ports) {
|
|
|
104086
104506
|
};
|
|
104087
104507
|
}
|
|
104088
104508
|
//#endregion
|
|
104509
|
+
//#region src/pipeline-analytics/retention/orphan-audit-ledgers.ts
|
|
104510
|
+
/** Audited collection → its records-kind ledger. Empty without records. */
|
|
104511
|
+
function orphanAuditLedgers(wiring) {
|
|
104512
|
+
if (wiring.records === void 0) return /* @__PURE__ */ new Map();
|
|
104513
|
+
return new Map([
|
|
104514
|
+
[FACES_COLLECTION, wireFaceLedger(wiring)],
|
|
104515
|
+
[PLATES_COLLECTION$1, wirePlateLedger(wiring)],
|
|
104516
|
+
[TRACK_ALIASES_COLLECTION, wireTrackAliasLedger(wiring)]
|
|
104517
|
+
]);
|
|
104518
|
+
}
|
|
104519
|
+
/** {@link OrphanAuditDeps.pageKeyset} over those ledgers; `null` for any other collection. */
|
|
104520
|
+
function keysetPager(ledgers) {
|
|
104521
|
+
return async (collection, cursor, limit) => {
|
|
104522
|
+
const ledger = ledgers.get(collection);
|
|
104523
|
+
if (ledger === void 0) return null;
|
|
104524
|
+
const page = await ledger.pageById(cursor, limit);
|
|
104525
|
+
return {
|
|
104526
|
+
rows: page.rows.map((row) => ({
|
|
104527
|
+
key: row.id,
|
|
104528
|
+
data: row.data
|
|
104529
|
+
})),
|
|
104530
|
+
nextCursor: page.nextCursor
|
|
104531
|
+
};
|
|
104532
|
+
};
|
|
104533
|
+
}
|
|
104534
|
+
/** The kind's count for a ledger-backed collection, `undefined` for any other. */
|
|
104535
|
+
async function ledgerSize(ledgers, collection) {
|
|
104536
|
+
const ledger = ledgers.get(collection);
|
|
104537
|
+
return ledger === void 0 ? void 0 : ledger.count();
|
|
104538
|
+
}
|
|
104539
|
+
/**
|
|
104540
|
+
* The plate reclaim through the plate ledger — records on a hub, the legacy
|
|
104541
|
+
* collection only without records. With `assert`, a row whose `trackId` is not
|
|
104542
|
+
* the dead track's is KEPT (logged), never deleted.
|
|
104543
|
+
*/
|
|
104544
|
+
function plateDeleter(plates, logger) {
|
|
104545
|
+
return async (plateId, assert) => {
|
|
104546
|
+
const row = await plates.get(plateId);
|
|
104547
|
+
if (row === null) return false;
|
|
104548
|
+
if (assert !== void 0 && row.data["trackId"] !== assert.trackId) {
|
|
104549
|
+
const deviceId = row.data["deviceId"];
|
|
104550
|
+
logger.warn("orphan audit: plate row now names another track — kept", {
|
|
104551
|
+
...typeof deviceId === "number" ? { tags: { deviceId } } : {},
|
|
104552
|
+
meta: {
|
|
104553
|
+
plateId,
|
|
104554
|
+
expectedTrackId: assert.trackId
|
|
104555
|
+
}
|
|
104556
|
+
});
|
|
104557
|
+
return false;
|
|
104558
|
+
}
|
|
104559
|
+
await plates.deleteKey(plateId);
|
|
104560
|
+
return true;
|
|
104561
|
+
};
|
|
104562
|
+
}
|
|
104563
|
+
function auditCollectionPorts(store, ledgers) {
|
|
104564
|
+
return {
|
|
104565
|
+
pageKeyset: keysetPager(ledgers),
|
|
104566
|
+
pageByOffset: async (collection, offset, limit) => {
|
|
104567
|
+
return (await store.query.query({
|
|
104568
|
+
collection,
|
|
104569
|
+
filter: {
|
|
104570
|
+
orderBy: {
|
|
104571
|
+
field: "id",
|
|
104572
|
+
direction: "asc"
|
|
104573
|
+
},
|
|
104574
|
+
limit,
|
|
104575
|
+
offset
|
|
104576
|
+
}
|
|
104577
|
+
})).map((row) => ({
|
|
104578
|
+
key: row.id,
|
|
104579
|
+
data: row.data
|
|
104580
|
+
}));
|
|
104581
|
+
},
|
|
104582
|
+
size: async (collection) => {
|
|
104583
|
+
const counted = await ledgerSize(ledgers, collection);
|
|
104584
|
+
if (counted !== void 0) return counted;
|
|
104585
|
+
return store.count.query({ collection });
|
|
104586
|
+
}
|
|
104587
|
+
};
|
|
104588
|
+
}
|
|
104589
|
+
//#endregion
|
|
104089
104590
|
//#region src/pipeline-analytics/retention/retired-device-settings.ts
|
|
104090
104591
|
/**
|
|
104091
104592
|
* Every retired knob, with the sentence that explains it. The list is the
|
|
@@ -114843,6 +115344,7 @@ var customActions = {
|
|
|
114843
115344
|
...orphanAuditActions,
|
|
114844
115345
|
...mediaReclaimActions,
|
|
114845
115346
|
...locationDiscardActions,
|
|
115347
|
+
...mediaForgetActions,
|
|
114846
115348
|
...photoEnrollActions,
|
|
114847
115349
|
...faceRescoreActions,
|
|
114848
115350
|
...stillnessCalibrationActions
|
|
@@ -116633,6 +117135,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
116633
117135
|
orphanAuditState = { ...IDLE_ORPHAN_AUDIT };
|
|
116634
117136
|
/** Frozen-location leftover wipe (events/ on a drained HDD). Detached. */
|
|
116635
117137
|
discardJob = null;
|
|
117138
|
+
/** The narrow media-row forget's wiring (`storage.forgetMissingMedia`). */
|
|
117139
|
+
mediaForgetDeps = null;
|
|
116636
117140
|
/**
|
|
116637
117141
|
* The CLIP indexes' walks (D649), one resume cursor per index, kept in their
|
|
116638
117142
|
* OWN field rather than in {@link orphanAuditCursors}: `runOrphanAudit`
|
|
@@ -116841,10 +117345,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
116841
117345
|
const isHub = ownNodeId === "hub";
|
|
116842
117346
|
const ncHandlers = isHub ? this.buildNcActionHandlers(api) : void 0;
|
|
116843
117347
|
if (isHub) this.reconcileTapePinsInBackground();
|
|
117348
|
+
this.mediaForgetDeps = {
|
|
117349
|
+
listLocations: async () => api.storage.listLocations.query({}),
|
|
117350
|
+
localNodeId: ownNodeId,
|
|
117351
|
+
store: api.settingsStore,
|
|
117352
|
+
logger: logger.child("MediaForget")
|
|
117353
|
+
};
|
|
116844
117354
|
this.discardJob = createDiscardJob({
|
|
116845
117355
|
listLocations: async () => api.storage.listLocations.query({}),
|
|
116846
117356
|
localNodeId: ownNodeId,
|
|
116847
117357
|
store: api.settingsStore,
|
|
117358
|
+
retrainFrameLedger: wireRetrainFrameLedger(this.eventLedgerWiring(api.settingsStore)),
|
|
116848
117359
|
deleteTrackEvents: async (trackId) => {
|
|
116849
117360
|
await this.eventStore?.deleteByTrack(trackId);
|
|
116850
117361
|
},
|
|
@@ -116865,6 +117376,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
116865
117376
|
...orphanAuditActions,
|
|
116866
117377
|
...mediaReclaimActions,
|
|
116867
117378
|
...locationDiscardActions,
|
|
117379
|
+
...mediaForgetActions,
|
|
116868
117380
|
...photoEnrollActions,
|
|
116869
117381
|
...faceRescoreActions,
|
|
116870
117382
|
...stillnessCalibrationActions
|
|
@@ -117460,6 +117972,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
117460
117972
|
});
|
|
117461
117973
|
this.mediaRelocate = new MediaRelocateEngine({
|
|
117462
117974
|
store: api.settingsStore,
|
|
117975
|
+
retrainFrameLedger: wireRetrainFrameLedger(eventLedgerWiring),
|
|
117463
117976
|
storage,
|
|
117464
117977
|
logger: logger.child("MediaRelocate"),
|
|
117465
117978
|
now: () => Date.now(),
|
|
@@ -125202,36 +125715,29 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
125202
125715
|
const trackStore = this.trackStore;
|
|
125203
125716
|
const eventStore = this.eventStore;
|
|
125204
125717
|
const mediaStore = this.mediaStore;
|
|
125205
|
-
|
|
125718
|
+
const settingsStore = this.settingsStore;
|
|
125719
|
+
if (!trackStore || !eventStore || !mediaStore || !settingsStore) {
|
|
125206
125720
|
if (!alreadyHeld) this.orphanAuditRunning = false;
|
|
125207
125721
|
return null;
|
|
125208
125722
|
}
|
|
125209
125723
|
try {
|
|
125210
125724
|
const limits = await this.orphanAuditLimits(overrides);
|
|
125725
|
+
const auditLedgers = orphanAuditLedgers(this.eventLedgerWiring(settingsStore));
|
|
125726
|
+
const plateLedger = wirePlateLedger(this.eventLedgerWiring(settingsStore));
|
|
125727
|
+
const collectionPorts = auditCollectionPorts(settingsStore, auditLedgers);
|
|
125211
125728
|
const deps = {
|
|
125729
|
+
pageKeyset: collectionPorts.pageKeyset,
|
|
125730
|
+
stop: () => this.shuttingDown,
|
|
125212
125731
|
page: async (collection, shard, offset, limit) => {
|
|
125213
125732
|
if (collection === MEDIA_COLLECTION) return (await mediaStore.pageForAudit(shard?.value ?? "", offset, limit)).map((row) => ({
|
|
125214
125733
|
key: String(row.mediaId),
|
|
125215
125734
|
data: row.data
|
|
125216
125735
|
}));
|
|
125217
|
-
return (
|
|
125218
|
-
collection,
|
|
125219
|
-
filter: {
|
|
125220
|
-
orderBy: {
|
|
125221
|
-
field: "id",
|
|
125222
|
-
direction: "asc"
|
|
125223
|
-
},
|
|
125224
|
-
limit,
|
|
125225
|
-
offset
|
|
125226
|
-
}
|
|
125227
|
-
}) ?? []).map((row) => ({
|
|
125228
|
-
key: row.id,
|
|
125229
|
-
data: row.data
|
|
125230
|
-
}));
|
|
125736
|
+
return collectionPorts.pageByOffset(collection, offset, limit);
|
|
125231
125737
|
},
|
|
125232
125738
|
size: async (collection, shard) => {
|
|
125233
125739
|
if (collection === MEDIA_COLLECTION) return mediaStore.countForAudit(shard?.value ?? "");
|
|
125234
|
-
return
|
|
125740
|
+
return collectionPorts.size(collection);
|
|
125235
125741
|
},
|
|
125236
125742
|
liveTrackIds: (ids) => this.liveTrackIdsThroughAliases(ids, trackStore),
|
|
125237
125743
|
liveEventIds: (table, ids) => eventStore.existingEventIdsIn(table, ids),
|
|
@@ -125254,6 +125760,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
125254
125760
|
if (aliasStore === null) throw new Error("track alias store not bound");
|
|
125255
125761
|
return await aliasStore.deleteAlias(aliasId);
|
|
125256
125762
|
},
|
|
125763
|
+
deletePlate: plateDeleter(plateLedger, this.ctx.logger),
|
|
125257
125764
|
deleteRow: settingsRowDeleterOf(() => this.settingsStore),
|
|
125258
125765
|
logger: this.ctx.logger
|
|
125259
125766
|
});
|
|
@@ -126675,7 +127182,12 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
126675
127182
|
jobId: null
|
|
126676
127183
|
},
|
|
126677
127184
|
"storage.discardLocationStatus": async () => this.discardJob?.status() ?? IDLE_DISCARD,
|
|
126678
|
-
"storage.discardLocationCancel": async () => this.discardJob?.cancel() ?? { requested: false }
|
|
127185
|
+
"storage.discardLocationCancel": async () => this.discardJob?.cancel() ?? { requested: false },
|
|
127186
|
+
"storage.forgetMissingMedia": async (input) => {
|
|
127187
|
+
const deps = this.mediaForgetDeps;
|
|
127188
|
+
if (deps === null) throw new Error("storage.forgetMissingMedia: not initialised");
|
|
127189
|
+
return forgetMissingMediaRows(deps, input);
|
|
127190
|
+
}
|
|
126679
127191
|
};
|
|
126680
127192
|
}
|
|
126681
127193
|
/**
|