@camstack/addon-post-analysis 1.2.127 → 1.2.128
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/{dist-BDy0xpfI.mjs → dist-CRDS5ykk.mjs} +217 -51
- package/dist/{dist-BarcMD8R.js → dist-D6O14xf_.js} +217 -51
- 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-Dj-k47_c.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BZm5TUIO.mjs} +3 -3
- package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Kj0oid5Z.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-UGfPXz4L.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-XLZIUPmq.mjs → hostInit-CQz_Ev1M.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +296 -6
- package/dist/pipeline-analytics/index.mjs +296 -6
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as customAction, A as NcTaxonomySchema, At as sleep, B as TIMELAPSE_DENSE_FLOOR_SEC, Bt as string, C as NcRuleSchema, Ct as errMsg, D as NcSnoozeSchema, Dt as hydrateSchema, E as NcSnoozeInputSchema, Et as createEvent, Ft as literal, G as VISIT_MERGE_GAP_MS, H as TimelapseRulePatchSchema, Ht as EventCategory, I as SCENE_DEFAULT_ANCHOR_THRESHOLD, It as number, J as assertTimelapseCadences, K as addonWidgetsSourceCapability, L as SCENE_DEFAULT_UNCOVERED_POLICY, Lt as object, Mt as array, N as RECORDING_EXPORT_MAX_READ_BYTES, Nt as boolean, O as NcSnoozeSuppressedSchema, Ot as isDeviceScopedCap, P as RetrainStatusSchema, Pt as discriminatedUnion, Q as cosineSimilarity$1, R as SCENE_DIVERGED, Rt as partialRecord, S as NcRulePatchSchema, St as zoneAnalyticsCapability, T as NcScheduleSchema, Tt as DeviceType, U as TimelapseRuleSchema, V as TimelapseRuleInputSchema, Vt as unknown, W as TrackSourceSchema, X as audioModeOf, Y as audioMetricsCapability, Z as buildEventKindDescriptor, _ as NC_DEFAULT_SNOOZE_MINUTES, _t as sceneMonitorCapability, a as DEFAULT_EVENT_COLOR, b as NcConditionDescriptorSchema, bt as vectorDimFromBase64, ct as kebabToCamel, d as EVENT_PAD_MS, dt as pickClusterStepModels, et as defineCustomActions, f as FULL_IMAGE_BBOX, ft as pipelineAnalyticsCapability, g as NC_CONDITION_CATALOG, h as NC_ALARM_SYSTEM_EVENT_KINDS, ht as readTimelapseGeneratedAt, i as CLUSTER_MODEL_SCOPED_STEPS, it as faceGalleryCapability, j as OpsLogEntrySchema, jt as _enum, k as NcSystemEventKindSchema, kt as nodePin, l as DeclaredDevices, lt as notificationRulesCapability, m as MACRO_LABELS, mt as readDeviceStateFrom, n as AnalyticsGroupRecordSchema, ot as isDetectionMacroClass, p as LabelAttributionSchema, pt as plateGalleryCapability, q as alarmPanelCapability, r as BaseDevice, rt as encodeVectorBase64, s as DETECTION_MACRO_CLASSES, st as isScheduleActive, t as AnalyticsGroupDetailSchema, tt as deriveRecordingMode, u as EVENT_KIND_BY_CAP, vt as subKindsOf, w as NcRuleTargetSchema, wt as BaseAddon, x as NcRuleInputSchema, xt as videoclipsCapability, y as NC_TAXONOMY, yt as systemEventFilterApplies, z as SceneMonitorSchema, zt as record } from "../dist-
|
|
1
|
+
import { $ as customAction, A as NcTaxonomySchema, At as sleep, B as TIMELAPSE_DENSE_FLOOR_SEC, Bt as string, C as NcRuleSchema, Ct as errMsg, D as NcSnoozeSchema, Dt as hydrateSchema, E as NcSnoozeInputSchema, Et as createEvent, Ft as literal, G as VISIT_MERGE_GAP_MS, H as TimelapseRulePatchSchema, Ht as EventCategory, I as SCENE_DEFAULT_ANCHOR_THRESHOLD, It as number, J as assertTimelapseCadences, K as addonWidgetsSourceCapability, L as SCENE_DEFAULT_UNCOVERED_POLICY, Lt as object, Mt as array, N as RECORDING_EXPORT_MAX_READ_BYTES, Nt as boolean, O as NcSnoozeSuppressedSchema, Ot as isDeviceScopedCap, P as RetrainStatusSchema, Pt as discriminatedUnion, Q as cosineSimilarity$1, R as SCENE_DIVERGED, Rt as partialRecord, S as NcRulePatchSchema, St as zoneAnalyticsCapability, T as NcScheduleSchema, Tt as DeviceType, U as TimelapseRuleSchema, V as TimelapseRuleInputSchema, Vt as unknown, W as TrackSourceSchema, X as audioModeOf, Y as audioMetricsCapability, Z as buildEventKindDescriptor, _ as NC_DEFAULT_SNOOZE_MINUTES, _t as sceneMonitorCapability, a as DEFAULT_EVENT_COLOR, b as NcConditionDescriptorSchema, bt as vectorDimFromBase64, ct as kebabToCamel, d as EVENT_PAD_MS, dt as pickClusterStepModels, et as defineCustomActions, f as FULL_IMAGE_BBOX, ft as pipelineAnalyticsCapability, g as NC_CONDITION_CATALOG, h as NC_ALARM_SYSTEM_EVENT_KINDS, ht as readTimelapseGeneratedAt, i as CLUSTER_MODEL_SCOPED_STEPS, it as faceGalleryCapability, j as OpsLogEntrySchema, jt as _enum, k as NcSystemEventKindSchema, kt as nodePin, l as DeclaredDevices, lt as notificationRulesCapability, m as MACRO_LABELS, mt as readDeviceStateFrom, n as AnalyticsGroupRecordSchema, ot as isDetectionMacroClass, p as LabelAttributionSchema, pt as plateGalleryCapability, q as alarmPanelCapability, r as BaseDevice, rt as encodeVectorBase64, s as DETECTION_MACRO_CLASSES, st as isScheduleActive, t as AnalyticsGroupDetailSchema, tt as deriveRecordingMode, u as EVENT_KIND_BY_CAP, vt as subKindsOf, w as NcRuleTargetSchema, wt as BaseAddon, x as NcRuleInputSchema, xt as videoclipsCapability, y as NC_TAXONOMY, yt as systemEventFilterApplies, z as SceneMonitorSchema, zt as record } from "../dist-CRDS5ykk.mjs";
|
|
2
2
|
import { t as __exportAll } from "../embedding-encoder/index.mjs";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import { promises } from "node:fs";
|
|
@@ -31799,11 +31799,13 @@ var MediaStore = class {
|
|
|
31799
31799
|
store;
|
|
31800
31800
|
logger;
|
|
31801
31801
|
writeGate;
|
|
31802
|
+
resolveEventMediaLocation;
|
|
31802
31803
|
constructor(deps) {
|
|
31803
31804
|
this.storage = deps.storage;
|
|
31804
31805
|
this.store = deps.store;
|
|
31805
31806
|
this.logger = deps.logger;
|
|
31806
31807
|
this.writeGate = deps.writeGate;
|
|
31808
|
+
this.resolveEventMediaLocation = deps.resolveEventMediaLocation;
|
|
31807
31809
|
}
|
|
31808
31810
|
gated(mutation) {
|
|
31809
31811
|
return this.writeGate ? this.writeGate.run(mutation) : mutation();
|
|
@@ -31824,7 +31826,8 @@ var MediaStore = class {
|
|
|
31824
31826
|
async putUnchecked(params) {
|
|
31825
31827
|
const key = buildKey(params);
|
|
31826
31828
|
const path = buildPath(params);
|
|
31827
|
-
const
|
|
31829
|
+
const staticLocation = locationForOwnerKind(params.ownerKind);
|
|
31830
|
+
const location = staticLocation === "eventMedia" && this.resolveEventMediaLocation ? await this.resolveEventMediaLocation(params.deviceId) : staticLocation;
|
|
31828
31831
|
const record = {
|
|
31829
31832
|
deviceId: params.deviceId,
|
|
31830
31833
|
ownerKind: params.ownerKind,
|
|
@@ -41478,6 +41481,21 @@ var MediaCaptureLogAggregator = class {
|
|
|
41478
41481
|
}
|
|
41479
41482
|
};
|
|
41480
41483
|
//#endregion
|
|
41484
|
+
//#region src/pipeline-analytics/media-locations.ts
|
|
41485
|
+
/**
|
|
41486
|
+
* Picks the location a new `eventMedia` blob should be written to, or `null`
|
|
41487
|
+
* when none is writable — the caller MUST refuse the write rather than fall
|
|
41488
|
+
* back to a read-only location, which would fail far downstream as an opaque
|
|
41489
|
+
* filesystem error instead of a named storage decision.
|
|
41490
|
+
*/
|
|
41491
|
+
function pickWritableMediaLocation(locations, currentDefaultId) {
|
|
41492
|
+
const writable = locations.filter((l) => l.enabled && !l.readOnly);
|
|
41493
|
+
if (writable.length === 0) return null;
|
|
41494
|
+
const keep = writable.find((l) => l.id === currentDefaultId);
|
|
41495
|
+
if (keep) return keep.id;
|
|
41496
|
+
return writable.toSorted((a, b) => b.headroomBytes - a.headroomBytes)[0]?.id ?? null;
|
|
41497
|
+
}
|
|
41498
|
+
//#endregion
|
|
41481
41499
|
//#region src/pipeline-analytics/media-relocate-engine.ts
|
|
41482
41500
|
var PAGE_SIZE = 200;
|
|
41483
41501
|
var DEFAULT_THROTTLE_MBPS = 40;
|
|
@@ -56971,6 +56989,144 @@ function rowFromMetadata(id, metadata) {
|
|
|
56971
56989
|
};
|
|
56972
56990
|
}
|
|
56973
56991
|
//#endregion
|
|
56992
|
+
//#region src/pipeline-analytics/store/settings-raw-mirror.ts
|
|
56993
|
+
/** How long a mirrored entry is served before a refresh is started behind the
|
|
56994
|
+
* next reader. See the module doc for why this matches the per-kind caches'
|
|
56995
|
+
* own window rather than introducing a second staleness bound. */
|
|
56996
|
+
var RAW_SETTINGS_STALE_MS = 5e3;
|
|
56997
|
+
/**
|
|
56998
|
+
* How long a FAILED refresh is not retried.
|
|
56999
|
+
*
|
|
57000
|
+
* A refresh that throws leaves `fetchedAt` where it was, so without a
|
|
57001
|
+
* cooldown every subsequent reader would start another attempt — a broken
|
|
57002
|
+
* settings store would be hammered at frame rate by the very mechanism built
|
|
57003
|
+
* to stop that.
|
|
57004
|
+
*/
|
|
57005
|
+
var RAW_SETTINGS_MISS_COOLDOWN_MS = 5e3;
|
|
57006
|
+
/**
|
|
57007
|
+
* The addon's per-device raw blob (`pipeline-analytics:addon-devices`), kept
|
|
57008
|
+
* fresh OFF every per-kind settings cache's hot path.
|
|
57009
|
+
*
|
|
57010
|
+
* `resolve` is COLD-awaiting and single-flight: a burst of settings kinds
|
|
57011
|
+
* (or frames) missing on the same device in the same tick costs ONE read,
|
|
57012
|
+
* not one per caller — the exact failure this class exists to remove.
|
|
57013
|
+
*/
|
|
57014
|
+
var RawDeviceBlobMirror = class {
|
|
57015
|
+
deps;
|
|
57016
|
+
staleMs;
|
|
57017
|
+
missCooldownMs;
|
|
57018
|
+
entries = /* @__PURE__ */ new Map();
|
|
57019
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
57020
|
+
now;
|
|
57021
|
+
constructor(deps, staleMs = RAW_SETTINGS_STALE_MS, missCooldownMs = RAW_SETTINGS_MISS_COOLDOWN_MS) {
|
|
57022
|
+
this.deps = deps;
|
|
57023
|
+
this.staleMs = staleMs;
|
|
57024
|
+
this.missCooldownMs = missCooldownMs;
|
|
57025
|
+
this.now = deps.now ?? (() => Date.now());
|
|
57026
|
+
}
|
|
57027
|
+
/**
|
|
57028
|
+
* The device's raw stored blob.
|
|
57029
|
+
*
|
|
57030
|
+
* COLD awaits — the first settings kind to touch a device has to get the
|
|
57031
|
+
* real answer. WARM returns immediately, stale or not, and starts the
|
|
57032
|
+
* refresh behind the caller.
|
|
57033
|
+
*/
|
|
57034
|
+
async resolve(deviceId) {
|
|
57035
|
+
const entry = this.entries.get(deviceId);
|
|
57036
|
+
if (entry === void 0) return this.load(deviceId);
|
|
57037
|
+
if (this.now() - entry.fetchedAt >= this.staleMs) this.load(deviceId);
|
|
57038
|
+
return entry.value;
|
|
57039
|
+
}
|
|
57040
|
+
/** Drop a device's entry. Called by every in-process writer of this
|
|
57041
|
+
* device's row, so an operator's own change is never served stale. */
|
|
57042
|
+
invalidate(deviceId) {
|
|
57043
|
+
this.entries.delete(deviceId);
|
|
57044
|
+
}
|
|
57045
|
+
invalidateAll() {
|
|
57046
|
+
this.entries.clear();
|
|
57047
|
+
}
|
|
57048
|
+
async load(deviceId) {
|
|
57049
|
+
const pending = this.inFlight.get(deviceId);
|
|
57050
|
+
if (pending !== void 0) return pending;
|
|
57051
|
+
const run = this.deps.read(deviceId).then((value) => {
|
|
57052
|
+
this.entries.set(deviceId, {
|
|
57053
|
+
value,
|
|
57054
|
+
fetchedAt: this.now()
|
|
57055
|
+
});
|
|
57056
|
+
return value;
|
|
57057
|
+
}).catch((err) => {
|
|
57058
|
+
this.deps.onRefreshFailed?.(deviceId, err);
|
|
57059
|
+
const existing = this.entries.get(deviceId);
|
|
57060
|
+
if (existing !== void 0) {
|
|
57061
|
+
existing.fetchedAt = this.now() - this.staleMs + this.missCooldownMs;
|
|
57062
|
+
return existing.value;
|
|
57063
|
+
}
|
|
57064
|
+
return {};
|
|
57065
|
+
}).finally(() => {
|
|
57066
|
+
this.inFlight.delete(deviceId);
|
|
57067
|
+
});
|
|
57068
|
+
this.inFlight.set(deviceId, run);
|
|
57069
|
+
return run;
|
|
57070
|
+
}
|
|
57071
|
+
};
|
|
57072
|
+
/**
|
|
57073
|
+
* The addon's ONE `addon_settings` row (`pipeline-analytics:addon-settings`),
|
|
57074
|
+
* kept fresh OFF every per-kind settings cache's hot path. Same shape as
|
|
57075
|
+
* {@link RawDeviceBlobMirror}, singleton rather than keyed — there is exactly
|
|
57076
|
+
* one addon store per process.
|
|
57077
|
+
*/
|
|
57078
|
+
var RawGlobalStoreMirror = class {
|
|
57079
|
+
deps;
|
|
57080
|
+
staleMs;
|
|
57081
|
+
missCooldownMs;
|
|
57082
|
+
entry = null;
|
|
57083
|
+
inFlight = null;
|
|
57084
|
+
now;
|
|
57085
|
+
constructor(deps, staleMs = RAW_SETTINGS_STALE_MS, missCooldownMs = RAW_SETTINGS_MISS_COOLDOWN_MS) {
|
|
57086
|
+
this.deps = deps;
|
|
57087
|
+
this.staleMs = staleMs;
|
|
57088
|
+
this.missCooldownMs = missCooldownMs;
|
|
57089
|
+
this.now = deps.now ?? (() => Date.now());
|
|
57090
|
+
}
|
|
57091
|
+
async resolve() {
|
|
57092
|
+
const entry = this.entry;
|
|
57093
|
+
if (entry === null) return this.load();
|
|
57094
|
+
if (this.now() - entry.fetchedAt >= this.staleMs) this.load();
|
|
57095
|
+
return entry.value;
|
|
57096
|
+
}
|
|
57097
|
+
/** Drop the mirrored store. Called by every in-process global-settings
|
|
57098
|
+
* writer (`onConfigChanged`), so an operator's own change is never served
|
|
57099
|
+
* stale. */
|
|
57100
|
+
invalidate() {
|
|
57101
|
+
this.entry = null;
|
|
57102
|
+
}
|
|
57103
|
+
async load() {
|
|
57104
|
+
if (this.inFlight !== null) return this.inFlight;
|
|
57105
|
+
const run = this.deps.read().then((value) => {
|
|
57106
|
+
this.entry = {
|
|
57107
|
+
value,
|
|
57108
|
+
fetchedAt: this.now()
|
|
57109
|
+
};
|
|
57110
|
+
return value;
|
|
57111
|
+
}).catch((err) => {
|
|
57112
|
+
this.deps.onRefreshFailed?.(err);
|
|
57113
|
+
const existing = this.entry;
|
|
57114
|
+
if (existing !== null) {
|
|
57115
|
+
this.entry = {
|
|
57116
|
+
value: existing.value,
|
|
57117
|
+
fetchedAt: this.now() - this.staleMs + this.missCooldownMs
|
|
57118
|
+
};
|
|
57119
|
+
return existing.value;
|
|
57120
|
+
}
|
|
57121
|
+
return {};
|
|
57122
|
+
}).finally(() => {
|
|
57123
|
+
this.inFlight = null;
|
|
57124
|
+
});
|
|
57125
|
+
this.inFlight = run;
|
|
57126
|
+
return run;
|
|
57127
|
+
}
|
|
57128
|
+
};
|
|
57129
|
+
//#endregion
|
|
56974
57130
|
//#region src/pipeline-analytics/track-retention-sweep.ts
|
|
56975
57131
|
/**
|
|
56976
57132
|
* Sweep every device with persisted tracks: skip retention-disabled devices,
|
|
@@ -58027,6 +58183,14 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
58027
58183
|
/** Event-media relocation engine (entity-routing Phase 4). */
|
|
58028
58184
|
mediaRelocate = null;
|
|
58029
58185
|
mediaLocationStorage = null;
|
|
58186
|
+
/**
|
|
58187
|
+
* Cached pick for NEW `eventMedia` writes (`pickWritableMediaLocation`).
|
|
58188
|
+
* Resolved once, never per blob (no-RPC-per-blob, same discipline as
|
|
58189
|
+
* `mediaLocationStorage`'s own root cache) — dropped together with it by
|
|
58190
|
+
* `refreshStorageLocationsForMigration`, since a location's `readOnly` /
|
|
58191
|
+
* `enabled` flags only change via operator reconfig.
|
|
58192
|
+
*/
|
|
58193
|
+
eventMediaWriteLocationCache = null;
|
|
58030
58194
|
mediaWriteGate = new MediaWriteGate();
|
|
58031
58195
|
storageMigrationLeaseId = null;
|
|
58032
58196
|
/** Per-camera history of LINKED-device sensor state changes (Part B). */
|
|
@@ -58426,6 +58590,33 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
58426
58590
|
audioEpisodeTracks = /* @__PURE__ */ new Map();
|
|
58427
58591
|
audioEpisodeLastPersistMs = /* @__PURE__ */ new Map();
|
|
58428
58592
|
settingsCaches = new DeviceSettingsCacheHub();
|
|
58593
|
+
/**
|
|
58594
|
+
* The addon-level store and per-device blob, mirrored ONCE and shared by
|
|
58595
|
+
* every settings kind above — see `store/settings-raw-mirror.ts`. Before
|
|
58596
|
+
* this, each kind's cache miss called `resolveGlobalStore()` +
|
|
58597
|
+
* `readDeviceBlob()` directly, so eight kinds expiring in the same window
|
|
58598
|
+
* paid for the same two reads eight times: `get
|
|
58599
|
+
* pipeline-analytics:addon-settings` at 547–694/min, `get
|
|
58600
|
+
* pipeline-analytics:addon-devices` at ~700/min, live 2026-08-26. Invalidated
|
|
58601
|
+
* by every in-process writer: `applyDeviceSettingsPatch` and the retired-
|
|
58602
|
+
* setting purges drop the device mirror, `onConfigChanged` (this process's
|
|
58603
|
+
* own `updateGlobalSettings`) drops the global mirror.
|
|
58604
|
+
*/
|
|
58605
|
+
rawDeviceBlobMirror = new RawDeviceBlobMirror({
|
|
58606
|
+
read: (deviceId) => this.readDeviceBlob(deviceId),
|
|
58607
|
+
onRefreshFailed: (deviceId, err) => {
|
|
58608
|
+
this.ctx.logger.debug("raw device-blob mirror refresh failed — serving last value", {
|
|
58609
|
+
tags: { deviceId },
|
|
58610
|
+
meta: { error: err instanceof Error ? err.message : String(err) }
|
|
58611
|
+
});
|
|
58612
|
+
}
|
|
58613
|
+
});
|
|
58614
|
+
rawGlobalStoreMirror = new RawGlobalStoreMirror({
|
|
58615
|
+
read: () => this.resolveGlobalStore(),
|
|
58616
|
+
onRefreshFailed: (err) => {
|
|
58617
|
+
this.ctx.logger.debug("raw global-store mirror refresh failed — serving last value", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
58618
|
+
}
|
|
58619
|
+
});
|
|
58429
58620
|
/** Avoids a store read every ~30 Hz audio frame. */
|
|
58430
58621
|
audioSettingsCache = this.settingsCaches.registerDeviceCache("audio-detection", SETTINGS_CACHE_TTL_MS);
|
|
58431
58622
|
sensitivitySettingsCache = this.settingsCaches.registerDeviceCache("detection-sensitivity", SETTINGS_CACHE_TTL_MS);
|
|
@@ -59145,6 +59336,53 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
59145
59336
|
this.mediaLocationStorage = routed;
|
|
59146
59337
|
return routed;
|
|
59147
59338
|
}
|
|
59339
|
+
/**
|
|
59340
|
+
* Resolves which `eventMedia` location a NEW footage-class blob should be
|
|
59341
|
+
* written to (`MediaStoreDeps.resolveEventMediaLocation`). Reflects
|
|
59342
|
+
* `config.readOnly` — an operator draining a location marks it read-only,
|
|
59343
|
+
* and new writes must never land there (the relocate mover, exposed as
|
|
59344
|
+
* `relocateMedia`, empties what is already on it in the background).
|
|
59345
|
+
*
|
|
59346
|
+
* Cached after the first successful resolution — this is a per-blob write
|
|
59347
|
+
* path, and the write-side provider is a declared no-RPC-per-blob bypass
|
|
59348
|
+
* (D-note in `location-aware-media-storage.ts`); re-querying the storage
|
|
59349
|
+
* cap on every blob would undo that. Dropped by
|
|
59350
|
+
* `refreshStorageLocationsForMigration`, same trigger as
|
|
59351
|
+
* `mediaLocationStorage.invalidateRoots()`.
|
|
59352
|
+
*
|
|
59353
|
+
* Degrades to the bare `eventMedia` type ref (pre-2026-08-26 behaviour)
|
|
59354
|
+
* when the storage cap itself is unreachable — a transient RPC failure (or
|
|
59355
|
+
* a test harness that never wires `api.storage`) must not turn every media
|
|
59356
|
+
* write into a hard failure the way "no writable location" does.
|
|
59357
|
+
*/
|
|
59358
|
+
async resolveEventMediaWriteLocation(api, logger, deviceId) {
|
|
59359
|
+
const cached = this.eventMediaWriteLocationCache;
|
|
59360
|
+
if (cached !== null) return cached;
|
|
59361
|
+
let locations;
|
|
59362
|
+
try {
|
|
59363
|
+
locations = await api.storage.listLocations.query({ type: DEFAULT_MEDIA_LOCATION });
|
|
59364
|
+
} catch (err) {
|
|
59365
|
+
logger.debug("pipeline-analytics: eventMedia location list unavailable — writing through the default ref", { meta: { error: errMsg(err) } });
|
|
59366
|
+
this.eventMediaWriteLocationCache = DEFAULT_MEDIA_LOCATION;
|
|
59367
|
+
return DEFAULT_MEDIA_LOCATION;
|
|
59368
|
+
}
|
|
59369
|
+
const picked = pickWritableMediaLocation(locations.map((l) => ({
|
|
59370
|
+
id: l.id,
|
|
59371
|
+
readOnly: l.config["readOnly"] === true,
|
|
59372
|
+
enabled: l.enabled !== false,
|
|
59373
|
+
headroomBytes: l.capacity?.availableBytes ?? 0
|
|
59374
|
+
})), locations.find((l) => l.isDefault)?.id ?? "eventMedia");
|
|
59375
|
+
if (picked === null) {
|
|
59376
|
+
const seen = locations.length > 0 ? locations.map((l) => l.id).join(", ") : "(none registered)";
|
|
59377
|
+
logger.error("pipeline-analytics: no writable eventMedia storage location — refusing the write instead of failing far downstream as a filesystem error", {
|
|
59378
|
+
tags: { deviceId },
|
|
59379
|
+
meta: { locationsSeen: seen }
|
|
59380
|
+
});
|
|
59381
|
+
throw new Error(`no writable eventMedia storage location (seen: ${seen})`);
|
|
59382
|
+
}
|
|
59383
|
+
this.eventMediaWriteLocationCache = picked;
|
|
59384
|
+
return picked;
|
|
59385
|
+
}
|
|
59148
59386
|
/** Constructs every SQLite-backed store plus the stationary/package-drop/
|
|
59149
59387
|
* sensor plumbing, in the exact pre-S7 order. Returns the non-null bundle
|
|
59150
59388
|
* so the rest of boot consumes the stores without re-narrowing. */
|
|
@@ -59203,7 +59441,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
59203
59441
|
storage,
|
|
59204
59442
|
store: api.settingsStore,
|
|
59205
59443
|
logger: logger.child("MediaStore"),
|
|
59206
|
-
writeGate: this.mediaWriteGate
|
|
59444
|
+
writeGate: this.mediaWriteGate,
|
|
59445
|
+
resolveEventMediaLocation: (deviceId) => this.resolveEventMediaWriteLocation(api, logger, deviceId)
|
|
59207
59446
|
});
|
|
59208
59447
|
this.mediaStore = mediaStore;
|
|
59209
59448
|
const retrainFrames = new RetrainFrameStore({
|
|
@@ -60511,6 +60750,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
60511
60750
|
this.forgetDeviceProcessors(data.deviceId);
|
|
60512
60751
|
this.levelStateByDevice.delete(data.deviceId);
|
|
60513
60752
|
this.settingsCaches.clearDevice(data.deviceId);
|
|
60753
|
+
this.rawDeviceBlobMirror.invalidate(data.deviceId);
|
|
60514
60754
|
}
|
|
60515
60755
|
}
|
|
60516
60756
|
/** `DeviceUnregistered` handler — subscription topology lives in
|
|
@@ -60535,6 +60775,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
60535
60775
|
}
|
|
60536
60776
|
this.audioMarkers?.forgetDevice(deviceId);
|
|
60537
60777
|
this.settingsCaches.clearDevice(deviceId);
|
|
60778
|
+
this.rawDeviceBlobMirror.invalidate(deviceId);
|
|
60538
60779
|
this.bindingCache?.invalidate(deviceId);
|
|
60539
60780
|
this.detectionCoverage?.forget(deviceId);
|
|
60540
60781
|
this.zoneAnalytics?.forgetDevice(deviceId);
|
|
@@ -60963,6 +61204,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
60963
61204
|
}] : []
|
|
60964
61205
|
];
|
|
60965
61206
|
}
|
|
61207
|
+
/**
|
|
61208
|
+
* `BaseAddon.updateGlobalSettings` calls this after the write lands —
|
|
61209
|
+
* gated to the LOCAL node's own write (a sibling-node write returns before
|
|
61210
|
+
* reaching it), which is exactly the scope `rawGlobalStoreMirror` needs to
|
|
61211
|
+
* invalidate: the mirror only ever answers for THIS process, so it only
|
|
61212
|
+
* needs to drop on THIS process's own write (D224).
|
|
61213
|
+
*/
|
|
61214
|
+
async onConfigChanged() {
|
|
61215
|
+
await super.onConfigChanged();
|
|
61216
|
+
this.rawGlobalStoreMirror.invalidate();
|
|
61217
|
+
}
|
|
60966
61218
|
async onShutdown() {
|
|
60967
61219
|
this.shuttingDown = true;
|
|
60968
61220
|
this.sceneEngine?.stop();
|
|
@@ -61018,6 +61270,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
61018
61270
|
this.gatedNodeByDevice.clear();
|
|
61019
61271
|
this.levelStateByDevice.clear();
|
|
61020
61272
|
this.settingsCaches.clearAll();
|
|
61273
|
+
this.rawDeviceBlobMirror.invalidateAll();
|
|
61274
|
+
this.rawGlobalStoreMirror.invalidate();
|
|
61021
61275
|
this.confirmationDetectorStepByNode.clear();
|
|
61022
61276
|
this.trackStore?.clearAll();
|
|
61023
61277
|
this.stationaryRegistry = null;
|
|
@@ -61730,10 +61984,16 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
61730
61984
|
* auto-assigning — see `__tests__/global-settings-cascade.spec.ts`).
|
|
61731
61985
|
* `resolveGlobalStore()` is the sanctioned BaseAddon read (per-node
|
|
61732
61986
|
* projection applied; a raw `readAddonStore` would leak `@node` keys if a
|
|
61733
|
-
* perNode field is ever declared).
|
|
61987
|
+
* perNode field is ever declared).
|
|
61988
|
+
*
|
|
61989
|
+
* Both raw reads go through `rawGlobalStoreMirror` / `rawDeviceBlobMirror`
|
|
61990
|
+
* rather than straight to the store: this method is the ONE place all
|
|
61991
|
+
* eight settings kinds funnel through on a cache miss, so calling the raw
|
|
61992
|
+
* reads directly here means eight kinds expiring in the same window each
|
|
61993
|
+
* pay for the same two reads — see `store/settings-raw-mirror.ts`. */
|
|
61734
61994
|
async readDeviceSettings(deviceId, resolve) {
|
|
61735
|
-
const globalRaw = this.ctxIfReady !== null ? await this.
|
|
61736
|
-
const deviceRaw = await this.
|
|
61995
|
+
const globalRaw = this.ctxIfReady !== null ? await this.rawGlobalStoreMirror.resolve() : {};
|
|
61996
|
+
const deviceRaw = await this.rawDeviceBlobMirror.resolve(deviceId);
|
|
61737
61997
|
return resolve({
|
|
61738
61998
|
...globalRaw,
|
|
61739
61999
|
...deviceRaw
|
|
@@ -63404,6 +63664,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
63404
63664
|
});
|
|
63405
63665
|
}
|
|
63406
63666
|
await settings.writeDeviceStore(deviceId, plan.patch);
|
|
63667
|
+
this.rawDeviceBlobMirror.invalidate(deviceId);
|
|
63407
63668
|
} catch (err) {
|
|
63408
63669
|
this.retiredSettingsChecked.delete(deviceId);
|
|
63409
63670
|
this.ctx.logger.warn("retired retention settings migration failed — will retry", {
|
|
@@ -63475,6 +63736,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
63475
63736
|
deviceId
|
|
63476
63737
|
});
|
|
63477
63738
|
await settings.writeDeviceStore(deviceId, plan.patch);
|
|
63739
|
+
this.rawDeviceBlobMirror.invalidate(deviceId);
|
|
63478
63740
|
} catch (err) {
|
|
63479
63741
|
this.storedSuggestionCheckedDevices.delete(deviceId);
|
|
63480
63742
|
this.ctx.logger.warn("face suggestion threshold purge failed — will retry", {
|
|
@@ -65621,6 +65883,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
65621
65883
|
async refreshStorageLocationsForMigration(input) {
|
|
65622
65884
|
if (this.storageMigrationLeaseId !== input.leaseId) throw new Error("event-media storage migration lease does not match");
|
|
65623
65885
|
this.mediaLocationStorage?.invalidateRoots();
|
|
65886
|
+
this.eventMediaWriteLocationCache = null;
|
|
65624
65887
|
return { refreshed: true };
|
|
65625
65888
|
}
|
|
65626
65889
|
async startStorageMigrationMove(input) {
|
|
@@ -65634,6 +65897,32 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
65634
65897
|
async cancelStorageMigrationMove(input) {
|
|
65635
65898
|
return { cancelled: this.mediaRelocate?.cancel(input.jobId) ?? false };
|
|
65636
65899
|
}
|
|
65900
|
+
/**
|
|
65901
|
+
* Move event media between locations — the OPERATOR's mover, no lease.
|
|
65902
|
+
* Twin of `recording.relocateFootage`: same engine (`this.mediaRelocate`)
|
|
65903
|
+
* the lease-gated coordinator move uses, runnable in the background
|
|
65904
|
+
* without pausing anything. `startStorageMigrationMove` above stays
|
|
65905
|
+
* lease-gated for the orchestrated migration; this method must never touch
|
|
65906
|
+
* `storageMigrationLeaseId`.
|
|
65907
|
+
*/
|
|
65908
|
+
async relocateMedia(input) {
|
|
65909
|
+
if (!this.mediaRelocate) throw new Error("media relocation unavailable");
|
|
65910
|
+
const jobId = this.mediaRelocate.start(input);
|
|
65911
|
+
this.ctx.logger.info("pipeline-analytics: media relocate job enqueued", { meta: {
|
|
65912
|
+
jobId,
|
|
65913
|
+
to: input.toLocationId
|
|
65914
|
+
} });
|
|
65915
|
+
return { jobId };
|
|
65916
|
+
}
|
|
65917
|
+
/** Every relocate job this addon knows about, newest first (in RAM: the
|
|
65918
|
+
* move is resumable, so a lost list costs nothing but the display). */
|
|
65919
|
+
async listRelocateMediaJobs() {
|
|
65920
|
+
return this.mediaRelocate?.list() ?? [];
|
|
65921
|
+
}
|
|
65922
|
+
/** Cancel a running or queued relocate job. */
|
|
65923
|
+
async cancelRelocateMedia(input) {
|
|
65924
|
+
return { cancelled: this.mediaRelocate?.cancel(input.jobId) ?? false };
|
|
65925
|
+
}
|
|
65637
65926
|
async listOpsLog(input) {
|
|
65638
65927
|
return this.queryFacade.listOpsLog(input);
|
|
65639
65928
|
}
|
|
@@ -66027,6 +66316,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
66027
66316
|
}
|
|
66028
66317
|
async applyDeviceSettingsPatch(input) {
|
|
66029
66318
|
await this.updateDeviceSettings(input.deviceId, input.patch);
|
|
66319
|
+
this.rawDeviceBlobMirror.invalidate(input.deviceId);
|
|
66030
66320
|
this.audioSettingsCache.delete(input.deviceId);
|
|
66031
66321
|
this.sensitivitySettingsCache.delete(input.deviceId);
|
|
66032
66322
|
this.trackingSettingsCache.delete(input.deviceId);
|
|
@@ -30,7 +30,7 @@ async function d(e) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
async function f() {
|
|
33
|
-
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-
|
|
33
|
+
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BZm5TUIO.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-post-analysis",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.128",
|
|
4
4
|
"description": "Post-Analysis bundle — enrichment, embedding-encoder, pipeline-analytics. Multi-entry npm package shipping addons that consume pipeline output.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|