@camstack/addon-pipeline 1.2.128 → 1.2.130
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/{addon-utils-BnprivSs.js → addon-utils-DH78p79i.js} +1 -1
- package/dist/audio-analyzer/index.js +3 -3
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/detection-pipeline/index.js +241 -95
- package/dist/detection-pipeline/index.mjs +239 -93
- package/dist/{dist-BZUXb5S5.js → dist-DOw-izoI.js} +243 -75
- package/dist/{dist-HLAOw6Pe.mjs → dist-Wt81pN5Y.mjs} +243 -75
- package/dist/{event-loop-stall-monitor-BdArVUlx.js → event-loop-stall-monitor-Bld7jNCb.js} +1 -1
- package/dist/{event-loop-stall-monitor-BH-cL_hg.mjs → event-loop-stall-monitor-CtQCOZqm.mjs} +1 -1
- package/dist/{lazy-sharp-DTDXrhMG.js → lazy-sharp-BbNI2ZD-.js} +1 -1
- package/dist/motion-wasm/index.js +2 -2
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +4 -4
- package/dist/pipeline-runner/index.mjs +3 -3
- package/dist/{process-memory-CDPRLalN.mjs → process-memory-1_D3EZT9.mjs} +1 -1
- package/dist/{process-memory-Dh1SyzdJ.js → process-memory-D9H5qjDl.js} +1 -1
- package/dist/recorder/index.js +467 -29
- package/dist/recorder/index.mjs +465 -28
- package/dist/{segment-demux-js-D-TLcHW5.mjs → segment-demux-js-BW-Q-j2A.mjs} +1 -1
- package/dist/{segment-demux-js-DRutggd6.js → segment-demux-js-C2jBBqGW.js} +1 -1
- package/dist/session-decode/decode-worker-child.js +2 -2
- package/dist/session-decode/decode-worker-child.mjs +1 -1
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BDR5z0Z9.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-Dw-iwP4y.mjs} +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Bme2OZQp.mjs +26 -0
- package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Do2DEtRt.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-D36MoVV1.mjs} +1 -1
- package/dist/stream-broker/demux-worker-child.js +1 -1
- package/dist/stream-broker/demux-worker-child.mjs +1 -1
- package/dist/stream-broker/{hostInit-Dmw5LWmP.mjs → hostInit-DEVCEYgu.mjs} +3 -3
- package/dist/stream-broker/index.js +906 -423
- package/dist/stream-broker/index.mjs +906 -423
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-BxkCoVjw.js → worker-protocol-nAZl_Rsv.js} +1 -1
- package/dist/{worker-protocol-h7Smb_0i.mjs → worker-protocol-rpnr7Ldd.mjs} +1 -1
- package/package.json +1 -1
- package/python/__pycache__/inference_pool.cpython-312.pyc +0 -0
- package/python/__pycache__/inference_pool.cpython-313.pyc +0 -0
- package/python/__pycache__/inference_pool.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_backpressure.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/__pycache__/test_inference_pool_backpressure.cpython-312.pyc +0 -0
- package/python/__pycache__/test_inference_pool_backpressure.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_coreml_cache.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/__pycache__/test_inference_pool_coreml_cache.cpython-312.pyc +0 -0
- package/python/__pycache__/test_inference_pool_coreml_cache.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_device_selection.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/__pycache__/test_inference_pool_device_selection.cpython-312.pyc +0 -0
- package/python/__pycache__/test_inference_pool_device_selection.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_layout.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/__pycache__/test_inference_pool_layout.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_memstats.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/__pycache__/test_inference_pool_memstats.cpython-312.pyc +0 -0
- package/python/__pycache__/test_inference_pool_memstats.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_ov_ppp.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/__pycache__/test_inference_pool_ov_ppp.cpython-312.pyc +0 -0
- package/python/__pycache__/test_inference_pool_ov_ppp.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_preprocess.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/__pycache__/test_inference_pool_preprocess.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_reconfigure.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_static_batch.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/__pycache__/test_inference_pool_static_batch.cpython-312.pyc +0 -0
- package/python/__pycache__/test_inference_pool_static_batch.cpython-314.pyc +0 -0
- package/python/__pycache__/yamnet_audio.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/__init__.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/__init__.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/_safety.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/arcface.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/arcface.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/arcface.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/clip.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/clip.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/clip.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/ctc.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/ctc.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/ctc.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/plate_slots.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/plate_slots.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/rfdetr.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/rfdetr.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/saliency.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/saliency.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/saliency.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/scrfd.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/scrfd.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/scrfd.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/softmax.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/softmax.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/softmax.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/ssd.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/ssd.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/ssd.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/test_clip.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/test_clip.cpython-313-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/test_ctc.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/test_ctc.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/test_ctc.cpython-313-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/test_ctc.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/test_ctc.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/test_plate_slots.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/test_rfdetr.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/test_rfdetr.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/test_saliency.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/test_saliency.cpython-313-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/test_saliency.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/test_softmax.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/test_softmax.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/test_yolo_nms.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/test_yolo_nms.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/test_yolo_nms.cpython-39.pyc +0 -0
- package/python/postprocessors/__pycache__/test_yolonas.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/yamnet.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yamnet.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/yamnet.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo.cpython-39.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo_seg.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo_seg.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo_seg.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/yolonas.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yolonas.cpython-314.pyc +0 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Cs8bmrWP.mjs +0 -26
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Bt as lazy, Et as createEvent, F as customAction, Gt as string, Ht as number, It as _enum, L as defineCustomActions, Lt as array, Rt as boolean, St as BaseAddon, Ut as object, Vt as literal, f as DEFAULT_NATIVE_LEASE_SETTINGS, gt as errMsg, it as pipelineRunnerCapability, jt as nodePin, nt as pickNativeLeaseOverride, p as DEVICE_BACKEND_TO_FORMAT, qt as EventCategory, tt as pickDetailCropConvention, u as DEFAULT_DETAIL_CROP_CONVENTION, z as deriveDetailCropRect } from "../dist-
|
|
2
|
-
import { i as resolveFrameViewGeometry, r as localFrameRegistry, t as attributeStall, u as getStepDefinition } from "../event-loop-stall-monitor-
|
|
3
|
-
import { a as nativeLeaseSettingEnv, t as isWorkerReply } from "../worker-protocol-
|
|
1
|
+
import { Bt as lazy, Et as createEvent, F as customAction, Gt as string, Ht as number, It as _enum, L as defineCustomActions, Lt as array, Rt as boolean, St as BaseAddon, Ut as object, Vt as literal, f as DEFAULT_NATIVE_LEASE_SETTINGS, gt as errMsg, it as pipelineRunnerCapability, jt as nodePin, nt as pickNativeLeaseOverride, p as DEVICE_BACKEND_TO_FORMAT, qt as EventCategory, tt as pickDetailCropConvention, u as DEFAULT_DETAIL_CROP_CONVENTION, z as deriveDetailCropRect } from "../dist-Wt81pN5Y.mjs";
|
|
2
|
+
import { i as resolveFrameViewGeometry, r as localFrameRegistry, t as attributeStall, u as getStepDefinition } from "../event-loop-stall-monitor-CtQCOZqm.mjs";
|
|
3
|
+
import { a as nativeLeaseSettingEnv, t as isWorkerReply } from "../worker-protocol-rpnr7Ldd.mjs";
|
|
4
4
|
import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
|
|
5
5
|
import { t as getSharp } from "../lazy-sharp-BtT5x-qP.mjs";
|
|
6
6
|
import { n as withDetectionIntent } from "../restream-intent-B4BXZra7.mjs";
|
package/dist/recorder/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const require_dist = require("../dist-
|
|
1
|
+
const require_dist = require("../dist-DOw-izoI.js");
|
|
2
2
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
3
3
|
const require_restream_intent = require("../restream-intent-Cv9x3jmu.js");
|
|
4
|
-
const require_addon_utils = require("../addon-utils-
|
|
4
|
+
const require_addon_utils = require("../addon-utils-DH78p79i.js");
|
|
5
5
|
const require_retire_root_keys = require("../retire-root-keys-KE6D6Xh_.js");
|
|
6
6
|
let node_crypto = require("node:crypto");
|
|
7
7
|
let node_child_process = require("node:child_process");
|
|
@@ -336,6 +336,70 @@ function fragmentRangeFor(index, offsetTicks) {
|
|
|
336
336
|
sampleIndex: chosen
|
|
337
337
|
};
|
|
338
338
|
}
|
|
339
|
+
/**
|
|
340
|
+
* Convert a byte budget into the SET of consecutive fragments that fits,
|
|
341
|
+
* starting at the fragment covering `offsetTicks` and walking FORWARD —
|
|
342
|
+
* matching the read direction a scrub or a sequential-forward strategy
|
|
343
|
+
* actually uses. Pure: `index` is the already-parsed `tfra` table, so this
|
|
344
|
+
* never touches the segment's payload.
|
|
345
|
+
*
|
|
346
|
+
* Greedy: adds the next fragment only while the running total stays within
|
|
347
|
+
* `maxBytes`. The anchor fragment is ALWAYS included, even when it alone
|
|
348
|
+
* exceeds `maxBytes` — {@link FragmentBudgetPlan.oversizeFragment} says so
|
|
349
|
+
* explicitly rather than the caller receiving an empty set and reading it as
|
|
350
|
+
* "nothing to read" (D49's letter, applied to a byte budget: a fallible
|
|
351
|
+
* computation must never silently look like an unbind).
|
|
352
|
+
*/
|
|
353
|
+
function fragmentsWithinBudget(index, offsetTicks, maxBytes) {
|
|
354
|
+
const { samples, mfraOffset } = index;
|
|
355
|
+
let lo = 0;
|
|
356
|
+
let hi = samples.length - 1;
|
|
357
|
+
let anchor = 0;
|
|
358
|
+
while (lo <= hi) {
|
|
359
|
+
const mid = lo + hi >> 1;
|
|
360
|
+
const s = samples[mid];
|
|
361
|
+
if (s === void 0) break;
|
|
362
|
+
if (s.timeTicks <= offsetTicks) {
|
|
363
|
+
anchor = mid;
|
|
364
|
+
lo = mid + 1;
|
|
365
|
+
} else hi = mid - 1;
|
|
366
|
+
}
|
|
367
|
+
const endOf = (i) => samples[i + 1]?.moofOffset ?? mfraOffset;
|
|
368
|
+
const anchorStart = samples[anchor]?.moofOffset ?? 0;
|
|
369
|
+
const anchorEnd = endOf(anchor);
|
|
370
|
+
const fragments = [{
|
|
371
|
+
start: anchorStart,
|
|
372
|
+
end: anchorEnd,
|
|
373
|
+
sampleIndex: anchor
|
|
374
|
+
}];
|
|
375
|
+
let total = anchorEnd - anchorStart;
|
|
376
|
+
const oversizeFragment = total > maxBytes;
|
|
377
|
+
let i = anchor;
|
|
378
|
+
while (i + 1 < samples.length) {
|
|
379
|
+
const nextIndex = i + 1;
|
|
380
|
+
const nextSample = samples[nextIndex];
|
|
381
|
+
if (nextSample === void 0) break;
|
|
382
|
+
const nextStart = nextSample.moofOffset;
|
|
383
|
+
const nextEnd = endOf(nextIndex);
|
|
384
|
+
const nextSize = nextEnd - nextStart;
|
|
385
|
+
if (total + nextSize > maxBytes) break;
|
|
386
|
+
fragments.push({
|
|
387
|
+
start: nextStart,
|
|
388
|
+
end: nextEnd,
|
|
389
|
+
sampleIndex: nextIndex
|
|
390
|
+
});
|
|
391
|
+
total += nextSize;
|
|
392
|
+
i = nextIndex;
|
|
393
|
+
}
|
|
394
|
+
const last = fragments[fragments.length - 1];
|
|
395
|
+
return {
|
|
396
|
+
fragments,
|
|
397
|
+
start: fragments[0].start,
|
|
398
|
+
end: last.end,
|
|
399
|
+
reachesSegmentEnd: last.end >= mfraOffset,
|
|
400
|
+
oversizeFragment
|
|
401
|
+
};
|
|
402
|
+
}
|
|
339
403
|
//#endregion
|
|
340
404
|
//#region src/recorder/mfra-capture.ts
|
|
341
405
|
/**
|
|
@@ -2737,6 +2801,272 @@ function activeModeForConfig(config) {
|
|
|
2737
2801
|
return require_dist.deriveRecordingMode(config);
|
|
2738
2802
|
}
|
|
2739
2803
|
//#endregion
|
|
2804
|
+
//#region src/recorder/addon/store/read-census.ts
|
|
2805
|
+
/**
|
|
2806
|
+
* Longest window the census may be armed for, in ms.
|
|
2807
|
+
*
|
|
2808
|
+
* Ten minutes is several report windows - enough to catch a sweep that fires
|
|
2809
|
+
* every few minutes - and short enough that an operator who forgets about it
|
|
2810
|
+
* pays for ten minutes, not until the next restart.
|
|
2811
|
+
*/
|
|
2812
|
+
var CENSUS_MAX_WINDOW_MS = 10 * 6e4;
|
|
2813
|
+
/** Store procedures worth attributing. Reads only: a write is an operator
|
|
2814
|
+
* gesture or a pipeline product, and neither is a mystery. */
|
|
2815
|
+
var CENSUSED_PROCEDURES = new Set([
|
|
2816
|
+
"query",
|
|
2817
|
+
"count",
|
|
2818
|
+
"get",
|
|
2819
|
+
"histogram",
|
|
2820
|
+
"getAll"
|
|
2821
|
+
]);
|
|
2822
|
+
/**
|
|
2823
|
+
* Can `new Proxy` wrap this, and is it worth wrapping?
|
|
2824
|
+
*
|
|
2825
|
+
* Functions count. The generated store client is a **tRPC proxy** and every
|
|
2826
|
+
* intermediate node of one is CALLABLE — `typeof client.query` is `'function'`,
|
|
2827
|
+
* not `'object'`. A wrapper that tested only for `'object'` handed the whole
|
|
2828
|
+
* client back unwrapped, deployed, and reported zero `query` and zero `count`
|
|
2829
|
+
* call sites while the engine was recording thousands a minute against the same
|
|
2830
|
+
* tables. Silence read as "nobody in this addon does it".
|
|
2831
|
+
*/
|
|
2832
|
+
function isProxyable(value) {
|
|
2833
|
+
return typeof value === "object" && value !== null || typeof value === "function";
|
|
2834
|
+
}
|
|
2835
|
+
function rowsOf(value) {
|
|
2836
|
+
if (Array.isArray(value)) return value.length;
|
|
2837
|
+
if (typeof value === "number") return value;
|
|
2838
|
+
if (value === void 0 || value === null) return 0;
|
|
2839
|
+
return 1;
|
|
2840
|
+
}
|
|
2841
|
+
/**
|
|
2842
|
+
* One V8 stack line reduced to `Function@line`.
|
|
2843
|
+
*
|
|
2844
|
+
* A deployed addon is ONE bundled file, so the path is identical on every frame
|
|
2845
|
+
* and carries no information; the line number does. A frame that parses as
|
|
2846
|
+
* neither keeps its trimmed text rather than being dropped - an unrecognised
|
|
2847
|
+
* frame is still an attribution, and dropping it would silently shift the site
|
|
2848
|
+
* one frame up.
|
|
2849
|
+
*/
|
|
2850
|
+
function compactFrame(line) {
|
|
2851
|
+
const trimmed = line.trim().replace(/^at\s+/, "");
|
|
2852
|
+
const named = /^(.+?)\s+\((?:.*?):(\d+):\d+\)$/.exec(trimmed);
|
|
2853
|
+
if (named !== null) return `${named[1]}@${named[2]}`;
|
|
2854
|
+
const bare = /^(?:.*?):(\d+):\d+$/.exec(trimmed);
|
|
2855
|
+
if (bare !== null) return `@${bare[1]}`;
|
|
2856
|
+
return trimmed;
|
|
2857
|
+
}
|
|
2858
|
+
/** The census's view of a stack: the top {@link CENSUS_STACK_FRAMES} frames,
|
|
2859
|
+
* innermost first, joined by ` < ` so a line reads caller-ward. */
|
|
2860
|
+
function siteFromStack(stack) {
|
|
2861
|
+
if (stack === void 0) return "unknown";
|
|
2862
|
+
const frames = stack.split("\n").slice(1, 4);
|
|
2863
|
+
if (frames.length === 0) return "unknown";
|
|
2864
|
+
return frames.map(compactFrame).join(" < ");
|
|
2865
|
+
}
|
|
2866
|
+
/**
|
|
2867
|
+
* Armed-window call-site counter.
|
|
2868
|
+
*
|
|
2869
|
+
* Disarmed it is one comparison per read and captures nothing - the stack is
|
|
2870
|
+
* the whole cost, and it is not taken.
|
|
2871
|
+
*/
|
|
2872
|
+
var StoreReadCensus = class {
|
|
2873
|
+
now;
|
|
2874
|
+
counts = /* @__PURE__ */ new Map();
|
|
2875
|
+
armedUntil = 0;
|
|
2876
|
+
armedAt = 0;
|
|
2877
|
+
constructor(now = () => Date.now()) {
|
|
2878
|
+
this.now = now;
|
|
2879
|
+
}
|
|
2880
|
+
/** Arm for `forMs`, clamped to {@link CENSUS_MAX_WINDOW_MS}. Re-arming
|
|
2881
|
+
* restarts the window and CLEARS the counts: two overlapping measurements in
|
|
2882
|
+
* one table is a table nobody can divide by a duration. */
|
|
2883
|
+
arm(forMs) {
|
|
2884
|
+
const at = this.now();
|
|
2885
|
+
this.counts.clear();
|
|
2886
|
+
this.armedAt = at;
|
|
2887
|
+
this.armedUntil = at + Math.max(0, Math.min(forMs, CENSUS_MAX_WINDOW_MS));
|
|
2888
|
+
}
|
|
2889
|
+
disarm() {
|
|
2890
|
+
this.armedUntil = 0;
|
|
2891
|
+
}
|
|
2892
|
+
get armed() {
|
|
2893
|
+
return this.now() < this.armedUntil;
|
|
2894
|
+
}
|
|
2895
|
+
/** How long the current (or just-finished) window collected, in ms. The
|
|
2896
|
+
* divisor for a calls-per-minute rate - a count without it is a number that
|
|
2897
|
+
* cannot be compared to the next one. */
|
|
2898
|
+
elapsedMs() {
|
|
2899
|
+
const at = this.now();
|
|
2900
|
+
return Math.max(0, Math.min(at, this.armedUntil) - this.armedAt);
|
|
2901
|
+
}
|
|
2902
|
+
/**
|
|
2903
|
+
* The call site of whoever called `cutoff`, or `null` when disarmed.
|
|
2904
|
+
*
|
|
2905
|
+
* **Taken BEFORE the await, always.** The first version of this class
|
|
2906
|
+
* captured the stack next to the counter, after the wrapped call had
|
|
2907
|
+
* resolved - and by then the synchronous caller is gone: V8 stitches an async
|
|
2908
|
+
* frame in its place and the site reads
|
|
2909
|
+
* `Proxy.<anonymous> < processTicksAndRejections`, which is the census
|
|
2910
|
+
* describing itself. It passed one of its own tests anyway, because that one
|
|
2911
|
+
* caller happened to RETURN the promise rather than await it. An instrument
|
|
2912
|
+
* that reports its own frames is worse than no instrument: the counts are
|
|
2913
|
+
* real, so the wrong name gets believed.
|
|
2914
|
+
*
|
|
2915
|
+
* `cutoff` is the wrapper function itself, so every frame from the wrapper
|
|
2916
|
+
* inwards is dropped and frame 0 is the code that asked.
|
|
2917
|
+
*/
|
|
2918
|
+
siteOf(cutoff) {
|
|
2919
|
+
if (!this.armed) return null;
|
|
2920
|
+
const holder = {};
|
|
2921
|
+
Error.captureStackTrace(holder, cutoff);
|
|
2922
|
+
return siteFromStack(holder.stack);
|
|
2923
|
+
}
|
|
2924
|
+
/**
|
|
2925
|
+
* Count one read at an already-captured site.
|
|
2926
|
+
*
|
|
2927
|
+
* A `null` site means the census was disarmed when the call STARTED. It is
|
|
2928
|
+
* not counted even if it lands inside a later window - a read belongs to the
|
|
2929
|
+
* window that issued it.
|
|
2930
|
+
*/
|
|
2931
|
+
record(op, site, result) {
|
|
2932
|
+
if (site === null) return;
|
|
2933
|
+
const key = `${op} ${site}`;
|
|
2934
|
+
const existing = this.counts.get(key);
|
|
2935
|
+
if (existing === void 0) {
|
|
2936
|
+
this.counts.set(key, {
|
|
2937
|
+
op,
|
|
2938
|
+
site,
|
|
2939
|
+
calls: 1,
|
|
2940
|
+
rows: rowsOf(result)
|
|
2941
|
+
});
|
|
2942
|
+
return;
|
|
2943
|
+
}
|
|
2944
|
+
existing.calls += 1;
|
|
2945
|
+
existing.rows += rowsOf(result);
|
|
2946
|
+
}
|
|
2947
|
+
/** Busiest first. Does NOT clear - a window may be read as many times as the
|
|
2948
|
+
* operator likes and always describes the same window. */
|
|
2949
|
+
rows() {
|
|
2950
|
+
return [...this.counts.values()].map((r) => ({
|
|
2951
|
+
op: r.op,
|
|
2952
|
+
site: r.site,
|
|
2953
|
+
calls: r.calls,
|
|
2954
|
+
rows: r.rows
|
|
2955
|
+
})).sort((a, b) => b.calls - a.calls);
|
|
2956
|
+
}
|
|
2957
|
+
};
|
|
2958
|
+
/**
|
|
2959
|
+
* Wrap a settings-store client so every read is counted.
|
|
2960
|
+
*
|
|
2961
|
+
* A `Proxy` over the generated tRPC client rather than a hand-written facade:
|
|
2962
|
+
* the client has a procedure per cap method and a facade would need updating
|
|
2963
|
+
* every time the cap gains one - which is how a diagnostic quietly stops seeing
|
|
2964
|
+
* half the traffic. Unrecognised procedures pass straight through.
|
|
2965
|
+
*/
|
|
2966
|
+
function censusStoreClient(client, census) {
|
|
2967
|
+
return new Proxy(client, { get(target, prop, receiver) {
|
|
2968
|
+
const procedure = Reflect.get(target, prop, receiver);
|
|
2969
|
+
if (typeof prop !== "string" || !CENSUSED_PROCEDURES.has(prop)) return procedure;
|
|
2970
|
+
if (!isProxyable(procedure)) return procedure;
|
|
2971
|
+
const op = prop;
|
|
2972
|
+
return new Proxy(procedure, { get(procTarget, procProp, procReceiver) {
|
|
2973
|
+
const call = Reflect.get(procTarget, procProp, procReceiver);
|
|
2974
|
+
if (procProp !== "query" && procProp !== "mutate") return call;
|
|
2975
|
+
if (typeof call !== "function") return call;
|
|
2976
|
+
const wrapped = async (...args) => {
|
|
2977
|
+
const site = census.siteOf(wrapped);
|
|
2978
|
+
const result = await Reflect.apply(call, procTarget, args);
|
|
2979
|
+
census.record(op, site, result);
|
|
2980
|
+
return result;
|
|
2981
|
+
};
|
|
2982
|
+
return wrapped;
|
|
2983
|
+
} });
|
|
2984
|
+
} });
|
|
2985
|
+
}
|
|
2986
|
+
/** Log the census as one line, including when it collected nothing - silence
|
|
2987
|
+
* after an arm is indistinguishable from a census that never armed. */
|
|
2988
|
+
function logCensus(census, logger, topN) {
|
|
2989
|
+
const rows = census.rows();
|
|
2990
|
+
const elapsedMs = census.elapsedMs();
|
|
2991
|
+
const perMin = (calls) => elapsedMs === 0 ? 0 : Math.round(calls * 6e4 / elapsedMs);
|
|
2992
|
+
logger.info("store read census", { meta: {
|
|
2993
|
+
elapsedMs,
|
|
2994
|
+
sites: rows.length,
|
|
2995
|
+
calls: rows.reduce((acc, r) => acc + r.calls, 0),
|
|
2996
|
+
top: rows.slice(0, topN).map((r) => ({
|
|
2997
|
+
op: r.op,
|
|
2998
|
+
site: r.site,
|
|
2999
|
+
calls: r.calls,
|
|
3000
|
+
perMin: perMin(r.calls),
|
|
3001
|
+
rows: r.rows
|
|
3002
|
+
}))
|
|
3003
|
+
} });
|
|
3004
|
+
return rows;
|
|
3005
|
+
}
|
|
3006
|
+
/**
|
|
3007
|
+
* `armMs: 0` (or omitted) READS the window that already ran — that is how a
|
|
3008
|
+
* measurement is collected after its window closed, and re-arming to read
|
|
3009
|
+
* would throw away exactly the numbers being asked for (`StoreReadCensus.arm`
|
|
3010
|
+
* clears on every call).
|
|
3011
|
+
*/
|
|
3012
|
+
var StoreCensusInputSchema = require_dist.object({
|
|
3013
|
+
armMs: require_dist.number().int().min(0).max(CENSUS_MAX_WINDOW_MS).optional(),
|
|
3014
|
+
limit: require_dist.number().int().positive().max(200).optional()
|
|
3015
|
+
});
|
|
3016
|
+
var StoreCensusRowSchema = require_dist.object({
|
|
3017
|
+
op: require_dist.string(),
|
|
3018
|
+
site: require_dist.string(),
|
|
3019
|
+
calls: require_dist.number(),
|
|
3020
|
+
/** Calls extrapolated to a minute, directly comparable with the live-hub
|
|
3021
|
+
* profiler's own `get recorder:recording-configs` rate. */
|
|
3022
|
+
perMin: require_dist.number(),
|
|
3023
|
+
rows: require_dist.number()
|
|
3024
|
+
});
|
|
3025
|
+
var StoreCensusOutputSchema = require_dist.object({
|
|
3026
|
+
armed: require_dist.boolean(),
|
|
3027
|
+
elapsedMs: require_dist.number(),
|
|
3028
|
+
sites: require_dist.number(),
|
|
3029
|
+
calls: require_dist.number(),
|
|
3030
|
+
rows: require_dist.array(StoreCensusRowSchema).readonly()
|
|
3031
|
+
});
|
|
3032
|
+
var recorderDebugActions = require_dist.defineCustomActions({ "debug.storeCensus": require_dist.customAction(StoreCensusInputSchema, StoreCensusOutputSchema, { auth: "admin" }) });
|
|
3033
|
+
/**
|
|
3034
|
+
* Arm-or-read, pulled out of the addon class so it is testable without
|
|
3035
|
+
* booting `RecorderV2Addon` — feed it a bare `StoreReadCensus` and a logger.
|
|
3036
|
+
*
|
|
3037
|
+
* The result is also logged (via {@link logCensus}), because a measurement
|
|
3038
|
+
* that exists only in an HTTP response is a measurement that is gone when the
|
|
3039
|
+
* next session asks what happened.
|
|
3040
|
+
*/
|
|
3041
|
+
function handleStoreCensusAction(census, logger, input) {
|
|
3042
|
+
const limit = input.limit ?? 200;
|
|
3043
|
+
if (input.armMs !== void 0 && input.armMs > 0) {
|
|
3044
|
+
census.arm(input.armMs);
|
|
3045
|
+
return {
|
|
3046
|
+
armed: true,
|
|
3047
|
+
elapsedMs: 0,
|
|
3048
|
+
sites: 0,
|
|
3049
|
+
calls: 0,
|
|
3050
|
+
rows: []
|
|
3051
|
+
};
|
|
3052
|
+
}
|
|
3053
|
+
const rows = logCensus(census, logger, limit);
|
|
3054
|
+
const elapsedMs = census.elapsedMs();
|
|
3055
|
+
return {
|
|
3056
|
+
armed: census.armed,
|
|
3057
|
+
elapsedMs,
|
|
3058
|
+
sites: rows.length,
|
|
3059
|
+
calls: rows.reduce((acc, r) => acc + r.calls, 0),
|
|
3060
|
+
rows: rows.slice(0, limit).map((r) => ({
|
|
3061
|
+
op: r.op,
|
|
3062
|
+
site: r.site,
|
|
3063
|
+
calls: r.calls,
|
|
3064
|
+
perMin: elapsedMs === 0 ? 0 : Math.round(r.calls * 6e4 / elapsedMs),
|
|
3065
|
+
rows: r.rows
|
|
3066
|
+
}))
|
|
3067
|
+
};
|
|
3068
|
+
}
|
|
3069
|
+
//#endregion
|
|
2740
3070
|
//#region src/recorder/addon/directory-handler.ts
|
|
2741
3071
|
var HOUR_MS$3 = 36e5;
|
|
2742
3072
|
/** A window wider than this is refused: it is a client bug, and answering it
|
|
@@ -4094,6 +4424,18 @@ function buildMasterPlaylist(variants) {
|
|
|
4094
4424
|
* against a real captured segment with every read recorded — "never the whole
|
|
4095
4425
|
* file" is asserted in bytes.
|
|
4096
4426
|
*/
|
|
4427
|
+
/**
|
|
4428
|
+
* Byte budget for the `readGopBytes` fallback read, used with
|
|
4429
|
+
* {@link GopRangeArgs.maxBytes}.
|
|
4430
|
+
*
|
|
4431
|
+
* Same conceptual ceiling as `SCRUB_WHOLE_SEGMENT_MAX_BYTES` in
|
|
4432
|
+
* `stream-broker/recorded/recorded-feeder.ts` (D163, 1.5 MB) — declared here
|
|
4433
|
+
* independently rather than imported, because that module is the CLIENT
|
|
4434
|
+
* feeder (a different addon-internal concern; importing its whole dependency
|
|
4435
|
+
* graph for one number is the wrong direction). If D163's number moves,
|
|
4436
|
+
* this one moves with it.
|
|
4437
|
+
*/
|
|
4438
|
+
var SCRUB_GOP_BUDGET_MAX_BYTES = 15e5;
|
|
4097
4439
|
/** Ticks → ms on the video timescale, rounded like the demux anchors samples. */
|
|
4098
4440
|
function ticksToMs(ticks, timescale) {
|
|
4099
4441
|
return Math.round(ticks / timescale * 1e3);
|
|
@@ -4123,11 +4465,36 @@ async function readGopRange(file, args) {
|
|
|
4123
4465
|
if (init === null) return null;
|
|
4124
4466
|
const videoIndex = index.trackId === init.videoTrackId ? index : parsed === null ? null : reparseTail(parsed.tail, file.size, init.videoTrackId);
|
|
4125
4467
|
if (videoIndex === null) return null;
|
|
4126
|
-
const
|
|
4468
|
+
const offsetTicks = Math.max(0, args.epochMs - args.segmentStartMs) / 1e3 * init.videoTimescale;
|
|
4469
|
+
let start;
|
|
4470
|
+
let end;
|
|
4471
|
+
let firstSampleIndex;
|
|
4472
|
+
let lastSampleIndex;
|
|
4473
|
+
let reachesSegmentEnd;
|
|
4474
|
+
let oversizeFragment;
|
|
4475
|
+
if (args.maxBytes === void 0) {
|
|
4476
|
+
const single = fragmentRangeFor(videoIndex, offsetTicks);
|
|
4477
|
+
start = single.start;
|
|
4478
|
+
end = single.end;
|
|
4479
|
+
firstSampleIndex = single.sampleIndex;
|
|
4480
|
+
lastSampleIndex = single.sampleIndex;
|
|
4481
|
+
reachesSegmentEnd = videoIndex.samples[single.sampleIndex + 1] === void 0;
|
|
4482
|
+
oversizeFragment = false;
|
|
4483
|
+
} else {
|
|
4484
|
+
const plan = fragmentsWithinBudget(videoIndex, offsetTicks, args.maxBytes);
|
|
4485
|
+
const firstFragment = plan.fragments[0];
|
|
4486
|
+
const lastFragment = plan.fragments[plan.fragments.length - 1];
|
|
4487
|
+
start = plan.start;
|
|
4488
|
+
end = plan.end;
|
|
4489
|
+
firstSampleIndex = firstFragment.sampleIndex;
|
|
4490
|
+
lastSampleIndex = lastFragment.sampleIndex;
|
|
4491
|
+
reachesSegmentEnd = plan.reachesSegmentEnd;
|
|
4492
|
+
oversizeFragment = plan.oversizeFragment;
|
|
4493
|
+
}
|
|
4127
4494
|
if (start <= 0 || end <= start || end > file.size) return null;
|
|
4128
4495
|
const fragment = await read(start, end - start);
|
|
4129
|
-
const startTicks = videoIndex.samples[
|
|
4130
|
-
const nextTicks = videoIndex.samples[
|
|
4496
|
+
const startTicks = videoIndex.samples[firstSampleIndex]?.timeTicks ?? 0;
|
|
4497
|
+
const nextTicks = videoIndex.samples[lastSampleIndex + 1]?.timeTicks;
|
|
4131
4498
|
const gopStartOffsetMs = ticksToMs(startTicks, init.videoTimescale);
|
|
4132
4499
|
const gopDurMs = nextTicks !== void 0 ? ticksToMs(nextTicks - startTicks, init.videoTimescale) : Math.max(1, args.segmentDurMs - gopStartOffsetMs);
|
|
4133
4500
|
const data = new Uint8Array(head.length + fragment.length);
|
|
@@ -4137,7 +4504,9 @@ async function readGopRange(file, args) {
|
|
|
4137
4504
|
data,
|
|
4138
4505
|
gopStartMs: args.segmentStartMs + gopStartOffsetMs,
|
|
4139
4506
|
gopDurMs,
|
|
4140
|
-
bytesRead
|
|
4507
|
+
bytesRead,
|
|
4508
|
+
reachesSegmentEnd,
|
|
4509
|
+
oversizeFragment
|
|
4141
4510
|
};
|
|
4142
4511
|
}
|
|
4143
4512
|
/**
|
|
@@ -4670,6 +5039,30 @@ function resolveSegmentFile(deps, deviceId, profile, startMs) {
|
|
|
4670
5039
|
abs: node_path.default.join(loc.root, seg.path)
|
|
4671
5040
|
};
|
|
4672
5041
|
}
|
|
5042
|
+
/**
|
|
5043
|
+
* The log line's `meta`, or null when `readGopBytes` served nothing worth a
|
|
5044
|
+
* line. Split out from the handler so the decision is testable against a
|
|
5045
|
+
* plain object — no real fMP4 bytes needed to prove a >1.5 MB fragment
|
|
5046
|
+
* triggers it.
|
|
5047
|
+
*
|
|
5048
|
+
* The single-fragment budget overshoot is the notable case, not the routine
|
|
5049
|
+
* one: a `high`/`mid` scrub goes through this path on every tick by design
|
|
5050
|
+
* (client-side `profile-degraded`, already logged once per drag on the
|
|
5051
|
+
* feeder), and logging every such call here would spam nothing new. A
|
|
5052
|
+
* fragment that alone exceeds the budget is different — it is never returned
|
|
5053
|
+
* empty (never-empty is {@link fragmentsWithinBudget}'s contract), but an
|
|
5054
|
+
* operator debugging a slow drag needs to know it happened.
|
|
5055
|
+
*/
|
|
5056
|
+
function oversizeFragmentLogMeta(gop, args) {
|
|
5057
|
+
if (!gop.oversizeFragment) return null;
|
|
5058
|
+
return {
|
|
5059
|
+
profile: args.profile,
|
|
5060
|
+
startMs: args.startMs,
|
|
5061
|
+
epochMs: args.epochMs,
|
|
5062
|
+
fragmentBytes: gop.data.length,
|
|
5063
|
+
maxBytes: SCRUB_GOP_BUDGET_MAX_BYTES
|
|
5064
|
+
};
|
|
5065
|
+
}
|
|
4673
5066
|
/** Default ranged opener: ONE `fs.open` per GOP answer serving all its reads. */
|
|
4674
5067
|
async function openRangeFromDisk(absPath, size) {
|
|
4675
5068
|
const handle = await node_fs.promises.open(absPath, "r");
|
|
@@ -4889,13 +5282,25 @@ function buildRecordingProvider(deps) {
|
|
|
4889
5282
|
segmentStartMs: seg.startMs,
|
|
4890
5283
|
segmentDurMs: seg.durMs,
|
|
4891
5284
|
epochMs,
|
|
4892
|
-
seededIndex: deps.mfraFor?.(deviceId, profile, startMs)
|
|
5285
|
+
seededIndex: deps.mfraFor?.(deviceId, profile, startMs),
|
|
5286
|
+
maxBytes: SCRUB_GOP_BUDGET_MAX_BYTES
|
|
4893
5287
|
});
|
|
4894
|
-
if (gop !== null)
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
5288
|
+
if (gop !== null) {
|
|
5289
|
+
const oversizeMeta = oversizeFragmentLogMeta(gop, {
|
|
5290
|
+
profile,
|
|
5291
|
+
startMs,
|
|
5292
|
+
epochMs
|
|
5293
|
+
});
|
|
5294
|
+
if (oversizeMeta !== null) deps.logger.warn("recording: readGopBytes served one fragment that alone exceeds the scrub budget", {
|
|
5295
|
+
tags: { deviceId },
|
|
5296
|
+
meta: oversizeMeta
|
|
5297
|
+
});
|
|
5298
|
+
return {
|
|
5299
|
+
data: gop.data,
|
|
5300
|
+
gopStartMs: gop.gopStartMs,
|
|
5301
|
+
gopDurMs: gop.gopDurMs
|
|
5302
|
+
};
|
|
5303
|
+
}
|
|
4899
5304
|
} finally {
|
|
4900
5305
|
await file.close();
|
|
4901
5306
|
}
|
|
@@ -11176,6 +11581,23 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
|
|
|
11176
11581
|
retentionSweeper = null;
|
|
11177
11582
|
/** Reversible migration pause lease; never reflected in RecordingConfig. */
|
|
11178
11583
|
storageMigrationLeaseId = null;
|
|
11584
|
+
/**
|
|
11585
|
+
* Call-site attribution for `recorder:recording-configs` / `-exports` /
|
|
11586
|
+
* `-ops-log` row reads (`store/read-census.ts`).
|
|
11587
|
+
*
|
|
11588
|
+
* Disarmed by default; armed for a bounded window through
|
|
11589
|
+
* `debug.storeCensus`. It exists because the live-hub profiler names a
|
|
11590
|
+
* TABLE — `get recorder:recording-configs` ran 581-892 calls/min on
|
|
11591
|
+
* 2026-08-26, finding a row only ~56% of the time — and cannot say WHICH of
|
|
11592
|
+
* `loadDeviceConfig`'s several cap-handler call sites (`getStatus`,
|
|
11593
|
+
* `getDeviceConfig`, `rescanStorage`, `pruneFootage`) is issuing them.
|
|
11594
|
+
* Picking the reader that LOOKS like a loop has cost this repo half a day
|
|
11595
|
+
* twice, wrong both times — this measures instead.
|
|
11596
|
+
*/
|
|
11597
|
+
readCensus = new StoreReadCensus();
|
|
11598
|
+
/** Memoised so every `configStore()` / `exportsStore()` / `opsLogStore()`
|
|
11599
|
+
* call shares ONE census instead of each wrapping the raw client afresh. */
|
|
11600
|
+
censusedSettingsStoreClient = null;
|
|
11179
11601
|
constructor() {
|
|
11180
11602
|
super({ ...DEFAULT_CONFIG });
|
|
11181
11603
|
}
|
|
@@ -11580,20 +12002,24 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
|
|
|
11580
12002
|
}
|
|
11581
12003
|
});
|
|
11582
12004
|
warnLostBands(this.configStore(), this.ctx.logger);
|
|
11583
|
-
return {
|
|
11584
|
-
|
|
11585
|
-
|
|
11586
|
-
|
|
11587
|
-
|
|
11588
|
-
|
|
11589
|
-
|
|
11590
|
-
|
|
11591
|
-
|
|
11592
|
-
|
|
11593
|
-
|
|
11594
|
-
|
|
11595
|
-
|
|
11596
|
-
|
|
12005
|
+
return {
|
|
12006
|
+
providers: [
|
|
12007
|
+
{
|
|
12008
|
+
capability: require_dist.recordingCapability,
|
|
12009
|
+
provider: recordingProvider
|
|
12010
|
+
},
|
|
12011
|
+
{
|
|
12012
|
+
capability: require_dist.storageEvictableCapability,
|
|
12013
|
+
provider: evictableProvider
|
|
12014
|
+
},
|
|
12015
|
+
{
|
|
12016
|
+
capability: require_dist.recordingExportCapability,
|
|
12017
|
+
provider: exportProvider
|
|
12018
|
+
}
|
|
12019
|
+
],
|
|
12020
|
+
customActions: recorderDebugActions,
|
|
12021
|
+
actionHandlers: { "debug.storeCensus": async (input) => handleStoreCensusAction(this.readCensus, this.ctx.logger.child("Census"), input) }
|
|
12022
|
+
};
|
|
11597
12023
|
}
|
|
11598
12024
|
async onShutdown() {
|
|
11599
12025
|
this.eventUnsub?.();
|
|
@@ -12242,17 +12668,29 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
|
|
|
12242
12668
|
this.ctx.logger.warn("recorder: retiring the flattened settings keys failed", { meta: { error: require_dist.errMsg(err) } });
|
|
12243
12669
|
}
|
|
12244
12670
|
}
|
|
12671
|
+
/**
|
|
12672
|
+
* `ctx.api.settingsStore`, wrapped ONCE through {@link censusStoreClient}.
|
|
12673
|
+
*
|
|
12674
|
+
* LAZY (see `calendar` above): `ctx` is not assigned until `initialize()`
|
|
12675
|
+
* runs, so this cannot be a field initialiser. The wrap is permanent and
|
|
12676
|
+
* cheap when disarmed (one `get` trap and a comparison) — what is armed and
|
|
12677
|
+
* disarmed is `this.readCensus`, through `debug.storeCensus`, not this seam.
|
|
12678
|
+
*/
|
|
12679
|
+
censusedSettingsStore() {
|
|
12680
|
+
this.censusedSettingsStoreClient ??= censusStoreClient(this.ctx.api.settingsStore, this.readCensus);
|
|
12681
|
+
return this.censusedSettingsStoreClient;
|
|
12682
|
+
}
|
|
12245
12683
|
configStore() {
|
|
12246
|
-
return recordingConfigsState(this.
|
|
12684
|
+
return recordingConfigsState(this.censusedSettingsStore(), this.ctx.logger);
|
|
12247
12685
|
}
|
|
12248
12686
|
/** Durable handle over the exportId→ExportRecord blob (same `BaseAddon.state()`
|
|
12249
12687
|
* wrapper as the config store). */
|
|
12250
12688
|
exportsStore() {
|
|
12251
|
-
return recordingExportsState(this.
|
|
12689
|
+
return recordingExportsState(this.censusedSettingsStore(), this.ctx.logger);
|
|
12252
12690
|
}
|
|
12253
12691
|
/** Durable handle over the bounded recordings ops-log ring. */
|
|
12254
12692
|
opsLogStore() {
|
|
12255
|
-
return recordingOpsLogState(this.
|
|
12693
|
+
return recordingOpsLogState(this.censusedSettingsStore(), this.ctx.logger);
|
|
12256
12694
|
}
|
|
12257
12695
|
/** Durable handle over the recorder-owned placement blob (assignments + volume ids). */
|
|
12258
12696
|
placementStore() {
|