@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.mjs CHANGED
@@ -5373,7 +5373,7 @@ var ZodIssueCode = {
5373
5373
  var ZodFirstPartyTypeKind;
5374
5374
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5375
5375
  //#endregion
5376
- //#region ../types/dist/sleep-C-v96WpE.mjs
5376
+ //#region ../types/dist/sleep-gU6hqv4_.mjs
5377
5377
  /**
5378
5378
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5379
5379
  * window to float samples (D455).
@@ -8008,7 +8008,59 @@ function errMsg$1(err) {
8008
8008
  return String(err);
8009
8009
  }
8010
8010
  //#endregion
8011
- //#region ../types/dist/composition-BK-GTFYb.mjs
8011
+ //#region ../types/dist/composition-Dq42XWp0.mjs
8012
+ /**
8013
+ * The ffmpeg build every CamStack node downloads, per platform and arch.
8014
+ *
8015
+ * ## Why a download, on every node, including the container
8016
+ *
8017
+ * The version of ffmpeg used to be CODE: baked into the image, changeable only
8018
+ * by rebuilding and rolling it. Now it is DATA — this file states the release,
8019
+ * and a node that boots with a different one downloads it. Changing ffmpeg is a
8020
+ * framework bump, not an image rebuild.
8021
+ *
8022
+ * It also settles the licence question by removing it. FFmpeg's licence is
8023
+ * chosen at compile time: the core is LGPL 2.1+, `--enable-gpl` makes it GPL
8024
+ * (for x264/x265), `--enable-version3` makes that v3, and `--enable-nonfree`
8025
+ * makes the result undistributable. These are GPL v3 builds, and we do not
8026
+ * distribute them: the user's own node fetches the archive from the project
8027
+ * that publishes it, so we convey no GPL work and inherit none of the
8028
+ * obligations that come with conveying one. That is the whole reason the image
8029
+ * no longer installs ffmpeg at all — not a technical preference.
8030
+ *
8031
+ * ## Why jellyfin-ffmpeg
8032
+ *
8033
+ * One vendor covering linux-amd64, linux-arm64, darwin-arm64 and darwin-x64
8034
+ * from one release, with the widest hardware surface of anything maintained:
8035
+ * VAAPI, QSV, NVENC/NVDEC, AMF, Vulkan, OpenCL and DRM on amd64, plus Rockchip
8036
+ * MPP on arm64. It reaches the vendor libraries through dlopen trampolines,
8037
+ * so the binary is not linked against a driver stack it may not find.
8038
+ *
8039
+ * **What it still needs from the host, measured 2026-09-19.** On the Unraid
8040
+ * HOST, which has no libva, `-vaapi_device` aborts the process outright —
8041
+ * `implib-gen: libva-drm.so.2: failed to load library ... Assertion '0 &&
8042
+ * "Assertion in generated code"' failed`. Inside our container, which installs
8043
+ * libva plus the iHD driver, the same command encodes: verified end to end with
8044
+ * `testsrc → format=nv12,hwupload → h264_vaapi`. So the image drops ffmpeg and
8045
+ * KEEPS the Intel media stack; the drivers are the part a binary cannot bring.
8046
+ *
8047
+ * ## Why each artifact carries a checksum and a claim
8048
+ *
8049
+ * The download is now the only source, so `sha256` is verified before anything
8050
+ * is extracted — jellyfin publishes no checksums on its release assets, so
8051
+ * these are ours, taken at the version bump. And `expectedHwaccels` is what the
8052
+ * build is supposed to be able to do: a binary that probes short of its own
8053
+ * claim is a source that changed under us, which is precisely what went
8054
+ * unnoticed for months when a static build turned out to carry no VAAPI at all
8055
+ * (D536).
8056
+ */
8057
+ /** The pinned jellyfin-ffmpeg release. Changing this is the version change. */
8058
+ var FFMPEG_RELEASE = "8.1.2-5";
8059
+ var BASE = `https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v${FFMPEG_RELEASE}`;
8060
+ function portable(target) {
8061
+ return `${BASE}/jellyfin-ffmpeg_${FFMPEG_RELEASE}_portable_${target}-gpl.tar.xz`;
8062
+ }
8063
+ portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac64");
8012
8064
  /**
8013
8065
  * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
8014
8066
  * valutatore puro del suo FRONTE.
@@ -9904,6 +9956,82 @@ var ArchivedDebugNoteQueryInputSchema = object({
9904
9956
  /** Max rows returned, newest-first. */
9905
9957
  limit: number().int().min(1).max(1e3).optional()
9906
9958
  });
9959
+ var TrackRelationKindSchema = _enum(["same-subject", "different-subjects"]);
9960
+ var TrackSideSchema = _enum(["before", "after"]);
9961
+ var TrackPurityValueSchema = _enum(["mixed", "not-a-subject"]);
9962
+ var TrackRelationSchema = object({
9963
+ id: string(),
9964
+ deviceId: number().int(),
9965
+ leftTrackId: string(),
9966
+ rightTrackId: string(),
9967
+ leftSide: TrackSideSchema.nullable(),
9968
+ rightSide: TrackSideSchema.nullable(),
9969
+ relation: TrackRelationKindSchema,
9970
+ presence: literal("continuous").nullable(),
9971
+ leftStartedAt: number().int().nullable(),
9972
+ rightStartedAt: number().int().nullable(),
9973
+ createdAt: number().int(),
9974
+ updatedAt: number().int(),
9975
+ author: string().nullable()
9976
+ });
9977
+ var TrackPuritySchema = object({
9978
+ id: string(),
9979
+ deviceId: number().int(),
9980
+ sourceTrackId: string(),
9981
+ purity: TrackPurityValueSchema,
9982
+ switchAtMs: number().int().nullable(),
9983
+ startedAt: number().int().nullable(),
9984
+ createdAt: number().int(),
9985
+ updatedAt: number().int(),
9986
+ author: string().nullable()
9987
+ });
9988
+ var SetTrackRelationInputSchema = object({
9989
+ deviceId: number().int(),
9990
+ trackId: string().min(1),
9991
+ otherTrackId: string().min(1),
9992
+ relation: TrackRelationKindSchema,
9993
+ presence: literal("continuous").optional(),
9994
+ trackSide: TrackSideSchema.optional(),
9995
+ otherSide: TrackSideSchema.optional()
9996
+ });
9997
+ var ClearTrackRelationInputSchema = object({
9998
+ deviceId: number().int(),
9999
+ trackId: string().min(1),
10000
+ otherTrackId: string().min(1)
10001
+ });
10002
+ var ClearTrackRelationResultSchema = object({ cleared: boolean() });
10003
+ var SetTrackPurityInputSchema = object({
10004
+ deviceId: number().int(),
10005
+ trackId: string().min(1),
10006
+ purity: _enum([
10007
+ "pure",
10008
+ "mixed",
10009
+ "not-a-subject"
10010
+ ]),
10011
+ switchAtMs: number().int().optional()
10012
+ });
10013
+ /** `null` when `pure` cleared the row. */
10014
+ var SetTrackPurityResultSchema = object({ purity: TrackPuritySchema.nullable() });
10015
+ var ListTrackRelationsInputSchema = object({
10016
+ deviceId: number().int(),
10017
+ trackId: string().optional(),
10018
+ since: number().int().optional(),
10019
+ until: number().int().optional(),
10020
+ limit: number().int().min(1).max(5e3).optional()
10021
+ });
10022
+ var ListTrackRelationsResultSchema = object({
10023
+ relations: array(TrackRelationSchema).readonly(),
10024
+ purities: array(TrackPuritySchema).readonly(),
10025
+ truncated: boolean()
10026
+ });
10027
+ _enum([
10028
+ "unknown-track",
10029
+ "cross-device",
10030
+ "self-relation",
10031
+ "contradiction",
10032
+ "switch-outside-track",
10033
+ "switch-needs-mixed"
10034
+ ]);
9907
10035
  /**
9908
10036
  * Ops-log — the durable, append-only operations audit shared by the
9909
10037
  * recordings and events management surfaces.
@@ -14048,6 +14176,70 @@ var CompositionBlockStateSchema = object({
14048
14176
  reason: string().optional(),
14049
14177
  fields: array(CompositionFieldStateSchema)
14050
14178
  });
14179
+ var NUMBER = {
14180
+ kind: "fixed",
14181
+ result: ["number"]
14182
+ };
14183
+ var BOOLEAN = {
14184
+ kind: "fixed",
14185
+ result: ["boolean"]
14186
+ };
14187
+ Object.freeze({
14188
+ min: NUMBER,
14189
+ max: NUMBER,
14190
+ abs: NUMBER,
14191
+ floor: NUMBER,
14192
+ ceil: NUMBER,
14193
+ sqrt: NUMBER,
14194
+ round: NUMBER,
14195
+ pow: NUMBER,
14196
+ clamp: NUMBER,
14197
+ avg: NUMBER,
14198
+ sum: NUMBER,
14199
+ age: NUMBER,
14200
+ convert: NUMBER,
14201
+ coalesce: { kind: "coalesce" },
14202
+ any: BOOLEAN,
14203
+ all: BOOLEAN,
14204
+ within: BOOLEAN,
14205
+ latest: {
14206
+ kind: "fixed",
14207
+ result: ["number", "null"]
14208
+ },
14209
+ number: {
14210
+ kind: "fixed",
14211
+ result: ["number", "null"]
14212
+ },
14213
+ rose: {
14214
+ kind: "fixed",
14215
+ result: ["number", "null"]
14216
+ },
14217
+ count: NUMBER
14218
+ });
14219
+ `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
14220
+ /**
14221
+ * What a composition may BUILD on a device type (G2, D676): the caps a new
14222
+ * device of that type, or an existing one that lacks them, can be given. The
14223
+ * blockers are the validator's own refusal codes, and a spec compares this
14224
+ * listing with `planComposition` for every capability — the editor can never
14225
+ * offer a feature the save would refuse for the same reason.
14226
+ */
14227
+ var ComposableFieldSchema = object({
14228
+ path: string(),
14229
+ kind: CompositionFieldKindSchema,
14230
+ nullable: boolean(),
14231
+ required: boolean(),
14232
+ enumValues: array(string()).optional()
14233
+ });
14234
+ var ComposableCapabilitiesSchema = object({ caps: array(object({
14235
+ cap: string(),
14236
+ fields: array(ComposableFieldSchema),
14237
+ /** Present for an item-array cap (`consumables.items`). */
14238
+ itemArray: object({
14239
+ path: string(),
14240
+ keyField: string()
14241
+ }).optional()
14242
+ })) });
14051
14243
  /** Where a block runs. The operator chooses — a block driving a device on an
14052
14244
  * agent is the reason placement is not fixed to the hub. */
14053
14245
  var CoreBlockPlacementSchema = union([literal("hub"), string().min(1)]);
@@ -14212,7 +14404,7 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
14212
14404
  }), method(object({ blockId: string() }), CompositionGetReadSchema, { auth: "admin" }), method(object({}), CompositionListReadSchema, { auth: "admin" }), method(object({
14213
14405
  addonId: string().min(1),
14214
14406
  stableId: string().min(1)
14215
- }), CompositionCustomizationReadSchema, { auth: "admin" }), method(object({
14407
+ }), CompositionCustomizationReadSchema, { auth: "admin" }), method(object({ deviceType: _enum(DeviceType) }), ComposableCapabilitiesSchema, { auth: "admin" }), method(object({
14216
14408
  composition: CompositionSchema,
14217
14409
  blockId: string().optional()
14218
14410
  }), CompositionValidationSchema, {
@@ -23076,7 +23268,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
23076
23268
  deviceId: number(),
23077
23269
  trackId: string(),
23078
23270
  flags: TrackFlagsPatchSchema
23079
- }), TrackFlagsSchema, { kind: "mutation" }), method(ArchivedDebugNoteQueryInputSchema, array(ArchivedDebugNoteSchema).readonly(), { kind: "query" }), method(BirthDecisionQueryInputSchema, array(BirthDecisionRecordSchema).readonly(), { kind: "query" }), method(object({}), EventStoreFootprintSchema, {
23271
+ }), 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, {
23080
23272
  kind: "query",
23081
23273
  auth: "admin"
23082
23274
  }), method(object({ deviceId: number().int().optional() }), EventMediaKindBreakdownSchema, {
@@ -25670,6 +25862,16 @@ var NodeInferenceDevicesSchema = object({
25670
25862
  * Strictly the control plane: never sees a frame, never calls inference,
25671
25863
  * never drains queues. The runner does that work locally on each agent.
25672
25864
  */
25865
+ /**
25866
+ * The input of `setCameraSensors` (G5, D676): the ONE typed write path of a
25867
+ * camera's attached sensors (D672). `config: null` removes the attachment —
25868
+ * the camera reads `not-configured` again, never `unreadable`.
25869
+ */
25870
+ var SetCameraSensorsInputSchema = object({
25871
+ deviceId: number().int().nonnegative(),
25872
+ /** `null` removes the attachment: the camera goes back to `not-configured`. */
25873
+ config: CameraSensorsConfigSchema.nullable()
25874
+ });
25673
25875
  var pipelineOrchestratorCapability = {
25674
25876
  name: "pipeline-orchestrator",
25675
25877
  scope: "system",
@@ -26135,6 +26337,18 @@ var pipelineOrchestratorCapability = {
26135
26337
  */
26136
26338
  getCameraStatuses: method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()),
26137
26339
  /**
26340
+ * Attach sensors to a camera, or detach them with `config: null` (D672
26341
+ * configuration, G5/D676). Written to the orchestrator's per-device store
26342
+ * under the same key `applyDeviceSettingsPatch` writes, and announced as a
26343
+ * settings change so the camera-sensors bridge refreshes. Refused by name
26344
+ * — nothing written — for a device that cannot be read, does not exist,
26345
+ * is not a camera, or names itself as its own sensor.
26346
+ */
26347
+ setCameraSensors: method(SetCameraSensorsInputSchema, object({ success: literal(true) }), {
26348
+ kind: "mutation",
26349
+ auth: "admin"
26350
+ }),
26351
+ /**
26138
26352
  * Disk-wins fleet reconcile after a recordings wipe. Starts the walk in
26139
26353
  * the addon process and returns immediately with the job snapshot — the
26140
26354
  * work outlives the tRPC/UDS 60s timeout. Poll `getReconcileFromDiskStatus`.
@@ -37164,47 +37378,6 @@ var zoneRulesCapability = {
37164
37378
  */
37165
37379
  durability: "restored"
37166
37380
  };
37167
- var NUMBER = {
37168
- kind: "fixed",
37169
- result: ["number"]
37170
- };
37171
- var BOOLEAN = {
37172
- kind: "fixed",
37173
- result: ["boolean"]
37174
- };
37175
- Object.freeze({
37176
- min: NUMBER,
37177
- max: NUMBER,
37178
- abs: NUMBER,
37179
- floor: NUMBER,
37180
- ceil: NUMBER,
37181
- sqrt: NUMBER,
37182
- round: NUMBER,
37183
- pow: NUMBER,
37184
- clamp: NUMBER,
37185
- avg: NUMBER,
37186
- sum: NUMBER,
37187
- age: NUMBER,
37188
- convert: NUMBER,
37189
- coalesce: { kind: "coalesce" },
37190
- any: BOOLEAN,
37191
- all: BOOLEAN,
37192
- within: BOOLEAN,
37193
- latest: {
37194
- kind: "fixed",
37195
- result: ["number", "null"]
37196
- },
37197
- number: {
37198
- kind: "fixed",
37199
- result: ["number", "null"]
37200
- },
37201
- rose: {
37202
- kind: "fixed",
37203
- result: ["number", "null"]
37204
- },
37205
- count: NUMBER
37206
- });
37207
- `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
37208
37381
  /**
37209
37382
  * Accessory device helpers — shared across drivers.
37210
37383
  *
@@ -39655,6 +39828,12 @@ Object.freeze({
39655
39828
  addonId: null,
39656
39829
  access: "view"
39657
39830
  },
39831
+ "coreBlocks.listComposableCaps": {
39832
+ capName: "core-blocks",
39833
+ capScope: "system",
39834
+ addonId: null,
39835
+ access: "view"
39836
+ },
39658
39837
  "coreBlocks.listCompositions": {
39659
39838
  capName: "core-blocks",
39660
39839
  capScope: "system",
@@ -42469,6 +42648,12 @@ Object.freeze({
42469
42648
  addonId: null,
42470
42649
  access: "create"
42471
42650
  },
42651
+ "pipelineAnalytics.clearTrackRelation": {
42652
+ capName: "pipeline-analytics",
42653
+ capScope: "device",
42654
+ addonId: null,
42655
+ access: "delete"
42656
+ },
42472
42657
  "pipelineAnalytics.clearTracks": {
42473
42658
  capName: "pipeline-analytics",
42474
42659
  capScope: "device",
@@ -42721,6 +42906,12 @@ Object.freeze({
42721
42906
  addonId: null,
42722
42907
  access: "view"
42723
42908
  },
42909
+ "pipelineAnalytics.listTrackRelations": {
42910
+ capName: "pipeline-analytics",
42911
+ capScope: "device",
42912
+ addonId: null,
42913
+ access: "view"
42914
+ },
42724
42915
  "pipelineAnalytics.listTracks": {
42725
42916
  capName: "pipeline-analytics",
42726
42917
  capScope: "device",
@@ -42835,6 +43026,18 @@ Object.freeze({
42835
43026
  addonId: null,
42836
43027
  access: "create"
42837
43028
  },
43029
+ "pipelineAnalytics.setTrackPurity": {
43030
+ capName: "pipeline-analytics",
43031
+ capScope: "device",
43032
+ addonId: null,
43033
+ access: "create"
43034
+ },
43035
+ "pipelineAnalytics.setTrackRelation": {
43036
+ capName: "pipeline-analytics",
43037
+ capScope: "device",
43038
+ addonId: null,
43039
+ access: "create"
43040
+ },
42838
43041
  "pipelineAnalytics.startStorageMigrationMove": {
42839
43042
  capName: "pipeline-analytics",
42840
43043
  capScope: "device",
@@ -43339,6 +43542,12 @@ Object.freeze({
43339
43542
  addonId: null,
43340
43543
  access: "create"
43341
43544
  },
43545
+ "pipelineOrchestrator.setCameraSensors": {
43546
+ capName: "pipeline-orchestrator",
43547
+ capScope: "system",
43548
+ addonId: null,
43549
+ access: "create"
43550
+ },
43342
43551
  "pipelineOrchestrator.setCameraStepOverride": {
43343
43552
  capName: "pipeline-orchestrator",
43344
43553
  capScope: "system",
@@ -46749,6 +46958,11 @@ Object.freeze({
46749
46958
  form: "single",
46750
46959
  optional: false
46751
46960
  }],
46961
+ "pipelineAnalytics.clearTrackRelation": [{
46962
+ name: "deviceId",
46963
+ form: "single",
46964
+ optional: false
46965
+ }],
46752
46966
  "pipelineAnalytics.clearTracks": [{
46753
46967
  name: "deviceId",
46754
46968
  form: "single",
@@ -46899,6 +47113,11 @@ Object.freeze({
46899
47113
  form: "single",
46900
47114
  optional: false
46901
47115
  }],
47116
+ "pipelineAnalytics.listTrackRelations": [{
47117
+ name: "deviceId",
47118
+ form: "single",
47119
+ optional: false
47120
+ }],
46902
47121
  "pipelineAnalytics.listTracks": [{
46903
47122
  name: "deviceId",
46904
47123
  form: "single",
@@ -46969,6 +47188,16 @@ Object.freeze({
46969
47188
  form: "single",
46970
47189
  optional: false
46971
47190
  }],
47191
+ "pipelineAnalytics.setTrackPurity": [{
47192
+ name: "deviceId",
47193
+ form: "single",
47194
+ optional: false
47195
+ }],
47196
+ "pipelineAnalytics.setTrackRelation": [{
47197
+ name: "deviceId",
47198
+ form: "single",
47199
+ optional: false
47200
+ }],
46972
47201
  "pipelineAnalytics.wipeAllAnalytics": [{
46973
47202
  name: "deviceId",
46974
47203
  form: "single",
@@ -47049,6 +47278,11 @@ Object.freeze({
47049
47278
  form: "single",
47050
47279
  optional: false
47051
47280
  }],
47281
+ "pipelineOrchestrator.setCameraSensors": [{
47282
+ name: "deviceId",
47283
+ form: "single",
47284
+ optional: false
47285
+ }],
47052
47286
  "pipelineOrchestrator.setCameraStepOverride": [{
47053
47287
  name: "deviceId",
47054
47288
  form: "single",
@@ -49670,6 +49904,52 @@ var AudioSubscriptionController = class {
49670
49904
  }
49671
49905
  };
49672
49906
  //#endregion
49907
+ //#region src/camera-sensors/write.ts
49908
+ /**
49909
+ * The ONE typed write path of a camera's attached sensors (D672 config,
49910
+ * G5/D676) — `pipelineOrchestrator.setCameraSensors`.
49911
+ *
49912
+ * The input is already validated by the cap schema at the router. Here it is
49913
+ * refused BY NAME, and logged with the device tag, before anything is written
49914
+ * (D391): a device that could not be read (a failed read changes nothing,
49915
+ * D49), an unknown device, a non-camera, a camera named as its own sensor.
49916
+ * The value is then handed to `writeDeviceStore` under the CS-1 key, which the
49917
+ * addon binds to `applyDeviceSettingsPatch` — so the D672 patch gate, the
49918
+ * PARSED value, the store and the settings-updated event that refreshes the
49919
+ * bridge's resolver are ONE path, never a copy of it. `null` clears: the
49920
+ * device store keeps a null key, and `parseCameraSensorsSetting` reads it as
49921
+ * not-configured.
49922
+ */
49923
+ function refuse(deps, deviceId, reason) {
49924
+ deps.logger.warn("camera sensors write refused", {
49925
+ tags: { deviceId },
49926
+ meta: { reason }
49927
+ });
49928
+ throw new Error(`pipeline-orchestrator: camera sensors of ${deviceId} not saved — ${reason}`);
49929
+ }
49930
+ async function readOrRefuse(deps, deviceId) {
49931
+ let device;
49932
+ try {
49933
+ device = await deps.readDevice(deviceId);
49934
+ } catch (err) {
49935
+ return refuse(deps, deviceId, `could not read device ${deviceId}: ${errMsg$1(err)}`);
49936
+ }
49937
+ if (device === null) return refuse(deps, deviceId, `no device ${deviceId}`);
49938
+ return device;
49939
+ }
49940
+ async function writeCameraSensors(deps, input) {
49941
+ const { deviceId, config } = input;
49942
+ const device = await readOrRefuse(deps, deviceId);
49943
+ if (device.type !== DeviceType.Camera) return refuse(deps, deviceId, `device ${deviceId} is not a camera`);
49944
+ 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");
49945
+ await deps.writeDeviceStore(deviceId, { [CAMERA_SENSORS_SETTINGS_KEY]: config });
49946
+ deps.logger.info("camera sensors saved", {
49947
+ tags: { deviceId },
49948
+ meta: { sensors: config === null ? 0 : config.sensors.length }
49949
+ });
49950
+ return { success: true };
49951
+ }
49952
+ //#endregion
49673
49953
  //#region src/disk-reconcile-fleet.ts
49674
49954
  async function reconcileFleetFromDisk(deps) {
49675
49955
  const deviceIds = await deps.listDeviceIds();
@@ -65634,6 +65914,37 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
65634
65914
  };
65635
65915
  }
65636
65916
  /**
65917
+ * Refuse a `setCameraStepOverride` write for a `deviceKey` the target node
65918
+ * POSITIVELY reports it does not have. Reuses `getNodeInferenceDevices` —
65919
+ * the same merged probe the admin-ui `DeviceStepOverrideTab` already reads
65920
+ * its device list from — so "valid" here means exactly what the operator's
65921
+ * picker offered.
65922
+ *
65923
+ * D315: "unknown" must never read as "absent". An unreachable node
65924
+ * (`reachable:false`, the read itself failed) proves nothing about what the
65925
+ * node has, so the write is ACCEPTED with a WARN rather than refused — only
65926
+ * a node that actually ANSWERED and left the key out of its list is
65927
+ * grounds for refusal.
65928
+ */
65929
+ async assertCameraStepOverrideDeviceKeyKnown(deviceId, nodeId, deviceKey) {
65930
+ const { reachable, devices } = await this.getNodeInferenceDevices({ nodeId });
65931
+ if (!reachable) {
65932
+ this.ctx.logger.warn("setCameraStepOverride: node unreachable — deviceKey could not be validated", {
65933
+ tags: { deviceId },
65934
+ meta: {
65935
+ deviceId,
65936
+ nodeId,
65937
+ deviceKey
65938
+ }
65939
+ });
65940
+ return;
65941
+ }
65942
+ if (devices.some((d) => d.key === deviceKey)) return;
65943
+ const validKeys = devices.map((d) => d.key).toSorted();
65944
+ const validKeysText = validKeys.length > 0 ? validKeys.join(", ") : "(none reported)";
65945
+ throw new Error(`setCameraStepOverride: node "${nodeId}" does not report inference device "${deviceKey}" — valid keys: ${validKeysText}`);
65946
+ }
65947
+ /**
65637
65948
  * The dispatcher-facing eligible inference devices for a node (audit F1/F4):
65638
65949
  * the enabled AND live-available accelerators as a `deviceKey → weight` map,
65639
65950
  * merging the live probe with the stored opt-OUT map (AUTO = all discovered
@@ -66050,6 +66361,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
66050
66361
  return (await this.settingsStore.readCameraSettingsMap())[String(input.deviceId)]?.stepOverridesByDevice ?? null;
66051
66362
  }
66052
66363
  async setCameraStepOverride(input) {
66364
+ if (input.patch !== null) await this.assertCameraStepOverrideDeviceKeyKnown(input.deviceId, input.agentNodeId, input.deviceKey);
66053
66365
  await this.settingsStore.writeCameraStepOverride(input.deviceId, input.agentNodeId, input.deviceKey, input.addonId, input.patch);
66054
66366
  await this.emitResolvedCameraUpdated(input.deviceId);
66055
66367
  return { success: true };
@@ -66187,6 +66499,37 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
66187
66499
  return this.deviceConfig.applyDeviceSettingsPatch(input);
66188
66500
  }
66189
66501
  /**
66502
+ * The one typed write path of a camera's attached sensors (G5, D676) — see
66503
+ * `camera-sensors/write.ts`. Its refusals run first; the store write then
66504
+ * goes THROUGH `applyDeviceSettingsPatch`, so the D672 patch gate, the
66505
+ * parsed value, the key and the `OrchestrationSettingsUpdated` event are the
66506
+ * one path a camera configured through the device page already takes.
66507
+ */
66508
+ async setCameraSensors(input) {
66509
+ const ctx = this.ctx;
66510
+ const deviceConfig = this.deviceConfig;
66511
+ if (deviceConfig === null) throw new Error("pipeline-orchestrator: not initialised — camera sensors not saved");
66512
+ const api = ctx.api;
66513
+ return writeCameraSensors({
66514
+ readDevice: async (deviceId) => {
66515
+ if (!api) throw new Error("the device registry is not reachable");
66516
+ const dev = await api.deviceManager.getDevice.query({ deviceId });
66517
+ return dev === null || dev === void 0 ? null : {
66518
+ type: dev.type,
66519
+ addonId: dev.addonId,
66520
+ stableId: dev.stableId
66521
+ };
66522
+ },
66523
+ writeDeviceStore: async (deviceId, patch) => {
66524
+ await deviceConfig.applyDeviceSettingsPatch({
66525
+ deviceId,
66526
+ patch
66527
+ });
66528
+ },
66529
+ logger: ctx.logger.child("camera-sensors")
66530
+ }, input);
66531
+ }
66532
+ /**
66190
66533
  * Apply the subset of global settings that affect runtime behaviour
66191
66534
  * (balancer thresholds, broker timeout, failover policy) into the
66192
66535
  * in-memory state so subsequent `balance()` / `handleNodeDisconnect`
@@ -30,7 +30,7 @@ async function d(e) {
30
30
  }
31
31
  }
32
32
  async function f() {
33
- return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-DSjT-nms.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-D8341pPN.mjs")).catch((e) => {
34
34
  throw l = void 0, e;
35
35
  }), l;
36
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-pipeline-orchestrator",
3
- "version": "1.2.264",
3
+ "version": "1.2.266",
4
4
  "description": "Hub-side camera-to-agent load balancer — tracks runner capacity and dispatches attachCamera calls to the optimal pipeline-runner instance",
5
5
  "keywords": [
6
6
  "camstack",