@camstack/addon-post-analysis 1.2.126 → 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 +418 -28
- package/dist/pipeline-analytics/index.mjs +418 -28
- 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;
|
|
@@ -46311,6 +46329,50 @@ var AUDIO_COLUMNS = [
|
|
|
46311
46329
|
type: "JSON"
|
|
46312
46330
|
}
|
|
46313
46331
|
];
|
|
46332
|
+
/**
|
|
46333
|
+
* The columns `slimMotion` reads. `id` is the primary key — always returned,
|
|
46334
|
+
* never part of a projection.
|
|
46335
|
+
*
|
|
46336
|
+
* What it leaves behind is the point: `regions` is a JSON array with one entry
|
|
46337
|
+
* per motion rectangle, and it is the only reason a motion row is not tiny.
|
|
46338
|
+
* `frameWidth` / `frameHeight` go with it — they exist to interpret `regions`
|
|
46339
|
+
* and mean nothing without it.
|
|
46340
|
+
*/
|
|
46341
|
+
var SLIM_MOTION_COLUMNS = [
|
|
46342
|
+
"deviceId",
|
|
46343
|
+
"timestamp",
|
|
46344
|
+
"regionCount"
|
|
46345
|
+
];
|
|
46346
|
+
/**
|
|
46347
|
+
* The columns `slimObject` reads — and ONLY those.
|
|
46348
|
+
*
|
|
46349
|
+
* Four JSON columns are dropped (`bbox`, `zones`, `labelMeta`, `subLabelMeta`)
|
|
46350
|
+
* plus the numbers that only exist to argue about a value after the fact
|
|
46351
|
+
* (`confidence`, `labelScore`, `subLabelScore`) and the frame geometry that,
|
|
46352
|
+
* like motion's, is meaningless without the box it describes.
|
|
46353
|
+
*
|
|
46354
|
+
* `label` / `subLabel` stay because the timeline renders `subLabel ?? label`,
|
|
46355
|
+
* which cannot be evaluated from half a row. `frameId` / `trackId` /
|
|
46356
|
+
* `importance` stay because they are the grouping and sort keys the events page
|
|
46357
|
+
* needs — all three are light.
|
|
46358
|
+
*
|
|
46359
|
+
* ⚠ `slimObject` calls `readTieredLabelColumns`, which reads all SIX tiered
|
|
46360
|
+
* columns and returns the four unprojected ones as `undefined`. That is
|
|
46361
|
+
* harmless here only because `slimObject` uses exactly `label` and `subLabel`
|
|
46362
|
+
* from what it gets back. If it ever starts reading a score, this list is the
|
|
46363
|
+
* thing that has to learn about it — the failure would otherwise be a score
|
|
46364
|
+
* that reads as absent on every slim row.
|
|
46365
|
+
*/
|
|
46366
|
+
var SLIM_OBJECT_COLUMNS = [
|
|
46367
|
+
"deviceId",
|
|
46368
|
+
"timestamp",
|
|
46369
|
+
"className",
|
|
46370
|
+
"frameId",
|
|
46371
|
+
"trackId",
|
|
46372
|
+
"importance",
|
|
46373
|
+
"label",
|
|
46374
|
+
"subLabel"
|
|
46375
|
+
];
|
|
46314
46376
|
var COMMON_INDEXES = (prefix) => [{
|
|
46315
46377
|
name: `idx_${prefix}_device_ts`,
|
|
46316
46378
|
columns: ["deviceId", "timestamp"]
|
|
@@ -46494,6 +46556,19 @@ var EventStore = class {
|
|
|
46494
46556
|
filter
|
|
46495
46557
|
})]);
|
|
46496
46558
|
}
|
|
46559
|
+
/**
|
|
46560
|
+
* Spreadable `columns` for a slim read — omitted entirely for `full`.
|
|
46561
|
+
*
|
|
46562
|
+
* The projection is decided in ONE place per collection ({@link
|
|
46563
|
+
* SLIM_MOTION_COLUMNS} / {@link SLIM_OBJECT_COLUMNS}) because a projection is
|
|
46564
|
+
* a promise between the SELECT and the mapper: the day they disagree nothing
|
|
46565
|
+
* fails. SQLite returns the row without the column, `slimObject` reads
|
|
46566
|
+
* `data['className']`, gets `undefined`, and hands back an event whose class
|
|
46567
|
+
* is missing — indistinguishable from a row that never had one.
|
|
46568
|
+
*/
|
|
46569
|
+
slimColumns(projection, columns) {
|
|
46570
|
+
return projection === "slim" ? { columns } : {};
|
|
46571
|
+
}
|
|
46497
46572
|
buildFilter(q) {
|
|
46498
46573
|
const filter = {
|
|
46499
46574
|
where: { deviceId: q.deviceId },
|
|
@@ -46509,7 +46584,8 @@ var EventStore = class {
|
|
|
46509
46584
|
async queryMotion(q) {
|
|
46510
46585
|
const rows = await this.store.query.query({
|
|
46511
46586
|
collection: MOTION_EVENTS_COLLECTION,
|
|
46512
|
-
filter: this.buildFilter(q)
|
|
46587
|
+
filter: this.buildFilter(q),
|
|
46588
|
+
...this.slimColumns(q.projection, SLIM_MOTION_COLUMNS)
|
|
46513
46589
|
});
|
|
46514
46590
|
if (q.projection === "slim") return rows.map((r) => slimMotion(r.id, stripNulls$1(r.data)));
|
|
46515
46591
|
return rows.map((r) => {
|
|
@@ -46579,7 +46655,8 @@ var EventStore = class {
|
|
|
46579
46655
|
}
|
|
46580
46656
|
const rows = await this.store.query.query({
|
|
46581
46657
|
collection: OBJECT_EVENTS_COLLECTION,
|
|
46582
|
-
filter
|
|
46658
|
+
filter,
|
|
46659
|
+
...this.slimColumns(q.projection, SLIM_OBJECT_COLUMNS)
|
|
46583
46660
|
});
|
|
46584
46661
|
if (q.projection === "slim") return rows.map((r) => slimObject(r.id, stripNulls$1(r.data)));
|
|
46585
46662
|
return rows.map((r) => {
|
|
@@ -49238,6 +49315,16 @@ function trackColumnsFor(projection, needsPositions = false) {
|
|
|
49238
49315
|
if (projection !== "slim") return void 0;
|
|
49239
49316
|
return needsPositions ? [...SLIM_TRACK_COLUMN_NAMES, "positions"] : SLIM_TRACK_COLUMN_NAMES;
|
|
49240
49317
|
}
|
|
49318
|
+
/**
|
|
49319
|
+
* The SELECT list for an IDS-ONLY recent page: the primary key (free, always
|
|
49320
|
+
* returned) plus `lastSeen`.
|
|
49321
|
+
*
|
|
49322
|
+
* `lastSeen` is not an optimisation to shave off later — {@link
|
|
49323
|
+
* TrackStore.recentPage} orders on it, windows on it and breaks
|
|
49324
|
+
* same-millisecond ties with it. Reading ids without it would produce a page in
|
|
49325
|
+
* whatever order the engine felt like and a cursor that cannot resume.
|
|
49326
|
+
*/
|
|
49327
|
+
var RECENT_IDS_COLUMNS = ["lastSeen"];
|
|
49241
49328
|
/** Spreadable `columns` for a store query — omitted entirely for `full`. */
|
|
49242
49329
|
function projectionArg(projection, needsPositions = false) {
|
|
49243
49330
|
const columns = trackColumnsFor(projection, needsPositions);
|
|
@@ -50492,10 +50579,48 @@ var TrackStore = class {
|
|
|
50492
50579
|
* Errors propagate (no partial merges): a failed device query fails the
|
|
50493
50580
|
* page rather than silently returning an incomplete window.
|
|
50494
50581
|
*/
|
|
50582
|
+
/**
|
|
50583
|
+
* A page of RECENT track ids, and nothing else.
|
|
50584
|
+
*
|
|
50585
|
+
* Two callers only ever wanted the strings — `collectLiveTrackIds` (the
|
|
50586
|
+
* orphan audit's live set) and `listTrackIdPage` (the embedding rebuild's
|
|
50587
|
+
* cursor walk) — and both were reading whole tracks to build them. This reads
|
|
50588
|
+
* the primary key plus `lastSeen`, which is not a saving to be traded away:
|
|
50589
|
+
* the cursor, the descending order and the same-millisecond tie-break are all
|
|
50590
|
+
* defined on `lastSeen`, so a page without it would be unordered and
|
|
50591
|
+
* unresumable.
|
|
50592
|
+
*
|
|
50593
|
+
* It shares {@link recentPage} with {@link queryRecent} rather than repeating
|
|
50594
|
+
* the window/cursor arithmetic. Two copies of a paging rule is how one of
|
|
50595
|
+
* them learns about a new bound and the other does not.
|
|
50596
|
+
*/
|
|
50597
|
+
async queryRecentIds(params) {
|
|
50598
|
+
const { page, nextCursor } = await this.recentPage(params, RECENT_IDS_COLUMNS);
|
|
50599
|
+
return {
|
|
50600
|
+
trackIds: page.map((r) => r.id),
|
|
50601
|
+
nextCursor
|
|
50602
|
+
};
|
|
50603
|
+
}
|
|
50495
50604
|
async queryRecent(params) {
|
|
50605
|
+
const { page, nextCursor } = await this.recentPage(params, trackColumnsFor(params.projection));
|
|
50606
|
+
return {
|
|
50607
|
+
tracks: page.map((r) => this.rowToTrack(r.id, r.data, params.projection)),
|
|
50608
|
+
nextCursor
|
|
50609
|
+
};
|
|
50610
|
+
}
|
|
50611
|
+
/**
|
|
50612
|
+
* The window, cursor and fan-out arithmetic behind every "recent tracks"
|
|
50613
|
+
* read — shared so the SELECT list is the only thing a caller varies.
|
|
50614
|
+
*
|
|
50615
|
+
* `columns` is passed straight through to the engine (`undefined` = the whole
|
|
50616
|
+
* row). Everything else here is projection-agnostic on purpose: which rows a
|
|
50617
|
+
* page contains, and in what order, must not depend on how much of each row
|
|
50618
|
+
* the caller asked to read.
|
|
50619
|
+
*/
|
|
50620
|
+
async recentPage(params, columns) {
|
|
50496
50621
|
const deviceIds = [...new Set(params.deviceIds)];
|
|
50497
50622
|
if (deviceIds.length === 0) return {
|
|
50498
|
-
|
|
50623
|
+
page: [],
|
|
50499
50624
|
nextCursor: null
|
|
50500
50625
|
};
|
|
50501
50626
|
const requested = Math.min(Math.max(params.limit ?? RECENT_DEFAULT_LIMIT, 1), RECENT_MAX_LIMIT);
|
|
@@ -50519,7 +50644,7 @@ var TrackStore = class {
|
|
|
50519
50644
|
const winHi = params.until ?? Number.MAX_SAFE_INTEGER;
|
|
50520
50645
|
const hi = cursor !== null ? Math.min(cursor.lastSeen, winHi) : winHi;
|
|
50521
50646
|
if (hi < lo) return {
|
|
50522
|
-
|
|
50647
|
+
page: [],
|
|
50523
50648
|
nextCursor: null
|
|
50524
50649
|
};
|
|
50525
50650
|
const stationaryExclusion = params.excludeStationaryPromotions === true ? { whereNot: { closedReason: "stationary-promotion" } } : {};
|
|
@@ -50535,7 +50660,7 @@ var TrackStore = class {
|
|
|
50535
50660
|
},
|
|
50536
50661
|
...pageLimit !== void 0 ? { limit: pageLimit } : {}
|
|
50537
50662
|
},
|
|
50538
|
-
...
|
|
50663
|
+
...columns === void 0 ? {} : { columns }
|
|
50539
50664
|
});
|
|
50540
50665
|
const perDevice = await Promise.all(deviceIds.map(async (deviceId) => {
|
|
50541
50666
|
if (cursor === null || cursor.lastSeen > hi) return fetchRange(deviceId, [lo, hi], limit + 1);
|
|
@@ -50559,10 +50684,9 @@ var TrackStore = class {
|
|
|
50559
50684
|
}
|
|
50560
50685
|
candidates.sort(compareRecentDesc);
|
|
50561
50686
|
const page = candidates.slice(0, limit);
|
|
50562
|
-
const tracks = page.map((r) => this.rowToTrack(r.id, r.data, params.projection));
|
|
50563
50687
|
const last = page[page.length - 1];
|
|
50564
50688
|
return {
|
|
50565
|
-
|
|
50689
|
+
page,
|
|
50566
50690
|
nextCursor: candidates.length > limit && last !== void 0 ? encodeRecentCursor({
|
|
50567
50691
|
lastSeen: last.lastSeen,
|
|
50568
50692
|
trackId: last.id
|
|
@@ -56865,6 +56989,144 @@ function rowFromMetadata(id, metadata) {
|
|
|
56865
56989
|
};
|
|
56866
56990
|
}
|
|
56867
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
|
|
56868
57130
|
//#region src/pipeline-analytics/track-retention-sweep.ts
|
|
56869
57131
|
/**
|
|
56870
57132
|
* Sweep every device with persisted tracks: skip retention-disabled devices,
|
|
@@ -57921,6 +58183,14 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
57921
58183
|
/** Event-media relocation engine (entity-routing Phase 4). */
|
|
57922
58184
|
mediaRelocate = null;
|
|
57923
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;
|
|
57924
58194
|
mediaWriteGate = new MediaWriteGate();
|
|
57925
58195
|
storageMigrationLeaseId = null;
|
|
57926
58196
|
/** Per-camera history of LINKED-device sensor state changes (Part B). */
|
|
@@ -58320,6 +58590,33 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
58320
58590
|
audioEpisodeTracks = /* @__PURE__ */ new Map();
|
|
58321
58591
|
audioEpisodeLastPersistMs = /* @__PURE__ */ new Map();
|
|
58322
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
|
+
});
|
|
58323
58620
|
/** Avoids a store read every ~30 Hz audio frame. */
|
|
58324
58621
|
audioSettingsCache = this.settingsCaches.registerDeviceCache("audio-detection", SETTINGS_CACHE_TTL_MS);
|
|
58325
58622
|
sensitivitySettingsCache = this.settingsCaches.registerDeviceCache("detection-sensitivity", SETTINGS_CACHE_TTL_MS);
|
|
@@ -59039,6 +59336,53 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
59039
59336
|
this.mediaLocationStorage = routed;
|
|
59040
59337
|
return routed;
|
|
59041
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
|
+
}
|
|
59042
59386
|
/** Constructs every SQLite-backed store plus the stationary/package-drop/
|
|
59043
59387
|
* sensor plumbing, in the exact pre-S7 order. Returns the non-null bundle
|
|
59044
59388
|
* so the rest of boot consumes the stores without re-narrowing. */
|
|
@@ -59097,7 +59441,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
59097
59441
|
storage,
|
|
59098
59442
|
store: api.settingsStore,
|
|
59099
59443
|
logger: logger.child("MediaStore"),
|
|
59100
|
-
writeGate: this.mediaWriteGate
|
|
59444
|
+
writeGate: this.mediaWriteGate,
|
|
59445
|
+
resolveEventMediaLocation: (deviceId) => this.resolveEventMediaWriteLocation(api, logger, deviceId)
|
|
59101
59446
|
});
|
|
59102
59447
|
this.mediaStore = mediaStore;
|
|
59103
59448
|
const retrainFrames = new RetrainFrameStore({
|
|
@@ -60405,6 +60750,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
60405
60750
|
this.forgetDeviceProcessors(data.deviceId);
|
|
60406
60751
|
this.levelStateByDevice.delete(data.deviceId);
|
|
60407
60752
|
this.settingsCaches.clearDevice(data.deviceId);
|
|
60753
|
+
this.rawDeviceBlobMirror.invalidate(data.deviceId);
|
|
60408
60754
|
}
|
|
60409
60755
|
}
|
|
60410
60756
|
/** `DeviceUnregistered` handler — subscription topology lives in
|
|
@@ -60429,6 +60775,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
60429
60775
|
}
|
|
60430
60776
|
this.audioMarkers?.forgetDevice(deviceId);
|
|
60431
60777
|
this.settingsCaches.clearDevice(deviceId);
|
|
60778
|
+
this.rawDeviceBlobMirror.invalidate(deviceId);
|
|
60432
60779
|
this.bindingCache?.invalidate(deviceId);
|
|
60433
60780
|
this.detectionCoverage?.forget(deviceId);
|
|
60434
60781
|
this.zoneAnalytics?.forgetDevice(deviceId);
|
|
@@ -60857,6 +61204,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
60857
61204
|
}] : []
|
|
60858
61205
|
];
|
|
60859
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
|
+
}
|
|
60860
61218
|
async onShutdown() {
|
|
60861
61219
|
this.shuttingDown = true;
|
|
60862
61220
|
this.sceneEngine?.stop();
|
|
@@ -60912,6 +61270,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
60912
61270
|
this.gatedNodeByDevice.clear();
|
|
60913
61271
|
this.levelStateByDevice.clear();
|
|
60914
61272
|
this.settingsCaches.clearAll();
|
|
61273
|
+
this.rawDeviceBlobMirror.invalidateAll();
|
|
61274
|
+
this.rawGlobalStoreMirror.invalidate();
|
|
60915
61275
|
this.confirmationDetectorStepByNode.clear();
|
|
60916
61276
|
this.trackStore?.clearAll();
|
|
60917
61277
|
this.stationaryRegistry = null;
|
|
@@ -61624,10 +61984,16 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
61624
61984
|
* auto-assigning — see `__tests__/global-settings-cascade.spec.ts`).
|
|
61625
61985
|
* `resolveGlobalStore()` is the sanctioned BaseAddon read (per-node
|
|
61626
61986
|
* projection applied; a raw `readAddonStore` would leak `@node` keys if a
|
|
61627
|
-
* 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`. */
|
|
61628
61994
|
async readDeviceSettings(deviceId, resolve) {
|
|
61629
|
-
const globalRaw = this.ctxIfReady !== null ? await this.
|
|
61630
|
-
const deviceRaw = await this.
|
|
61995
|
+
const globalRaw = this.ctxIfReady !== null ? await this.rawGlobalStoreMirror.resolve() : {};
|
|
61996
|
+
const deviceRaw = await this.rawDeviceBlobMirror.resolve(deviceId);
|
|
61631
61997
|
return resolve({
|
|
61632
61998
|
...globalRaw,
|
|
61633
61999
|
...deviceRaw
|
|
@@ -63298,6 +63664,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
63298
63664
|
});
|
|
63299
63665
|
}
|
|
63300
63666
|
await settings.writeDeviceStore(deviceId, plan.patch);
|
|
63667
|
+
this.rawDeviceBlobMirror.invalidate(deviceId);
|
|
63301
63668
|
} catch (err) {
|
|
63302
63669
|
this.retiredSettingsChecked.delete(deviceId);
|
|
63303
63670
|
this.ctx.logger.warn("retired retention settings migration failed — will retry", {
|
|
@@ -63369,6 +63736,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
63369
63736
|
deviceId
|
|
63370
63737
|
});
|
|
63371
63738
|
await settings.writeDeviceStore(deviceId, plan.patch);
|
|
63739
|
+
this.rawDeviceBlobMirror.invalidate(deviceId);
|
|
63372
63740
|
} catch (err) {
|
|
63373
63741
|
this.storedSuggestionCheckedDevices.delete(deviceId);
|
|
63374
63742
|
this.ctx.logger.warn("face suggestion threshold purge failed — will retry", {
|
|
@@ -64354,11 +64722,11 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
64354
64722
|
const trackStore = this.trackStore;
|
|
64355
64723
|
if (!trackStore) return live;
|
|
64356
64724
|
for (const deviceId of deviceIds) {
|
|
64357
|
-
const page = await trackStore.
|
|
64725
|
+
const page = await trackStore.queryRecentIds({
|
|
64358
64726
|
deviceIds: [deviceId],
|
|
64359
64727
|
limit: EMBEDDING_ORPHAN_SCAN_LIMIT
|
|
64360
64728
|
});
|
|
64361
|
-
for (const
|
|
64729
|
+
for (const trackId of page.trackIds) live.add(trackId);
|
|
64362
64730
|
}
|
|
64363
64731
|
return live;
|
|
64364
64732
|
}
|
|
@@ -64681,19 +65049,13 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
64681
65049
|
logger: this.ctx.logger
|
|
64682
65050
|
}));
|
|
64683
65051
|
const deps = {
|
|
64684
|
-
listTrackIdPage: async (cursor) => {
|
|
64685
|
-
|
|
64686
|
-
|
|
64687
|
-
|
|
64688
|
-
|
|
64689
|
-
|
|
64690
|
-
|
|
64691
|
-
});
|
|
64692
|
-
return {
|
|
64693
|
-
trackIds: page.tracks.map((t) => t.trackId),
|
|
64694
|
-
nextCursor: page.nextCursor
|
|
64695
|
-
};
|
|
64696
|
-
},
|
|
65052
|
+
listTrackIdPage: async (cursor) => trackStore.queryRecentIds({
|
|
65053
|
+
deviceIds,
|
|
65054
|
+
...input.since !== void 0 ? { since: input.since } : {},
|
|
65055
|
+
...input.until !== void 0 ? { until: input.until } : {},
|
|
65056
|
+
...cursor !== null ? { cursor } : {},
|
|
65057
|
+
limit: REBUILD_PAGE_SIZE
|
|
65058
|
+
}),
|
|
64697
65059
|
loadSource: (trackId) => loadRebuildSource({
|
|
64698
65060
|
media: mediaStore,
|
|
64699
65061
|
events: eventStore
|
|
@@ -65521,6 +65883,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
65521
65883
|
async refreshStorageLocationsForMigration(input) {
|
|
65522
65884
|
if (this.storageMigrationLeaseId !== input.leaseId) throw new Error("event-media storage migration lease does not match");
|
|
65523
65885
|
this.mediaLocationStorage?.invalidateRoots();
|
|
65886
|
+
this.eventMediaWriteLocationCache = null;
|
|
65524
65887
|
return { refreshed: true };
|
|
65525
65888
|
}
|
|
65526
65889
|
async startStorageMigrationMove(input) {
|
|
@@ -65534,6 +65897,32 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
65534
65897
|
async cancelStorageMigrationMove(input) {
|
|
65535
65898
|
return { cancelled: this.mediaRelocate?.cancel(input.jobId) ?? false };
|
|
65536
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
|
+
}
|
|
65537
65926
|
async listOpsLog(input) {
|
|
65538
65927
|
return this.queryFacade.listOpsLog(input);
|
|
65539
65928
|
}
|
|
@@ -65927,6 +66316,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
65927
66316
|
}
|
|
65928
66317
|
async applyDeviceSettingsPatch(input) {
|
|
65929
66318
|
await this.updateDeviceSettings(input.deviceId, input.patch);
|
|
66319
|
+
this.rawDeviceBlobMirror.invalidate(input.deviceId);
|
|
65930
66320
|
this.audioSettingsCache.delete(input.deviceId);
|
|
65931
66321
|
this.sensitivitySettingsCache.delete(input.deviceId);
|
|
65932
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",
|