@camstack/addon-pipeline-orchestrator 1.2.264 → 1.2.266

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.js CHANGED
@@ -5401,7 +5401,7 @@ var ZodIssueCode = {
5401
5401
  var ZodFirstPartyTypeKind;
5402
5402
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5403
5403
  //#endregion
5404
- //#region ../types/dist/sleep-C-v96WpE.mjs
5404
+ //#region ../types/dist/sleep-gU6hqv4_.mjs
5405
5405
  /**
5406
5406
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5407
5407
  * window to float samples (D455).
@@ -8036,7 +8036,59 @@ function errMsg$1(err) {
8036
8036
  return String(err);
8037
8037
  }
8038
8038
  //#endregion
8039
- //#region ../types/dist/composition-BK-GTFYb.mjs
8039
+ //#region ../types/dist/composition-Dq42XWp0.mjs
8040
+ /**
8041
+ * The ffmpeg build every CamStack node downloads, per platform and arch.
8042
+ *
8043
+ * ## Why a download, on every node, including the container
8044
+ *
8045
+ * The version of ffmpeg used to be CODE: baked into the image, changeable only
8046
+ * by rebuilding and rolling it. Now it is DATA — this file states the release,
8047
+ * and a node that boots with a different one downloads it. Changing ffmpeg is a
8048
+ * framework bump, not an image rebuild.
8049
+ *
8050
+ * It also settles the licence question by removing it. FFmpeg's licence is
8051
+ * chosen at compile time: the core is LGPL 2.1+, `--enable-gpl` makes it GPL
8052
+ * (for x264/x265), `--enable-version3` makes that v3, and `--enable-nonfree`
8053
+ * makes the result undistributable. These are GPL v3 builds, and we do not
8054
+ * distribute them: the user's own node fetches the archive from the project
8055
+ * that publishes it, so we convey no GPL work and inherit none of the
8056
+ * obligations that come with conveying one. That is the whole reason the image
8057
+ * no longer installs ffmpeg at all — not a technical preference.
8058
+ *
8059
+ * ## Why jellyfin-ffmpeg
8060
+ *
8061
+ * One vendor covering linux-amd64, linux-arm64, darwin-arm64 and darwin-x64
8062
+ * from one release, with the widest hardware surface of anything maintained:
8063
+ * VAAPI, QSV, NVENC/NVDEC, AMF, Vulkan, OpenCL and DRM on amd64, plus Rockchip
8064
+ * MPP on arm64. It reaches the vendor libraries through dlopen trampolines,
8065
+ * so the binary is not linked against a driver stack it may not find.
8066
+ *
8067
+ * **What it still needs from the host, measured 2026-09-19.** On the Unraid
8068
+ * HOST, which has no libva, `-vaapi_device` aborts the process outright —
8069
+ * `implib-gen: libva-drm.so.2: failed to load library ... Assertion '0 &&
8070
+ * "Assertion in generated code"' failed`. Inside our container, which installs
8071
+ * libva plus the iHD driver, the same command encodes: verified end to end with
8072
+ * `testsrc → format=nv12,hwupload → h264_vaapi`. So the image drops ffmpeg and
8073
+ * KEEPS the Intel media stack; the drivers are the part a binary cannot bring.
8074
+ *
8075
+ * ## Why each artifact carries a checksum and a claim
8076
+ *
8077
+ * The download is now the only source, so `sha256` is verified before anything
8078
+ * is extracted — jellyfin publishes no checksums on its release assets, so
8079
+ * these are ours, taken at the version bump. And `expectedHwaccels` is what the
8080
+ * build is supposed to be able to do: a binary that probes short of its own
8081
+ * claim is a source that changed under us, which is precisely what went
8082
+ * unnoticed for months when a static build turned out to carry no VAAPI at all
8083
+ * (D536).
8084
+ */
8085
+ /** The pinned jellyfin-ffmpeg release. Changing this is the version change. */
8086
+ var FFMPEG_RELEASE = "8.1.2-5";
8087
+ var BASE = `https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v${FFMPEG_RELEASE}`;
8088
+ function portable(target) {
8089
+ return `${BASE}/jellyfin-ffmpeg_${FFMPEG_RELEASE}_portable_${target}-gpl.tar.xz`;
8090
+ }
8091
+ portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac64");
8040
8092
  /**
8041
8093
  * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
8042
8094
  * valutatore puro del suo FRONTE.
@@ -9932,6 +9984,82 @@ var ArchivedDebugNoteQueryInputSchema = object({
9932
9984
  /** Max rows returned, newest-first. */
9933
9985
  limit: number().int().min(1).max(1e3).optional()
9934
9986
  });
9987
+ var TrackRelationKindSchema = _enum(["same-subject", "different-subjects"]);
9988
+ var TrackSideSchema = _enum(["before", "after"]);
9989
+ var TrackPurityValueSchema = _enum(["mixed", "not-a-subject"]);
9990
+ var TrackRelationSchema = object({
9991
+ id: string(),
9992
+ deviceId: number().int(),
9993
+ leftTrackId: string(),
9994
+ rightTrackId: string(),
9995
+ leftSide: TrackSideSchema.nullable(),
9996
+ rightSide: TrackSideSchema.nullable(),
9997
+ relation: TrackRelationKindSchema,
9998
+ presence: literal("continuous").nullable(),
9999
+ leftStartedAt: number().int().nullable(),
10000
+ rightStartedAt: number().int().nullable(),
10001
+ createdAt: number().int(),
10002
+ updatedAt: number().int(),
10003
+ author: string().nullable()
10004
+ });
10005
+ var TrackPuritySchema = object({
10006
+ id: string(),
10007
+ deviceId: number().int(),
10008
+ sourceTrackId: string(),
10009
+ purity: TrackPurityValueSchema,
10010
+ switchAtMs: number().int().nullable(),
10011
+ startedAt: number().int().nullable(),
10012
+ createdAt: number().int(),
10013
+ updatedAt: number().int(),
10014
+ author: string().nullable()
10015
+ });
10016
+ var SetTrackRelationInputSchema = object({
10017
+ deviceId: number().int(),
10018
+ trackId: string().min(1),
10019
+ otherTrackId: string().min(1),
10020
+ relation: TrackRelationKindSchema,
10021
+ presence: literal("continuous").optional(),
10022
+ trackSide: TrackSideSchema.optional(),
10023
+ otherSide: TrackSideSchema.optional()
10024
+ });
10025
+ var ClearTrackRelationInputSchema = object({
10026
+ deviceId: number().int(),
10027
+ trackId: string().min(1),
10028
+ otherTrackId: string().min(1)
10029
+ });
10030
+ var ClearTrackRelationResultSchema = object({ cleared: boolean() });
10031
+ var SetTrackPurityInputSchema = object({
10032
+ deviceId: number().int(),
10033
+ trackId: string().min(1),
10034
+ purity: _enum([
10035
+ "pure",
10036
+ "mixed",
10037
+ "not-a-subject"
10038
+ ]),
10039
+ switchAtMs: number().int().optional()
10040
+ });
10041
+ /** `null` when `pure` cleared the row. */
10042
+ var SetTrackPurityResultSchema = object({ purity: TrackPuritySchema.nullable() });
10043
+ var ListTrackRelationsInputSchema = object({
10044
+ deviceId: number().int(),
10045
+ trackId: string().optional(),
10046
+ since: number().int().optional(),
10047
+ until: number().int().optional(),
10048
+ limit: number().int().min(1).max(5e3).optional()
10049
+ });
10050
+ var ListTrackRelationsResultSchema = object({
10051
+ relations: array(TrackRelationSchema).readonly(),
10052
+ purities: array(TrackPuritySchema).readonly(),
10053
+ truncated: boolean()
10054
+ });
10055
+ _enum([
10056
+ "unknown-track",
10057
+ "cross-device",
10058
+ "self-relation",
10059
+ "contradiction",
10060
+ "switch-outside-track",
10061
+ "switch-needs-mixed"
10062
+ ]);
9935
10063
  /**
9936
10064
  * Ops-log — the durable, append-only operations audit shared by the
9937
10065
  * recordings and events management surfaces.
@@ -14076,6 +14204,70 @@ var CompositionBlockStateSchema = object({
14076
14204
  reason: string().optional(),
14077
14205
  fields: array(CompositionFieldStateSchema)
14078
14206
  });
14207
+ var NUMBER = {
14208
+ kind: "fixed",
14209
+ result: ["number"]
14210
+ };
14211
+ var BOOLEAN = {
14212
+ kind: "fixed",
14213
+ result: ["boolean"]
14214
+ };
14215
+ Object.freeze({
14216
+ min: NUMBER,
14217
+ max: NUMBER,
14218
+ abs: NUMBER,
14219
+ floor: NUMBER,
14220
+ ceil: NUMBER,
14221
+ sqrt: NUMBER,
14222
+ round: NUMBER,
14223
+ pow: NUMBER,
14224
+ clamp: NUMBER,
14225
+ avg: NUMBER,
14226
+ sum: NUMBER,
14227
+ age: NUMBER,
14228
+ convert: NUMBER,
14229
+ coalesce: { kind: "coalesce" },
14230
+ any: BOOLEAN,
14231
+ all: BOOLEAN,
14232
+ within: BOOLEAN,
14233
+ latest: {
14234
+ kind: "fixed",
14235
+ result: ["number", "null"]
14236
+ },
14237
+ number: {
14238
+ kind: "fixed",
14239
+ result: ["number", "null"]
14240
+ },
14241
+ rose: {
14242
+ kind: "fixed",
14243
+ result: ["number", "null"]
14244
+ },
14245
+ count: NUMBER
14246
+ });
14247
+ `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
14248
+ /**
14249
+ * What a composition may BUILD on a device type (G2, D676): the caps a new
14250
+ * device of that type, or an existing one that lacks them, can be given. The
14251
+ * blockers are the validator's own refusal codes, and a spec compares this
14252
+ * listing with `planComposition` for every capability — the editor can never
14253
+ * offer a feature the save would refuse for the same reason.
14254
+ */
14255
+ var ComposableFieldSchema = object({
14256
+ path: string(),
14257
+ kind: CompositionFieldKindSchema,
14258
+ nullable: boolean(),
14259
+ required: boolean(),
14260
+ enumValues: array(string()).optional()
14261
+ });
14262
+ var ComposableCapabilitiesSchema = object({ caps: array(object({
14263
+ cap: string(),
14264
+ fields: array(ComposableFieldSchema),
14265
+ /** Present for an item-array cap (`consumables.items`). */
14266
+ itemArray: object({
14267
+ path: string(),
14268
+ keyField: string()
14269
+ }).optional()
14270
+ })) });
14079
14271
  /** Where a block runs. The operator chooses — a block driving a device on an
14080
14272
  * agent is the reason placement is not fixed to the hub. */
14081
14273
  var CoreBlockPlacementSchema = union([literal("hub"), string().min(1)]);
@@ -14240,7 +14432,7 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
14240
14432
  }), method(object({ blockId: string() }), CompositionGetReadSchema, { auth: "admin" }), method(object({}), CompositionListReadSchema, { auth: "admin" }), method(object({
14241
14433
  addonId: string().min(1),
14242
14434
  stableId: string().min(1)
14243
- }), CompositionCustomizationReadSchema, { auth: "admin" }), method(object({
14435
+ }), CompositionCustomizationReadSchema, { auth: "admin" }), method(object({ deviceType: _enum(DeviceType) }), ComposableCapabilitiesSchema, { auth: "admin" }), method(object({
14244
14436
  composition: CompositionSchema,
14245
14437
  blockId: string().optional()
14246
14438
  }), CompositionValidationSchema, {
@@ -23104,7 +23296,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
23104
23296
  deviceId: number(),
23105
23297
  trackId: string(),
23106
23298
  flags: TrackFlagsPatchSchema
23107
- }), TrackFlagsSchema, { kind: "mutation" }), method(ArchivedDebugNoteQueryInputSchema, array(ArchivedDebugNoteSchema).readonly(), { kind: "query" }), method(BirthDecisionQueryInputSchema, array(BirthDecisionRecordSchema).readonly(), { kind: "query" }), method(object({}), EventStoreFootprintSchema, {
23299
+ }), TrackFlagsSchema, { kind: "mutation" }), method(ArchivedDebugNoteQueryInputSchema, array(ArchivedDebugNoteSchema).readonly(), { kind: "query" }), method(SetTrackRelationInputSchema, TrackRelationSchema, { kind: "mutation" }), method(ClearTrackRelationInputSchema, ClearTrackRelationResultSchema, { kind: "mutation" }), method(SetTrackPurityInputSchema, SetTrackPurityResultSchema, { kind: "mutation" }), method(ListTrackRelationsInputSchema, ListTrackRelationsResultSchema, { kind: "query" }), method(BirthDecisionQueryInputSchema, array(BirthDecisionRecordSchema).readonly(), { kind: "query" }), method(object({}), EventStoreFootprintSchema, {
23108
23300
  kind: "query",
23109
23301
  auth: "admin"
23110
23302
  }), method(object({ deviceId: number().int().optional() }), EventMediaKindBreakdownSchema, {
@@ -25698,6 +25890,16 @@ var NodeInferenceDevicesSchema = object({
25698
25890
  * Strictly the control plane: never sees a frame, never calls inference,
25699
25891
  * never drains queues. The runner does that work locally on each agent.
25700
25892
  */
25893
+ /**
25894
+ * The input of `setCameraSensors` (G5, D676): the ONE typed write path of a
25895
+ * camera's attached sensors (D672). `config: null` removes the attachment —
25896
+ * the camera reads `not-configured` again, never `unreadable`.
25897
+ */
25898
+ var SetCameraSensorsInputSchema = object({
25899
+ deviceId: number().int().nonnegative(),
25900
+ /** `null` removes the attachment: the camera goes back to `not-configured`. */
25901
+ config: CameraSensorsConfigSchema.nullable()
25902
+ });
25701
25903
  var pipelineOrchestratorCapability = {
25702
25904
  name: "pipeline-orchestrator",
25703
25905
  scope: "system",
@@ -26163,6 +26365,18 @@ var pipelineOrchestratorCapability = {
26163
26365
  */
26164
26366
  getCameraStatuses: method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()),
26165
26367
  /**
26368
+ * Attach sensors to a camera, or detach them with `config: null` (D672
26369
+ * configuration, G5/D676). Written to the orchestrator's per-device store
26370
+ * under the same key `applyDeviceSettingsPatch` writes, and announced as a
26371
+ * settings change so the camera-sensors bridge refreshes. Refused by name
26372
+ * — nothing written — for a device that cannot be read, does not exist,
26373
+ * is not a camera, or names itself as its own sensor.
26374
+ */
26375
+ setCameraSensors: method(SetCameraSensorsInputSchema, object({ success: literal(true) }), {
26376
+ kind: "mutation",
26377
+ auth: "admin"
26378
+ }),
26379
+ /**
26166
26380
  * Disk-wins fleet reconcile after a recordings wipe. Starts the walk in
26167
26381
  * the addon process and returns immediately with the job snapshot — the
26168
26382
  * work outlives the tRPC/UDS 60s timeout. Poll `getReconcileFromDiskStatus`.
@@ -37192,47 +37406,6 @@ var zoneRulesCapability = {
37192
37406
  */
37193
37407
  durability: "restored"
37194
37408
  };
37195
- var NUMBER = {
37196
- kind: "fixed",
37197
- result: ["number"]
37198
- };
37199
- var BOOLEAN = {
37200
- kind: "fixed",
37201
- result: ["boolean"]
37202
- };
37203
- Object.freeze({
37204
- min: NUMBER,
37205
- max: NUMBER,
37206
- abs: NUMBER,
37207
- floor: NUMBER,
37208
- ceil: NUMBER,
37209
- sqrt: NUMBER,
37210
- round: NUMBER,
37211
- pow: NUMBER,
37212
- clamp: NUMBER,
37213
- avg: NUMBER,
37214
- sum: NUMBER,
37215
- age: NUMBER,
37216
- convert: NUMBER,
37217
- coalesce: { kind: "coalesce" },
37218
- any: BOOLEAN,
37219
- all: BOOLEAN,
37220
- within: BOOLEAN,
37221
- latest: {
37222
- kind: "fixed",
37223
- result: ["number", "null"]
37224
- },
37225
- number: {
37226
- kind: "fixed",
37227
- result: ["number", "null"]
37228
- },
37229
- rose: {
37230
- kind: "fixed",
37231
- result: ["number", "null"]
37232
- },
37233
- count: NUMBER
37234
- });
37235
- `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
37236
37409
  /**
37237
37410
  * Accessory device helpers — shared across drivers.
37238
37411
  *
@@ -39683,6 +39856,12 @@ Object.freeze({
39683
39856
  addonId: null,
39684
39857
  access: "view"
39685
39858
  },
39859
+ "coreBlocks.listComposableCaps": {
39860
+ capName: "core-blocks",
39861
+ capScope: "system",
39862
+ addonId: null,
39863
+ access: "view"
39864
+ },
39686
39865
  "coreBlocks.listCompositions": {
39687
39866
  capName: "core-blocks",
39688
39867
  capScope: "system",
@@ -42497,6 +42676,12 @@ Object.freeze({
42497
42676
  addonId: null,
42498
42677
  access: "create"
42499
42678
  },
42679
+ "pipelineAnalytics.clearTrackRelation": {
42680
+ capName: "pipeline-analytics",
42681
+ capScope: "device",
42682
+ addonId: null,
42683
+ access: "delete"
42684
+ },
42500
42685
  "pipelineAnalytics.clearTracks": {
42501
42686
  capName: "pipeline-analytics",
42502
42687
  capScope: "device",
@@ -42749,6 +42934,12 @@ Object.freeze({
42749
42934
  addonId: null,
42750
42935
  access: "view"
42751
42936
  },
42937
+ "pipelineAnalytics.listTrackRelations": {
42938
+ capName: "pipeline-analytics",
42939
+ capScope: "device",
42940
+ addonId: null,
42941
+ access: "view"
42942
+ },
42752
42943
  "pipelineAnalytics.listTracks": {
42753
42944
  capName: "pipeline-analytics",
42754
42945
  capScope: "device",
@@ -42863,6 +43054,18 @@ Object.freeze({
42863
43054
  addonId: null,
42864
43055
  access: "create"
42865
43056
  },
43057
+ "pipelineAnalytics.setTrackPurity": {
43058
+ capName: "pipeline-analytics",
43059
+ capScope: "device",
43060
+ addonId: null,
43061
+ access: "create"
43062
+ },
43063
+ "pipelineAnalytics.setTrackRelation": {
43064
+ capName: "pipeline-analytics",
43065
+ capScope: "device",
43066
+ addonId: null,
43067
+ access: "create"
43068
+ },
42866
43069
  "pipelineAnalytics.startStorageMigrationMove": {
42867
43070
  capName: "pipeline-analytics",
42868
43071
  capScope: "device",
@@ -43367,6 +43570,12 @@ Object.freeze({
43367
43570
  addonId: null,
43368
43571
  access: "create"
43369
43572
  },
43573
+ "pipelineOrchestrator.setCameraSensors": {
43574
+ capName: "pipeline-orchestrator",
43575
+ capScope: "system",
43576
+ addonId: null,
43577
+ access: "create"
43578
+ },
43370
43579
  "pipelineOrchestrator.setCameraStepOverride": {
43371
43580
  capName: "pipeline-orchestrator",
43372
43581
  capScope: "system",
@@ -46777,6 +46986,11 @@ Object.freeze({
46777
46986
  form: "single",
46778
46987
  optional: false
46779
46988
  }],
46989
+ "pipelineAnalytics.clearTrackRelation": [{
46990
+ name: "deviceId",
46991
+ form: "single",
46992
+ optional: false
46993
+ }],
46780
46994
  "pipelineAnalytics.clearTracks": [{
46781
46995
  name: "deviceId",
46782
46996
  form: "single",
@@ -46927,6 +47141,11 @@ Object.freeze({
46927
47141
  form: "single",
46928
47142
  optional: false
46929
47143
  }],
47144
+ "pipelineAnalytics.listTrackRelations": [{
47145
+ name: "deviceId",
47146
+ form: "single",
47147
+ optional: false
47148
+ }],
46930
47149
  "pipelineAnalytics.listTracks": [{
46931
47150
  name: "deviceId",
46932
47151
  form: "single",
@@ -46997,6 +47216,16 @@ Object.freeze({
46997
47216
  form: "single",
46998
47217
  optional: false
46999
47218
  }],
47219
+ "pipelineAnalytics.setTrackPurity": [{
47220
+ name: "deviceId",
47221
+ form: "single",
47222
+ optional: false
47223
+ }],
47224
+ "pipelineAnalytics.setTrackRelation": [{
47225
+ name: "deviceId",
47226
+ form: "single",
47227
+ optional: false
47228
+ }],
47000
47229
  "pipelineAnalytics.wipeAllAnalytics": [{
47001
47230
  name: "deviceId",
47002
47231
  form: "single",
@@ -47077,6 +47306,11 @@ Object.freeze({
47077
47306
  form: "single",
47078
47307
  optional: false
47079
47308
  }],
47309
+ "pipelineOrchestrator.setCameraSensors": [{
47310
+ name: "deviceId",
47311
+ form: "single",
47312
+ optional: false
47313
+ }],
47080
47314
  "pipelineOrchestrator.setCameraStepOverride": [{
47081
47315
  name: "deviceId",
47082
47316
  form: "single",
@@ -49698,6 +49932,52 @@ var AudioSubscriptionController = class {
49698
49932
  }
49699
49933
  };
49700
49934
  //#endregion
49935
+ //#region src/camera-sensors/write.ts
49936
+ /**
49937
+ * The ONE typed write path of a camera's attached sensors (D672 config,
49938
+ * G5/D676) — `pipelineOrchestrator.setCameraSensors`.
49939
+ *
49940
+ * The input is already validated by the cap schema at the router. Here it is
49941
+ * refused BY NAME, and logged with the device tag, before anything is written
49942
+ * (D391): a device that could not be read (a failed read changes nothing,
49943
+ * D49), an unknown device, a non-camera, a camera named as its own sensor.
49944
+ * The value is then handed to `writeDeviceStore` under the CS-1 key, which the
49945
+ * addon binds to `applyDeviceSettingsPatch` — so the D672 patch gate, the
49946
+ * PARSED value, the store and the settings-updated event that refreshes the
49947
+ * bridge's resolver are ONE path, never a copy of it. `null` clears: the
49948
+ * device store keeps a null key, and `parseCameraSensorsSetting` reads it as
49949
+ * not-configured.
49950
+ */
49951
+ function refuse(deps, deviceId, reason) {
49952
+ deps.logger.warn("camera sensors write refused", {
49953
+ tags: { deviceId },
49954
+ meta: { reason }
49955
+ });
49956
+ throw new Error(`pipeline-orchestrator: camera sensors of ${deviceId} not saved — ${reason}`);
49957
+ }
49958
+ async function readOrRefuse(deps, deviceId) {
49959
+ let device;
49960
+ try {
49961
+ device = await deps.readDevice(deviceId);
49962
+ } catch (err) {
49963
+ return refuse(deps, deviceId, `could not read device ${deviceId}: ${errMsg$1(err)}`);
49964
+ }
49965
+ if (device === null) return refuse(deps, deviceId, `no device ${deviceId}`);
49966
+ return device;
49967
+ }
49968
+ async function writeCameraSensors(deps, input) {
49969
+ const { deviceId, config } = input;
49970
+ const device = await readOrRefuse(deps, deviceId);
49971
+ if (device.type !== DeviceType.Camera) return refuse(deps, deviceId, `device ${deviceId} is not a camera`);
49972
+ if (config !== null && config.sensors.some((s) => s.addonId === device.addonId && s.stableId === device.stableId)) return refuse(deps, deviceId, "a camera cannot be its own sensor");
49973
+ await deps.writeDeviceStore(deviceId, { [CAMERA_SENSORS_SETTINGS_KEY]: config });
49974
+ deps.logger.info("camera sensors saved", {
49975
+ tags: { deviceId },
49976
+ meta: { sensors: config === null ? 0 : config.sensors.length }
49977
+ });
49978
+ return { success: true };
49979
+ }
49980
+ //#endregion
49701
49981
  //#region src/disk-reconcile-fleet.ts
49702
49982
  async function reconcileFleetFromDisk(deps) {
49703
49983
  const deviceIds = await deps.listDeviceIds();
@@ -65662,6 +65942,37 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
65662
65942
  };
65663
65943
  }
65664
65944
  /**
65945
+ * Refuse a `setCameraStepOverride` write for a `deviceKey` the target node
65946
+ * POSITIVELY reports it does not have. Reuses `getNodeInferenceDevices` —
65947
+ * the same merged probe the admin-ui `DeviceStepOverrideTab` already reads
65948
+ * its device list from — so "valid" here means exactly what the operator's
65949
+ * picker offered.
65950
+ *
65951
+ * D315: "unknown" must never read as "absent". An unreachable node
65952
+ * (`reachable:false`, the read itself failed) proves nothing about what the
65953
+ * node has, so the write is ACCEPTED with a WARN rather than refused — only
65954
+ * a node that actually ANSWERED and left the key out of its list is
65955
+ * grounds for refusal.
65956
+ */
65957
+ async assertCameraStepOverrideDeviceKeyKnown(deviceId, nodeId, deviceKey) {
65958
+ const { reachable, devices } = await this.getNodeInferenceDevices({ nodeId });
65959
+ if (!reachable) {
65960
+ this.ctx.logger.warn("setCameraStepOverride: node unreachable — deviceKey could not be validated", {
65961
+ tags: { deviceId },
65962
+ meta: {
65963
+ deviceId,
65964
+ nodeId,
65965
+ deviceKey
65966
+ }
65967
+ });
65968
+ return;
65969
+ }
65970
+ if (devices.some((d) => d.key === deviceKey)) return;
65971
+ const validKeys = devices.map((d) => d.key).toSorted();
65972
+ const validKeysText = validKeys.length > 0 ? validKeys.join(", ") : "(none reported)";
65973
+ throw new Error(`setCameraStepOverride: node "${nodeId}" does not report inference device "${deviceKey}" — valid keys: ${validKeysText}`);
65974
+ }
65975
+ /**
65665
65976
  * The dispatcher-facing eligible inference devices for a node (audit F1/F4):
65666
65977
  * the enabled AND live-available accelerators as a `deviceKey → weight` map,
65667
65978
  * merging the live probe with the stored opt-OUT map (AUTO = all discovered
@@ -66078,6 +66389,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
66078
66389
  return (await this.settingsStore.readCameraSettingsMap())[String(input.deviceId)]?.stepOverridesByDevice ?? null;
66079
66390
  }
66080
66391
  async setCameraStepOverride(input) {
66392
+ if (input.patch !== null) await this.assertCameraStepOverrideDeviceKeyKnown(input.deviceId, input.agentNodeId, input.deviceKey);
66081
66393
  await this.settingsStore.writeCameraStepOverride(input.deviceId, input.agentNodeId, input.deviceKey, input.addonId, input.patch);
66082
66394
  await this.emitResolvedCameraUpdated(input.deviceId);
66083
66395
  return { success: true };
@@ -66215,6 +66527,37 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
66215
66527
  return this.deviceConfig.applyDeviceSettingsPatch(input);
66216
66528
  }
66217
66529
  /**
66530
+ * The one typed write path of a camera's attached sensors (G5, D676) — see
66531
+ * `camera-sensors/write.ts`. Its refusals run first; the store write then
66532
+ * goes THROUGH `applyDeviceSettingsPatch`, so the D672 patch gate, the
66533
+ * parsed value, the key and the `OrchestrationSettingsUpdated` event are the
66534
+ * one path a camera configured through the device page already takes.
66535
+ */
66536
+ async setCameraSensors(input) {
66537
+ const ctx = this.ctx;
66538
+ const deviceConfig = this.deviceConfig;
66539
+ if (deviceConfig === null) throw new Error("pipeline-orchestrator: not initialised — camera sensors not saved");
66540
+ const api = ctx.api;
66541
+ return writeCameraSensors({
66542
+ readDevice: async (deviceId) => {
66543
+ if (!api) throw new Error("the device registry is not reachable");
66544
+ const dev = await api.deviceManager.getDevice.query({ deviceId });
66545
+ return dev === null || dev === void 0 ? null : {
66546
+ type: dev.type,
66547
+ addonId: dev.addonId,
66548
+ stableId: dev.stableId
66549
+ };
66550
+ },
66551
+ writeDeviceStore: async (deviceId, patch) => {
66552
+ await deviceConfig.applyDeviceSettingsPatch({
66553
+ deviceId,
66554
+ patch
66555
+ });
66556
+ },
66557
+ logger: ctx.logger.child("camera-sensors")
66558
+ }, input);
66559
+ }
66560
+ /**
66218
66561
  * Apply the subset of global settings that affect runtime behaviour
66219
66562
  * (balancer thresholds, broker timeout, failover policy) into the
66220
66563
  * in-memory state so subsequent `balance()` / `handleNodeDisconnect`