@camstack/addon-pipeline-orchestrator 1.2.289 → 1.2.290

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/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { randomUUID } from "node:crypto";
1
+ import { createHash, randomUUID } from "node:crypto";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
@@ -8048,7 +8048,7 @@ import { errMsg } from '@camstack/types'
8048
8048
  * Extract a human-readable message from an unknown error value.
8049
8049
  * Replaces the ubiquitous `errMsg(err)` pattern.
8050
8050
  */
8051
- function errMsg$1(err) {
8051
+ function errMsg$2(err) {
8052
8052
  if (err instanceof Error) return err.message;
8053
8053
  if (typeof err === "string") return err;
8054
8054
  return String(err);
@@ -11551,7 +11551,53 @@ var ModelFormatEntrySchema = object({
11551
11551
  /** Multi-file payload (directory members or sibling files). */
11552
11552
  files: array(string()).readonly().optional(),
11553
11553
  /** Runtime(s) that can use this format. If omitted, inferred from ModelFormat key */
11554
- runtimes: array(_enum(["python"])).readonly().optional()
11554
+ runtimes: array(_enum(["python"])).readonly().optional(),
11555
+ /**
11556
+ * Content revision of the artefact, for one hosted OFF the CamStack mirror.
11557
+ * A format on the mirror leaves it unset: its revision (the sha256 of its
11558
+ * weight file) comes from the downloader's generated table, kept in step
11559
+ * with the mirror by `scripts/check-hosted-model-revisions.ts` (D744). A
11560
+ * node records the revision it downloaded next to the file
11561
+ * (`<file>.revision`); a copy with no record or a different one is stale
11562
+ * unless its weight file hashes to the revision, and a stale copy is
11563
+ * downloaded again, the old one serving until the new one is complete. No
11564
+ * revision at all ⇒ a file on disk is current, whatever it holds (D743).
11565
+ */
11566
+ revision: string().min(1).optional()
11567
+ });
11568
+ /**
11569
+ * What a node holds for one (model, format) (D746). `current` is a complete
11570
+ * copy at its catalog revision — exactly what `downloaded: true` means.
11571
+ * `stale` is bytes on disk that are not (yet) proven current under D743/D744:
11572
+ * no or a different revision record, or a missing sibling. `missing` is no
11573
+ * copy. `downloaded` alone folds `stale` into "not downloaded", which hid
11574
+ * reclaimable bytes and a copy about to be replaced.
11575
+ */
11576
+ var ModelOnDiskStateSchema = _enum([
11577
+ "current",
11578
+ "stale",
11579
+ "missing"
11580
+ ]);
11581
+ /**
11582
+ * The numeric precision of one format's BUILD (D746) — reported only when the
11583
+ * build states it (file-name token, an INT8 variant, an EdgeTPU compile, a
11584
+ * CamStack Core ML export). Absent means "not stated", never "fp32".
11585
+ */
11586
+ var ModelFormatPrecisionSchema = _enum([
11587
+ "fp32",
11588
+ "fp16",
11589
+ "int8"
11590
+ ]);
11591
+ /**
11592
+ * One format of one model as a node's `pipelineExecutor.getSchema` reports it.
11593
+ * `onDisk` / `precision` are optional for version skew: a runner that predates
11594
+ * them omits them, and a reader falls back to `downloaded`.
11595
+ */
11596
+ var PipelineModelFormatStatusSchema = object({
11597
+ downloaded: boolean(),
11598
+ sizeMB: number(),
11599
+ onDisk: ModelOnDiskStateSchema.optional(),
11600
+ precision: ModelFormatPrecisionSchema.optional()
11555
11601
  });
11556
11602
  /**
11557
11603
  * Extra file that must be downloaded alongside the model (e.g., labels JSON, dict.txt).
@@ -24297,7 +24343,7 @@ var FrameRefSchema = object({
24297
24343
  timestamp: number(),
24298
24344
  capturedAt: number().optional()
24299
24345
  });
24300
- var ModelFormatSchema$1 = _enum([
24346
+ var ModelFormatSchema$2 = _enum([
24301
24347
  "onnx",
24302
24348
  "coreml",
24303
24349
  "openvino",
@@ -24315,7 +24361,7 @@ var PipelineSlotSchema = _enum([
24315
24361
  var PipelineEngineChoiceSchema = object({
24316
24362
  runtime: literal("python"),
24317
24363
  backend: string(),
24318
- format: ModelFormatSchema$1,
24364
+ format: ModelFormatSchema$2,
24319
24365
  device: string().optional()
24320
24366
  });
24321
24367
  /**
@@ -24372,10 +24418,7 @@ var PipelineTemplateSchema$1 = object({
24372
24418
  var PipelineModelOptionSchema = object({
24373
24419
  id: string(),
24374
24420
  name: string(),
24375
- formats: record(string(), object({
24376
- downloaded: boolean(),
24377
- sizeMB: number()
24378
- })),
24421
+ formats: record(string(), PipelineModelFormatStatusSchema),
24379
24422
  group: ModelVariantGroupSchema.optional(),
24380
24423
  legacy: boolean().optional(),
24381
24424
  provider: ModelProviderIdSchema.optional(),
@@ -24590,6 +24633,32 @@ var pipelineExecutorCapability = {
24590
24633
  auth: "admin"
24591
24634
  }),
24592
24635
  /**
24636
+ * Best settings (D745): the MODEL-BOUND thresholds (`bindsToModel` keys of
24637
+ * the object-detection root, and their binding keys) stored in THIS node's
24638
+ * per-camera detector settings — and, unless `dryRun`, remove them so the
24639
+ * running model's own defaults apply. Every other key of a camera's store
24640
+ * is kept. `nodeId` is the ROUTING key. `deviceIds` absent ⇒ every camera
24641
+ * with a store. A camera whose store could not be read is `refused`, never
24642
+ * reported as clean.
24643
+ */
24644
+ clearModelBoundDeviceOverrides: method(object({
24645
+ nodeId: string(),
24646
+ deviceIds: array(number()).readonly().optional(),
24647
+ dryRun: boolean()
24648
+ }), object({
24649
+ entries: array(object({
24650
+ deviceId: number(),
24651
+ keys: array(string()).readonly()
24652
+ })).readonly(),
24653
+ refused: array(object({
24654
+ deviceId: number(),
24655
+ reason: string()
24656
+ })).readonly()
24657
+ }), {
24658
+ kind: "mutation",
24659
+ auth: "admin"
24660
+ }),
24661
+ /**
24593
24662
  * Which of THIS node's inference devices the executor currently refuses,
24594
24663
  * and why. `nodeId` is the ROUTING key (stripped by the generated router).
24595
24664
  *
@@ -24684,12 +24753,12 @@ var pipelineExecutorCapability = {
24684
24753
  downloadModel: method(object({
24685
24754
  addonId: string(),
24686
24755
  modelId: string(),
24687
- format: ModelFormatSchema$1
24756
+ format: ModelFormatSchema$2
24688
24757
  }), DownloadModelResultSchema, { kind: "mutation" }),
24689
24758
  deleteModel: method(object({
24690
24759
  addonId: string(),
24691
24760
  modelId: string(),
24692
- format: ModelFormatSchema$1
24761
+ format: ModelFormatSchema$2
24693
24762
  }), object({ success: literal(true) }), { kind: "mutation" }),
24694
24763
  /**
24695
24764
  * Stateless single-frame execution. Callers (runner, benchmark) pass
@@ -24935,6 +25004,204 @@ var pipelineExecutorCapability = {
24935
25004
  getDetectionConfigSchema: method(_void(), ConfigUISchemaNullableBridge)
24936
25005
  }
24937
25006
  };
25007
+ /**
25008
+ * The wire shapes of the Best settings plan and apply (D745), used by the
25009
+ * `pipeline-orchestrator` cap. The recommender's own types
25010
+ * (`recommend.ts`, `demand.ts`) are the TS face of the same data; these
25011
+ * schemas are what crosses the cap boundary.
25012
+ */
25013
+ var ModelFormatSchema$1 = _enum(MODEL_FORMATS);
25014
+ var HardwareClassIdSchema = _enum([
25015
+ "intel-core-ultra",
25016
+ "intel-n-series",
25017
+ "apple-m-base",
25018
+ "apple-m-pro",
25019
+ "unknown"
25020
+ ]);
25021
+ var AcceleratorKindSchema = _enum([
25022
+ "intel-npu",
25023
+ "intel-igpu",
25024
+ "x86-cpu",
25025
+ "arm-cpu",
25026
+ "apple-ane",
25027
+ "coral"
25028
+ ]);
25029
+ var RecommendationReasonSchema = discriminatedUnion("kind", [
25030
+ object({
25031
+ kind: literal("fits"),
25032
+ utilisation: number()
25033
+ }),
25034
+ object({
25035
+ kind: literal("tie-faster"),
25036
+ over: string(),
25037
+ recallDelta: number()
25038
+ }),
25039
+ object({
25040
+ kind: literal("stepped-down"),
25041
+ from: string(),
25042
+ to: string(),
25043
+ limitedBy: string()
25044
+ }),
25045
+ object({
25046
+ kind: literal("no-fit-lightest"),
25047
+ utilisation: number()
25048
+ }),
25049
+ object({
25050
+ kind: literal("derived-latency"),
25051
+ modelId: string()
25052
+ }),
25053
+ object({
25054
+ kind: literal("estimated-load"),
25055
+ observedForMs: number().nullable()
25056
+ }),
25057
+ object({ kind: literal("cpu-device") }),
25058
+ object({ kind: literal("coral-untouched") }),
25059
+ object({ kind: literal("device-disabled") }),
25060
+ object({ kind: literal("device-unavailable") }),
25061
+ object({ kind: literal("node-offline") }),
25062
+ object({ kind: literal("no-hardware-facts") }),
25063
+ object({
25064
+ kind: literal("no-measured-data"),
25065
+ hardwareClass: HardwareClassIdSchema
25066
+ }),
25067
+ object({
25068
+ kind: literal("no-family-build"),
25069
+ format: ModelFormatSchema$1
25070
+ })
25071
+ ]);
25072
+ var DeviceDemandSchema = object({
25073
+ nodeId: string(),
25074
+ deviceKey: string(),
25075
+ demandFps: number(),
25076
+ maxCameraFps: number(),
25077
+ alwaysOnCameras: number(),
25078
+ onMotionSessions: number(),
25079
+ onMotionSource: _enum([
25080
+ "observed-24h",
25081
+ "estimate",
25082
+ "none"
25083
+ ]),
25084
+ observedForMs: number().nullable()
25085
+ });
25086
+ var DeviceModelChoiceSchema = object({
25087
+ modelId: string(),
25088
+ p50Ms: number(),
25089
+ fps: number(),
25090
+ measured: boolean(),
25091
+ utilisation: number(),
25092
+ personRecall: number()
25093
+ });
25094
+ var DeviceRecommendationSchema = object({
25095
+ deviceKey: string(),
25096
+ accelerator: AcceleratorKindSchema.nullable(),
25097
+ enabled: boolean(),
25098
+ available: boolean(),
25099
+ demand: DeviceDemandSchema.nullable(),
25100
+ chosen: DeviceModelChoiceSchema.nullable(),
25101
+ reasons: array(RecommendationReasonSchema).readonly()
25102
+ });
25103
+ var NodeRecommendationSchema = object({
25104
+ nodeId: string(),
25105
+ reachable: boolean(),
25106
+ hardwareClass: HardwareClassIdSchema.nullable(),
25107
+ cpuModel: string().nullable(),
25108
+ skipped: boolean(),
25109
+ acceleratorModelId: string().nullable(),
25110
+ devices: array(DeviceRecommendationSchema).readonly(),
25111
+ reasons: array(RecommendationReasonSchema).readonly()
25112
+ });
25113
+ var ModelPinChangeSchema = object({
25114
+ stepId: string(),
25115
+ before: string().nullable(),
25116
+ after: string().nullable()
25117
+ });
25118
+ var NodeDeviceChangeSchema = object({
25119
+ nodeId: string(),
25120
+ deviceKey: string(),
25121
+ madeExplicit: boolean(),
25122
+ modelPins: array(ModelPinChangeSchema).readonly(),
25123
+ thresholds: array(object({
25124
+ stepId: string(),
25125
+ keys: array(string()).readonly()
25126
+ })).readonly()
25127
+ });
25128
+ var CameraOverrideClearSchema = object({
25129
+ deviceId: number(),
25130
+ source: _enum(["step-override", "wholesale"]),
25131
+ nodeId: string(),
25132
+ deviceKey: string().nullable(),
25133
+ stepId: string(),
25134
+ modelIdBefore: string().nullable(),
25135
+ keys: array(string()).readonly()
25136
+ });
25137
+ var DetectorStoreClearSchema = object({
25138
+ deviceId: number(),
25139
+ keys: array(string()).readonly()
25140
+ });
25141
+ var BestSettingsWarningSchema = discriminatedUnion("kind", [
25142
+ object({
25143
+ kind: literal("detector-store-cluster-wide"),
25144
+ skippedNodes: array(string()).readonly()
25145
+ }),
25146
+ object({
25147
+ kind: literal("detector-store-unreadable"),
25148
+ error: string()
25149
+ }),
25150
+ object({
25151
+ kind: literal("coral-enabled"),
25152
+ nodeId: string(),
25153
+ deviceKey: string()
25154
+ })
25155
+ ]);
25156
+ var BestSettingsOptionsSchema = object({
25157
+ /** Share of each accelerator's throughput kept free (0.3–0.5). */
25158
+ headroom: number().min(.3).max(.5).default(.4) });
25159
+ var BestSettingsPlanSchema = object({
25160
+ /** Hash of everything the apply will write — the apply refuses a different one. */
25161
+ planHash: string(),
25162
+ family: literal("yolov9"),
25163
+ headroom: number(),
25164
+ generatedAt: number(),
25165
+ nodes: array(NodeRecommendationSchema).readonly(),
25166
+ nodeChanges: array(NodeDeviceChangeSchema).readonly(),
25167
+ cameraClears: array(CameraOverrideClearSchema).readonly(),
25168
+ detectorStoreClears: array(DetectorStoreClearSchema).readonly(),
25169
+ warnings: array(BestSettingsWarningSchema).readonly()
25170
+ });
25171
+ var BestSettingsApplyInputSchema = object({
25172
+ planHash: string(),
25173
+ headroom: number().min(.3).max(.5).default(.4)
25174
+ });
25175
+ var BestSettingsPhaseResultSchema = object({
25176
+ phase: _enum([
25177
+ "download",
25178
+ "camera-overrides",
25179
+ "detector-thresholds",
25180
+ "node-models",
25181
+ "redispatch"
25182
+ ]),
25183
+ ok: boolean(),
25184
+ detail: string()
25185
+ });
25186
+ var BestSettingsApplyResultSchema = discriminatedUnion("kind", [
25187
+ object({
25188
+ kind: literal("applied"),
25189
+ planHash: string(),
25190
+ phases: array(BestSettingsPhaseResultSchema).readonly()
25191
+ }),
25192
+ object({
25193
+ kind: literal("aborted"),
25194
+ phases: array(BestSettingsPhaseResultSchema).readonly()
25195
+ }),
25196
+ object({
25197
+ kind: literal("stale"),
25198
+ plan: BestSettingsPlanSchema
25199
+ }),
25200
+ object({
25201
+ kind: literal("refused"),
25202
+ reason: _enum(["apply-in-progress"])
25203
+ })
25204
+ ]);
24938
25205
  object({
24939
25206
  activeCameras: number(),
24940
25207
  throttledCameras: number(),
@@ -27121,6 +27388,27 @@ var pipelineOrchestratorCapability = {
27121
27388
  kind: "mutation",
27122
27389
  auth: "admin"
27123
27390
  }),
27391
+ /**
27392
+ * Best settings (D745) — DRY RUN. Recommends ONE detector family (YOLOv9)
27393
+ * for the whole cluster from each node's static hardware class, the
27394
+ * measured latency table and the detection demand, and lists every write
27395
+ * the apply would make: the node model pins set, the enrichment pins and
27396
+ * model-bound thresholds removed (node level and per camera), and the
27397
+ * per-camera detector thresholds cleared. Writes nothing. Step
27398
+ * enablement, device enablement, pins, recording, notifications, zones and
27399
+ * rules are never part of it. `planHash` names exactly this diff.
27400
+ */
27401
+ planBestSettings: method(BestSettingsOptionsSchema, BestSettingsPlanSchema, { auth: "admin" }),
27402
+ /**
27403
+ * Best settings (D745) — APPLY the plan the operator confirmed. Recomputes
27404
+ * the plan and refuses (`stale`, with the fresh plan) when its hash is not
27405
+ * `planHash`, so what is written is exactly the confirmed diff. Offline
27406
+ * nodes are skipped; there is no undo.
27407
+ */
27408
+ applyBestSettings: method(BestSettingsApplyInputSchema, BestSettingsApplyResultSchema, {
27409
+ kind: "mutation",
27410
+ auth: "admin"
27411
+ }),
27124
27412
  /** Read one camera's settings. Null when never touched (inherits agent defaults fully). */
27125
27413
  getCameraSettings: method(object({ deviceId: number() }), CameraPipelineSettingsSchema.nullable()),
27126
27414
  /** Set or clear the 3-state toggle for one (camera, addonId). Pass `enabled: null` to clear and revert to agent default. */
@@ -42922,6 +43210,12 @@ Object.freeze({
42922
43210
  addonId: null,
42923
43211
  access: "delete"
42924
43212
  },
43213
+ "pipelineExecutor.clearModelBoundDeviceOverrides": {
43214
+ capName: "pipeline-executor",
43215
+ capScope: "system",
43216
+ addonId: null,
43217
+ access: "delete"
43218
+ },
42925
43219
  "pipelineExecutor.deleteModel": {
42926
43220
  capName: "pipeline-executor",
42927
43221
  capScope: "system",
@@ -43138,6 +43432,12 @@ Object.freeze({
43138
43432
  addonId: null,
43139
43433
  access: "view"
43140
43434
  },
43435
+ "pipelineOrchestrator.applyBestSettings": {
43436
+ capName: "pipeline-orchestrator",
43437
+ capScope: "system",
43438
+ addonId: null,
43439
+ access: "create"
43440
+ },
43141
43441
  "pipelineOrchestrator.applyDeviceSettingsPatch": {
43142
43442
  capName: "pipeline-orchestrator",
43143
43443
  capScope: "system",
@@ -43312,6 +43612,12 @@ Object.freeze({
43312
43612
  addonId: null,
43313
43613
  access: "create"
43314
43614
  },
43615
+ "pipelineOrchestrator.planBestSettings": {
43616
+ capName: "pipeline-orchestrator",
43617
+ capScope: "system",
43618
+ addonId: null,
43619
+ access: "view"
43620
+ },
43315
43621
  "pipelineOrchestrator.rebalance": {
43316
43622
  capName: "pipeline-orchestrator",
43317
43623
  capScope: "system",
@@ -48762,6 +49068,11 @@ Object.freeze({
48762
49068
  form: "single",
48763
49069
  optional: false
48764
49070
  }],
49071
+ "pipelineExecutor.clearModelBoundDeviceOverrides": [{
49072
+ name: "deviceIds",
49073
+ form: "array",
49074
+ optional: true
49075
+ }],
48765
49076
  "pipelineExecutor.runPipeline": [{
48766
49077
  name: "deviceId",
48767
49078
  form: "single",
@@ -50656,6 +50967,830 @@ var REMOVED_MODEL_IDS = {
50656
50967
  function isRemovedModelId(stepId, modelId) {
50657
50968
  return REMOVED_MODEL_IDS[stepId]?.includes(modelId) ?? false;
50658
50969
  }
50970
+ /** Suffix of the sibling key that names the model a stored value was made for. */
50971
+ var MODEL_BINDING_SUFFIX = "ModelId";
50972
+ /** `minConfidencePerson` → `minConfidencePersonModelId`. */
50973
+ function modelBindingKey(key) {
50974
+ return `${key}${MODEL_BINDING_SUFFIX}`;
50975
+ }
50976
+ /**
50977
+ * The keys of every `bindsToModel` field of a step's config schema, groups
50978
+ * flattened (D738). One definition for the runner (which applies the binding)
50979
+ * and the orchestrator's Best settings (which clears those overrides, D745).
50980
+ */
50981
+ function collectModelBoundKeys(fields) {
50982
+ return fields.flatMap((field) => {
50983
+ if (field.type === "group") return collectModelBoundKeys(field.fields);
50984
+ return "key" in field && "bindsToModel" in field && field.bindsToModel === true ? [field.key] : [];
50985
+ });
50986
+ }
50987
+ var APPLE_CHIP = /^Apple M(\d+)(?:\s+(Pro|Max|Ultra))?\b/i;
50988
+ var INTEL_CORE_ULTRA = /Core\(TM\)\s+Ultra|Core Ultra/i;
50989
+ var INTEL_N_SERIES = /Intel\(R\)\s+N\d{2,3}\b|\bIntel\s+N\d{2,3}\b/i;
50990
+ function parseAppleChip(cpuModel) {
50991
+ const match = APPLE_CHIP.exec(cpuModel.trim());
50992
+ if (match === null) return null;
50993
+ const generation = Number(match[1]);
50994
+ const variantRaw = (match[2] ?? "base").toLowerCase();
50995
+ return {
50996
+ generation,
50997
+ variant: variantRaw === "pro" || variantRaw === "max" || variantRaw === "ultra" ? variantRaw : "base"
50998
+ };
50999
+ }
51000
+ /**
51001
+ * Classify a node. Apple base chips (M1–M4) map to the measured M2 column, the
51002
+ * Pro/Max/Ultra chips to the measured M3 Pro column — the M2 was measured at
51003
+ * 1.0–1.2× the M3 Pro on the Neural Engine, so the generation inside a variant
51004
+ * matters less than the column's own error.
51005
+ */
51006
+ function classifyInferenceHardware(hw) {
51007
+ const cpuModel = hw.cpuModel;
51008
+ const apple = parseAppleChip(cpuModel);
51009
+ if (apple !== null) return {
51010
+ hardwareClass: apple.variant === "base" ? "apple-m-base" : "apple-m-pro",
51011
+ cpuModel,
51012
+ apple
51013
+ };
51014
+ if (INTEL_CORE_ULTRA.test(cpuModel)) return {
51015
+ hardwareClass: "intel-core-ultra",
51016
+ cpuModel
51017
+ };
51018
+ if (INTEL_N_SERIES.test(cpuModel)) return {
51019
+ hardwareClass: "intel-n-series",
51020
+ cpuModel
51021
+ };
51022
+ return {
51023
+ hardwareClass: "unknown",
51024
+ cpuModel
51025
+ };
51026
+ }
51027
+ /**
51028
+ * The accelerator kind of one of a node's device keys (`openvino:npu`,
51029
+ * `openvino:gpu`, `coreml:all`, `edgetpu:usb`, `cpu`, `onnx:cpu`, …). `null`
51030
+ * for a key this table has no column for.
51031
+ */
51032
+ function acceleratorKindOf(deviceKey, hw) {
51033
+ const [backend = "", device = ""] = deviceKey.split(":");
51034
+ if (backend === "edgetpu") return "coral";
51035
+ if (backend === "coreml") return device === "cpu" ? "arm-cpu" : "apple-ane";
51036
+ if (backend === "openvino" && device === "npu") return "intel-npu";
51037
+ if (backend === "openvino" && device === "gpu") return "intel-igpu";
51038
+ if (backend === "cpu" || device === "cpu") return hw.arch === "arm64" ? "arm-cpu" : "x86-cpu";
51039
+ return null;
51040
+ }
51041
+ var DETECTOR_PERF = {
51042
+ "intel-core-ultra": {
51043
+ "intel-npu": {
51044
+ "yolov9t-320": {
51045
+ p50Ms: 2.9,
51046
+ fps: 383,
51047
+ measured: true
51048
+ },
51049
+ "yolov9t-320-int8": {
51050
+ p50Ms: 3.8,
51051
+ fps: 333,
51052
+ measured: true
51053
+ },
51054
+ "yolov9t-640": {
51055
+ p50Ms: 7.4,
51056
+ fps: 139,
51057
+ measured: true
51058
+ },
51059
+ "yolov9t-640-int8": {
51060
+ p50Ms: 7.5,
51061
+ fps: 144,
51062
+ measured: true
51063
+ },
51064
+ "yolov9s-320": {
51065
+ p50Ms: 4.4,
51066
+ fps: 241,
51067
+ measured: true
51068
+ },
51069
+ "yolov9s-320-int8": {
51070
+ p50Ms: 4.1,
51071
+ fps: 264,
51072
+ measured: true
51073
+ },
51074
+ "yolov9s-640": {
51075
+ p50Ms: 12.7,
51076
+ fps: 82,
51077
+ measured: true
51078
+ },
51079
+ "yolov9s-640-int8": {
51080
+ p50Ms: 10.7,
51081
+ fps: 99,
51082
+ measured: true
51083
+ },
51084
+ "yolov9m-320": {
51085
+ p50Ms: 8.4,
51086
+ fps: 123,
51087
+ measured: true
51088
+ },
51089
+ "yolov9m-320-int8": {
51090
+ p50Ms: 6.5,
51091
+ fps: 165,
51092
+ measured: true
51093
+ },
51094
+ "yolov9m-640": {
51095
+ p50Ms: 29.5,
51096
+ fps: 32,
51097
+ measured: true
51098
+ },
51099
+ "yolov9m-640-int8": {
51100
+ p50Ms: 19.5,
51101
+ fps: 52,
51102
+ measured: true
51103
+ }
51104
+ },
51105
+ "intel-igpu": {
51106
+ "yolov9t-320": {
51107
+ p50Ms: 2.6,
51108
+ fps: 405,
51109
+ measured: true
51110
+ },
51111
+ "yolov9t-320-int8": {
51112
+ p50Ms: 2.8,
51113
+ fps: 324,
51114
+ measured: true
51115
+ },
51116
+ "yolov9t-640": {
51117
+ p50Ms: 6.5,
51118
+ fps: 157,
51119
+ measured: true
51120
+ },
51121
+ "yolov9t-640-int8": {
51122
+ p50Ms: 5.6,
51123
+ fps: 185,
51124
+ measured: true
51125
+ },
51126
+ "yolov9s-320": {
51127
+ p50Ms: 4.6,
51128
+ fps: 200,
51129
+ measured: true
51130
+ },
51131
+ "yolov9s-320-int8": {
51132
+ p50Ms: 4.1,
51133
+ fps: 239,
51134
+ measured: true
51135
+ },
51136
+ "yolov9s-640": {
51137
+ p50Ms: 15.2,
51138
+ fps: 69,
51139
+ measured: true
51140
+ },
51141
+ "yolov9s-640-int8": {
51142
+ p50Ms: 10.5,
51143
+ fps: 99,
51144
+ measured: true
51145
+ },
51146
+ "yolov9m-320": {
51147
+ p50Ms: 11.4,
51148
+ fps: 88,
51149
+ measured: true
51150
+ },
51151
+ "yolov9m-320-int8": {
51152
+ p50Ms: 7,
51153
+ fps: 127,
51154
+ measured: true
51155
+ },
51156
+ "yolov9m-640": {
51157
+ p50Ms: 40,
51158
+ fps: 25,
51159
+ measured: true
51160
+ },
51161
+ "yolov9m-640-int8": {
51162
+ p50Ms: 23.5,
51163
+ fps: 44,
51164
+ measured: true
51165
+ }
51166
+ },
51167
+ "x86-cpu": {
51168
+ "yolov9t-320": {
51169
+ p50Ms: 6.9,
51170
+ fps: 145,
51171
+ measured: true
51172
+ },
51173
+ "yolov9t-320-int8": {
51174
+ p50Ms: 2.7,
51175
+ fps: 405,
51176
+ measured: true
51177
+ },
51178
+ "yolov9t-640": {
51179
+ p50Ms: 23,
51180
+ fps: 44,
51181
+ measured: true
51182
+ },
51183
+ "yolov9t-640-int8": {
51184
+ p50Ms: 8.8,
51185
+ fps: 124,
51186
+ measured: true
51187
+ },
51188
+ "yolov9s-320": {
51189
+ p50Ms: 15.8,
51190
+ fps: 63,
51191
+ measured: true
51192
+ },
51193
+ "yolov9s-320-int8": {
51194
+ p50Ms: 6.5,
51195
+ fps: 180,
51196
+ measured: true
51197
+ },
51198
+ "yolov9s-640": {
51199
+ p50Ms: 58.2,
51200
+ fps: 17,
51201
+ measured: true
51202
+ },
51203
+ "yolov9s-640-int8": {
51204
+ p50Ms: 20.1,
51205
+ fps: 52,
51206
+ measured: true
51207
+ },
51208
+ "yolov9m-320": {
51209
+ p50Ms: 40,
51210
+ fps: 25,
51211
+ measured: true
51212
+ },
51213
+ "yolov9m-320-int8": {
51214
+ p50Ms: 14.2,
51215
+ fps: 74,
51216
+ measured: true
51217
+ },
51218
+ "yolov9m-640": {
51219
+ p50Ms: 155.8,
51220
+ fps: 6,
51221
+ measured: true
51222
+ },
51223
+ "yolov9m-640-int8": {
51224
+ p50Ms: 52.4,
51225
+ fps: 19,
51226
+ measured: true
51227
+ }
51228
+ }
51229
+ },
51230
+ "intel-n-series": {
51231
+ "intel-igpu": {
51232
+ "yolov9t-320": {
51233
+ p50Ms: 15,
51234
+ fps: 48,
51235
+ measured: true
51236
+ },
51237
+ "yolov9t-320-int8": {
51238
+ p50Ms: 14.1,
51239
+ fps: 67,
51240
+ measured: true
51241
+ },
51242
+ "yolov9t-640": {
51243
+ p50Ms: 38.5,
51244
+ fps: 26,
51245
+ measured: true
51246
+ },
51247
+ "yolov9t-640-int8": {
51248
+ p50Ms: 33.1,
51249
+ fps: 33,
51250
+ measured: true
51251
+ },
51252
+ "yolov9s-320": {
51253
+ p50Ms: 41.4,
51254
+ fps: 24,
51255
+ measured: true
51256
+ },
51257
+ "yolov9s-320-int8": {
51258
+ p50Ms: 23.3,
51259
+ fps: 34,
51260
+ measured: true
51261
+ },
51262
+ "yolov9s-640": {
51263
+ p50Ms: 127.4,
51264
+ fps: 8,
51265
+ measured: true
51266
+ },
51267
+ "yolov9s-640-int8": {
51268
+ p50Ms: 92.9,
51269
+ fps: 11,
51270
+ measured: true
51271
+ },
51272
+ "yolov9m-320": {
51273
+ p50Ms: 63.5,
51274
+ fps: 16,
51275
+ measured: true
51276
+ },
51277
+ "yolov9m-320-int8": {
51278
+ p50Ms: 39.4,
51279
+ fps: 26,
51280
+ measured: true
51281
+ },
51282
+ "yolov9m-640": {
51283
+ p50Ms: 215.5,
51284
+ fps: 5,
51285
+ measured: true
51286
+ },
51287
+ "yolov9m-640-int8": {
51288
+ p50Ms: 139.1,
51289
+ fps: 7,
51290
+ measured: true
51291
+ }
51292
+ },
51293
+ "x86-cpu": {
51294
+ "yolov9t-320": {
51295
+ p50Ms: 86.2,
51296
+ fps: 12,
51297
+ measured: true
51298
+ },
51299
+ "yolov9t-320-int8": {
51300
+ p50Ms: 52.7,
51301
+ fps: 20,
51302
+ measured: true
51303
+ },
51304
+ "yolov9t-640": {
51305
+ p50Ms: 322.1,
51306
+ fps: 3,
51307
+ measured: true
51308
+ },
51309
+ "yolov9t-640-int8": {
51310
+ p50Ms: 205.6,
51311
+ fps: 5,
51312
+ measured: true
51313
+ },
51314
+ "yolov9s-320": {
51315
+ p50Ms: 164.4,
51316
+ fps: 6,
51317
+ measured: true
51318
+ },
51319
+ "yolov9s-320-int8": {
51320
+ p50Ms: 86.9,
51321
+ fps: 12,
51322
+ measured: true
51323
+ },
51324
+ "yolov9s-640": {
51325
+ p50Ms: 341.3,
51326
+ fps: 3,
51327
+ measured: true
51328
+ },
51329
+ "yolov9s-640-int8": {
51330
+ p50Ms: 185.3,
51331
+ fps: 5,
51332
+ measured: true
51333
+ },
51334
+ "yolov9m-320": {
51335
+ p50Ms: 166.8,
51336
+ fps: 6,
51337
+ measured: true
51338
+ },
51339
+ "yolov9m-320-int8": {
51340
+ p50Ms: 226.3,
51341
+ fps: 4,
51342
+ measured: true
51343
+ },
51344
+ "yolov9m-640": {
51345
+ p50Ms: 663.5,
51346
+ fps: 2,
51347
+ measured: true
51348
+ },
51349
+ "yolov9m-640-int8": {
51350
+ p50Ms: 324.4,
51351
+ fps: 3,
51352
+ measured: true
51353
+ }
51354
+ }
51355
+ },
51356
+ "apple-m-pro": { "apple-ane": {
51357
+ "yolov9t-320": {
51358
+ p50Ms: 1.1,
51359
+ fps: 952,
51360
+ measured: true
51361
+ },
51362
+ "yolov9t-640": {
51363
+ p50Ms: 3,
51364
+ fps: 333,
51365
+ measured: true
51366
+ },
51367
+ "yolov9s-320": {
51368
+ p50Ms: 1.5,
51369
+ fps: 667,
51370
+ measured: true
51371
+ },
51372
+ "yolov9s-640": {
51373
+ p50Ms: 5.1,
51374
+ fps: 196,
51375
+ measured: true
51376
+ },
51377
+ "yolov9m-320": {
51378
+ p50Ms: 3.2,
51379
+ fps: 312,
51380
+ measured: true
51381
+ },
51382
+ "yolov9m-640": {
51383
+ p50Ms: 10.2,
51384
+ fps: 98,
51385
+ measured: true
51386
+ }
51387
+ } },
51388
+ "apple-m-base": { "apple-ane": {
51389
+ "yolov9t-320": {
51390
+ p50Ms: 1.32,
51391
+ fps: 758,
51392
+ measured: false
51393
+ },
51394
+ "yolov9t-640": {
51395
+ p50Ms: 3.45,
51396
+ fps: 290,
51397
+ measured: true
51398
+ },
51399
+ "yolov9s-320": {
51400
+ p50Ms: 1.8,
51401
+ fps: 556,
51402
+ measured: false
51403
+ },
51404
+ "yolov9s-640": {
51405
+ p50Ms: 5.46,
51406
+ fps: 183,
51407
+ measured: true
51408
+ },
51409
+ "yolov9m-320": {
51410
+ p50Ms: 3.79,
51411
+ fps: 264,
51412
+ measured: true
51413
+ },
51414
+ "yolov9m-640": {
51415
+ p50Ms: 10.34,
51416
+ fps: 97,
51417
+ measured: true
51418
+ }
51419
+ } }
51420
+ };
51421
+ var DETECTOR_ACCURACY = {
51422
+ "yolov9m-320": {
51423
+ personPrecision: .979,
51424
+ personRecall: .649
51425
+ },
51426
+ "yolov9m-320-int8": {
51427
+ personPrecision: .974,
51428
+ personRecall: .684
51429
+ },
51430
+ "yolov9m-640": {
51431
+ personPrecision: .976,
51432
+ personRecall: .854
51433
+ },
51434
+ "yolov9m-640-int8": {
51435
+ personPrecision: .976,
51436
+ personRecall: .847
51437
+ },
51438
+ "yolov9s-320": {
51439
+ personPrecision: .958,
51440
+ personRecall: .479
51441
+ },
51442
+ "yolov9s-320-int8": {
51443
+ personPrecision: .969,
51444
+ personRecall: .438
51445
+ },
51446
+ "yolov9s-640": {
51447
+ personPrecision: .986,
51448
+ personRecall: .753
51449
+ },
51450
+ "yolov9s-640-int8": {
51451
+ personPrecision: .963,
51452
+ personRecall: .823
51453
+ },
51454
+ "yolov9t-320": {
51455
+ personPrecision: .946,
51456
+ personRecall: .368
51457
+ },
51458
+ "yolov9t-320-int8": {
51459
+ personPrecision: .943,
51460
+ personRecall: .347
51461
+ },
51462
+ "yolov9t-640": {
51463
+ personPrecision: .979,
51464
+ personRecall: .646
51465
+ },
51466
+ "yolov9t-640-int8": {
51467
+ personPrecision: .979,
51468
+ personRecall: .646
51469
+ }
51470
+ };
51471
+ var DEFAULT_DEMAND_OPTIONS = {
51472
+ onMotionConcurrency: .25,
51473
+ onMotionMinConcurrent: 2
51474
+ };
51475
+ function deviceId(nodeId, deviceKey) {
51476
+ return `${nodeId}\u0000${deviceKey}`;
51477
+ }
51478
+ function charges(load) {
51479
+ return load.mode !== "disabled" && load.runsPipelineRoot && load.fps > 0;
51480
+ }
51481
+ /** The highest `count` rates, summed. */
51482
+ function topRatesSum(rates, count) {
51483
+ return rates.toSorted((a, b) => b - a).slice(0, Math.max(0, count)).reduce((sum, r) => sum + r, 0);
51484
+ }
51485
+ /** Concurrent on-motion sessions assumed cluster-wide without an observation. */
51486
+ function estimatedConcurrentSessions(cameraCount, options) {
51487
+ if (cameraCount <= 0) return 0;
51488
+ const share = Math.ceil(options.onMotionConcurrency * cameraCount);
51489
+ return Math.min(cameraCount, Math.max(share, Math.min(cameraCount, options.onMotionMinConcurrent)));
51490
+ }
51491
+ /**
51492
+ * Demand on every eligible device of the cluster.
51493
+ */
51494
+ function estimateDeviceDemand(input) {
51495
+ const options = input.options ?? DEFAULT_DEMAND_OPTIONS;
51496
+ const eligibleById = new Map(input.eligible.map((d) => [deviceId(d.nodeId, d.deviceKey), d]));
51497
+ const peakById = new Map(input.peaks.map((p) => [deviceId(p.nodeId, p.deviceKey), p]));
51498
+ const weightByNode = /* @__PURE__ */ new Map();
51499
+ const eligibleCountByNode = /* @__PURE__ */ new Map();
51500
+ for (const d of input.eligible) {
51501
+ weightByNode.set(d.nodeId, (weightByNode.get(d.nodeId) ?? 0) + Math.max(d.weight, 0));
51502
+ eligibleCountByNode.set(d.nodeId, (eligibleCountByNode.get(d.nodeId) ?? 0) + 1);
51503
+ }
51504
+ const pinnedFps = /* @__PURE__ */ new Map();
51505
+ const alwaysOnFps = /* @__PURE__ */ new Map();
51506
+ const alwaysOnCount = /* @__PURE__ */ new Map();
51507
+ const maxFps = /* @__PURE__ */ new Map();
51508
+ const onMotionRates = [];
51509
+ const bump = (map, key, by) => {
51510
+ map.set(key, (map.get(key) ?? 0) + by);
51511
+ };
51512
+ const noteMax = (key, fps) => {
51513
+ maxFps.set(key, Math.max(maxFps.get(key) ?? 0, fps));
51514
+ };
51515
+ for (const cam of input.cameras) {
51516
+ if (!charges(cam)) continue;
51517
+ if (cam.devicePin !== null) {
51518
+ const key = deviceId(cam.devicePin.nodeId, cam.devicePin.deviceKey);
51519
+ if (!eligibleById.has(key)) continue;
51520
+ bump(pinnedFps, key, cam.fps);
51521
+ noteMax(key, cam.fps);
51522
+ continue;
51523
+ }
51524
+ if (cam.mode === "always-on" && cam.standingNodeId !== null) {
51525
+ const nodeWeight = weightByNode.get(cam.standingNodeId) ?? 0;
51526
+ for (const d of input.eligible) {
51527
+ if (d.nodeId !== cam.standingNodeId || nodeWeight <= 0) continue;
51528
+ const key = deviceId(d.nodeId, d.deviceKey);
51529
+ const share = Math.max(d.weight, 0) / nodeWeight;
51530
+ bump(alwaysOnFps, key, cam.fps * share);
51531
+ bump(alwaysOnCount, key, share);
51532
+ noteMax(key, cam.fps);
51533
+ }
51534
+ continue;
51535
+ }
51536
+ onMotionRates.push(cam.fps);
51537
+ }
51538
+ const fastestOnMotion = onMotionRates.reduce((m, r) => Math.max(m, r), 0);
51539
+ const nodeCount = weightByNode.size;
51540
+ const estimatedTotal = estimatedConcurrentSessions(onMotionRates.length, options);
51541
+ return input.eligible.map((d) => {
51542
+ const key = deviceId(d.nodeId, d.deviceKey);
51543
+ const peak = peakById.get(key) ?? (eligibleCountByNode.get(d.nodeId) === 1 ? peakById.get(deviceId(d.nodeId, "")) : void 0);
51544
+ const alwaysOn = alwaysOnCount.get(key) ?? 0;
51545
+ let onMotionSessions = 0;
51546
+ let onMotionSource = "none";
51547
+ if (onMotionRates.length > 0) if (peak !== void 0 && peak.observedForMs >= 864e5) {
51548
+ onMotionSessions = Math.max(0, Math.round(peak.peakSessions - alwaysOn));
51549
+ onMotionSource = "observed-24h";
51550
+ } else {
51551
+ const nodeWeight = weightByNode.get(d.nodeId) ?? 0;
51552
+ const share = nodeCount > 0 && nodeWeight > 0 ? Math.max(d.weight, 0) / nodeWeight / nodeCount : 0;
51553
+ onMotionSessions = Math.ceil(estimatedTotal * share);
51554
+ onMotionSource = "estimate";
51555
+ }
51556
+ const demandFps = (pinnedFps.get(key) ?? 0) + (alwaysOnFps.get(key) ?? 0) + topRatesSum(onMotionRates, onMotionSessions);
51557
+ return {
51558
+ nodeId: d.nodeId,
51559
+ deviceKey: d.deviceKey,
51560
+ demandFps: Math.round(demandFps * 100) / 100,
51561
+ maxCameraFps: Math.max(maxFps.get(key) ?? 0, fastestOnMotion),
51562
+ alwaysOnCameras: Math.round(alwaysOn * 100) / 100,
51563
+ onMotionSessions,
51564
+ onMotionSource,
51565
+ observedForMs: peak?.observedForMs ?? null
51566
+ };
51567
+ }).toSorted((a, b) => a.nodeId === b.nodeId ? a.deviceKey.localeCompare(b.deviceKey) : a.nodeId.localeCompare(b.nodeId));
51568
+ }
51569
+ /** The family the cluster runs. */
51570
+ var BEST_SETTINGS_FAMILY = "yolov9";
51571
+ function round(value, digits = 3) {
51572
+ const f = 10 ** digits;
51573
+ return Math.round(value * f) / f;
51574
+ }
51575
+ function utilisationOf(cell, demand) {
51576
+ return cell.fps > 0 ? round((demand?.demandFps ?? 0) / cell.fps) : Number.POSITIVE_INFINITY;
51577
+ }
51578
+ function fitsOn(cell, demand, headroom) {
51579
+ const budget = cell.fps * (1 - headroom);
51580
+ return budget >= (demand?.demandFps ?? 0) && budget >= (demand?.maxCameraFps ?? 0);
51581
+ }
51582
+ function candidatesFor(contexts, formatsByModel) {
51583
+ return Object.keys(DETECTOR_ACCURACY).filter((id) => id.startsWith(BEST_SETTINGS_FAMILY)).filter((id) => contexts.every((c) => c.column[id] !== void 0 && (formatsByModel[id] ?? []).includes(c.device.format))).map((id) => ({
51584
+ modelId: id,
51585
+ personRecall: DETECTOR_ACCURACY[id]?.personRecall ?? 0
51586
+ })).toSorted((a, b) => b.personRecall - a.personRecall || a.modelId.localeCompare(b.modelId));
51587
+ }
51588
+ function totalLatency(contexts, modelId) {
51589
+ return contexts.reduce((sum, c) => sum + (c.column[modelId]?.p50Ms ?? Number.POSITIVE_INFINITY), 0);
51590
+ }
51591
+ function worstUtilisation(contexts, modelId) {
51592
+ return contexts.reduce((worst, c) => {
51593
+ const cell = c.column[modelId];
51594
+ return Math.max(worst, cell ? utilisationOf(cell, c.demand) : Number.POSITIVE_INFINITY);
51595
+ }, 0);
51596
+ }
51597
+ /** One model for a group of devices that must agree (step 2–4 of the header). */
51598
+ function pickForGroup(contexts, formatsByModel, headroom) {
51599
+ const candidates = candidatesFor(contexts, formatsByModel);
51600
+ if (candidates.length === 0) return null;
51601
+ const fitting = candidates.filter((c) => contexts.every((ctx) => {
51602
+ const cell = ctx.column[c.modelId];
51603
+ return cell !== void 0 && fitsOn(cell, ctx.demand, headroom);
51604
+ }));
51605
+ if (fitting.length === 0) {
51606
+ const lightest = candidates.toSorted((a, b) => worstUtilisation(contexts, a.modelId) - worstUtilisation(contexts, b.modelId))[0];
51607
+ if (lightest === void 0) return null;
51608
+ return {
51609
+ modelId: lightest.modelId,
51610
+ reasons: [{
51611
+ kind: "no-fit-lightest",
51612
+ utilisation: worstUtilisation(contexts, lightest.modelId)
51613
+ }]
51614
+ };
51615
+ }
51616
+ const best = fitting[0];
51617
+ if (best === void 0) return null;
51618
+ const chosen = fitting.filter((c) => best.personRecall - c.personRecall <= .02).toSorted((a, b) => totalLatency(contexts, a.modelId) - totalLatency(contexts, b.modelId) || b.personRecall - a.personRecall)[0] ?? best;
51619
+ const reasons = [];
51620
+ const mostAccurate = candidates[0];
51621
+ if (mostAccurate !== void 0 && mostAccurate.modelId !== best.modelId) {
51622
+ const limitedBy = contexts.find((ctx) => {
51623
+ const cell = ctx.column[mostAccurate.modelId];
51624
+ return cell === void 0 || !fitsOn(cell, ctx.demand, headroom);
51625
+ })?.device.key ?? "";
51626
+ reasons.push({
51627
+ kind: "stepped-down",
51628
+ from: mostAccurate.modelId,
51629
+ to: best.modelId,
51630
+ limitedBy
51631
+ });
51632
+ }
51633
+ if (chosen.modelId !== best.modelId) reasons.push({
51634
+ kind: "tie-faster",
51635
+ over: best.modelId,
51636
+ recallDelta: round(best.personRecall - chosen.personRecall)
51637
+ });
51638
+ return {
51639
+ modelId: chosen.modelId,
51640
+ reasons
51641
+ };
51642
+ }
51643
+ function choiceOn(ctx, modelId) {
51644
+ const cell = ctx.column[modelId];
51645
+ if (cell === void 0) return null;
51646
+ return {
51647
+ modelId,
51648
+ p50Ms: cell.p50Ms,
51649
+ fps: cell.fps,
51650
+ measured: cell.measured,
51651
+ utilisation: utilisationOf(cell, ctx.demand),
51652
+ personRecall: DETECTOR_ACCURACY[modelId]?.personRecall ?? 0
51653
+ };
51654
+ }
51655
+ function deviceReasonsFor(ctx, choice) {
51656
+ const out = [{
51657
+ kind: "fits",
51658
+ utilisation: choice.utilisation
51659
+ }];
51660
+ if (!choice.measured) out.push({
51661
+ kind: "derived-latency",
51662
+ modelId: choice.modelId
51663
+ });
51664
+ if (ctx.demand?.onMotionSource === "estimate") out.push({
51665
+ kind: "estimated-load",
51666
+ observedForMs: ctx.demand.observedForMs
51667
+ });
51668
+ return out;
51669
+ }
51670
+ function skippedNode(node, reason, hardwareClass) {
51671
+ return {
51672
+ nodeId: node.nodeId,
51673
+ reachable: node.reachable,
51674
+ hardwareClass,
51675
+ cpuModel: node.hardware?.cpuModel ?? null,
51676
+ skipped: true,
51677
+ acceleratorModelId: null,
51678
+ devices: node.devices.map((d) => ({
51679
+ deviceKey: d.key,
51680
+ accelerator: node.hardware ? acceleratorKindOf(d.key, node.hardware) : null,
51681
+ enabled: d.enabled,
51682
+ available: d.available,
51683
+ demand: null,
51684
+ chosen: null,
51685
+ reasons: []
51686
+ })),
51687
+ reasons: [reason]
51688
+ };
51689
+ }
51690
+ /** Recommend one node. See the module header. */
51691
+ function recommendNode(input) {
51692
+ const { node } = input;
51693
+ const headroom = input.headroom ?? .4;
51694
+ const perf = input.perf ?? DETECTOR_PERF;
51695
+ if (!node.reachable) return skippedNode(node, { kind: "node-offline" }, null);
51696
+ if (node.hardware === null) return skippedNode(node, { kind: "no-hardware-facts" }, null);
51697
+ const hardware = node.hardware;
51698
+ const { hardwareClass, cpuModel } = classifyInferenceHardware(hardware);
51699
+ const classTable = perf[hardwareClass];
51700
+ if (hardwareClass === "unknown" || classTable === void 0) return skippedNode(node, {
51701
+ kind: "no-measured-data",
51702
+ hardwareClass
51703
+ }, hardwareClass);
51704
+ const demandByKey = new Map(input.demand.filter((d) => d.nodeId === node.nodeId).map((d) => [d.deviceKey, d]));
51705
+ const devices = node.devices.toSorted((a, b) => a.key.localeCompare(b.key));
51706
+ const contexts = /* @__PURE__ */ new Map();
51707
+ const early = /* @__PURE__ */ new Map();
51708
+ for (const device of devices) {
51709
+ const accelerator = acceleratorKindOf(device.key, hardware);
51710
+ if (accelerator === "coral") {
51711
+ early.set(device.key, device.enabled ? [{ kind: "coral-untouched" }] : []);
51712
+ continue;
51713
+ }
51714
+ if (!device.enabled) {
51715
+ early.set(device.key, [{ kind: "device-disabled" }]);
51716
+ continue;
51717
+ }
51718
+ if (!device.available) {
51719
+ early.set(device.key, [{ kind: "device-unavailable" }]);
51720
+ continue;
51721
+ }
51722
+ const column = accelerator === null ? void 0 : classTable[accelerator];
51723
+ if (accelerator === null || column === void 0) {
51724
+ early.set(device.key, [{
51725
+ kind: "no-family-build",
51726
+ format: device.format
51727
+ }]);
51728
+ continue;
51729
+ }
51730
+ contexts.set(device.key, {
51731
+ device,
51732
+ accelerator,
51733
+ column,
51734
+ demand: demandByKey.get(device.key) ?? null
51735
+ });
51736
+ }
51737
+ const isCpu = (a) => a === "x86-cpu" || a === "arm-cpu";
51738
+ const accelerators = [...contexts.values()].filter((c) => !isCpu(c.accelerator));
51739
+ const cpus = [...contexts.values()].filter((c) => isCpu(c.accelerator));
51740
+ const chosenByKey = /* @__PURE__ */ new Map();
51741
+ const reasonsByKey = /* @__PURE__ */ new Map();
51742
+ const nodeReasons = [];
51743
+ let acceleratorModelId = null;
51744
+ const groupPick = accelerators.length > 0 ? pickForGroup(accelerators, node.formatsByModel, headroom) : null;
51745
+ if (groupPick !== null) {
51746
+ acceleratorModelId = groupPick.modelId;
51747
+ nodeReasons.push(...groupPick.reasons);
51748
+ for (const ctx of accelerators) {
51749
+ const choice = choiceOn(ctx, groupPick.modelId);
51750
+ if (choice === null) continue;
51751
+ chosenByKey.set(ctx.device.key, choice);
51752
+ reasonsByKey.set(ctx.device.key, deviceReasonsFor(ctx, choice));
51753
+ }
51754
+ } else for (const ctx of accelerators) reasonsByKey.set(ctx.device.key, [{
51755
+ kind: "no-family-build",
51756
+ format: ctx.device.format
51757
+ }]);
51758
+ for (const ctx of cpus) {
51759
+ const pick = pickForGroup([ctx], node.formatsByModel, headroom);
51760
+ const choice = pick === null ? null : choiceOn(ctx, pick.modelId);
51761
+ if (pick === null || choice === null) {
51762
+ reasonsByKey.set(ctx.device.key, [{
51763
+ kind: "no-family-build",
51764
+ format: ctx.device.format
51765
+ }]);
51766
+ continue;
51767
+ }
51768
+ chosenByKey.set(ctx.device.key, choice);
51769
+ reasonsByKey.set(ctx.device.key, [
51770
+ { kind: "cpu-device" },
51771
+ ...pick.reasons,
51772
+ ...deviceReasonsFor(ctx, choice)
51773
+ ]);
51774
+ }
51775
+ return {
51776
+ nodeId: node.nodeId,
51777
+ reachable: true,
51778
+ hardwareClass,
51779
+ cpuModel,
51780
+ skipped: chosenByKey.size === 0,
51781
+ acceleratorModelId,
51782
+ devices: devices.map((device) => ({
51783
+ deviceKey: device.key,
51784
+ accelerator: acceleratorKindOf(device.key, hardware),
51785
+ enabled: device.enabled,
51786
+ available: device.available,
51787
+ demand: demandByKey.get(device.key) ?? null,
51788
+ chosen: chosenByKey.get(device.key) ?? null,
51789
+ reasons: early.get(device.key) ?? reasonsByKey.get(device.key) ?? []
51790
+ })),
51791
+ reasons: nodeReasons
51792
+ };
51793
+ }
50659
51794
  /**
50660
51795
  * THE detail-crop convention — the single derivation of the rectangle a
50661
51796
  * detail/enrichment step (clip-embedding, face-detection, plate-detection…)
@@ -51430,7 +52565,7 @@ var AudioSubscriptionController = class {
51430
52565
  } catch (err) {
51431
52566
  this.deps.logger.error("audio re-attach failed", {
51432
52567
  tags: { deviceId },
51433
- meta: { error: errMsg$1(err) }
52568
+ meta: { error: errMsg$2(err) }
51434
52569
  });
51435
52570
  }
51436
52571
  }
@@ -51541,7 +52676,7 @@ var AudioSubscriptionController = class {
51541
52676
  this.closeMotionAudioWindow(deviceId, "motion-cooldown-elapsed").catch((err) => {
51542
52677
  this.deps.logger.warn("lazy audio: cooldown teardown failed", {
51543
52678
  tags: { deviceId },
51544
- meta: { error: errMsg$1(err) }
52679
+ meta: { error: errMsg$2(err) }
51545
52680
  });
51546
52681
  });
51547
52682
  }, cooldownMs);
@@ -51560,7 +52695,7 @@ var AudioSubscriptionController = class {
51560
52695
  this.closeMotionAudioWindow(deviceId, "quiet-window-elapsed").catch((err) => {
51561
52696
  this.deps.logger.warn("lazy audio: quiet-window teardown failed", {
51562
52697
  tags: { deviceId },
51563
- meta: { error: errMsg$1(err) }
52698
+ meta: { error: errMsg$2(err) }
51564
52699
  });
51565
52700
  });
51566
52701
  }, windowMs);
@@ -51762,7 +52897,7 @@ var AudioSubscriptionController = class {
51762
52897
  meta: {
51763
52898
  audioNodeId,
51764
52899
  brokerId: audioBrokerId,
51765
- error: errMsg$1(err)
52900
+ error: errMsg$2(err)
51766
52901
  }
51767
52902
  });
51768
52903
  });
@@ -51837,7 +52972,7 @@ async function readOrRefuse(deps, deviceId) {
51837
52972
  try {
51838
52973
  device = await deps.readDevice(deviceId);
51839
52974
  } catch (err) {
51840
- return refuse(deps, deviceId, `could not read device ${deviceId}: ${errMsg$1(err)}`);
52975
+ return refuse(deps, deviceId, `could not read device ${deviceId}: ${errMsg$2(err)}`);
51841
52976
  }
51842
52977
  if (device === null) return refuse(deps, deviceId, `no device ${deviceId}`);
51843
52978
  return device;
@@ -53167,7 +54302,7 @@ var LoadShedController = class LoadShedController {
53167
54302
  if (await this.tryClusterRelocate(deviceId).catch((err) => {
53168
54303
  this.deps.logger.warn("Load management: cluster relocate failed", {
53169
54304
  tags: { deviceId },
53170
- meta: { error: errMsg$1(err) }
54305
+ meta: { error: errMsg$2(err) }
53171
54306
  });
53172
54307
  return false;
53173
54308
  })) {
@@ -53181,7 +54316,7 @@ var LoadShedController = class LoadShedController {
53181
54316
  this.deps.api()?.pipelineRunner.detachCamera.mutate({ deviceId }).catch((err) => {
53182
54317
  this.deps.logger.warn("Load management: detachCamera failed", {
53183
54318
  tags: { deviceId },
53184
- meta: { error: errMsg$1(err) }
54319
+ meta: { error: errMsg$2(err) }
53185
54320
  });
53186
54321
  });
53187
54322
  this.ensureLoadShedResumeTimer();
@@ -53213,7 +54348,7 @@ var LoadShedController = class LoadShedController {
53213
54348
  tags: { deviceId },
53214
54349
  meta: {
53215
54350
  from: currentNode,
53216
- error: errMsg$1(err)
54351
+ error: errMsg$2(err)
53217
54352
  }
53218
54353
  });
53219
54354
  });
@@ -53256,7 +54391,7 @@ var LoadShedController = class LoadShedController {
53256
54391
  this.deps.redispatchSingleCamera(deviceId).catch((err) => {
53257
54392
  this.deps.logger.warn("Load management: auto-resume dispatch failed", {
53258
54393
  tags: { deviceId },
53259
- meta: { error: errMsg$1(err) }
54394
+ meta: { error: errMsg$2(err) }
53260
54395
  });
53261
54396
  });
53262
54397
  }
@@ -53421,7 +54556,7 @@ var AgentLoadService = class AgentLoadService {
53421
54556
  ms,
53422
54557
  outcome: "failed",
53423
54558
  load: null,
53424
- error: errMsg$1(outcome.error)
54559
+ error: errMsg$2(outcome.error)
53425
54560
  };
53426
54561
  return {
53427
54562
  nodeId,
@@ -53888,7 +55023,7 @@ function buildCameraSensorsPorts(api, logger) {
53888
55023
  } catch (err) {
53889
55024
  return {
53890
55025
  ok: false,
53891
- error: errMsg$1(err)
55026
+ error: errMsg$2(err)
53892
55027
  };
53893
55028
  }
53894
55029
  };
@@ -54152,7 +55287,7 @@ var CameraSensorsController = class {
54152
55287
  async #readFleet() {
54153
55288
  const read = await this.#deps.listCameraIds().catch((err) => ({
54154
55289
  kind: "unreadable",
54155
- error: errMsg$1(err)
55290
+ error: errMsg$2(err)
54156
55291
  }));
54157
55292
  if (read.kind === "unreadable") {
54158
55293
  this.#deps.logger.warn("camera-sensors: camera list unreadable — mappings stay unloaded", { meta: { error: read.error } });
@@ -54179,7 +55314,7 @@ var CameraSensorsController = class {
54179
55314
  } catch (err) {
54180
55315
  this.#deps.logger.error("camera-sensors: refresh failed — nothing applied", {
54181
55316
  tags: { deviceId: cameraId },
54182
- meta: { error: errMsg$1(err) }
55317
+ meta: { error: errMsg$2(err) }
54183
55318
  });
54184
55319
  }
54185
55320
  }
@@ -54272,7 +55407,7 @@ async function readCameraSensorsSetting(read) {
54272
55407
  } catch (err) {
54273
55408
  return {
54274
55409
  state: "unreadable",
54275
- reason: `settings read failed — ${errMsg$1(err)}`
55410
+ reason: `settings read failed — ${errMsg$2(err)}`
54276
55411
  };
54277
55412
  }
54278
55413
  }
@@ -54582,7 +55717,7 @@ var CameraMotionSlice = class {
54582
55717
  st.chain = st.chain.then(job).catch((err) => {
54583
55718
  this.#deps.logger.error("camera motion slice: write job failed", {
54584
55719
  tags: { deviceId },
54585
- meta: { error: errMsg$1(err) }
55720
+ meta: { error: errMsg$2(err) }
54586
55721
  });
54587
55722
  });
54588
55723
  }
@@ -54646,7 +55781,7 @@ var CameraMotionSlice = class {
54646
55781
  meta: {
54647
55782
  detected: slice.detected,
54648
55783
  reason,
54649
- error: errMsg$1(err)
55784
+ error: errMsg$2(err)
54650
55785
  }
54651
55786
  });
54652
55787
  return;
@@ -54676,7 +55811,7 @@ var CameraMotionSlice = class {
54676
55811
  async #republishAll() {
54677
55812
  const listed = await this.#deps.listCameraIds().catch((err) => ({
54678
55813
  kind: "unreadable",
54679
- error: errMsg$1(err)
55814
+ error: errMsg$2(err)
54680
55815
  }));
54681
55816
  if (!this.#open || this.#stopped) return;
54682
55817
  if (listed.kind === "unreadable") this.#deps.logger.warn("camera motion slice: camera list unreadable — only cameras with motion since boot are re-published", { meta: { error: listed.error } });
@@ -54798,7 +55933,7 @@ function hubMotionSliceStore(api) {
54798
55933
  } catch (err) {
54799
55934
  return {
54800
55935
  kind: "unreadable",
54801
- error: errMsg$1(err)
55936
+ error: errMsg$2(err)
54802
55937
  };
54803
55938
  }
54804
55939
  }
@@ -56670,7 +57805,7 @@ function warnSampled(deps, deviceId, source, message, meta) {
56670
57805
  function warnUnreachable(deps, deviceId, source, err) {
56671
57806
  warnSampled(deps, deviceId, source, UNREACHABLE_MESSAGE, {
56672
57807
  source,
56673
- error: errMsg$1(err)
57808
+ error: errMsg$2(err)
56674
57809
  });
56675
57810
  }
56676
57811
  /**
@@ -57944,7 +59079,7 @@ var ONBOARD_DETECTION_SOURCE = "onboard";
57944
59079
  * configs, and it reads them the same way the runner does.
57945
59080
  */
57946
59081
  var DETECTION_SOURCES_WHEN_ABSENT = ["pipeline"];
57947
- function errMsg(err) {
59082
+ function errMsg$1(err) {
57948
59083
  return err instanceof Error ? err.message : String(err);
57949
59084
  }
57950
59085
  /**
@@ -57963,7 +59098,7 @@ async function reconcileOnboardForwarding(input, deps) {
57963
59098
  log.warn("onboard forwarding not reconciled — could not tell whether the camera has the cap", { meta: {
57964
59099
  wantsOnboard,
57965
59100
  detectionSources: [...detectionSources],
57966
- error: errMsg(err)
59101
+ error: errMsg$1(err)
57967
59102
  } });
57968
59103
  return {
57969
59104
  kind: "read-failed",
@@ -57981,7 +59116,7 @@ async function reconcileOnboardForwarding(input, deps) {
57981
59116
  log.warn("onboard forwarding not reconciled — the vendor flag could not be read", { meta: {
57982
59117
  wantsOnboard,
57983
59118
  detectionSources: [...detectionSources],
57984
- error: errMsg(err)
59119
+ error: errMsg$1(err)
57985
59120
  } });
57986
59121
  return {
57987
59122
  kind: "read-failed",
@@ -57999,7 +59134,7 @@ async function reconcileOnboardForwarding(input, deps) {
57999
59134
  was: current,
58000
59135
  wanted: wantsOnboard,
58001
59136
  detectionSources: [...detectionSources],
58002
- error: errMsg(err)
59137
+ error: errMsg$1(err)
58003
59138
  } });
58004
59139
  return {
58005
59140
  kind: "write-failed",
@@ -58195,7 +59330,7 @@ var DetectionWiringController = class {
58195
59330
  try {
58196
59331
  raw = await this.deps.ctx().settings.readDeviceStore(deviceId);
58197
59332
  } catch (err) {
58198
- const msg = errMsg$1(err);
59333
+ const msg = errMsg$2(err);
58199
59334
  this.deps.logger.error("resolveDeviceDetectionSettings failed", {
58200
59335
  tags: { deviceId },
58201
59336
  meta: { error: msg }
@@ -58226,7 +59361,7 @@ var DetectionWiringController = class {
58226
59361
  motionDetectionEnabled
58227
59362
  });
58228
59363
  } catch (err) {
58229
- const msg = errMsg$1(err);
59364
+ const msg = errMsg$2(err);
58230
59365
  this.deps.logger.error("resolveDeviceDetectionSettings narrowing failed", {
58231
59366
  tags: { deviceId },
58232
59367
  meta: { error: msg }
@@ -58261,7 +59396,7 @@ var DetectionWiringController = class {
58261
59396
  } catch (err) {
58262
59397
  this.deps.logger.debug("fetchAssignedProfiles: slot read failed (transient) — treating as unknown", {
58263
59398
  tags: { deviceId },
58264
- meta: { error: errMsg$1(err) }
59399
+ meta: { error: errMsg$2(err) }
58265
59400
  });
58266
59401
  return null;
58267
59402
  }
@@ -58337,7 +59472,7 @@ var DetectionWiringController = class {
58337
59472
  if (config.motionSources.includes("analyzer")) try {
58338
59473
  await this.deps.session.ensureMotionWatch(deviceId, config);
58339
59474
  } catch (err) {
58340
- log.error("ensureMotionWatch failed", { meta: { error: errMsg$1(err) } });
59475
+ log.error("ensureMotionWatch failed", { meta: { error: errMsg$2(err) } });
58341
59476
  }
58342
59477
  log.info("detection armed (session-time) — watching for motion", { meta: { motion: config.motionSources.join("+") } });
58343
59478
  } else {
@@ -58358,14 +59493,14 @@ var DetectionWiringController = class {
58358
59493
  try {
58359
59494
  await this.deps.placement.dispatchCamera(runnerConfig);
58360
59495
  } catch (err) {
58361
- const msg = errMsg$1(err);
59496
+ const msg = errMsg$2(err);
58362
59497
  log.error("dispatchCamera failed", { meta: { error: msg } });
58363
59498
  }
58364
59499
  }
58365
59500
  try {
58366
59501
  await this.deps.audio.resubscribe(deviceId, config);
58367
59502
  } catch (err) {
58368
- const msg = errMsg$1(err);
59503
+ const msg = errMsg$2(err);
58369
59504
  log.error("subscribeAudioStream failed", { meta: { error: msg } });
58370
59505
  }
58371
59506
  if (config.detectionMode !== "on-motion") log.info("Detection dispatched", { meta: {
@@ -58381,7 +59516,7 @@ var DetectionWiringController = class {
58381
59516
  try {
58382
59517
  await this.deps.placement.releaseCamera({ deviceId });
58383
59518
  } catch (err) {
58384
- const msg = errMsg$1(err);
59519
+ const msg = errMsg$2(err);
58385
59520
  this.deps.logger.error("releaseCamera failed", {
58386
59521
  tags: { deviceId },
58387
59522
  meta: { error: msg }
@@ -58407,7 +59542,7 @@ var DetectionWiringController = class {
58407
59542
  } catch (err) {
58408
59543
  this.deps.logger.warn("failed to clear per-device orchestrator settings on removal", {
58409
59544
  tags: { deviceId },
58410
- meta: { error: errMsg$1(err) }
59545
+ meta: { error: errMsg$2(err) }
58411
59546
  });
58412
59547
  }
58413
59548
  this.deps.logger.info("Purged pipeline config for removed device", { tags: { deviceId } });
@@ -58431,7 +59566,7 @@ var DetectionWiringController = class {
58431
59566
  } catch (err) {
58432
59567
  this.deps.logger.debug("device existence check failed — proceeding as before", {
58433
59568
  tags: { deviceId },
58434
- meta: { error: errMsg$1(err) }
59569
+ meta: { error: errMsg$2(err) }
58435
59570
  });
58436
59571
  return false;
58437
59572
  }
@@ -58548,7 +59683,7 @@ var DetectionWiringController = class {
58548
59683
  await this.deps.attachOn(assignment.agentNodeId, nextConfig);
58549
59684
  log.info("pipeline.camera-updated: hot-reloaded", { tags: { nodeId: assignment.agentNodeId } });
58550
59685
  } catch (err) {
58551
- const msg = errMsg$1(err);
59686
+ const msg = errMsg$2(err);
58552
59687
  log.error("pipeline.camera-updated: attachOn failed", {
58553
59688
  tags: { nodeId: assignment.agentNodeId },
58554
59689
  meta: { error: msg }
@@ -58624,7 +59759,7 @@ var DetectionWiringController = class {
58624
59759
  this.deps.audio.resubscribe(deviceId, audioCfg).catch((err) => {
58625
59760
  this.deps.logger.warn("watchdog audio re-subscribe failed", {
58626
59761
  tags: { deviceId },
58627
- meta: { error: errMsg$1(err) }
59762
+ meta: { error: errMsg$2(err) }
58628
59763
  });
58629
59764
  });
58630
59765
  }
@@ -58823,7 +59958,7 @@ var DeviceConfigContributions = class {
58823
59958
  issue: configIssue
58824
59959
  } });
58825
59960
  this.deps.topology.refreshNodeCapabilities().then(() => this.deps.redispatchAllActiveCameras("ingest-owner-changed")).catch((err) => {
58826
- this.deps.ctx().logger.warn("ingest-owner-changed re-dispatch failed", { meta: { error: errMsg$1(err) } });
59961
+ this.deps.ctx().logger.warn("ingest-owner-changed re-dispatch failed", { meta: { error: errMsg$2(err) } });
58827
59962
  });
58828
59963
  }
58829
59964
  this.deps.clearOnSettingsChange();
@@ -59277,10 +60412,25 @@ function rootRefusal(input, droppedRoots) {
59277
60412
  droppedRoots
59278
60413
  };
59279
60414
  }
60415
+ /**
60416
+ * Whose provisioning an attach applies. The elected device when there is one.
60417
+ * With NO elected device the runner's default pool serves the camera; when the
60418
+ * node has exactly one eligible device the balancer returns
60419
+ * `single-eligible-device` and that default pool IS that device (a Mac's
60420
+ * `coreml:all`, an N100 with only its iGPU on), so its model pin and the
60421
+ * camera's override stored under its key apply (D745 — before, the empty key
60422
+ * was read, which no store writes, and the pin was silently never run).
60423
+ * Otherwise `''`: the node's folded settings with no hardware fields.
60424
+ */
60425
+ function provisioningKeyFor(input) {
60426
+ if (input.deviceKey !== void 0) return input.deviceKey;
60427
+ const eligibleKeys = Object.keys(input.eligible);
60428
+ return eligibleKeys.length === 1 ? eligibleKeys[0] ?? "" : "";
60429
+ }
59280
60430
  /** Steps 3 of the module header: provision the resolved structure. */
59281
60431
  async function provisionAttachPipeline(input, roster, resolved, store) {
59282
60432
  const { deviceId, nodeId, deviceKey } = input;
59283
- const electedKey = deviceKey ?? "";
60433
+ const electedKey = provisioningKeyFor(input);
59284
60434
  const rosterKeys = (roster ?? []).map((d) => d.deviceKey);
59285
60435
  const sources = await store.readDeviceProvisioning(deviceId, nodeId, [...new Set([electedKey, ...rosterKeys])]);
59286
60436
  const elected = sources.get(electedKey);
@@ -59888,7 +61038,7 @@ var PlacementService = class {
59888
61038
  async releaseCamera(input) {
59889
61039
  const current = this.deps.ledger.getAssignment(input.deviceId);
59890
61040
  if (current) await this.detachOn(current.agentNodeId, input.deviceId).catch((err) => {
59891
- const msg = errMsg$1(err);
61041
+ const msg = errMsg$2(err);
59892
61042
  this.deps.ctx().logger.debug("releaseCamera detach failed", {
59893
61043
  tags: { deviceId: input.deviceId },
59894
61044
  meta: { error: msg }
@@ -59916,7 +61066,7 @@ var PlacementService = class {
59916
61066
  pipelineNodeId: input.agentNodeId,
59917
61067
  [PREFERRED_AGENT_SETTING]: null
59918
61068
  }).catch((err) => {
59919
- const msg = errMsg$1(err);
61069
+ const msg = errMsg$2(err);
59920
61070
  ctx.logger.warn("assignPipeline: failed to persist pin", {
59921
61071
  tags: { deviceId: input.deviceId },
59922
61072
  meta: { error: msg }
@@ -59948,7 +61098,7 @@ var PlacementService = class {
59948
61098
  }
59949
61099
  const current = this.deps.ledger.getAssignment(input.deviceId);
59950
61100
  if (current && current.agentNodeId !== input.agentNodeId) await this.detachOn(current.agentNodeId, input.deviceId).catch((err) => {
59951
- const msg = errMsg$1(err);
61101
+ const msg = errMsg$2(err);
59952
61102
  ctx.logger.debug("assignPipeline detach-old failed", {
59953
61103
  tags: { deviceId: input.deviceId },
59954
61104
  meta: { error: msg }
@@ -59973,7 +61123,7 @@ var PlacementService = class {
59973
61123
  await ctx.settings?.writeDeviceStore(input.deviceId, { pipelineDeviceKey: clearing ? "auto" : input.deviceKey }).catch((err) => {
59974
61124
  ctx.logger.warn("setPipelineDevicePin: failed to persist device pin", {
59975
61125
  tags: { deviceId: input.deviceId },
59976
- meta: { error: errMsg$1(err) }
61126
+ meta: { error: errMsg$2(err) }
59977
61127
  });
59978
61128
  });
59979
61129
  const sessionNode = this.deps.activeSessionNode(input.deviceId);
@@ -59996,7 +61146,7 @@ var PlacementService = class {
59996
61146
  [PREFERRED_AGENT_SETTING]: null,
59997
61147
  pipelineNodeId: "auto"
59998
61148
  }).catch((err) => {
59999
- const msg = errMsg$1(err);
61149
+ const msg = errMsg$2(err);
60000
61150
  ctx.logger.warn("unassignPipeline: failed to clear pin", {
60001
61151
  tags: { deviceId: input.deviceId },
60002
61152
  meta: { error: msg }
@@ -60017,7 +61167,7 @@ var PlacementService = class {
60017
61167
  else {
60018
61168
  const targetNodeId = decision.agentNodeId;
60019
61169
  if (current && current.agentNodeId !== targetNodeId) await this.detachOn(current.agentNodeId, input.deviceId).catch((err) => {
60020
- const msg = errMsg$1(err);
61170
+ const msg = errMsg$2(err);
60021
61171
  ctx.logger.debug("unassignPipeline detach-old failed", {
60022
61172
  tags: { deviceId: input.deviceId },
60023
61173
  meta: { error: msg }
@@ -60037,7 +61187,7 @@ var PlacementService = class {
60037
61187
  }
60038
61188
  }
60039
61189
  if (current) await this.detachOn(current.agentNodeId, input.deviceId).catch((err) => {
60040
- const msg = errMsg$1(err);
61190
+ const msg = errMsg$2(err);
60041
61191
  ctx.logger.debug("unassignPipeline detach failed", {
60042
61192
  tags: { deviceId: input.deviceId },
60043
61193
  meta: { error: msg }
@@ -60056,7 +61206,7 @@ var PlacementService = class {
60056
61206
  async runAutoRebalance() {
60057
61207
  if (!this.deps.ctxIfReady()) return;
60058
61208
  await this.deps.reconcilePlacementFromRunners().catch((err) => {
60059
- this.deps.ctx().logger.debug("auto-rebalance: attachment reconcile failed", { meta: { error: errMsg$1(err) } });
61209
+ this.deps.ctx().logger.debug("auto-rebalance: attachment reconcile failed", { meta: { error: errMsg$2(err) } });
60060
61210
  });
60061
61211
  if (this.autoRebalanceInFlight) return;
60062
61212
  if (this.deps.topology.enabledNodes.length < 2) return;
@@ -60065,7 +61215,7 @@ var PlacementService = class {
60065
61215
  const { migrated } = await this.rebalance({ minImprovement: AUTO_REBALANCE_MIN_IMPROVEMENT });
60066
61216
  if (migrated > 0) this.deps.ctx().logger.info("auto-rebalance: corrected placement drift", { meta: { migrated } });
60067
61217
  } catch (err) {
60068
- this.deps.ctx().logger.debug("auto-rebalance pass failed", { meta: { error: errMsg$1(err) } });
61218
+ this.deps.ctx().logger.debug("auto-rebalance pass failed", { meta: { error: errMsg$2(err) } });
60069
61219
  } finally {
60070
61220
  this.autoRebalanceInFlight = false;
60071
61221
  }
@@ -60137,7 +61287,7 @@ var PlacementService = class {
60137
61287
  const targetConfig = withAttachPipeline(config, placement.pipeline);
60138
61288
  if (current) {
60139
61289
  await this.detachOn(current.agentNodeId, deviceId).catch((err) => {
60140
- const msg = errMsg$1(err);
61290
+ const msg = errMsg$2(err);
60141
61291
  ctx.logger.debug("rebalance detach-old failed", {
60142
61292
  tags: { deviceId },
60143
61293
  meta: { error: msg }
@@ -60542,7 +61692,7 @@ var NodeLifecycleHandler = class {
60542
61692
  nodeId: decision.agentNodeId
60543
61693
  } });
60544
61694
  } catch (err) {
60545
- const msg = errMsg$1(err);
61695
+ const msg = errMsg$2(err);
60546
61696
  this.deps.logger.error("Failover: attach failed", {
60547
61697
  tags: {
60548
61698
  deviceId,
@@ -60572,7 +61722,7 @@ var NodeLifecycleHandler = class {
60572
61722
  this.deps.schedulePendingRetry();
60573
61723
  if (failoverPolicy.onReconnect === "rebalance") {
60574
61724
  await this.deps.rebalance().catch((err) => {
60575
- const msg = errMsg$1(err);
61725
+ const msg = errMsg$2(err);
60576
61726
  this.deps.logger.error("rebalance after reconnect failed", { meta: { error: msg } });
60577
61727
  });
60578
61728
  return;
@@ -60598,7 +61748,7 @@ var NodeLifecycleHandler = class {
60598
61748
  this.deps.ledger.recordAssignment(deviceId, nodeId, "failover", false);
60599
61749
  restored++;
60600
61750
  } catch (err) {
60601
- const msg = errMsg$1(err);
61751
+ const msg = errMsg$2(err);
60602
61752
  this.deps.logger.error("restore attach failed", {
60603
61753
  tags: {
60604
61754
  deviceId,
@@ -60692,7 +61842,7 @@ var NodeLifecycleHandler = class {
60692
61842
  deviceId,
60693
61843
  nodeId
60694
61844
  },
60695
- meta: { error: errMsg$1(err) }
61845
+ meta: { error: errMsg$2(err) }
60696
61846
  });
60697
61847
  }
60698
61848
  this.deps.logger.info("readiness: detection-pipeline reseeded agent defaults + redispatched cameras", {
@@ -60706,7 +61856,7 @@ var NodeLifecycleHandler = class {
60706
61856
  this.reattachStaleEmpties(nodeId).catch((err) => {
60707
61857
  this.deps.logger.debug("readiness deferred sweep failed", {
60708
61858
  tags: { nodeId },
60709
- meta: { error: errMsg$1(err) }
61859
+ meta: { error: errMsg$2(err) }
60710
61860
  });
60711
61861
  });
60712
61862
  }, 3e3);
@@ -60717,7 +61867,7 @@ var NodeLifecycleHandler = class {
60717
61867
  tags: { nodeId },
60718
61868
  meta: {
60719
61869
  epoch: t.epoch,
60720
- error: errMsg$1(err)
61870
+ error: errMsg$2(err)
60721
61871
  }
60722
61872
  });
60723
61873
  }
@@ -60796,7 +61946,7 @@ var NodeLifecycleHandler = class {
60796
61946
  deviceId,
60797
61947
  nodeId
60798
61948
  },
60799
- meta: { error: errMsg$1(err) }
61949
+ meta: { error: errMsg$2(err) }
60800
61950
  });
60801
61951
  }
60802
61952
  }
@@ -61118,7 +62268,7 @@ var NodeStressController = class NodeStressController {
61118
62268
  if (this.timer) return;
61119
62269
  this.timer = setInterval(() => {
61120
62270
  this.sweep().catch((err) => {
61121
- this.deps.logger.warn("node-stress sweep failed", { meta: { error: errMsg$1(err) } });
62271
+ this.deps.logger.warn("node-stress sweep failed", { meta: { error: errMsg$2(err) } });
61122
62272
  });
61123
62273
  }, NodeStressController.SWEEP_INTERVAL_MS);
61124
62274
  }
@@ -61291,7 +62441,7 @@ var NodeStressController = class NodeStressController {
61291
62441
  meta: {
61292
62442
  from: plan.fromNodeId,
61293
62443
  to: plan.toNodeId,
61294
- error: errMsg$1(err)
62444
+ error: errMsg$2(err)
61295
62445
  }
61296
62446
  });
61297
62447
  } finally {
@@ -61988,7 +63138,7 @@ var NodeTopologyService = class {
61988
63138
  try {
61989
63139
  blob = await this.deps.readAgentSettingsMap();
61990
63140
  } catch (err) {
61991
- this.deps.logger.warn("refreshNodeCapabilities: agent settings read failed", { meta: { error: errMsg$1(err) } });
63141
+ this.deps.logger.warn("refreshNodeCapabilities: agent settings read failed", { meta: { error: errMsg$2(err) } });
61992
63142
  }
61993
63143
  const nodeIds = new Set(["hub"]);
61994
63144
  for (const nodeId of Object.keys(blob)) if (typeof nodeId === "string" && nodeId.length > 0 && !nodeId.includes("/")) nodeIds.add(nodeId);
@@ -62162,7 +63312,7 @@ function wireOrchestratorSubscriptions(deps) {
62162
63312
  const unsubInferenceResult = deps.eventBus.subscribe({ category: EventCategory.PipelineInferenceResult }, (event) => {
62163
63313
  if (!isEvent(event, EventCategory.PipelineInferenceResult)) return;
62164
63314
  deps.handleInferenceResult(event.data).catch((err) => {
62165
- const msg = errMsg$1(err);
63315
+ const msg = errMsg$2(err);
62166
63316
  deps.logger.error("PipelineInferenceResult post-processing failed", {
62167
63317
  tags: { deviceId: event.data.deviceId },
62168
63318
  meta: { error: msg }
@@ -62181,7 +63331,7 @@ function wireOrchestratorSubscriptions(deps) {
62181
63331
  deps.logger.info("received onProfileSlotsChanged", { tags: { deviceId } });
62182
63332
  deps.handleDeviceRegistered(deviceId).catch((err) => {
62183
63333
  if (isTornDown) return;
62184
- const msg = errMsg$1(err);
63334
+ const msg = errMsg$2(err);
62185
63335
  deps.logger.error("handleDeviceRegistered failed", {
62186
63336
  tags: { deviceId },
62187
63337
  meta: { error: msg }
@@ -62193,7 +63343,7 @@ function wireOrchestratorSubscriptions(deps) {
62193
63343
  const unsubDeviceUnregistered = deps.eventBus.subscribe({ category: EventCategory.DeviceUnregistered }, (event) => {
62194
63344
  const { deviceId } = event.data;
62195
63345
  deps.handleDeviceUnregistered(deviceId).catch((err) => {
62196
- const msg = errMsg$1(err);
63346
+ const msg = errMsg$2(err);
62197
63347
  deps.logger.error("handleDeviceUnregistered failed", {
62198
63348
  tags: { deviceId },
62199
63349
  meta: { error: msg }
@@ -62206,7 +63356,7 @@ function wireOrchestratorSubscriptions(deps) {
62206
63356
  deps.cameraSensors.requestCamera(deviceId, "settings-updated");
62207
63357
  deps.handleSettingsChanged(deviceId).catch((err) => {
62208
63358
  if (isTornDown) return;
62209
- const msg = errMsg$1(err);
63359
+ const msg = errMsg$2(err);
62210
63360
  deps.logger.error("handleSettingsChanged failed", {
62211
63361
  tags: { deviceId },
62212
63362
  meta: { error: msg }
@@ -62216,7 +63366,7 @@ function wireOrchestratorSubscriptions(deps) {
62216
63366
  const unsubCameraUpdated = deps.eventBus.subscribe({ category: EventCategory.PipelineCameraUpdated }, (event) => {
62217
63367
  if (!isEvent(event, EventCategory.PipelineCameraUpdated)) return;
62218
63368
  deps.handlePipelineCameraUpdated(event.data.deviceId, event.data.config).catch((err) => {
62219
- const msg = errMsg$1(err);
63369
+ const msg = errMsg$2(err);
62220
63370
  deps.logger.error("handlePipelineCameraUpdated failed", {
62221
63371
  tags: { deviceId: event.data.deviceId },
62222
63372
  meta: { error: msg }
@@ -62229,7 +63379,7 @@ function wireOrchestratorSubscriptions(deps) {
62229
63379
  if (!(reason === "wrapper-activated" || reason === "wrapper-deactivated") || !(capName === "detection-pipeline" || capName === "audio-analysis" || capName === "motion-detection")) return;
62230
63380
  deps.handleSettingsChanged(deviceId).catch((err) => {
62231
63381
  if (isTornDown) return;
62232
- const msg = errMsg$1(err);
63382
+ const msg = errMsg$2(err);
62233
63383
  deps.logger.error("binding-changed re-dispatch failed", {
62234
63384
  tags: { deviceId },
62235
63385
  meta: {
@@ -62257,7 +63407,7 @@ function wireOrchestratorSubscriptions(deps) {
62257
63407
  tags: { deviceId },
62258
63408
  meta: {
62259
63409
  detected,
62260
- error: errMsg$1(err)
63410
+ error: errMsg$2(err)
62261
63411
  }
62262
63412
  });
62263
63413
  });
@@ -62304,7 +63454,7 @@ function wireOrchestratorSubscriptions(deps) {
62304
63454
  tags: { deviceId },
62305
63455
  meta: {
62306
63456
  detected,
62307
- error: errMsg$1(err)
63457
+ error: errMsg$2(err)
62308
63458
  }
62309
63459
  });
62310
63460
  });
@@ -62570,7 +63720,7 @@ async function reattachAfterPackageZoneChange(deps, deviceId) {
62570
63720
  deviceId,
62571
63721
  nodeId
62572
63722
  },
62573
- meta: { error: errMsg$1(err) }
63723
+ meta: { error: errMsg$2(err) }
62574
63724
  });
62575
63725
  }
62576
63726
  }
@@ -62851,7 +64001,7 @@ async function resetRemovedModelPins(deps, trigger) {
62851
64001
  } catch (err) {
62852
64002
  deps.logger.warn("removed-model pin reset skipped — custom model registry unreadable", { meta: {
62853
64003
  trigger,
62854
- error: errMsg$1(err)
64004
+ error: errMsg$2(err)
62855
64005
  } });
62856
64006
  return [];
62857
64007
  }
@@ -62870,7 +64020,7 @@ async function resetRemovedModelPins(deps, trigger) {
62870
64020
  } catch (err) {
62871
64021
  deps.logger.warn("removed-model pin reset failed", { meta: {
62872
64022
  trigger,
62873
- error: errMsg$1(err)
64023
+ error: errMsg$2(err)
62874
64024
  } });
62875
64025
  return [];
62876
64026
  }
@@ -63229,6 +64379,21 @@ var PipelineSettingsStore = class PipelineSettingsStore {
63229
64379
  });
63230
64380
  }
63231
64381
  /**
64382
+ * Rewrite the whole agent-settings map in ONE read-modify-write under
64383
+ * {@link blobWriteLock} — atomic across every node of the blob (Best
64384
+ * settings, D745, writes several nodes' devices at once). `transform` must
64385
+ * be pure: it may run on a fresh read.
64386
+ */
64387
+ async rewriteAgentSettingsMap(transform) {
64388
+ await this.blobWriteLock.run(PipelineSettingsStore.AGENT_SETTINGS_LOCK_KEY, async () => {
64389
+ await this.agentSettingsState.set(transform(await this.readAgentSettingsMap()));
64390
+ });
64391
+ }
64392
+ /** Rewrite the whole camera-settings map in one durable `update()` (D745). */
64393
+ async rewriteCameraSettingsMap(transform) {
64394
+ await this.cameraSettingsState.update(transform);
64395
+ }
64396
+ /**
63232
64397
  * Drop one agent's persisted settings entry. Returns whether an entry
63233
64398
  * existed (`false` also when the store is already disposed — the locked
63234
64399
  * section becomes a no-op then). Serialized via {@link blobWriteLock} for
@@ -63485,6 +64650,10 @@ var PipelineSettingsStore = class PipelineSettingsStore {
63485
64650
  * epochs and operator resets — a bound, not a budget.
63486
64651
  */
63487
64652
  static CATALOG_FENCE_REREADS = 3;
64653
+ /** The node's catalog, read through the cache (awaits the node when cold). */
64654
+ async readCatalogForNode(nodeId) {
64655
+ return this.getCatalogForAgent(nodeId);
64656
+ }
63488
64657
  async getCatalogForAgent(nodeId) {
63489
64658
  const api = this.deps.api();
63490
64659
  if (!api) return null;
@@ -63502,7 +64671,7 @@ var PipelineSettingsStore = class PipelineSettingsStore {
63502
64671
  this.catalogReadFailing.add(nodeId);
63503
64672
  this.deps.logger.warn("node catalog read failed — placements on it wait or stay pending", {
63504
64673
  tags: { nodeId },
63505
- meta: { error: errMsg$1(err) }
64674
+ meta: { error: errMsg$2(err) }
63506
64675
  });
63507
64676
  }
63508
64677
  return null;
@@ -63589,7 +64758,7 @@ var PipelineSettingsStore = class PipelineSettingsStore {
63589
64758
  tags: { deviceId },
63590
64759
  meta: {
63591
64760
  capName: "detection-pipeline",
63592
- error: errMsg$1(err)
64761
+ error: errMsg$2(err)
63593
64762
  }
63594
64763
  });
63595
64764
  return true;
@@ -64422,7 +65591,7 @@ var ReconcileController = class ReconcileController {
64422
65591
  try {
64423
65592
  slots = await api.streamBroker.listAllProfileSlots.query();
64424
65593
  } catch (err) {
64425
- this.deps.logger.debug("dispatch reconcile skipped — stream-broker not ready", { meta: { error: errMsg$1(err) } });
65594
+ this.deps.logger.debug("dispatch reconcile skipped — stream-broker not ready", { meta: { error: errMsg$2(err) } });
64426
65595
  return;
64427
65596
  }
64428
65597
  const desired = desiredDeviceIdsFromSlots(slots);
@@ -64449,7 +65618,7 @@ var ReconcileController = class ReconcileController {
64449
65618
  } catch (err) {
64450
65619
  this.deps.logger.warn("dispatch reconcile: handleDeviceRegistered failed", {
64451
65620
  tags: { deviceId },
64452
- meta: { error: errMsg$1(err) }
65621
+ meta: { error: errMsg$2(err) }
64453
65622
  });
64454
65623
  }
64455
65624
  await this.reconcilePlacementFromRunners();
@@ -64477,7 +65646,7 @@ var ReconcileController = class ReconcileController {
64477
65646
  const devices = await api.deviceManager.listAll.query({ projection: "slim" });
64478
65647
  return new Set(devices.map((device) => device.id));
64479
65648
  } catch (err) {
64480
- this.deps.logger.debug("dispatch reconcile: deviceManager.listAll failed", { meta: { error: errMsg$1(err) } });
65649
+ this.deps.logger.debug("dispatch reconcile: deviceManager.listAll failed", { meta: { error: errMsg$2(err) } });
64481
65650
  return null;
64482
65651
  }
64483
65652
  }
@@ -64503,7 +65672,7 @@ var ReconcileController = class ReconcileController {
64503
65672
  } catch (err) {
64504
65673
  this.deps.logger.warn("dispatch reconcile: teardown of vanished device failed", {
64505
65674
  tags: { deviceId },
64506
- meta: { error: errMsg$1(err) }
65675
+ meta: { error: errMsg$2(err) }
64507
65676
  });
64508
65677
  }
64509
65678
  }
@@ -64560,7 +65729,7 @@ var ReconcileController = class ReconcileController {
64560
65729
  this.deps.logger.debug("placement reconcile skipped node — getLocalCameras failed", {
64561
65730
  tags: { nodeId },
64562
65731
  meta: {
64563
- error: outcome.kind === "rejected" ? errMsg$1(outcome.error) : "budget exceeded",
65732
+ error: outcome.kind === "rejected" ? errMsg$2(outcome.error) : "budget exceeded",
64564
65733
  ms
64565
65734
  }
64566
65735
  });
@@ -64590,7 +65759,7 @@ var ReconcileController = class ReconcileController {
64590
65759
  nodeId,
64591
65760
  deviceId
64592
65761
  },
64593
- meta: { error: errMsg$1(err) }
65762
+ meta: { error: errMsg$2(err) }
64594
65763
  });
64595
65764
  })));
64596
65765
  }
@@ -64632,7 +65801,7 @@ var ReconcileController = class ReconcileController {
64632
65801
  this.deps.handleDeviceRegistered(deviceId).catch((err) => {
64633
65802
  this.deps.logger.debug("slot-read retry: handleDeviceRegistered failed", {
64634
65803
  tags: { deviceId },
64635
- meta: { error: errMsg$1(err) }
65804
+ meta: { error: errMsg$2(err) }
64636
65805
  });
64637
65806
  });
64638
65807
  }, delay);
@@ -64710,7 +65879,7 @@ var ReconcileController = class ReconcileController {
64710
65879
  } catch (err) {
64711
65880
  this.deps.logger.warn("pending retry: dispatchCamera failed", {
64712
65881
  tags: { deviceId },
64713
- meta: { error: errMsg$1(err) }
65882
+ meta: { error: errMsg$2(err) }
64714
65883
  });
64715
65884
  stillPending++;
64716
65885
  }
@@ -64788,7 +65957,7 @@ var ReconcileController = class ReconcileController {
64788
65957
  await this.deps.detachOn(assignment.agentNodeId, deviceId).catch((err) => {
64789
65958
  this.deps.logger.debug("remote-health exhausted-detach failed", {
64790
65959
  tags: { deviceId },
64791
- meta: { error: errMsg$1(err) }
65960
+ meta: { error: errMsg$2(err) }
64792
65961
  });
64793
65962
  });
64794
65963
  return;
@@ -64811,14 +65980,14 @@ var ReconcileController = class ReconcileController {
64811
65980
  await this.deps.detachOn(assignment.agentNodeId, deviceId).catch((err) => {
64812
65981
  this.deps.logger.warn("remote-health detach failed", {
64813
65982
  tags: { deviceId },
64814
- meta: { error: errMsg$1(err) }
65983
+ meta: { error: errMsg$2(err) }
64815
65984
  });
64816
65985
  });
64817
65986
  this.deps.ledger.dropAssignment(deviceId, "silent");
64818
65987
  await this.deps.redispatchCamera(cfg).catch((err) => {
64819
65988
  this.deps.logger.warn("remote-health re-dispatch failed", {
64820
65989
  tags: { deviceId },
64821
- meta: { error: errMsg$1(err) }
65990
+ meta: { error: errMsg$2(err) }
64822
65991
  });
64823
65992
  });
64824
65993
  }
@@ -64993,7 +66162,7 @@ var OccupancyRecheckController = class {
64993
66162
  tags: { deviceId },
64994
66163
  meta: {
64995
66164
  deviceId,
64996
- error: errMsg$1(err)
66165
+ error: errMsg$2(err)
64997
66166
  }
64998
66167
  });
64999
66168
  });
@@ -65127,7 +66296,7 @@ var OccupancyRecheckController = class {
65127
66296
  tags: { deviceId },
65128
66297
  meta: {
65129
66298
  deviceId,
65130
- error: errMsg$1(err)
66299
+ error: errMsg$2(err)
65131
66300
  }
65132
66301
  });
65133
66302
  });
@@ -65182,7 +66351,7 @@ var OccupancyRecheckController = class {
65182
66351
  meta: {
65183
66352
  deviceId,
65184
66353
  nodeId,
65185
- error: errMsg$1(err),
66354
+ error: errMsg$2(err),
65186
66355
  kind
65187
66356
  }
65188
66357
  });
@@ -65191,7 +66360,7 @@ var OccupancyRecheckController = class {
65191
66360
  }
65192
66361
  const answer = await this.deps.requestBurstOn(nodeId, deviceId, frames, reasonTag).catch((err) => ({
65193
66362
  started: false,
65194
- error: errMsg$1(err)
66363
+ error: errMsg$2(err)
65195
66364
  }));
65196
66365
  if (!answer.started) {
65197
66366
  const refusedBecause = "refusedBecause" in answer ? answer.refusedBecause : void 0;
@@ -65239,7 +66408,7 @@ var OccupancyRecheckController = class {
65239
66408
  meta: {
65240
66409
  deviceId,
65241
66410
  nodeId,
65242
- error: errMsg$1(err)
66411
+ error: errMsg$2(err)
65243
66412
  }
65244
66413
  });
65245
66414
  });
@@ -65296,7 +66465,7 @@ var OccupancyRecheckController = class {
65296
66465
  meta: {
65297
66466
  deviceId,
65298
66467
  nodeId: burst.nodeId,
65299
- error: errMsg$1(err)
66468
+ error: errMsg$2(err)
65300
66469
  }
65301
66470
  });
65302
66471
  });
@@ -65953,7 +67122,7 @@ var SessionDispatchController = class {
65953
67122
  deviceId,
65954
67123
  nodeId: session.nodeId
65955
67124
  },
65956
- meta: { error: errMsg$1(err) }
67125
+ meta: { error: errMsg$2(err) }
65957
67126
  });
65958
67127
  });
65959
67128
  this.deps.dropAssignment(deviceId, "silent");
@@ -66170,7 +67339,7 @@ var SessionDispatchController = class {
66170
67339
  };
66171
67340
  if (reconcileBudgetExceeded) log.warn("detection session: placement census overran its budget — placing without it", { meta: { budgetMs: 500 } });
66172
67341
  else if (censusOutcome.kind === "rejected") log.warn("detection session: placement census failed — placing on the ledger as it is", { meta: {
66173
- error: errMsg$1(censusOutcome.error),
67342
+ error: errMsg$2(censusOutcome.error),
66174
67343
  reconcileMs
66175
67344
  } });
66176
67345
  const balanceStartedAt = Date.now();
@@ -66364,7 +67533,7 @@ var SessionDispatchController = class {
66364
67533
  await this.resolveSessionLifecycleLocked(deviceId, reason);
66365
67534
  }).catch((err) => {
66366
67535
  log.warn("detection session: lifecycle re-check failed", { meta: {
66367
- error: errMsg$1(err),
67536
+ error: errMsg$2(err),
66368
67537
  reason
66369
67538
  } });
66370
67539
  });
@@ -66469,7 +67638,7 @@ var SessionDispatchController = class {
66469
67638
  log.warn("detection session: teardown detach failed", {
66470
67639
  tags: { nodeId: session.nodeId },
66471
67640
  meta: {
66472
- error: errMsg$1(err),
67641
+ error: errMsg$2(err),
66473
67642
  reason
66474
67643
  }
66475
67644
  });
@@ -66576,7 +67745,7 @@ var SessionDispatchController = class {
66576
67745
  deviceId,
66577
67746
  nodeId: session.nodeId
66578
67747
  },
66579
- meta: { error: errMsg$1(err) }
67748
+ meta: { error: errMsg$2(err) }
66580
67749
  });
66581
67750
  });
66582
67751
  this.sessionRegistry.deregister(deviceId);
@@ -66597,7 +67766,7 @@ var SessionDispatchController = class {
66597
67766
  deviceId,
66598
67767
  nodeId: watchNodeId
66599
67768
  },
66600
- meta: { error: errMsg$1(err) }
67769
+ meta: { error: errMsg$2(err) }
66601
67770
  });
66602
67771
  });
66603
67772
  this.motionWatchByDevice.delete(deviceId);
@@ -66780,7 +67949,7 @@ async function sweepMirror(deps, hydrator, cameraIds) {
66780
67949
  tags: { deviceId },
66781
67950
  meta: {
66782
67951
  mirror: hydrator.mirror,
66783
- error: errMsg$1(err)
67952
+ error: errMsg$2(err)
66784
67953
  }
66785
67954
  });
66786
67955
  return "threw";
@@ -66820,7 +67989,7 @@ async function readCameraIds(deps) {
66820
67989
  lastError = err;
66821
67990
  }
66822
67991
  if (Date.now() >= deadline) {
66823
- deps.logger.warn("zones mirror boot hydration SKIPPED — camera list never became readable; mirror-only consumers keep whatever the last write left", { meta: { error: lastError === null ? "ctx.api unavailable" : errMsg$1(lastError) } });
67992
+ deps.logger.warn("zones mirror boot hydration SKIPPED — camera list never became readable; mirror-only consumers keep whatever the last write left", { meta: { error: lastError === null ? "ctx.api unavailable" : errMsg$2(lastError) } });
66824
67993
  return null;
66825
67994
  }
66826
67995
  await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
@@ -67441,6 +68610,7 @@ async function buildOrchestratorControllers(deps) {
67441
68610
  readGlobalSettings: () => globalSettings,
67442
68611
  onCensus: (entries) => {
67443
68612
  motionSliceGate?.noteCensus(entries);
68613
+ deps.onRunnerCensus(entries.flatMap((e) => e.load === null ? [] : [e.load]));
67444
68614
  providerSliceGate?.refreshIfStale("runner-census");
67445
68615
  }
67446
68616
  });
@@ -67467,7 +68637,7 @@ async function buildOrchestratorControllers(deps) {
67467
68637
  } catch (err) {
67468
68638
  return {
67469
68639
  kind: "unknown",
67470
- reason: errMsg$1(err)
68640
+ reason: errMsg$2(err)
67471
68641
  };
67472
68642
  }
67473
68643
  };
@@ -67552,14 +68722,14 @@ async function buildOrchestratorControllers(deps) {
67552
68722
  readGlobalSettings: () => globalSettings
67553
68723
  });
67554
68724
  await NodeStressMoveLedger.declare(deps.ctx().api.settingsStore).then(() => nodeStress.hydrate()).catch((err) => {
67555
- deps.ctx().logger.warn("node-stress move history unavailable — this boot starts cold", { meta: { error: errMsg$1(err) } });
68725
+ deps.ctx().logger.warn("node-stress move history unavailable — this boot starts cold", { meta: { error: errMsg$2(err) } });
67556
68726
  });
67557
68727
  await NodeStressLts.declare(deps.ctx().api.settingsStore).catch((err) => {
67558
- deps.ctx().logger.warn("node-stress statistics unavailable — no baseline this boot", { meta: { error: errMsg$1(err) } });
68728
+ deps.ctx().logger.warn("node-stress statistics unavailable — no baseline this boot", { meta: { error: errMsg$2(err) } });
67559
68729
  });
67560
68730
  const nodeStressLtsTimer = setInterval(() => {
67561
68731
  nodeStressLts.flushDue().catch((err) => {
67562
- deps.ctx().logger.warn("node-stress statistics flush failed", { meta: { error: errMsg$1(err) } });
68732
+ deps.ctx().logger.warn("node-stress statistics flush failed", { meta: { error: errMsg$2(err) } });
67563
68733
  });
67564
68734
  }, 6e4);
67565
68735
  nodeStress.start();
@@ -67709,7 +68879,7 @@ async function buildOrchestratorControllers(deps) {
67709
68879
  } catch (err) {
67710
68880
  return {
67711
68881
  kind: "unreadable",
67712
- error: errMsg$1(err)
68882
+ error: errMsg$2(err)
67713
68883
  };
67714
68884
  }
67715
68885
  };
@@ -67743,7 +68913,7 @@ async function buildOrchestratorControllers(deps) {
67743
68913
  } catch (err) {
67744
68914
  return {
67745
68915
  kind: "unreadable",
67746
- error: errMsg$1(err)
68916
+ error: errMsg$2(err)
67747
68917
  };
67748
68918
  }
67749
68919
  },
@@ -67774,7 +68944,7 @@ async function buildOrchestratorControllers(deps) {
67774
68944
  } catch (err) {
67775
68945
  return {
67776
68946
  kind: "unreadable",
67777
- error: errMsg$1(err)
68947
+ error: errMsg$2(err)
67778
68948
  };
67779
68949
  }
67780
68950
  },
@@ -67787,7 +68957,7 @@ async function buildOrchestratorControllers(deps) {
67787
68957
  } catch (err) {
67788
68958
  return {
67789
68959
  kind: "unreadable",
67790
- error: errMsg$1(err)
68960
+ error: errMsg$2(err)
67791
68961
  };
67792
68962
  }
67793
68963
  },
@@ -67858,7 +69028,7 @@ async function buildOrchestratorControllers(deps) {
67858
69028
  deps.applyRuntimeSettings(globalSettings);
67859
69029
  await topology.refreshNodeCapabilities();
67860
69030
  } catch (err) {
67861
- const msg = errMsg$1(err);
69031
+ const msg = errMsg$2(err);
67862
69032
  deps.ctx().logger.warn("orchestrator settings load failed — using defaults", { meta: { error: msg } });
67863
69033
  }
67864
69034
  const rawNodeId = deps.ctx().kernel.localNodeId ?? "hub";
@@ -67899,7 +69069,7 @@ async function buildOrchestratorControllers(deps) {
67899
69069
  audio.markNodeOffline(nodeId);
67900
69070
  deps.forgetInferenceDeviceProbe(nodeId);
67901
69071
  nodeLifecycle.handleNodeDisconnect(nodeId).catch((err) => {
67902
- const msg = errMsg$1(err);
69072
+ const msg = errMsg$2(err);
67903
69073
  deps.ctx().logger.error("failover after disconnect failed", {
67904
69074
  tags: { nodeId },
67905
69075
  meta: { error: msg }
@@ -67934,7 +69104,7 @@ async function buildOrchestratorControllers(deps) {
67934
69104
  deps.subscribeToAudioAnalyzerReadiness(nodeId);
67935
69105
  }
67936
69106
  nodeLifecycle.handleNodeConnect(nodeId).catch((err) => {
67937
- const msg = errMsg$1(err);
69107
+ const msg = errMsg$2(err);
67938
69108
  deps.ctx().logger.error("reconnect handling failed", {
67939
69109
  tags: { nodeId },
67940
69110
  meta: { error: msg }
@@ -67953,7 +69123,7 @@ async function buildOrchestratorControllers(deps) {
67953
69123
  meta: { capName }
67954
69124
  });
67955
69125
  nodeLifecycle.handleNodeDisconnect(nodeId).catch((err) => {
67956
- const msg = errMsg$1(err);
69126
+ const msg = errMsg$2(err);
67957
69127
  deps.ctx().logger.error("failover after cap-down failed", {
67958
69128
  tags: { nodeId },
67959
69129
  meta: {
@@ -68016,7 +69186,7 @@ async function buildOrchestratorControllers(deps) {
68016
69186
  doneFlag: settingsStore.bindingsMigrationDoneState,
68017
69187
  logger: deps.ctx().logger
68018
69188
  }).catch((err) => {
68019
- deps.ctxIfReady()?.logger.warn("bindings migration failed — will retry on next boot", { meta: { error: errMsg$1(err) } });
69189
+ deps.ctxIfReady()?.logger.warn("bindings migration failed — will retry on next boot", { meta: { error: errMsg$2(err) } });
68020
69190
  });
68021
69191
  migrateStepGating({
68022
69192
  readCameraSettings: () => settingsStore.readCameraSettingsMap(),
@@ -68029,7 +69199,7 @@ async function buildOrchestratorControllers(deps) {
68029
69199
  for (let i = 0; deps.ctxIfReady()?.api == null && i < 30; i++) await new Promise((r) => setTimeout(r, 200));
68030
69200
  if (deps.ctxIfReady() !== null) await deps.resetRemovedModelPins("boot");
68031
69201
  }).catch((err) => {
68032
- deps.ctxIfReady()?.logger.warn("step-gating / per-node-device migration failed — retry next boot", { meta: { error: errMsg$1(err) } });
69202
+ deps.ctxIfReady()?.logger.warn("step-gating / per-node-device migration failed — retry next boot", { meta: { error: errMsg$2(err) } });
68033
69203
  });
68034
69204
  const zoneRulesProvider = new ZoneRulesProvider({
68035
69205
  logger: deps.ctx().logger.child("zone-rules"),
@@ -68071,7 +69241,7 @@ async function buildOrchestratorControllers(deps) {
68071
69241
  deviceId,
68072
69242
  nodeId: assignment.agentNodeId
68073
69243
  },
68074
- meta: { error: errMsg$1(err) }
69244
+ meta: { error: errMsg$2(err) }
68075
69245
  });
68076
69246
  });
68077
69247
  }
@@ -68087,7 +69257,7 @@ async function buildOrchestratorControllers(deps) {
68087
69257
  }],
68088
69258
  logger: deps.ctx().logger.child("zones")
68089
69259
  }).catch((err) => {
68090
- deps.ctxIfReady()?.logger.warn("zones mirror boot hydration failed", { meta: { error: errMsg$1(err) } });
69260
+ deps.ctxIfReady()?.logger.warn("zones mirror boot hydration failed", { meta: { error: errMsg$2(err) } });
68091
69261
  });
68092
69262
  const unsubOrchestratorSubscriptions = wireOrchestratorSubscriptions({
68093
69263
  eventBus: deps.ctx().eventBus,
@@ -68148,10 +69318,10 @@ async function buildOrchestratorControllers(deps) {
68148
69318
  deviceSettingsSchema: () => deps.deviceSettingsSchema()
68149
69319
  });
68150
69320
  await detectionWiring.hydrateFeaturesMirror().catch((err) => {
68151
- deps.ctx().logger.warn("device-features mirror not restored — this boot starts cold", { meta: { error: errMsg$1(err) } });
69321
+ deps.ctx().logger.warn("device-features mirror not restored — this boot starts cold", { meta: { error: errMsg$2(err) } });
68152
69322
  });
68153
69323
  cameraSensors.start().catch((err) => {
68154
- deps.ctx().logger.warn("camera sensors: boot refresh failed — waiting for the next lifecycle event", { meta: { error: errMsg$1(err) } });
69324
+ deps.ctx().logger.warn("camera sensors: boot refresh failed — waiting for the next lifecycle event", { meta: { error: errMsg$2(err) } });
68155
69325
  });
68156
69326
  return {
68157
69327
  ledger,
@@ -68783,6 +69953,633 @@ var pipelineOrchestratorWidgets = [{
68783
69953
  defaultRows: 4
68784
69954
  }];
68785
69955
  //#endregion
69956
+ //#region src/best-settings/plan-diff.ts
69957
+ /**
69958
+ * The Best settings writes, as pure transforms of the orchestrator's two
69959
+ * settings blobs (D745). The plan runs them on the current blobs to SHOW the
69960
+ * diff; the apply runs the same functions inside each blob's read-modify-write.
69961
+ *
69962
+ * What changes — and nothing else:
69963
+ * - node level (`agentSettings[node].inferenceDevices[deviceKey].steps`): the
69964
+ * object-detection `modelId` of every recommended device is set; the
69965
+ * `modelId` pin of every other in-scope step is removed (each runs its
69966
+ * step default again); every model-bound threshold (and its binding key) is
69967
+ * removed from every device's step `settings`, so the running model's own
69968
+ * defaults apply (D738–D741). `enabled`, `weight`, `maxSessions`,
69969
+ * `jumpDeviceKey` and every other setting are kept.
69970
+ * - camera level, scoped to the touched nodes: the same `modelId` pins and
69971
+ * model-bound thresholds are removed from `stepOverridesByDevice[node]` and
69972
+ * from the wholesale `pipelineByAgent[node]` snapshot (recursively). A
69973
+ * wholesale step keeps `enabled`, `children`, `jumpDeviceKey` and its other
69974
+ * settings: the attach provisions the node pin over the snapshot
69975
+ * (`applyDeviceProvisioning`: override ?? base), so once the snapshot pin
69976
+ * is gone the node's recommended model runs.
69977
+ * - never: `stepToggles`, `pinnedAgentNodeId`, devices' enablement, pins of
69978
+ * embedding steps (cluster-scoped, changing one forces a re-index).
69979
+ */
69980
+ /** The root detector step. */
69981
+ var DETECTOR_STEP_ID = "object-detection";
69982
+ /** Steps whose `modelId` pin Best settings never touches. */
69983
+ function isModelPinKept(stepId) {
69984
+ return stepId.endsWith("-embedding");
69985
+ }
69986
+ function boundKeysFor(input, stepId) {
69987
+ return input.boundKeysByStep[stepId] ?? [];
69988
+ }
69989
+ /** The model-bound keys present in `settings` (values or bindings), and the rest. */
69990
+ function stripBound(settings, boundKeys) {
69991
+ if (settings === void 0) return {
69992
+ kept: void 0,
69993
+ removed: []
69994
+ };
69995
+ const kept = { ...settings };
69996
+ const removed = [];
69997
+ for (const key of boundKeys) for (const k of [key, modelBindingKey(key)]) if (k in kept) {
69998
+ delete kept[k];
69999
+ removed.push(k);
70000
+ }
70001
+ return {
70002
+ kept,
70003
+ removed: removed.toSorted()
70004
+ };
70005
+ }
70006
+ function pinInScope(stepId) {
70007
+ return !isModelPinKept(stepId);
70008
+ }
70009
+ /** Rebuild one stored step without the in-scope pin and bound keys. */
70010
+ function scrubStep(stepId, step, boundKeys, targetModel) {
70011
+ const before = step.modelId ?? null;
70012
+ let modelId = step.modelId;
70013
+ if (stepId === "object-detection") {
70014
+ if (targetModel !== void 0) modelId = targetModel;
70015
+ } else if (pinInScope(stepId)) modelId = void 0;
70016
+ const { kept, removed } = stripBound(step.settings, boundKeys);
70017
+ const after = modelId ?? null;
70018
+ const next = {
70019
+ ...modelId !== void 0 ? { modelId } : {},
70020
+ ...kept !== void 0 && Object.keys(kept).length > 0 ? { settings: kept } : {},
70021
+ ...step.jumpDeviceKey !== void 0 ? { jumpDeviceKey: step.jumpDeviceKey } : {}
70022
+ };
70023
+ return {
70024
+ next: Object.keys(next).length > 0 ? next : null,
70025
+ pin: before !== after ? {
70026
+ stepId,
70027
+ before,
70028
+ after
70029
+ } : null,
70030
+ cleared: removed
70031
+ };
70032
+ }
70033
+ /** The node-level transform. Returns the next blob and every change it made. */
70034
+ function applyNodeBestSettings(blob, input) {
70035
+ const next = { ...blob };
70036
+ const changes = [];
70037
+ for (const nodeId of [...input.touchedNodes].toSorted()) {
70038
+ const entry = blob[nodeId];
70039
+ const targets = new Map((input.targets.get(nodeId) ?? []).map((t) => [t.deviceKey, t]));
70040
+ const stored = entry?.inferenceDevices ?? {};
70041
+ const deviceKeys = [...new Set([...Object.keys(stored), ...targets.keys()])].toSorted();
70042
+ const nextDevices = {};
70043
+ for (const deviceKey of deviceKeys) {
70044
+ const device = stored[deviceKey];
70045
+ const target = targets.get(deviceKey);
70046
+ const steps = { ...device?.steps };
70047
+ if (target !== void 0 && steps["object-detection"] === void 0) steps[DETECTOR_STEP_ID] = {};
70048
+ const nextSteps = {};
70049
+ const pins = [];
70050
+ const thresholds = [];
70051
+ for (const stepId of Object.keys(steps).toSorted()) {
70052
+ const step = steps[stepId];
70053
+ if (step === void 0) continue;
70054
+ const scrubbed = scrubStep(stepId, step, boundKeysFor(input, stepId), target?.modelId);
70055
+ if (scrubbed.next !== null) nextSteps[stepId] = scrubbed.next;
70056
+ if (scrubbed.pin !== null) pins.push(scrubbed.pin);
70057
+ if (scrubbed.cleared.length > 0) thresholds.push({
70058
+ stepId,
70059
+ keys: scrubbed.cleared
70060
+ });
70061
+ }
70062
+ const madeExplicit = device === void 0;
70063
+ nextDevices[deviceKey] = {
70064
+ ...device !== void 0 ? (({ steps: _steps, ...rest }) => rest)(device) : { enabled: target?.effectiveEnabled ?? false },
70065
+ ...Object.keys(nextSteps).length > 0 ? { steps: nextSteps } : {}
70066
+ };
70067
+ if (pins.length > 0 || thresholds.length > 0) changes.push({
70068
+ nodeId,
70069
+ deviceKey,
70070
+ madeExplicit,
70071
+ modelPins: pins,
70072
+ thresholds
70073
+ });
70074
+ }
70075
+ if (entry === void 0 && deviceKeys.length === 0) continue;
70076
+ next[nodeId] = {
70077
+ ...entry ?? { maxCameras: null },
70078
+ ...deviceKeys.length > 0 ? { inferenceDevices: nextDevices } : {}
70079
+ };
70080
+ }
70081
+ return {
70082
+ next,
70083
+ changes
70084
+ };
70085
+ }
70086
+ /** Scrub a wholesale snapshot tree; collects one clear per changed step. */
70087
+ function scrubTree(steps, input, onClear) {
70088
+ return steps.map((step) => {
70089
+ const children = step.children ? scrubTree(step.children, input, onClear) : void 0;
70090
+ const inScope = step.addonId === "object-detection" || pinInScope(step.addonId);
70091
+ const { kept, removed } = stripBound(step.settings, boundKeysFor(input, step.addonId));
70092
+ const dropPin = inScope && step.modelId !== void 0;
70093
+ if (dropPin || removed.length > 0) onClear(step.addonId, dropPin ? step.modelId ?? null : null, removed);
70094
+ const { modelId: _modelId, settings: _settings, children: _children, ...rest } = step;
70095
+ return {
70096
+ ...rest,
70097
+ ...!dropPin && step.modelId !== void 0 ? { modelId: step.modelId } : {},
70098
+ ...kept !== void 0 && Object.keys(kept).length > 0 ? { settings: kept } : {},
70099
+ ...children !== void 0 ? { children } : {}
70100
+ };
70101
+ });
70102
+ }
70103
+ /** The camera-level transform. Returns the next blob and every override it removed. */
70104
+ function applyCameraBestSettings(blob, input) {
70105
+ const next = {};
70106
+ const clears = [];
70107
+ for (const key of Object.keys(blob).toSorted((a, b) => Number(a) - Number(b))) {
70108
+ const camera = blob[key];
70109
+ if (camera === void 0) continue;
70110
+ const deviceId = Number(key);
70111
+ let nextCamera = camera;
70112
+ const byNode = camera.stepOverridesByDevice;
70113
+ if (byNode !== void 0) {
70114
+ const nextByNode = {};
70115
+ for (const [nodeId, byDevice] of Object.entries(byNode)) {
70116
+ if (!input.touchedNodes.has(nodeId)) {
70117
+ nextByNode[nodeId] = byDevice;
70118
+ continue;
70119
+ }
70120
+ const nextByDevice = {};
70121
+ for (const [deviceKey, byStep] of Object.entries(byDevice)) {
70122
+ const nextByStep = {};
70123
+ for (const [stepId, patch] of Object.entries(byStep)) {
70124
+ const inScope = stepId === "object-detection" || pinInScope(stepId);
70125
+ const { kept, removed } = stripBound(patch.settings, boundKeysFor(input, stepId));
70126
+ const dropPin = inScope && patch.modelId !== void 0;
70127
+ if (dropPin || removed.length > 0) clears.push({
70128
+ deviceId,
70129
+ source: "step-override",
70130
+ nodeId,
70131
+ deviceKey,
70132
+ stepId,
70133
+ modelIdBefore: dropPin ? patch.modelId ?? null : null,
70134
+ keys: removed
70135
+ });
70136
+ const rebuilt = {
70137
+ ...!dropPin && patch.modelId !== void 0 ? { modelId: patch.modelId } : {},
70138
+ ...kept !== void 0 && Object.keys(kept).length > 0 ? { settings: kept } : {}
70139
+ };
70140
+ if (Object.keys(rebuilt).length > 0) nextByStep[stepId] = rebuilt;
70141
+ }
70142
+ if (Object.keys(nextByStep).length > 0) nextByDevice[deviceKey] = nextByStep;
70143
+ }
70144
+ if (Object.keys(nextByDevice).length > 0) nextByNode[nodeId] = nextByDevice;
70145
+ }
70146
+ const { stepOverridesByDevice: _drop, ...rest } = nextCamera;
70147
+ nextCamera = {
70148
+ ...rest,
70149
+ ...Object.keys(nextByNode).length > 0 ? { stepOverridesByDevice: nextByNode } : {}
70150
+ };
70151
+ }
70152
+ const wholesale = camera.pipelineByAgent;
70153
+ if (wholesale !== void 0) {
70154
+ const nextWholesale = {};
70155
+ for (const [nodeId, snapshot] of Object.entries(wholesale)) {
70156
+ if (!input.touchedNodes.has(nodeId)) {
70157
+ nextWholesale[nodeId] = snapshot;
70158
+ continue;
70159
+ }
70160
+ nextWholesale[nodeId] = {
70161
+ ...snapshot,
70162
+ steps: scrubTree(snapshot.steps, input, (stepId, modelIdBefore, keys) => {
70163
+ clears.push({
70164
+ deviceId,
70165
+ source: "wholesale",
70166
+ nodeId,
70167
+ deviceKey: null,
70168
+ stepId,
70169
+ modelIdBefore,
70170
+ keys
70171
+ });
70172
+ })
70173
+ };
70174
+ }
70175
+ nextCamera = {
70176
+ ...nextCamera,
70177
+ pipelineByAgent: nextWholesale
70178
+ };
70179
+ }
70180
+ next[key] = nextCamera;
70181
+ }
70182
+ return {
70183
+ next,
70184
+ clears
70185
+ };
70186
+ }
70187
+ //#endregion
70188
+ //#region src/best-settings/best-settings-service.ts
70189
+ /**
70190
+ * Best settings (D745): gather the inputs, compute the plan, and apply exactly
70191
+ * the plan the operator confirmed.
70192
+ *
70193
+ * The plan is the recommender (`@camstack/types` best-settings) over every
70194
+ * runner node, plus the diff of the writes (`plan-diff.ts`) and the
70195
+ * per-camera detector thresholds the hub's executor would clear. Its
70196
+ * `planHash` names everything the apply writes; the apply recomputes the plan
70197
+ * and refuses a different hash, so a configuration change between the two is
70198
+ * shown to the operator again instead of being written over.
70199
+ *
70200
+ * Apply order, each phase reported:
70201
+ * 1. download every newly picked detector on its node FIRST — a node never
70202
+ * switches to a model it does not have (an N100 compile takes 5–25 s, a
70203
+ * Core ML package 40 MB); any failure stops the apply before a write;
70204
+ * 2. camera overrides (one camera-blob read-modify-write);
70205
+ * 3. per-camera detector thresholds (the hub executor's store);
70206
+ * 4. node model pins (one agent-blob read-modify-write);
70207
+ * 5. re-dispatch the cameras of every touched node.
70208
+ * A failure after phase 2 leaves "overrides cleared, previous node model",
70209
+ * which is a valid configuration (the defaults run); re-running converges.
70210
+ */
70211
+ function errMsg(err) {
70212
+ return err instanceof Error ? err.message : String(err);
70213
+ }
70214
+ /** Canonical JSON (sorted keys) — the plan hash must not depend on key order. */
70215
+ function canonical(value) {
70216
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
70217
+ if (typeof value === "object" && value !== null) return `{${Object.entries(value).filter(([, v]) => v !== void 0).toSorted(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(",")}}`;
70218
+ return JSON.stringify(value);
70219
+ }
70220
+ function formatsByModelOf(catalog) {
70221
+ const out = {};
70222
+ for (const slot of catalog?.slots ?? []) for (const addon of slot.addons) {
70223
+ if (addon.id !== "object-detection") continue;
70224
+ for (const model of addon.models) out[model.id] = MODEL_FORMATS.filter((f) => f in model.formats);
70225
+ }
70226
+ return out;
70227
+ }
70228
+ function boundKeysOf(catalogs) {
70229
+ const byStep = /* @__PURE__ */ new Map();
70230
+ for (const catalog of catalogs) for (const slot of catalog?.slots ?? []) for (const addon of slot.addons) {
70231
+ const keys = collectModelBoundKeys(addon.configSchema ?? []);
70232
+ if (keys.length === 0) continue;
70233
+ const set = byStep.get(addon.id) ?? /* @__PURE__ */ new Set();
70234
+ for (const k of keys) set.add(k);
70235
+ byStep.set(addon.id, set);
70236
+ }
70237
+ return Object.fromEntries([...byStep].map(([step, set]) => [step, [...set].toSorted()]));
70238
+ }
70239
+ var BestSettingsService = class {
70240
+ deps;
70241
+ applying = false;
70242
+ constructor(deps) {
70243
+ this.deps = deps;
70244
+ }
70245
+ async plan(input) {
70246
+ return (await this.computePlan(input.headroom)).plan;
70247
+ }
70248
+ async computePlan(headroom) {
70249
+ const nodeIds = [...new Set(["hub", ...this.deps.runnerNodeIds()])].toSorted();
70250
+ const gathered = await Promise.all(nodeIds.map(async (nodeId) => {
70251
+ const [devices, hardware, catalog] = await Promise.all([
70252
+ this.deps.readNodeDevices(nodeId),
70253
+ this.deps.readHardware(nodeId).catch(() => null),
70254
+ this.deps.readCatalog(nodeId).catch(() => null)
70255
+ ]);
70256
+ return {
70257
+ nodeId,
70258
+ devices,
70259
+ hardware,
70260
+ catalog
70261
+ };
70262
+ }));
70263
+ const nodes = gathered.map((g) => ({
70264
+ nodeId: g.nodeId,
70265
+ reachable: g.devices.reachable && g.catalog !== null,
70266
+ hardware: g.hardware,
70267
+ devices: g.devices.devices.map((d) => ({
70268
+ key: d.key,
70269
+ format: d.format,
70270
+ enabled: d.enabled,
70271
+ available: d.available
70272
+ })),
70273
+ formatsByModel: formatsByModelOf(g.catalog)
70274
+ }));
70275
+ const eligible = gathered.flatMap((g) => g.devices.reachable ? g.devices.devices.filter((d) => d.enabled && d.available).map((d) => ({
70276
+ nodeId: g.nodeId,
70277
+ deviceKey: d.key,
70278
+ weight: d.weight
70279
+ })) : []);
70280
+ const demand = estimateDeviceDemand({
70281
+ cameras: await this.deps.cameraLoads(),
70282
+ eligible,
70283
+ peaks: this.deps.peaks()
70284
+ });
70285
+ const recommendations = nodes.map((node) => recommendNode({
70286
+ node,
70287
+ demand,
70288
+ headroom
70289
+ }));
70290
+ const touchedNodes = new Set(recommendations.filter((r) => !r.skipped).map((r) => r.nodeId));
70291
+ const targets = /* @__PURE__ */ new Map();
70292
+ const formats = /* @__PURE__ */ new Map();
70293
+ for (const rec of recommendations) {
70294
+ if (rec.skipped) continue;
70295
+ const node = nodes.find((n) => n.nodeId === rec.nodeId);
70296
+ const list = [];
70297
+ const byKey = /* @__PURE__ */ new Map();
70298
+ for (const d of rec.devices) {
70299
+ if (d.chosen === null) continue;
70300
+ list.push({
70301
+ deviceKey: d.deviceKey,
70302
+ modelId: d.chosen.modelId,
70303
+ effectiveEnabled: d.enabled
70304
+ });
70305
+ const format = node?.devices.find((x) => x.key === d.deviceKey)?.format;
70306
+ if (format !== void 0) byKey.set(d.deviceKey, format);
70307
+ }
70308
+ targets.set(rec.nodeId, list);
70309
+ formats.set(rec.nodeId, byKey);
70310
+ }
70311
+ const diffInput = {
70312
+ touchedNodes,
70313
+ targets,
70314
+ boundKeysByStep: boundKeysOf(gathered.map((g) => g.catalog))
70315
+ };
70316
+ const nodeChanges = applyNodeBestSettings(await this.deps.readAgentSettings(), diffInput).changes;
70317
+ const cameraClears = applyCameraBestSettings(await this.deps.readCameraSettings(), diffInput).clears;
70318
+ const warnings = [];
70319
+ let detectorStoreClears = [];
70320
+ if (touchedNodes.size > 0) try {
70321
+ const answer = await this.deps.detectorStore({ dryRun: true });
70322
+ detectorStoreClears = answer.entries.map((e) => ({
70323
+ deviceId: e.deviceId,
70324
+ keys: [...e.keys]
70325
+ }));
70326
+ if (answer.refused.length > 0) warnings.push({
70327
+ kind: "detector-store-unreadable",
70328
+ error: `${answer.refused.length} camera store(s) unreadable`
70329
+ });
70330
+ } catch (err) {
70331
+ warnings.push({
70332
+ kind: "detector-store-unreadable",
70333
+ error: errMsg(err)
70334
+ });
70335
+ }
70336
+ const skippedNodes = recommendations.filter((r) => r.skipped).map((r) => r.nodeId);
70337
+ if (detectorStoreClears.length > 0 && skippedNodes.length > 0) warnings.push({
70338
+ kind: "detector-store-cluster-wide",
70339
+ skippedNodes
70340
+ });
70341
+ for (const rec of recommendations) for (const d of rec.devices) if (d.accelerator === "coral" && d.enabled) warnings.push({
70342
+ kind: "coral-enabled",
70343
+ nodeId: rec.nodeId,
70344
+ deviceKey: d.deviceKey
70345
+ });
70346
+ return {
70347
+ plan: {
70348
+ planHash: createHash("sha256").update(canonical({
70349
+ version: 1,
70350
+ headroom,
70351
+ picks: recommendations.map((r) => ({
70352
+ nodeId: r.nodeId,
70353
+ skipped: r.skipped,
70354
+ devices: r.devices.map((d) => [d.deviceKey, d.chosen?.modelId ?? null])
70355
+ })),
70356
+ nodeChanges,
70357
+ cameraClears,
70358
+ detectorStoreClears
70359
+ })).digest("hex"),
70360
+ family: "yolov9",
70361
+ headroom,
70362
+ generatedAt: this.deps.now(),
70363
+ nodes: recommendations,
70364
+ nodeChanges: [...nodeChanges],
70365
+ cameraClears: [...cameraClears],
70366
+ detectorStoreClears,
70367
+ warnings
70368
+ },
70369
+ diffInput,
70370
+ formats
70371
+ };
70372
+ }
70373
+ async apply(input) {
70374
+ if (this.applying) return {
70375
+ kind: "refused",
70376
+ reason: "apply-in-progress"
70377
+ };
70378
+ this.applying = true;
70379
+ try {
70380
+ return await this.applyLocked(input);
70381
+ } finally {
70382
+ this.applying = false;
70383
+ }
70384
+ }
70385
+ async applyLocked(input) {
70386
+ const { plan, diffInput, formats } = await this.computePlan(input.headroom);
70387
+ if (plan.planHash !== input.planHash) {
70388
+ this.deps.logger.warn("best settings: stale plan refused", { meta: {
70389
+ confirmed: input.planHash,
70390
+ current: plan.planHash
70391
+ } });
70392
+ return {
70393
+ kind: "stale",
70394
+ plan
70395
+ };
70396
+ }
70397
+ const phases = [];
70398
+ const downloads = plan.nodeChanges.flatMap((c) => c.modelPins.filter((p) => p.stepId === "object-detection" && p.after !== null && p.after !== p.before).map((p) => ({
70399
+ nodeId: c.nodeId,
70400
+ modelId: p.after ?? "",
70401
+ format: formats.get(c.nodeId)?.get(c.deviceKey)
70402
+ })));
70403
+ const unique = new Map(downloads.map((d) => [`${d.nodeId}|${d.modelId}|${d.format}`, d]));
70404
+ try {
70405
+ for (const d of unique.values()) {
70406
+ if (d.format === void 0) continue;
70407
+ await this.deps.downloadModel(d.nodeId, d.modelId, d.format);
70408
+ }
70409
+ phases.push({
70410
+ phase: "download",
70411
+ ok: true,
70412
+ detail: `${unique.size} model(s) present on their nodes`
70413
+ });
70414
+ } catch (err) {
70415
+ phases.push({
70416
+ phase: "download",
70417
+ ok: false,
70418
+ detail: errMsg(err)
70419
+ });
70420
+ this.deps.logger.warn("best settings: a model download failed — nothing was written", { meta: { error: errMsg(err) } });
70421
+ return {
70422
+ kind: "aborted",
70423
+ phases
70424
+ };
70425
+ }
70426
+ try {
70427
+ await this.deps.rewriteCameraSettings((prev) => applyCameraBestSettings(prev, diffInput).next);
70428
+ for (const c of plan.cameraClears) this.deps.logger.info("best settings: camera override cleared", {
70429
+ tags: {
70430
+ deviceId: c.deviceId,
70431
+ nodeId: c.nodeId
70432
+ },
70433
+ meta: {
70434
+ deviceId: c.deviceId,
70435
+ source: c.source,
70436
+ deviceKey: c.deviceKey,
70437
+ stepId: c.stepId,
70438
+ modelIdBefore: c.modelIdBefore,
70439
+ keys: c.keys
70440
+ }
70441
+ });
70442
+ phases.push({
70443
+ phase: "camera-overrides",
70444
+ ok: true,
70445
+ detail: `${plan.cameraClears.length} cleared`
70446
+ });
70447
+ } catch (err) {
70448
+ phases.push({
70449
+ phase: "camera-overrides",
70450
+ ok: false,
70451
+ detail: errMsg(err)
70452
+ });
70453
+ }
70454
+ if (plan.detectorStoreClears.length > 0) try {
70455
+ const answer = await this.deps.detectorStore({
70456
+ dryRun: false,
70457
+ deviceIds: plan.detectorStoreClears.map((c) => c.deviceId)
70458
+ });
70459
+ phases.push({
70460
+ phase: "detector-thresholds",
70461
+ ok: answer.refused.length === 0,
70462
+ detail: `${answer.entries.length} cleared, ${answer.refused.length} refused`
70463
+ });
70464
+ } catch (err) {
70465
+ phases.push({
70466
+ phase: "detector-thresholds",
70467
+ ok: false,
70468
+ detail: errMsg(err)
70469
+ });
70470
+ }
70471
+ try {
70472
+ await this.deps.rewriteAgentSettings((prev) => applyNodeBestSettings(prev, diffInput).next);
70473
+ for (const c of plan.nodeChanges) for (const pin of c.modelPins) this.deps.logger.info("best settings: node model set", {
70474
+ tags: { nodeId: c.nodeId },
70475
+ meta: {
70476
+ nodeId: c.nodeId,
70477
+ deviceKey: c.deviceKey,
70478
+ stepId: pin.stepId,
70479
+ from: pin.before,
70480
+ to: pin.after
70481
+ }
70482
+ });
70483
+ phases.push({
70484
+ phase: "node-models",
70485
+ ok: true,
70486
+ detail: `${plan.nodeChanges.length} device(s) updated`
70487
+ });
70488
+ } catch (err) {
70489
+ phases.push({
70490
+ phase: "node-models",
70491
+ ok: false,
70492
+ detail: errMsg(err)
70493
+ });
70494
+ }
70495
+ this.deps.invalidateCatalog();
70496
+ const failed = [];
70497
+ for (const nodeId of [...diffInput.touchedNodes].toSorted()) try {
70498
+ await this.deps.redispatch(nodeId);
70499
+ } catch (err) {
70500
+ failed.push(`${nodeId}: ${errMsg(err)}`);
70501
+ }
70502
+ phases.push({
70503
+ phase: "redispatch",
70504
+ ok: failed.length === 0,
70505
+ detail: failed.length === 0 ? `${diffInput.touchedNodes.size} node(s)` : failed.join("; ")
70506
+ });
70507
+ for (const rec of plan.nodes.filter((r) => r.skipped)) this.deps.logger.info("best settings: node skipped", {
70508
+ tags: { nodeId: rec.nodeId },
70509
+ meta: {
70510
+ nodeId: rec.nodeId,
70511
+ reasons: rec.reasons
70512
+ }
70513
+ });
70514
+ this.deps.logger.info("best settings: applied", { meta: {
70515
+ planHash: plan.planHash,
70516
+ nodeChanges: plan.nodeChanges.length,
70517
+ cameraClears: plan.cameraClears.length,
70518
+ detectorStoreClears: plan.detectorStoreClears.length,
70519
+ phases
70520
+ } });
70521
+ return {
70522
+ kind: "applied",
70523
+ planHash: plan.planHash,
70524
+ phases
70525
+ };
70526
+ }
70527
+ };
70528
+ //#endregion
70529
+ //#region src/best-settings/device-peak-tracker.ts
70530
+ var BUCKET_MS = 3600 * 1e3;
70531
+ var DevicePeakTracker = class DevicePeakTracker {
70532
+ now;
70533
+ series = /* @__PURE__ */ new Map();
70534
+ constructor(now = () => Date.now()) {
70535
+ this.now = now;
70536
+ }
70537
+ static key(nodeId, deviceKey) {
70538
+ return `${nodeId}\u0000${deviceKey}`;
70539
+ }
70540
+ /** Fold one runner census in. A runner that did not answer is simply absent. */
70541
+ noteCensus(loads) {
70542
+ const now = this.now();
70543
+ const bucket = now - now % BUCKET_MS;
70544
+ for (const load of loads) {
70545
+ const rows = load.devices.length > 0 ? load.devices : [{
70546
+ deviceKey: "",
70547
+ attachedCameras: load.attachedCameras
70548
+ }];
70549
+ for (const device of rows) {
70550
+ const key = DevicePeakTracker.key(load.nodeId, device.deviceKey);
70551
+ let s = this.series.get(key);
70552
+ if (s === void 0) {
70553
+ s = {
70554
+ firstSeenAt: now,
70555
+ buckets: /* @__PURE__ */ new Map()
70556
+ };
70557
+ this.series.set(key, s);
70558
+ }
70559
+ s.buckets.set(bucket, Math.max(s.buckets.get(bucket) ?? 0, device.attachedCameras));
70560
+ for (const start of s.buckets.keys()) if (start + BUCKET_MS <= now - 864e5) s.buckets.delete(start);
70561
+ }
70562
+ }
70563
+ }
70564
+ /** Every observed device's peak over the window and how long it has been watched. */
70565
+ peaks() {
70566
+ const now = this.now();
70567
+ const out = [];
70568
+ for (const [key, s] of this.series) {
70569
+ const [nodeId = "", deviceKey = ""] = key.split("\0");
70570
+ let peak = 0;
70571
+ for (const [start, value] of s.buckets) if (start + BUCKET_MS > now - 864e5) peak = Math.max(peak, value);
70572
+ out.push({
70573
+ nodeId,
70574
+ deviceKey,
70575
+ peakSessions: peak,
70576
+ observedForMs: now - s.firstSeenAt
70577
+ });
70578
+ }
70579
+ return out.toSorted((a, b) => a.nodeId === b.nodeId ? a.deviceKey.localeCompare(b.deviceKey) : a.nodeId.localeCompare(b.nodeId));
70580
+ }
70581
+ };
70582
+ //#endregion
68786
70583
  //#region src/index.ts
68787
70584
  /**
68788
70585
  * addon-pipeline-orchestrator — hub-side camera-to-agent load balancer.
@@ -68850,6 +70647,9 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
68850
70647
  * not a claim this flag makes.
68851
70648
  */
68852
70649
  clusterModelChoiceReady = false;
70650
+ /** The per-device 24 h peak of concurrent sessions, fed by every runner census (D745). */
70651
+ devicePeaks = new DevicePeakTracker();
70652
+ bestSettingsService = null;
68853
70653
  /**
68854
70654
  * Single owner of the shared placement state (`assignments`/`cameraConfigs`/
68855
70655
  * `pendingReasons`/`dispatchingDevices`) — see `placement-ledger.ts` (S6
@@ -69126,6 +70926,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
69126
70926
  rebalance: (opts) => this.rebalance(opts),
69127
70927
  setCameraPipelineForAgent: (input) => this.setCameraPipelineForAgent(input),
69128
70928
  maintenancePaused: () => this.storageMigrationLeaseId !== null,
70929
+ onRunnerCensus: (loads) => this.devicePeaks.noteCensus(loads),
69129
70930
  setTopology: (topology) => {
69130
70931
  this.topology = topology;
69131
70932
  },
@@ -69267,7 +71068,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
69267
71068
  for (const deviceId of this.detectionWiring.activeDetectionDeviceIds()) try {
69268
71069
  await this.releaseCamera({ deviceId });
69269
71070
  } catch (err) {
69270
- const msg = errMsg$1(err);
71071
+ const msg = errMsg$2(err);
69271
71072
  this.ctx.logger.debug("releaseCamera failed during shutdown", {
69272
71073
  tags: { deviceId },
69273
71074
  meta: { error: msg }
@@ -70062,7 +71863,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
70062
71863
  tags: { nodeId },
70063
71864
  meta: {
70064
71865
  nodeId,
70065
- error: errMsg$1(err)
71866
+ error: errMsg$2(err)
70066
71867
  }
70067
71868
  });
70068
71869
  return null;
@@ -70168,7 +71969,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
70168
71969
  tags: { nodeId },
70169
71970
  meta: {
70170
71971
  nodeId,
70171
- error: errMsg$1(err)
71972
+ error: errMsg$2(err)
70172
71973
  }
70173
71974
  });
70174
71975
  return {
@@ -70215,7 +72016,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
70215
72016
  } catch (err) {
70216
72017
  this.ctx.logger.warn("resetNodePipelineDefaults: executor clearDeviceOverrides failed (node offline?)", {
70217
72018
  tags: { nodeId: node },
70218
- meta: { error: errMsg$1(err) }
72019
+ meta: { error: errMsg$2(err) }
70219
72020
  });
70220
72021
  }
70221
72022
  this.settingsStore.invalidateCatalog();
@@ -70226,7 +72027,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
70226
72027
  } catch (err) {
70227
72028
  this.ctx.logger.warn("resetNodePipelineDefaults: effective-model probe failed (node offline?)", {
70228
72029
  tags: { nodeId: node },
70229
- meta: { error: errMsg$1(err) }
72030
+ meta: { error: errMsg$2(err) }
70230
72031
  });
70231
72032
  }
70232
72033
  this.ctx.logger.info("node pipeline reset to hardware-aware defaults", {
@@ -70242,6 +72043,77 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
70242
72043
  clearedCameraOverrides
70243
72044
  };
70244
72045
  }
72046
+ bestSettings() {
72047
+ if (this.bestSettingsService !== null) return this.bestSettingsService;
72048
+ const store = this.settingsStore;
72049
+ this.bestSettingsService = new BestSettingsService({
72050
+ logger: this.ctx.logger,
72051
+ runnerNodeIds: () => this.topology.collectRunnerNodeIds(),
72052
+ readNodeDevices: (nodeId) => this.getNodeInferenceDevices({ nodeId }),
72053
+ readHardware: async (nodeId) => {
72054
+ const api = this.ctx.api;
72055
+ if (!api) return null;
72056
+ return nodeId === this.localNodeId ? api.platformProbe.getHardware.query() : api.platformProbe.getHardware.query({ nodeId });
72057
+ },
72058
+ readCatalog: (nodeId) => store.readCatalogForNode(nodeId),
72059
+ readAgentSettings: () => store.readAgentSettingsMap(),
72060
+ readCameraSettings: () => store.readCameraSettingsMap(),
72061
+ rewriteAgentSettings: (transform) => store.rewriteAgentSettingsMap(transform),
72062
+ rewriteCameraSettings: (transform) => store.rewriteCameraSettingsMap(transform),
72063
+ cameraLoads: () => this.bestSettingsCameraLoads(),
72064
+ peaks: () => this.devicePeaks.peaks(),
72065
+ detectorStore: async (input) => {
72066
+ const api = this.ctx.api;
72067
+ if (!api) throw new Error("ctx.api unavailable");
72068
+ return api.pipelineExecutor.clearModelBoundDeviceOverrides.mutate({
72069
+ nodeId: this.localNodeId,
72070
+ dryRun: input.dryRun,
72071
+ ...input.deviceIds !== void 0 ? { deviceIds: input.deviceIds } : {}
72072
+ });
72073
+ },
72074
+ downloadModel: async (nodeId, modelId, format) => {
72075
+ const api = this.ctx.api;
72076
+ if (!api) throw new Error("ctx.api unavailable");
72077
+ await api.pipelineExecutor.downloadModel.mutate({
72078
+ addonId: "object-detection",
72079
+ modelId,
72080
+ format
72081
+ }, nodePin(nodeId));
72082
+ },
72083
+ invalidateCatalog: () => store.invalidateCatalog(),
72084
+ redispatch: (nodeId) => this.placement.redispatchCamerasOnAgent(nodeId),
72085
+ now: () => Date.now()
72086
+ });
72087
+ return this.bestSettingsService;
72088
+ }
72089
+ /** Every camera with detection started, as the recommender charges it (D745). */
72090
+ async bestSettingsCameraLoads() {
72091
+ const standing = new Map(this.getPipelineAssignments().map((a) => [a.deviceId, a.agentNodeId]));
72092
+ const cameraMap = await this.settingsStore.readCameraSettingsMap();
72093
+ const loads = [];
72094
+ for (const [deviceId, config] of this.detectionWiring.activeDetections) {
72095
+ const pinnedNode = cameraMap[String(deviceId)]?.pinnedAgentNodeId ?? standing.get(deviceId);
72096
+ const pinnedKey = await this.readPipelineDevicePin(deviceId);
72097
+ loads.push({
72098
+ deviceId,
72099
+ mode: config.detectionMode,
72100
+ fps: config.detectionFps,
72101
+ runsPipelineRoot: (config.detectionSources ?? ["pipeline"]).includes("pipeline"),
72102
+ standingNodeId: config.detectionMode === "always-on" ? standing.get(deviceId) ?? null : null,
72103
+ devicePin: pinnedKey !== null && pinnedNode !== void 0 ? {
72104
+ nodeId: pinnedNode,
72105
+ deviceKey: pinnedKey
72106
+ } : null
72107
+ });
72108
+ }
72109
+ return loads;
72110
+ }
72111
+ async planBestSettings(input) {
72112
+ return this.bestSettings().plan(input);
72113
+ }
72114
+ async applyBestSettings(input) {
72115
+ return this.bestSettings().apply(input);
72116
+ }
70245
72117
  async getCameraSettings(input) {
70246
72118
  return (await this.settingsStore.readCameraSettingsMap())[String(input.deviceId)] ?? null;
70247
72119
  }
@@ -70498,7 +72370,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
70498
72370
  tags: { deviceId },
70499
72371
  meta: {
70500
72372
  capName,
70501
- error: errMsg$1(err)
72373
+ error: errMsg$2(err)
70502
72374
  }
70503
72375
  });
70504
72376
  return true;