@camstack/system 1.2.57 → 1.2.59

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.
Files changed (69) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/addon-utils.js +1 -1
  4. package/dist/addon-utils.mjs +1 -1
  5. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +2 -2
  6. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +2 -2
  7. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +2 -2
  8. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +2 -2
  9. package/dist/builtins/alerts/alerts.addon.js +1 -1
  10. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -2
  12. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
  13. package/dist/builtins/console-logging/index.js +1 -1
  14. package/dist/builtins/console-logging/index.mjs +1 -1
  15. package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
  16. package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
  17. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  18. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  19. package/dist/builtins/doorbell/binding-mirror.d.ts +65 -0
  20. package/dist/builtins/doorbell/virtual-doorbell.addon.d.ts +56 -11
  21. package/dist/builtins/doorbell/virtual-doorbell.addon.js +248 -26
  22. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +248 -26
  23. package/dist/builtins/hub-forwarder/index.js +1 -1
  24. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  25. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  26. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  27. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  28. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  29. package/dist/builtins/local-network/local-network.addon.js +1 -1
  30. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  31. package/dist/builtins/loki-logging/index.js +1 -1
  32. package/dist/builtins/loki-logging/index.mjs +1 -1
  33. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  34. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  35. package/dist/builtins/platform-probe/index.js +1 -1
  36. package/dist/builtins/platform-probe/index.mjs +1 -1
  37. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  38. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  39. package/dist/builtins/snapshot/index.js +2 -2
  40. package/dist/builtins/snapshot/index.mjs +2 -2
  41. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +2 -2
  42. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +2 -2
  43. package/dist/builtins/sqlite-storage/index.d.ts +3 -0
  44. package/dist/builtins/sqlite-storage/index.js +8 -0
  45. package/dist/builtins/sqlite-storage/index.mjs +2 -2
  46. package/dist/builtins/sqlite-storage/retired-settings-keys.d.ts +94 -0
  47. package/dist/builtins/sqlite-storage/sqlite-pragmas.d.ts +85 -0
  48. package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +1 -0
  49. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +445 -2
  50. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +438 -3
  51. package/dist/builtins/sqlite-storage/wal-maintenance.d.ts +113 -0
  52. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  53. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  54. package/dist/builtins/system-config/system-config.addon.js +1 -1
  55. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  56. package/dist/builtins/winston-logging/index.js +1 -1
  57. package/dist/builtins/winston-logging/index.mjs +1 -1
  58. package/dist/{dist-DhL_zGRT.mjs → dist-BqJJWCS8.mjs} +994 -34
  59. package/dist/{dist-BupxnZOi.js → dist-CV6XTApk.js} +1005 -33
  60. package/dist/index.js +190 -42
  61. package/dist/index.mjs +191 -43
  62. package/dist/kernel/addon-installer.d.ts +55 -7
  63. package/dist/kernel/addon-manifest.d.ts +36 -16
  64. package/dist/kernel/transport/frame-codec.d.ts +43 -2
  65. package/dist/{manifest-python-deps-Ck4-9K9m.mjs → manifest-python-deps-CX5tGRqf.mjs} +95 -10
  66. package/dist/{manifest-python-deps-BqE5j0-O.js → manifest-python-deps-d4acYXVV.js} +95 -10
  67. package/package.json +1 -1
  68. package/dist/{model-download-service-hf0ookyy.js → model-download-service-Cpc6SWzV.js} +1 -1
  69. package/dist/{model-download-service-Cp9f4dk6.mjs → model-download-service-D8B-4ktF.mjs} +1 -1
@@ -2299,9 +2299,38 @@ var CAP_NODE_PIN_CONTEXT_KEY = "__camstackNodePin";
2299
2299
  /**
2300
2300
  * Build the tRPC request options that pin a single capability call to `nodeId`.
2301
2301
  * Pass as the second argument to `.query(input, …)` / `.mutate(input, …)`.
2302
+ *
2303
+ * ## The id is normalised here, and it has to be
2304
+ *
2305
+ * A forked addon reads its own node from `ctx.kernel.localNodeId`, and inside a
2306
+ * worker that value is a RUNNER id — `hub/export-hap`, not `hub`. Routing
2307
+ * compares a pin against real node ids, so such a pin matches nothing and the
2308
+ * call fails with `no provider registered for cap "…"`. The local-first
2309
+ * resolver already guarded against this (`localNodeId.split('/')[0]`), which
2310
+ * made the hazard invisible: unpinned calls worked, and only an explicit pin —
2311
+ * the thing you reach for when you specifically need THIS node — silently
2312
+ * addressed a node that does not exist.
2313
+ *
2314
+ * Cost of it being missing: `addon-export-hap` pinned `decoder.getInfo` to its
2315
+ * own node to read the host's hardware-decode backend. It never once answered,
2316
+ * so every HomeKit egress transcode decoded in SOFTWARE — including 4K H.265 —
2317
+ * while D67's whole premise was that the decoder addon is the authority on
2318
+ * hardware. The warn said `decoding in SOFTWARE` and read as "this node has no
2319
+ * hardware", which was false.
2320
+ *
2321
+ * Normalising in the ONE constructor fixes every caller at once, which is why
2322
+ * it is here and not at the call sites.
2302
2323
  */
2303
2324
  function nodePin(nodeId) {
2304
- return { context: { [CAP_NODE_PIN_CONTEXT_KEY]: nodeId } };
2325
+ return { context: { [CAP_NODE_PIN_CONTEXT_KEY]: toNodeId(nodeId) } };
2326
+ }
2327
+ /**
2328
+ * A runner id is `<nodeId>/<addonId>`; a node id has no slash. Taking the head
2329
+ * is idempotent, so passing an already-clean id costs nothing.
2330
+ */
2331
+ function toNodeId(idOrRunnerId) {
2332
+ const head = idOrRunnerId.split("/")[0];
2333
+ return head === void 0 || head.length === 0 ? idOrRunnerId : head;
2305
2334
  }
2306
2335
  /**
2307
2336
  * Generic types for capability definitions.
@@ -3028,6 +3057,14 @@ var VideoEncodeSchema = z.object({
3028
3057
  "main",
3029
3058
  "high"
3030
3059
  ]).optional(),
3060
+ /**
3061
+ * `-level`, e.g. `'3.1'`. A consumer that ADVERTISES a level in its SDP
3062
+ * (`profile-level-id=42e01f` is Baseline 3.1) must constrain the encoder to
3063
+ * it, or it ships a stream that does not match its own advertisement — the
3064
+ * defect class that kept HomeKit black for a year and that Alexa carried
3065
+ * silently. Optional because a browser negotiates the level itself.
3066
+ */
3067
+ level: z.string().optional(),
3031
3068
  width: z.number().int().positive().optional(),
3032
3069
  height: z.number().int().positive().optional(),
3033
3070
  fps: z.number().positive().optional(),
@@ -3078,6 +3115,29 @@ var EncodeProfileSchema = z.object({
3078
3115
  outputArgs: z.array(z.string()).optional()
3079
3116
  });
3080
3117
  /**
3118
+ * The shape every live egress starts from: H.264 Baseline 3.1 at 720p25.
3119
+ * Baseline because it is the one profile every consumer in this repo decodes
3120
+ * (Echo, iOS, an old browser); 3.1 because that is what the SDPs advertise.
3121
+ */
3122
+ var BASE_LIVE_EGRESS_PROFILE = {
3123
+ video: {
3124
+ codec: "h264",
3125
+ profile: "baseline",
3126
+ level: "3.1",
3127
+ width: 1280,
3128
+ height: 720,
3129
+ fps: 25,
3130
+ bitrateKbps: 2500,
3131
+ gopFrames: 25,
3132
+ bf: 0,
3133
+ preset: "veryfast",
3134
+ tune: "zerolatency"
3135
+ },
3136
+ audio: "passthrough"
3137
+ };
3138
+ ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
3139
+ ({ ...BASE_LIVE_EGRESS_PROFILE });
3140
+ /**
3081
3141
  * Deep wiring healthcheck — snapshot of active reachability probes across
3082
3142
  * every declared capability + widget of every installed plugin, on every
3083
3143
  * node. Produced by the backend `WiringHealthService` and surfaced via
@@ -3126,6 +3186,154 @@ z.object({
3126
3186
  unreachable: z.number()
3127
3187
  })
3128
3188
  });
3189
+ /**
3190
+ * Per-camera FUNCTION SWITCHES — the one coherent on/off surface over the
3191
+ * pipeline functions an operator thinks in terms of.
3192
+ *
3193
+ * ## This file adds no state
3194
+ *
3195
+ * Every switch here is a VIEW onto an authority that already existed
3196
+ * ([D62](../../../../docs/decisions/adr-0062.md)). The whole point of the
3197
+ * group is that there is exactly one place each function is turned off, and
3198
+ * the group routes to it:
3199
+ *
3200
+ * | Switch | Authority | Proven "off stops the work" gate |
3201
+ * | --- | --- | --- |
3202
+ * | `stream-broker` | `deviceManager.setDisabled` | `StreamBrokerManager.reconcileAllCatalogs` releases the brokers; `ensureBroker` refuses re-creation |
3203
+ * | `object-detection` | `deviceManager.setWrapperActive('detection-pipeline')` | `PipelineSettingsStore.resolvePipelineForDevice` returns `{ steps: [], audio: null }` |
3204
+ * | `audio-analysis` | `deviceManager.setWrapperActive('audio-analysis')` | `AudioSubscriptionController.subscribeAudioStream` returns `null` before opening the stream |
3205
+ * | `recording` | `recording.setDeviceConfig` → `RecordingConfig.enabled` | `band-decision.shouldRecord` returns false; the controller detaches the device |
3206
+ * | `notifications` | `notificationRules.setDeviceMuted` | `NotificationCenter.evaluateAndEnqueue` returns before any rule is evaluated |
3207
+ * | `privacy-mask` | `privacyMask.setMask({ enabled })` → the CAMERA | the camera blanks the masked regions itself; every stream and recording carries the black boxes |
3208
+ * | `device-audio` | `privacyMask.setAudioEnabled` → the CAMERA | the camera stops encoding an audio track at all; every consumer sees silent video |
3209
+ *
3210
+ * ## The two switches whose authority is not on this server
3211
+ *
3212
+ * `privacy-mask` and `device-audio` write the CAMERA. That is not a loophole
3213
+ * in "the group stores nothing" — it is the purest form of it: the camera
3214
+ * holds the fact, every read is a read-through, and there is no server-side
3215
+ * copy that could drift. Their availability therefore cannot come from
3216
+ * `listBindableCapsForDeviceType` (a device-NATIVE cap carries no wrappers and
3217
+ * is filtered out there); it comes from the cap's own camera-probed
3218
+ * `privacyMask.getOptions()`, which is strictly more honest — it answers for
3219
+ * THIS camera rather than for the device type
3220
+ * ([D74](../../../../docs/decisions/adr-0074.md)).
3221
+ *
3222
+ * ## `privacy-mask` is the one row whose ON is not "the function is working"
3223
+ *
3224
+ * Every other switch means *this camera's function is doing its job*, so
3225
+ * `enabled: false` is a thing an operator took away. `privacy-mask` means **the
3226
+ * MASK is active** — `enabled: true` is video deliberately obscured. The
3227
+ * polarity is not a choice made here: `addon-export-hap`'s privacy `Switch`
3228
+ * (`builders/privacy-switch.ts`) already mirrors `patch.enabled` verbatim, and
3229
+ * a HomeKit toggle that disagreed with the app's toggle for the same camera is
3230
+ * worse than either surface not having one.
3231
+ *
3232
+ * Two consequences follow and both are load-bearing:
3233
+ *
3234
+ * - **It never counts as `switchedOff`.** `countsAsSwitchedOff` is `false` for
3235
+ * exactly this row. With the polarity above, every camera that has NOT drawn
3236
+ * a privacy mask would otherwise report `switchedOff: ['privacy-mask']` — the
3237
+ * normal, healthy state of most cameras rendered as an operator disablement.
3238
+ * - **Its cost line names BOTH directions.** `costWhenOff` is rendered
3239
+ * unconditionally by both clients, so for this row it has to read correctly
3240
+ * whichever way the switch is sitting.
3241
+ *
3242
+ * The wrapper-binding pair is not a new idea: `legacy-migrations.ts` already
3243
+ * migrated the legacy `audioEnabled` / `pipelineEnabled` /
3244
+ * `motionDetectionEnabled` booleans ONTO `setWrapperActive`. The group is the
3245
+ * surface that decision never got.
3246
+ *
3247
+ * ## Two rules that are load-bearing
3248
+ *
3249
+ * - **Recording's switch is `enabled`, never the bands.** `bands` is the only
3250
+ * authored intent and `mode` is derived from it (`deriveRecordingMode`).
3251
+ * Expressing "off" by clearing bands destroys the operator's schedule and
3252
+ * turning the camera back on would then silently record nothing.
3253
+ * - **A switch that is off must be reported as off**, not merely produce
3254
+ * nothing. {@link CameraSwitch.enabled} is what a status surface renders as
3255
+ * "disabled by an operator" instead of "broken" — see
3256
+ * `CameraStatus.switchedOff`.
3257
+ */
3258
+ /**
3259
+ * The functions the operator named — five on 2026-08-05, plus the camera's own
3260
+ * microphone on 2026-08-07. Deliberately NOT one id per pipeline step: face
3261
+ * recognition and plate/LPR are per-step toggles on
3262
+ * `pipelineOrchestrator.setCameraStepToggle` and belong in the pipeline
3263
+ * editor, not in a safety group.
3264
+ */
3265
+ var CameraSwitchIdSchema = z.enum([
3266
+ "stream-broker",
3267
+ "object-detection",
3268
+ "privacy-mask",
3269
+ "device-audio",
3270
+ "audio-analysis",
3271
+ "recording",
3272
+ "notifications"
3273
+ ]);
3274
+ /**
3275
+ * WHERE the switch's state actually lives. A discriminated union rather than a
3276
+ * string so both the writer (the orchestrator's `setCameraSwitch`) and any
3277
+ * reader can exhaustively narrow — and so "the group added a parallel map" is
3278
+ * a compile error rather than a review comment.
3279
+ */
3280
+ var CameraSwitchAuthoritySchema = z.discriminatedUnion("kind", [
3281
+ z.object({ kind: z.literal("device-disabled") }),
3282
+ z.object({
3283
+ kind: z.literal("wrapper-binding"),
3284
+ capName: z.string()
3285
+ }),
3286
+ z.object({ kind: z.literal("recording-config") }),
3287
+ z.object({ kind: z.literal("notification-mute") }),
3288
+ z.object({
3289
+ kind: z.literal("camera-audio"),
3290
+ capName: z.string()
3291
+ }),
3292
+ z.object({
3293
+ kind: z.literal("camera-mask"),
3294
+ capName: z.string()
3295
+ })
3296
+ ]);
3297
+ /**
3298
+ * Why a switch is not offered for this camera. Rendered instead of the
3299
+ * control, never as a dead control — an absent function and a broken one must
3300
+ * not look the same.
3301
+ */
3302
+ var CameraSwitchUnavailableReasonSchema = z.enum([
3303
+ "no-provider",
3304
+ "source-unreachable",
3305
+ "not-configured"
3306
+ ]);
3307
+ /**
3308
+ * One switch, resolved for one camera.
3309
+ *
3310
+ * `label` and `costWhenOff` travel ON THE WIRE rather than being looked up
3311
+ * client-side: the viewer is a separate repository that does not import
3312
+ * `@camstack/types`, and a cost line duplicated in two clients is a cost line
3313
+ * that will disagree with itself. Five rows per camera is nothing.
3314
+ */
3315
+ var CameraSwitchSchema = z.object({
3316
+ id: CameraSwitchIdSchema,
3317
+ label: z.string(),
3318
+ /**
3319
+ * What the operator LOSES while this is off, in one sentence. Required, not
3320
+ * optional: a switch that cannot say what it costs should not ship.
3321
+ */
3322
+ costWhenOff: z.string(),
3323
+ /** False = do not render a control. `unavailableReason` says why. */
3324
+ available: z.boolean(),
3325
+ unavailableReason: CameraSwitchUnavailableReasonSchema.optional(),
3326
+ /** Current state. Meaningless when `available` is false — read it as `true`. */
3327
+ enabled: z.boolean(),
3328
+ authority: CameraSwitchAuthoritySchema
3329
+ });
3330
+ /** The whole group for one camera. */
3331
+ var CameraSwitchGroupSchema = z.object({
3332
+ deviceId: z.number().int(),
3333
+ switches: z.array(CameraSwitchSchema).readonly(),
3334
+ /** Unix ms when the group was composed server-side. */
3335
+ fetchedAt: z.number()
3336
+ });
3129
3337
  /** Friendly display labels for stream quality IDs. */
3130
3338
  var STREAM_QUALITY_LABELS = {
3131
3339
  high: "High",
@@ -3175,14 +3383,16 @@ var OpsLogOpSchema = z.enum([
3175
3383
  "manual-delete",
3176
3384
  "rescan",
3177
3385
  "retention-run",
3178
- "relocate"
3386
+ "relocate",
3387
+ "orphan-audit"
3179
3388
  ]);
3180
3389
  /** Why the operation ran. */
3181
3390
  var OpsLogReasonSchema = z.enum([
3182
3391
  "retention",
3183
3392
  "quota",
3184
3393
  "manual",
3185
- "operator"
3394
+ "operator",
3395
+ "maintenance"
3186
3396
  ]);
3187
3397
  /** One audit row, shared verbatim by both domains. */
3188
3398
  var OpsLogEntrySchema = z.object({
@@ -5409,6 +5619,126 @@ var RtpSourceSchema = z.object({
5409
5619
  encoder: z.string(),
5410
5620
  pipelineKey: z.string()
5411
5621
  });
5622
+ /**
5623
+ * The encode request — **structured and serialisable, with NO raw-flag escape
5624
+ * hatch.** This is deliberate and it is the one lesson taken from
5625
+ * `getStreamWithCodec`: that method's `outputArgs: string[]` is simultaneously
5626
+ * its extensibility mechanism AND part of `pipelineKeyFor`'s sharing key, so
5627
+ * adding a flag silently forks the shared child, and two consumers that mean
5628
+ * the same thing but spell it differently never share. Here every knob is a
5629
+ * NAMED field: a new requirement becomes a schema field (and a codegen run),
5630
+ * never an opaque array.
5631
+ *
5632
+ * `inputArgs` / `outputArgs` are omitted from the profile for the same reason.
5633
+ * The operator-facing derived-stream transform editor still has them — that is
5634
+ * a different surface (`publishCameraStream({ kind: 'derived' })`) with a
5635
+ * different purpose (reshaping a badly-behaved SOURCE), and it is unchanged.
5636
+ */
5637
+ var EgressEncodeSchema = EncodeProfileSchema.omit({
5638
+ inputArgs: true,
5639
+ outputArgs: true
5640
+ });
5641
+ /**
5642
+ * How the encoder is bounded. `'tight'` is a one-second VBV window for a
5643
+ * consumer whose budget is enforced per second (HomeKit); `'relaxed'` is two
5644
+ * seconds, letting a keyframe spike borrow from the next second (a browser,
5645
+ * an Echo). Named rather than numeric so the INTENT survives.
5646
+ */
5647
+ var EgressRateControlSchema = z.enum(["tight", "relaxed"]);
5648
+ var EgressTranscodeRequestSchema = z.object({
5649
+ deviceId: z.number().int().nonnegative(),
5650
+ /** Which published stream to read. */
5651
+ source: z.discriminatedUnion("kind", [z.object({
5652
+ kind: z.literal("profile"),
5653
+ profile: CamProfileSchema
5654
+ }), z.object({
5655
+ kind: z.literal("cam-stream"),
5656
+ camStreamId: z.string().min(1)
5657
+ })]),
5658
+ encode: EgressEncodeSchema,
5659
+ rateControl: EgressRateControlSchema.optional(),
5660
+ /**
5661
+ * `-bsf:v`. A consumer that negotiates its OWN SDP (HomeKit) cannot carry
5662
+ * out-of-band extradata and needs `dump_extra` on both the copy and encode
5663
+ * branches. Enumerated, not free text.
5664
+ */
5665
+ bitstreamFilter: z.enum([
5666
+ "dump_extra",
5667
+ "h264_mp4toannexb",
5668
+ "hevc_mp4toannexb"
5669
+ ]).optional(),
5670
+ /**
5671
+ * Publish the transcode as a LOCAL push cam stream, instead of leaving the
5672
+ * consumer to dial the returned url. The broker picks the id and returns it
5673
+ * as `camStreamId` — a caller-supplied one would be circular, since the
5674
+ * sharing key is computed FROM this request.
5675
+ *
5676
+ * The url is still returned and still the contract for a transcode pinned to
5677
+ * another node. But dialling it locally costs an RTSP round trip that changes
5678
+ * the transport underneath the consumer: a dialled stream is an RTP source,
5679
+ * so `isRtpSource()` is true and the session takes the RTP-passthrough +
5680
+ * repacketizer branch. The push branch — the one the derived mechanism has
5681
+ * live hours on — is never reached. Measured on Alexa: broker registered, RTP
5682
+ * arriving, key frame arriving, black screen, on a chain healthy at every
5683
+ * other point.
5684
+ *
5685
+ * Same idea the transport already applies to CALLS, where `classifyCapRoute`
5686
+ * gives priority to `hub-in-process` so a local call never leaves the node.
5687
+ * This is that rule for media.
5688
+ */
5689
+ publishLocally: z.boolean().optional(),
5690
+ pixelFormat: z.enum(["yuv420p", "nv12"]).optional(),
5691
+ /**
5692
+ * Operator/consumer override for decode hardware. ABSENT is the normal case
5693
+ * and the one that matters: the broker then resolves the backend from the
5694
+ * DECODER ADDON's per-node `probedBestHwaccel` (see
5695
+ * `@camstack/types` `ffmpeg/hwaccel.ts`), which is the ranking known to work
5696
+ * on this hardware — never the raw kernel resolver's qsv-first order.
5697
+ */
5698
+ decodeHwAccel: z.enum([
5699
+ "auto",
5700
+ "none",
5701
+ "videotoolbox",
5702
+ "vaapi",
5703
+ "qsv",
5704
+ "cuda"
5705
+ ]).optional(),
5706
+ /**
5707
+ * Host to embed in the returned restream `url`. The broker mints hub-local
5708
+ * `127.0.0.1` URLs; a consumer on another node passes a cluster-resolvable
5709
+ * host (`NodeTopologyService.reachableHostByNode`) so the returned URL is
5710
+ * dialable from there. Same contract as `getStreamWithCodec.hostname` —
5711
+ * `substituteRtspHost` rewrites only the dial address, never the restreamer.
5712
+ */
5713
+ hostname: z.string().optional(),
5714
+ /** Attribution for the broker panel. Never part of the sharing key. */
5715
+ tag: z.string().optional()
5716
+ });
5717
+ var EgressTranscodeSchema = z.object({
5718
+ /** Dial-able RTSP url (host-substituted when `hostname` was supplied). */
5719
+ url: z.string(),
5720
+ /** Release handle. Refcounted — the child dies when the last holder releases. */
5721
+ pipelineKey: z.string(),
5722
+ videoCodec: z.enum(["H264", "H265"]),
5723
+ resolution: z.object({
5724
+ width: z.number().int().positive(),
5725
+ height: z.number().int().positive()
5726
+ }),
5727
+ transcoded: z.boolean(),
5728
+ encoder: z.string(),
5729
+ /**
5730
+ * The decode backend the child ACTUALLY ran with — `null` for software.
5731
+ * Returned rather than assumed: a consumer that asked for hardware and got
5732
+ * software needs to be able to see that without reading the broker's logs.
5733
+ */
5734
+ decodeHwAccel: z.string().nullable(),
5735
+ /**
5736
+ * Set when `publishLocally` was honoured: attach to THIS instead of dialling
5737
+ * `url`, and the session takes the push/deframe transport rather than the
5738
+ * RTP-passthrough one. `null` means the consumer must dial.
5739
+ */
5740
+ camStreamId: z.string().nullable()
5741
+ });
5412
5742
  var streamBrokerCapability = {
5413
5743
  name: "stream-broker",
5414
5744
  scope: "system",
@@ -5580,6 +5910,40 @@ var streamBrokerCapability = {
5580
5910
  auth: "admin"
5581
5911
  }),
5582
5912
  /**
5913
+ * THE ffmpeg primitive. Acquire an encoded stream matching a structured
5914
+ * encode plan; the broker builds the argv through the ONE builder
5915
+ * (`@camstack/types` `ffmpeg/invocation.ts`), resolves decode hardware from
5916
+ * the DECODER ADDON's per-node ranking, and returns a dialable RTSP url.
5917
+ *
5918
+ * **Refcounted and deduplicated on EXACT match.** Two requesters whose
5919
+ * requests produce the same `egressTranscodeSharingKey` receive the SAME
5920
+ * `pipelineKey` and the same child process. Nearly-identical requests are
5921
+ * NOT merged.
5922
+ *
5923
+ * **The handle is immutable.** There is deliberately no `reconfigure`
5924
+ * method and this one never accepts a `pipelineKey` alongside encode
5925
+ * parameters: a consumer whose requirements change releases and
5926
+ * re-acquires. A mutable shared handle is exactly what made Alexa's old
5927
+ * `derived:alexa-<id>` stream a co-tenant hazard — one consumer's
5928
+ * downgrade dragged every other consumer down with it.
5929
+ *
5930
+ * Placement: unpinned, `classifyCapRoute` serves this hub-in-process
5931
+ * (Priority 1). A caller that wants the transcode elsewhere passes
5932
+ * `nodePin(nodeId)` and a `hostname` it can dial.
5933
+ */
5934
+ acquireEgressTranscode: method(EgressTranscodeRequestSchema, EgressTranscodeSchema, {
5935
+ kind: "mutation",
5936
+ auth: "admin"
5937
+ }),
5938
+ /** Drop one reference. The child dies when the last holder releases. */
5939
+ releaseEgressTranscode: method(z.object({ pipelineKey: z.string() }), z.object({
5940
+ released: z.boolean(),
5941
+ refcount: z.number().int().nonnegative()
5942
+ }), {
5943
+ kind: "mutation",
5944
+ auth: "admin"
5945
+ }),
5946
+ /**
5583
5947
  * ── Decoded audio-chunk plane (Phase 5 / D9) ──────────────────────
5584
5948
  *
5585
5949
  * The serialisable replacement for the live-object `IStreamBroker.
@@ -11467,12 +11831,13 @@ var NcConditionsSchema = z.object({
11467
11831
  * source; otherwise the subject's source must equal it. Legacy records
11468
11832
  * with no stamped source are treated as `pipeline`. The union spans both
11469
11833
  * record kinds — object events carry `pipeline` | `onboard`, synthetic
11470
- * tracks carry `sensor`.
11834
+ * tracks carry `sensor` (a linked device) or `audio` (a D62 audio marker).
11471
11835
  */
11472
11836
  source: z.enum([
11473
11837
  "pipeline",
11474
11838
  "onboard",
11475
11839
  "sensor",
11840
+ "audio",
11476
11841
  "any"
11477
11842
  ]).optional(),
11478
11843
  /**
@@ -12060,6 +12425,35 @@ var notificationRulesCapability = {
12060
12425
  auth: "admin"
12061
12426
  }),
12062
12427
  /**
12428
+ * PERMANENT per-camera mute — the notifications half of the per-camera
12429
+ * function switch group ([D61](../../../../docs/decisions/adr-0067.md)).
12430
+ *
12431
+ * Deliberately NOT a snooze. A snooze is bounded at
12432
+ * {@link NC_SNOOZE_MAX_MINUTES} on purpose — "a snooze that could not
12433
+ * expire would be an outage the operator asked for once and forgot" — and
12434
+ * widening it to express "this camera never notifies" would destroy that
12435
+ * property for every snooze. A mute is the other thing: an explicit,
12436
+ * indefinite, admin-only decision, visible in the switch group next to the
12437
+ * other four, and reported on `CameraStatus.switchedOff` so a silent
12438
+ * camera never reads as a working one.
12439
+ *
12440
+ * Returned as ONE list rather than a per-camera query: the group's reader
12441
+ * needs every camera's state, and a per-camera fan-out over the viewer's
12442
+ * single WebSocket is N frames serialised on one socket.
12443
+ */
12444
+ listDeviceMutes: method(z.object({}), z.object({ mutedDeviceIds: z.array(z.number().int()).readonly() }), { auth: "admin" }),
12445
+ /**
12446
+ * Mute or unmute one camera. Idempotent; an unmute of a camera that was
12447
+ * never muted succeeds.
12448
+ */
12449
+ setDeviceMuted: method(z.object({
12450
+ deviceId: z.number().int(),
12451
+ muted: z.boolean()
12452
+ }), z.object({ success: z.literal(true) }), {
12453
+ kind: "mutation",
12454
+ auth: "admin"
12455
+ }),
12456
+ /**
12063
12457
  * Dry-run a rule against recently persisted records (object events for
12064
12458
  * `immediate`, closed tracks for `track-end`). Mutation kind only to
12065
12459
  * carry the full rule object safely; no side effects.
@@ -12471,12 +12865,60 @@ var TrackAudioLabelSchema = z.object({
12471
12865
  });
12472
12866
  /**
12473
12867
  * How a track was produced. `pipeline` (default / absent) = the spatial
12474
- * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
12475
- * linked sensor/control state change (no positions; carries a snapshot). The
12476
- * spatial subsystems (tracker association, occupancy count, re-id/embedding,
12477
- * resurrection) MUST skip `sensor` tracks they have no bbox trajectory.
12868
+ * detection+tracking pipeline. Every OTHER value is a SYNTHETIC projection
12869
+ * no positions, a single snapshot, and no bbox trajectory at all:
12870
+ *
12871
+ * - `sensor` — a linked sensor/control device state change.
12872
+ * - `audio` — an audio event on the camera itself that was anomalous for
12873
+ * THAT camera, loud, and heard while nothing visual was happening (D62).
12874
+ *
12875
+ * The spatial subsystems (tracker association, occupancy count, re-id /
12876
+ * embedding, resurrection) MUST skip every synthetic source. Test for that
12877
+ * with `isSpatialTrack`, which allow-lists `pipeline` — a `!== 'sensor'`
12878
+ * check silently readmits every source added after it was written.
12478
12879
  */
12479
- var TrackSourceSchema = z.enum(["pipeline", "sensor"]);
12880
+ var TrackSourceSchema = z.enum([
12881
+ "pipeline",
12882
+ "sensor",
12883
+ "audio"
12884
+ ]);
12885
+ /**
12886
+ * Per-track OPERATOR flags — set by hand from the admin UI or the viewer, never
12887
+ * by the pipeline. Spread into `TrackSchema` and `KeyEventSchema` from one place
12888
+ * so the two surfaces cannot drift.
12889
+ *
12890
+ * **Absent ≠ false.** A track that has never been touched omits the field; an
12891
+ * explicitly un-flagged track carries `false`. Legacy rows written before the
12892
+ * columns existed read as absent, and a consumer that needs a boolean should say
12893
+ * `flag === true`, not `flag !== false`.
12894
+ *
12895
+ * What the flags DO is deliberately UNDEFINED at the time of writing: they are
12896
+ * operator curation, and the behaviour they drive will be specified separately.
12897
+ * In particular a `markForTrain` track is NOT pinned against retention — see
12898
+ * `docs/decisions/adr-0059.md` for why that is a store-level change, not a flag.
12899
+ */
12900
+ var TrackFlagFields = {
12901
+ /** Operator marked this track as training material. */
12902
+ markForTrain: z.boolean().optional(),
12903
+ /** Operator marked this track for diagnostic attention. */
12904
+ debug: z.boolean().optional()
12905
+ };
12906
+ /**
12907
+ * The write half: a PARTIAL patch. An omitted key is left untouched, so setting
12908
+ * one flag can never clear the other — the toggles are independent and are
12909
+ * driven from three surfaces that do not know about each other.
12910
+ */
12911
+ var TrackFlagsPatchSchema = z.object(TrackFlagFields);
12912
+ /**
12913
+ * The resolved flag state after a write. Both fields are REQUIRED here (absent
12914
+ * collapses to `false`) so a caller can drive a toggle's checked state off the
12915
+ * mutation result without a re-fetch.
12916
+ */
12917
+ var TrackFlagsSchema = z.object({
12918
+ trackId: z.string(),
12919
+ markForTrain: z.boolean(),
12920
+ debug: z.boolean()
12921
+ });
12480
12922
  var TrackSchema = z.object({
12481
12923
  trackId: z.string(),
12482
12924
  deviceId: z.number(),
@@ -12519,7 +12961,8 @@ var TrackSchema = z.object({
12519
12961
  /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
12520
12962
  * Populated from the persisted envelope columns on historical reads;
12521
12963
  * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
12522
- envelope: TrackEnvelopeSchema.optional()
12964
+ envelope: TrackEnvelopeSchema.optional(),
12965
+ ...TrackFlagFields
12523
12966
  });
12524
12967
  var BaseEventFields = {
12525
12968
  id: z.string(),
@@ -12740,7 +13183,8 @@ var KeyEventSchema = z.object({
12740
13183
  /** Highest-confidence ObjectEvent id for the track (empty when none). */
12741
13184
  bestEventId: z.string(),
12742
13185
  /** Track lifetime in ms (lastSeen - firstSeen). */
12743
- windowMs: z.number().optional()
13186
+ windowMs: z.number().optional(),
13187
+ ...TrackFlagFields
12744
13188
  });
12745
13189
  z.object({
12746
13190
  trackId: z.string(),
@@ -12826,7 +13270,31 @@ var RebuildObjectEmbeddingsInput = z.object({
12826
13270
  since: z.number().optional(),
12827
13271
  until: z.number().optional(),
12828
13272
  /** Stop after this many tracks; the result reports whether more remain. */
12829
- maxTracks: z.number().int().positive().optional()
13273
+ maxTracks: z.number().int().positive().optional(),
13274
+ /**
13275
+ * Run every embedding on THIS node instead of round-robining the fleet.
13276
+ *
13277
+ * Named `executeOnNodeId` and not `nodeId` on purpose: an inline `nodeId`
13278
+ * field in cap args is read by `parent-unowned-call.ts` as a ROUTING PIN, so
13279
+ * calling it that would pin the rebuild REQUEST itself to that node — the
13280
+ * rebuild orchestration lives on the hub, and only the per-track step runs
13281
+ * remotely. This field is data; the per-track pin is applied inside.
13282
+ *
13283
+ * Absent ⇒ round-robin over every online node whose runner can serve the
13284
+ * pinned model.
13285
+ */
13286
+ executeOnNodeId: z.string().optional(),
13287
+ /**
13288
+ * Milliseconds to wait between tracks; omit for the built-in default, `0` to
13289
+ * run flat out.
13290
+ *
13291
+ * A rebuild is bulk maintenance on hub-main's single thread. Measured
13292
+ * 2026-08-06, an unpaced pass held that thread busy 82.2 s out of 120 and
13293
+ * pushed `nodes.topology` from 0.25 s to 26 s for 43 minutes. The value in
13294
+ * force is logged at start and finish so a deliberately slow pass reads
13295
+ * differently from a stalled one.
13296
+ */
13297
+ pacingMs: z.number().int().nonnegative().optional()
12830
13298
  });
12831
13299
  /**
12832
13300
  * Result of emptying the CLIP index.
@@ -12860,13 +13328,23 @@ var RebuildStatusSchema = z.object({
12860
13328
  /** Tracks with no usable detection box. */
12861
13329
  missingBbox: z.number(),
12862
13330
  /**
12863
- * Tracks the pipeline REFUSED rather than broke on: the camera is not
12864
- * attached, or `clip-embedding` is not enabled in its step tree. Separate
12865
- * from `failed` because the remedy is a configuration change, not an engine
12866
- * investigation and because a pass over decommissioned cameras would
12867
- * otherwise read as a total engine outage.
13331
+ * Tracks an executing node REFUSED rather than broke on an unreadable key
13332
+ * frame, a step that threw. Separate from `failed` because the remedy is
13333
+ * different, and because a whole camera silently contributing zero vectors
13334
+ * is the shape of failure a rebuild must never hide.
12868
13335
  */
12869
13336
  notRunnable: z.number(),
13337
+ /**
13338
+ * The pass stopped because NO node could serve the pinned model.
13339
+ *
13340
+ * Distinct from `notRunnable` on purpose: that one says "this track was
13341
+ * refused", this one says "the cluster cannot do this work at all" — every
13342
+ * candidate node either lacks the `clip-embedding` step, lacks a build of the
13343
+ * pinned model for its engine format, or dropped out. The remedy is a model /
13344
+ * engine change, not a per-camera one. Non-zero here always comes with
13345
+ * `complete: false`.
13346
+ */
13347
+ noCapableNode: z.number(),
12870
13348
  failed: z.number(),
12871
13349
  /** Set once a pass ends: true only when EVERYTHING was covered. */
12872
13350
  complete: z.boolean().nullable(),
@@ -13056,6 +13534,31 @@ var pipelineAnalyticsCapability = {
13056
13534
  auth: "admin"
13057
13535
  }),
13058
13536
  /**
13537
+ * Set the per-track operator flags (`markForTrain`, `debug`) on ONE track.
13538
+ * The patch is PARTIAL — an omitted key is left untouched — because the
13539
+ * three surfaces that write it (admin Events grid, viewer track detail,
13540
+ * viewer cluster detail) each own one toggle and must not clobber the other.
13541
+ *
13542
+ * Writes the track ROW: `markForTrain`/`debug` are per-TRACK state, so they
13543
+ * live where `label` and `importance` live, not in any per-device settings
13544
+ * store. Updates the in-RAM active track too, so a flag set on a live track
13545
+ * survives its expiry-time persist.
13546
+ *
13547
+ * `auth: 'protected'` (the default), NOT `admin`: the viewer is an
13548
+ * authenticated non-admin surface and two of the three call sites are
13549
+ * there. Revisit if a flag ever gains an effect that costs storage —
13550
+ * `deleteTracks` next door is admin for exactly that reason.
13551
+ *
13552
+ * Returns the RESOLVED state of both flags (absent → `false`) so a caller
13553
+ * can drive its toggle without a re-fetch. Rejects an unknown track.
13554
+ */
13555
+ setTrackFlags: method(z.object({
13556
+ /** Log/audit scope only — the trackId is globally unique on its own. */
13557
+ deviceId: z.number(),
13558
+ trackId: z.string(),
13559
+ flags: TrackFlagsPatchSchema
13560
+ }), TrackFlagsSchema, { kind: "mutation" }),
13561
+ /**
13059
13562
  * Durable event-store footprint for the management UI: event rows
13060
13563
  * (motion + object + audio) counted per camera + total, plus the
13061
13564
  * event-owned media bytes on disk per camera + total. Stat/count-based,
@@ -13935,6 +14438,53 @@ var DetailResultSchema = z.object({
13935
14438
  nativeFaceShortSidePx: z.number().optional()
13936
14439
  });
13937
14440
  /**
14441
+ * Why an executing node REFUSED a stateless step run (`runStatelessStep`).
14442
+ *
14443
+ * A refusal is a first-class answer, not an error, because the caller's next
14444
+ * move depends on WHICH one it is — and because "the pass produced nothing"
14445
+ * must never be reachable without a named, counted cause. The two tiers:
14446
+ *
14447
+ * - **node-level** (`unknown-step`, `model-not-servable`) — this node can
14448
+ * never serve this (step, model) pair. The caller drops it from its rotation
14449
+ * and retries the same work elsewhere; nothing about the work changes.
14450
+ * - **work-level** (`unreadable-frame`, `execution-failed`) — this node is
14451
+ * fine, this one request is not. Retrying it on another node would only
14452
+ * spread the same failure.
14453
+ */
14454
+ var StatelessStepRefusalSchema = z.enum([
14455
+ "unknown-step",
14456
+ "model-not-servable",
14457
+ "unreadable-frame",
14458
+ "execution-failed"
14459
+ ]);
14460
+ /**
14461
+ * Answer to `runStatelessStep` — a discriminated union rather than a nullable
14462
+ * result, because `null` is exactly what made the camera-bound detail path
14463
+ * unable to tell "refused" from "never asked".
14464
+ */
14465
+ var RunStatelessStepResultSchema = z.discriminatedUnion("kind", [z.object({
14466
+ kind: z.literal("ran"),
14467
+ /** The node that actually executed it — the pin, echoed back for the log. */
14468
+ nodeId: z.string(),
14469
+ /**
14470
+ * The model the step ran with.
14471
+ *
14472
+ * The node verified this exact id has a build for the format it dispatched
14473
+ * on BEFORE running, so the executor's format resolution returns it
14474
+ * unchanged. A caller that pinned a model must compare this field and
14475
+ * treat a mismatch as a refusal — the whole point of the pin is that a
14476
+ * pass writes one feature space.
14477
+ */
14478
+ modelId: z.string(),
14479
+ details: z.array(DetailResultSchema)
14480
+ }), z.object({
14481
+ kind: z.literal("refused"),
14482
+ nodeId: z.string(),
14483
+ reason: StatelessStepRefusalSchema,
14484
+ /** Human-readable specifics — the format tried, the formats shipped, etc. */
14485
+ detail: z.string()
14486
+ })]);
14487
+ /**
13938
14488
  * Per-camera tunable ranges + defaults. Single source of truth used
13939
14489
  * by both the Zod data schema (validation + default fallback) and
13940
14490
  * the device settings UI (slider min/max/step). Touch one place and
@@ -14379,7 +14929,77 @@ var pipelineRunnerCapability = {
14379
14929
  cropJpeg: z.string().optional(),
14380
14930
  parent: DetailParentSchema,
14381
14931
  steps: z.array(z.string()).optional()
14382
- }), z.object({ details: z.array(DetailResultSchema) }).nullable(), { kind: "mutation" })
14932
+ }), z.object({ details: z.array(DetailResultSchema) }).nullable(), { kind: "mutation" }),
14933
+ /**
14934
+ * Run ONE enrichment step against caller-supplied pixels, with NO camera
14935
+ * session — no attach, no frame handle, no device affinity.
14936
+ *
14937
+ * ## Why this exists next to `runDetailSubtree` and not inside it
14938
+ *
14939
+ * `runDetailSubtree` resolves its step tree from the LIVE ATTACH STATE. That
14940
+ * is correct for it: its reason to exist is a frame HANDLE, and a handle is
14941
+ * only resolvable while a decode session is open. But it makes the method
14942
+ * useless to anything walking history — cameras run `detectionMode:
14943
+ * 'on-motion'` and detach ~30 s after motion stops, so a fleet-wide
14944
+ * embedding rebuild found most cameras detached and rebuilt only whatever
14945
+ * happened to be attached at that second (measured 2026-08-06: 1125 tracks
14946
+ * scanned, 121 rebuilt, 601 refused as "camera is not attached").
14947
+ *
14948
+ * A caller that ships its own pixels needs none of that machinery. So this
14949
+ * method takes the three things a step actually needs — an image, a box, a
14950
+ * step id — resolves the step from the CATALOG rather than from an
14951
+ * attachment, and runs it on whichever node the caller pinned.
14952
+ *
14953
+ * ## What it deliberately keeps
14954
+ *
14955
+ * The crop rectangle is derived by the SAME `deriveDetailCropRect` call the
14956
+ * live detail plane uses, from the same cluster-wide convention
14957
+ * ([D52](../../../docs/decisions/adr-0052.md)). `frameJpeg` is a FULL frame,
14958
+ * never a pre-cut tile, for exactly the reason `runDetailSubtree` documents:
14959
+ * a caller that cuts is a second feature space.
14960
+ *
14961
+ * ## What it deliberately drops
14962
+ *
14963
+ * No device jump and no `deviceKey`: the jump roster comes from the attach
14964
+ * config, which does not exist here. The step runs on the node's default
14965
+ * engine. This is a maintenance path, not a latency-sensitive one.
14966
+ *
14967
+ * ## Routing
14968
+ *
14969
+ * NOT device-bound. `sourceDeviceId` is DIAGNOSTIC — the camera whose track
14970
+ * these pixels came from, so every log line on the executing node can carry
14971
+ * `tags: { deviceId }`. It is deliberately not named `deviceId`: that field
14972
+ * is a routing hint that would send the call to the camera's owning node,
14973
+ * which is the placement constraint this method exists to remove. Callers
14974
+ * choose the node with `nodePin(nodeId)`; unpinned, an unowned call is
14975
+ * served hub-in-process like any other singleton.
14976
+ */
14977
+ runStatelessStep: method(z.object({
14978
+ /** Catalog step id, e.g. `clip-embedding`. */
14979
+ stepId: z.string(),
14980
+ /**
14981
+ * REQUIRED model pin. The node runs this exact model or refuses with
14982
+ * `model-not-servable` — it never substitutes a format default, because
14983
+ * a fleet pass that round-robins across nodes would then fill one index
14984
+ * from several encoders.
14985
+ */
14986
+ modelId: z.string(),
14987
+ /** FULL FRAME, base64 JPEG. The runner cuts — do NOT pre-crop. */
14988
+ frameJpeg: z.string(),
14989
+ /**
14990
+ * The subject box, NORMALISED [0,1] against `frameJpeg`. Normalised on
14991
+ * purpose: the caller stores boxes against a downscaled analysis frame
14992
+ * while the stored key frame is native-resolution, and the only side
14993
+ * that reliably knows the image's pixel dimensions is the side that
14994
+ * decodes it. Denormalising here removes a second reader of the
14995
+ * dimensions and the class of mismatch that comes with it.
14996
+ */
14997
+ bbox: NativeCropBboxSchema,
14998
+ /** Parent class of the subject (`person`, `vehicle`, …) — carried into the result. */
14999
+ className: z.string(),
15000
+ /** Camera the pixels came from. Diagnostics + log tags ONLY — never routing. */
15001
+ sourceDeviceId: z.number()
15002
+ }), RunStatelessStepResultSchema, { kind: "mutation" })
14383
15003
  }
14384
15004
  };
14385
15005
  var CameraPipelineConfigSchema = z.object({
@@ -14687,6 +15307,20 @@ var CameraStatusSchema = z.object({
14687
15307
  detection: CameraDetectionStatusSchema.nullable(),
14688
15308
  audio: CameraAudioStatusSchema.nullable(),
14689
15309
  recording: CameraRecordingStatusSchema.nullable(),
15310
+ /**
15311
+ * Per-camera function switches an OPERATOR has turned off
15312
+ * ([D61](../../../../docs/decisions/adr-0067.md)).
15313
+ *
15314
+ * This is the difference between DISABLED and BROKEN. A camera whose
15315
+ * `detection` block reports zero fps and whose `switchedOff` contains
15316
+ * `'object-detection'` was switched off by a person; the same camera with an
15317
+ * empty list is failing. Every status surface must render the two
15318
+ * differently — a quiet camera that looks identical to a dead one is the
15319
+ * silence-reads-as-never-happened trap this repo keeps paying for.
15320
+ *
15321
+ * Empty when nothing is off. Never contains a switch no provider offers.
15322
+ */
15323
+ switchedOff: z.array(CameraSwitchIdSchema).readonly(),
14690
15324
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
14691
15325
  fetchedAt: z.number()
14692
15326
  });
@@ -15086,6 +15720,43 @@ var pipelineOrchestratorCapability = {
15086
15720
  agentNodeId: z.string().optional()
15087
15721
  }), CameraPipelineConfigSchema),
15088
15722
  /**
15723
+ * The whole per-camera function switch group, DERIVED — never a stored
15724
+ * list ([D61](../../../../docs/decisions/adr-0067.md)).
15725
+ *
15726
+ * The group adds no state. Each switch is a view onto the authority that
15727
+ * already owned it (`deviceManager.setDisabled`,
15728
+ * `deviceManager.setWrapperActive`, `RecordingConfig.enabled`,
15729
+ * `notificationRules.setDeviceMuted`), and `switch.authority` says which.
15730
+ * Availability comes from `deviceManager.listBindableCapsForDeviceType`,
15731
+ * so a deployment with no audio analyzer renders no audio switch.
15732
+ *
15733
+ * `auth: 'view'` deliberately — a NON-admin must be able to see that a
15734
+ * camera is quiet because somebody switched it off. Only the mutation is
15735
+ * admin-gated.
15736
+ */
15737
+ getCameraSwitches: method(z.object({ deviceId: z.number() }), CameraSwitchGroupSchema),
15738
+ /**
15739
+ * Flip ONE switch, routed to its existing authority.
15740
+ *
15741
+ * Never writes a parallel map: `recording` patches `RecordingConfig.enabled`
15742
+ * and leaves `bands` byte-identical (clearing bands to express "off"
15743
+ * destroys the operator's authored schedule and turning the camera back on
15744
+ * would silently record nothing), and the two pipeline switches write the
15745
+ * SAME wrapper binding the legacy `pipelineEnabled` / `audioEnabled`
15746
+ * booleans were migrated onto.
15747
+ *
15748
+ * Rejects a switch this camera does not offer rather than persisting a
15749
+ * write nothing reads.
15750
+ */
15751
+ setCameraSwitch: method(z.object({
15752
+ deviceId: z.number(),
15753
+ switchId: CameraSwitchIdSchema,
15754
+ enabled: z.boolean()
15755
+ }), CameraSwitchGroupSchema, {
15756
+ kind: "mutation",
15757
+ auth: "admin"
15758
+ }),
15759
+ /**
15089
15760
  * Server-composed aggregated status for a single camera.
15090
15761
  *
15091
15762
  * Fans out in parallel (bounded, per-stage graceful degradation) to
@@ -15533,9 +16204,15 @@ var snapshotCapability = {
15533
16204
  * Bypass the cache freshness check and fetch directly from the
15534
16205
  * native (or stream-broker fallback). Triggered by the UI's
15535
16206
  * "refresh" button so an operator can force a fresh frame
15536
- * even when the cache is well within `snapshotMaxAgeMs`.
15537
- * On battery cams this WILL wake the camera — accept the
15538
- * cost only when the user explicitly asks for it.
16207
+ * even when the cache is well within the device's
16208
+ * `snapshotMaxAgeS` window.
16209
+ *
16210
+ * **`force` is an OPERATOR signal, not a freshness preference.** On a
16211
+ * battery camera it is the one thing that walks past the wrapper's
16212
+ * sleep gate and wakes the camera, so a background caller — a poller,
16213
+ * an event handler, a thumbnail — must NEVER set it. Every such caller
16214
+ * gets the cached frame, which on a sleeping battery camera is the
16215
+ * correct answer: stale but honest beats woken.
15539
16216
  */
15540
16217
  force: z.boolean().optional()
15541
16218
  }), SnapshotImageSchema.nullable()),
@@ -21990,12 +22667,30 @@ var pressureSensorCapability = {
21990
22667
  runtimeState: PressureSensorStatusSchema
21991
22668
  };
21992
22669
  /**
21993
- * Privacy mask = up to `maxRegions` SHAPES the camera blanks out (NOT a
21994
- * cell grid). Reolink `<shelterList>` zones are rectangles; Hikvision
21995
- * ISAPI `<RegionCoordinatesList>` zones are free polygons (this camera:
21996
- * exactly 4 vertices, not necessarily axis-aligned). The cap composes the
21997
- * shared rect|polygon subset of the MaskShape vocabulary. All coords are
21998
- * normalized 0..1 (top-left origin).
22670
+ * PRIVACY what the camera deliberately does not capture. Two planes:
22671
+ *
22672
+ * - **video**: up to `maxRegions` SHAPES the camera blanks out (NOT a cell
22673
+ * grid). Reolink `<shelterList>` zones are rectangles; Hikvision ISAPI
22674
+ * `<RegionCoordinatesList>` zones are free polygons (this camera: exactly
22675
+ * 4 vertices, not necessarily axis-aligned). The cap composes the shared
22676
+ * rect|polygon subset of the MaskShape vocabulary. All coords are
22677
+ * normalized 0..1 (top-left origin).
22678
+ * - **audio**: the camera's microphone. `setAudioEnabled(false)` stops the
22679
+ * camera encoding an audio track at all, so EVERY consumer — live view,
22680
+ * recording, the audio analyzer, an export — sees silent video. There is
22681
+ * no server-side copy of this fact; the camera is the store and every read
22682
+ * is a read-through, which is why a switch over it cannot drift
22683
+ * ([D62](../../../../docs/decisions/adr-0062.md)).
22684
+ *
22685
+ * Both belong here for one reason: they are the two things an operator turns
22686
+ * off when the answer to "what is this camera allowed to record" changes, and
22687
+ * both are applied ON the device, before anything leaves it.
22688
+ *
22689
+ * **The audio flag has exactly one writer.** `stream-params` used to carry a
22690
+ * per-profile `audio` in its patch schema — reachable from no UI and honoured
22691
+ * by one provider — and it was removed when this landed. A second writer onto
22692
+ * one device register is the shape of every knob this repo has shipped that
22693
+ * disagreed with the one the reader read.
21999
22694
  */
22000
22695
  /** A privacy-mask region's geometry — rectangle or free polygon. */
22001
22696
  var PrivacyMaskShapeSchema = z.discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
@@ -22007,21 +22702,45 @@ var PrivacyMaskRegionSchema = z.object({
22007
22702
  enabled: z.boolean(),
22008
22703
  shape: PrivacyMaskShapeSchema
22009
22704
  });
22010
- /** Current on-camera privacy-mask state — master enable + zones. */
22705
+ /** Current on-camera privacy state — mask master enable + zones + microphone. */
22011
22706
  var PrivacyMaskStatusSchema = z.object({
22012
22707
  enabled: z.boolean(),
22013
22708
  /** Active zones (normalized 0..1). Length ≤ maxRegions. */
22014
22709
  regions: z.array(PrivacyMaskRegionSchema),
22710
+ /**
22711
+ * Is the camera capturing sound right now? Read from the camera, never from
22712
+ * a server-side mirror.
22713
+ *
22714
+ * `null` means "no answer" — either this camera exposes no controllable
22715
+ * microphone (`getOptions().supportsAudioMute === false`) or the read
22716
+ * failed. A consumer must render `null` as UNKNOWN and never as `false`:
22717
+ * "the microphone is off" and "we could not ask" look identical to an
22718
+ * operator only until one of them is wrong.
22719
+ *
22720
+ * On a camera whose profiles carry the flag independently (Reolink writes
22721
+ * it per stream), `true` means AT LEAST ONE profile still carries audio —
22722
+ * privacy is only satisfied when every one of them is silent.
22723
+ */
22724
+ audioEnabled: z.boolean().nullable(),
22015
22725
  lastFetchedAt: z.number()
22016
22726
  });
22017
- /** Per-camera availability. */
22727
+ /** Per-camera availability. Probed, never assumed from the model name. */
22018
22728
  var PrivacyMaskOptionsSchema = z.object({
22019
22729
  /** Maximum number of supported zones. */
22020
22730
  maxRegions: z.number(),
22021
22731
  /** Shape kinds this camera accepts — Reolink: ['rect']; Hikvision: ['rect','polygon']. */
22022
22732
  supportedShapes: z.array(MaskShapeKindSchema),
22023
22733
  /** Polygon vertex bounds when 'polygon' is supported (Hikvision: {min:4,max:4}). */
22024
- polygonVertices: MaskPolygonVerticesSchema.optional()
22734
+ polygonVertices: MaskPolygonVerticesSchema.optional(),
22735
+ /**
22736
+ * Does this camera expose a microphone switch we can actually write?
22737
+ *
22738
+ * Camera-probed: `true` only when the firmware answered with an audio flag
22739
+ * we know how to patch. A camera that never answered is `false` — a control
22740
+ * the operator can press that changes nothing is worse than no control, and
22741
+ * the switch group renders "not available" instead.
22742
+ */
22743
+ supportsAudioMute: z.boolean()
22025
22744
  });
22026
22745
  /** Partial change — every field optional. */
22027
22746
  var PrivacyMaskPatchSchema = z.object({
@@ -22048,6 +22767,27 @@ var privacyMaskCapability = {
22048
22767
  }), z.void(), {
22049
22768
  kind: "mutation",
22050
22769
  auth: "admin"
22770
+ }),
22771
+ /**
22772
+ * Turn the camera's microphone on or off, at the camera.
22773
+ *
22774
+ * Deliberately its OWN mutation rather than a field on
22775
+ * {@link PrivacyMaskPatchSchema}: `patch.enabled` already means "the video
22776
+ * mask master switch", and overloading it would make one boolean mean two
22777
+ * unrelated things on the same call. It is also the only method here whose
22778
+ * write leaves the device in a state a later `getStatus` reads back
22779
+ * verbatim, which is what makes it safe as a switch authority.
22780
+ *
22781
+ * A camera whose `getOptions().supportsAudioMute` is false must REJECT
22782
+ * this rather than silently accept it — a write nothing applies is exactly
22783
+ * what the switch group exists to remove.
22784
+ */
22785
+ setAudioEnabled: method(z.object({
22786
+ deviceId: z.number(),
22787
+ enabled: z.boolean()
22788
+ }), z.void(), {
22789
+ kind: "mutation",
22790
+ auth: "admin"
22051
22791
  })
22052
22792
  },
22053
22793
  status: {
@@ -22432,6 +23172,21 @@ var LocateSegmentResultSchema = z.discriminatedUnion("kind", [z.object({
22432
23172
  })]);
22433
23173
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
22434
23174
  var ReadSegmentBytesResultSchema = z.object({ data: z.instanceof(Uint8Array) });
23175
+ /**
23176
+ * One GOP of a finalized segment, cut by byte range through the segment's own
23177
+ * `mfra` (D31 on the D42 feeder path). `data` is the `ftyp`+`moov` head plus
23178
+ * the single `moof`+`mdat` covering the requested instant — standalone-
23179
+ * demuxable, never the whole file. When the segment's index cannot be parsed
23180
+ * the provider degrades INSIDE the mechanism to the whole segment (still one
23181
+ * `data`, `gopStartMs` = the segment start) — a worse read, not another path.
23182
+ */
23183
+ var ReadGopBytesResultSchema = z.object({
23184
+ data: z.instanceof(Uint8Array),
23185
+ /** Absolute epoch ms of the returned fragment's first sample. */
23186
+ gopStartMs: z.number(),
23187
+ /** Media ms the returned fragment covers. */
23188
+ gopDurMs: z.number()
23189
+ });
22435
23190
  var recordingCapability = {
22436
23191
  name: "recording",
22437
23192
  scope: "system",
@@ -22499,6 +23254,18 @@ var recordingCapability = {
22499
23254
  kind: "query",
22500
23255
  auth: "admin"
22501
23256
  }),
23257
+ /** Read the single GOP of segment `startMs` covering `epochMs`, by mfra
23258
+ * byte range — the scrub-granular read (D31 letter on the feeder path).
23259
+ * See {@link ReadGopBytesResultSchema} for the degradation contract. */
23260
+ readGopBytes: method(z.object({
23261
+ deviceId: z.number(),
23262
+ profile: z.string(),
23263
+ startMs: z.number(),
23264
+ epochMs: z.number()
23265
+ }), ReadGopBytesResultSchema, {
23266
+ kind: "query",
23267
+ auth: "admin"
23268
+ }),
22502
23269
  setDeviceConfig: method(z.object({
22503
23270
  deviceId: z.number(),
22504
23271
  config: RecordingConfigSchema
@@ -23142,6 +23909,16 @@ var StreamProfileConfigSchema = z.object({
23142
23909
  "baseline"
23143
23910
  ]).optional(),
23144
23911
  gop: z.number().optional(),
23912
+ /**
23913
+ * Whether THIS profile currently carries an audio track. READ-ONLY here.
23914
+ *
23915
+ * There is no matching field on {@link StreamProfilePatchSchema}: the
23916
+ * camera's microphone is owned by `privacy-mask` (`setAudioEnabled`), which
23917
+ * writes every profile at once so "audio off" means silent everywhere. A
23918
+ * per-profile writer beside it would let a camera be half-muted and would be
23919
+ * a second knob onto one device register — the failure D62 exists to
23920
+ * prevent. Absent when the firmware does not report the flag.
23921
+ */
23145
23922
  audio: z.boolean().optional()
23146
23923
  });
23147
23924
  var StreamParamsStatusSchema = z.object({
@@ -23182,7 +23959,13 @@ var StreamParamsOptionsSchema = z.object({
23182
23959
  ext: StreamProfileOptionsSchema.optional()
23183
23960
  });
23184
23961
  /** A partial change to one profile — every field optional; a provider
23185
- * ignores fields it doesn't support. */
23962
+ * ignores fields it doesn't support.
23963
+ *
23964
+ * There is deliberately NO `audio` here. It existed until 2026-08-07,
23965
+ * reachable from no form and honoured by exactly one provider, while the
23966
+ * camera's microphone is a whole-device fact. It now has one writer,
23967
+ * `privacyMask.setAudioEnabled`, which writes every profile — see
23968
+ * `privacy-mask.cap.ts`. */
23186
23969
  var StreamProfilePatchSchema = z.object({
23187
23970
  width: z.number().optional(),
23188
23971
  height: z.number().optional(),
@@ -23195,8 +23978,7 @@ var StreamProfilePatchSchema = z.object({
23195
23978
  "main",
23196
23979
  "baseline"
23197
23980
  ]).optional(),
23198
- gop: z.number().optional(),
23199
- audio: z.boolean().optional()
23981
+ gop: z.number().optional()
23200
23982
  });
23201
23983
  var streamParamsCapability = {
23202
23984
  name: "stream-params",
@@ -27811,6 +28593,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
27811
28593
  addonId: null,
27812
28594
  access: "view"
27813
28595
  },
28596
+ "notificationRules.listDeviceMutes": {
28597
+ capName: "notification-rules",
28598
+ capScope: "system",
28599
+ addonId: null,
28600
+ access: "view"
28601
+ },
27814
28602
  "notificationRules.listRules": {
27815
28603
  capName: "notification-rules",
27816
28604
  capScope: "system",
@@ -27829,6 +28617,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
27829
28617
  addonId: null,
27830
28618
  access: "create"
27831
28619
  },
28620
+ "notificationRules.setDeviceMuted": {
28621
+ capName: "notification-rules",
28622
+ capScope: "system",
28623
+ addonId: null,
28624
+ access: "create"
28625
+ },
27832
28626
  "notificationRules.setRuleEnabled": {
27833
28627
  capName: "notification-rules",
27834
28628
  capScope: "system",
@@ -28105,6 +28899,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
28105
28899
  addonId: null,
28106
28900
  access: "view"
28107
28901
  },
28902
+ "pipelineAnalytics.setTrackFlags": {
28903
+ capName: "pipeline-analytics",
28904
+ capScope: "device",
28905
+ addonId: null,
28906
+ access: "create"
28907
+ },
28108
28908
  "pipelineAnalytics.wipeAllAnalytics": {
28109
28909
  capName: "pipeline-analytics",
28110
28910
  capScope: "device",
@@ -28411,6 +29211,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
28411
29211
  addonId: null,
28412
29212
  access: "view"
28413
29213
  },
29214
+ "pipelineOrchestrator.getCameraSwitches": {
29215
+ capName: "pipeline-orchestrator",
29216
+ capScope: "system",
29217
+ addonId: null,
29218
+ access: "view"
29219
+ },
28414
29220
  "pipelineOrchestrator.getCapabilityBindings": {
28415
29221
  capName: "pipeline-orchestrator",
28416
29222
  capScope: "system",
@@ -28543,6 +29349,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
28543
29349
  addonId: null,
28544
29350
  access: "create"
28545
29351
  },
29352
+ "pipelineOrchestrator.setCameraSwitch": {
29353
+ capName: "pipeline-orchestrator",
29354
+ capScope: "system",
29355
+ addonId: null,
29356
+ access: "create"
29357
+ },
28546
29358
  "pipelineOrchestrator.setCapabilityBinding": {
28547
29359
  capName: "pipeline-orchestrator",
28548
29360
  capScope: "system",
@@ -28633,6 +29445,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
28633
29445
  addonId: null,
28634
29446
  access: "create"
28635
29447
  },
29448
+ "pipelineRunner.runStatelessStep": {
29449
+ capName: "pipeline-runner",
29450
+ capScope: "system",
29451
+ addonId: null,
29452
+ access: "create"
29453
+ },
28636
29454
  "plateGallery.assignPlate": {
28637
29455
  capName: "plate-gallery",
28638
29456
  capScope: "system",
@@ -28765,6 +29583,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
28765
29583
  addonId: null,
28766
29584
  access: "view"
28767
29585
  },
29586
+ "privacyMask.setAudioEnabled": {
29587
+ capName: "privacy-mask",
29588
+ capScope: "device",
29589
+ addonId: null,
29590
+ access: "create"
29591
+ },
28768
29592
  "privacyMask.setMask": {
28769
29593
  capName: "privacy-mask",
28770
29594
  capScope: "device",
@@ -28933,6 +29757,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
28933
29757
  addonId: null,
28934
29758
  access: "create"
28935
29759
  },
29760
+ "recording.readGopBytes": {
29761
+ capName: "recording",
29762
+ capScope: "system",
29763
+ addonId: null,
29764
+ access: "view"
29765
+ },
28936
29766
  "recording.readSegmentBytes": {
28937
29767
  capName: "recording",
28938
29768
  capScope: "system",
@@ -29449,6 +30279,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
29449
30279
  addonId: null,
29450
30280
  access: "create"
29451
30281
  },
30282
+ "streamBroker.acquireEgressTranscode": {
30283
+ capName: "stream-broker",
30284
+ capScope: "system",
30285
+ addonId: null,
30286
+ access: "create"
30287
+ },
29452
30288
  "streamBroker.assignProfile": {
29453
30289
  capName: "stream-broker",
29454
30290
  capScope: "system",
@@ -29557,6 +30393,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
29557
30393
  addonId: null,
29558
30394
  access: "create"
29559
30395
  },
30396
+ "streamBroker.releaseEgressTranscode": {
30397
+ capName: "stream-broker",
30398
+ capScope: "system",
30399
+ addonId: null,
30400
+ access: "create"
30401
+ },
29560
30402
  "streamBroker.releaseStreamWithCodec": {
29561
30403
  capName: "stream-broker",
29562
30404
  capScope: "system",
@@ -30318,6 +31160,42 @@ TimelapseRuleInputSchema.extend({
30318
31160
  updatedAt: z.number()
30319
31161
  });
30320
31162
  /**
31163
+ * The addon id as the REGISTRY, the wire and the durable stores spell it.
31164
+ *
31165
+ * `AddonContext.id` does not agree with itself across the two context
31166
+ * factories:
31167
+ *
31168
+ * - a FORKED addon gets the bare manifest id
31169
+ * (`kernel/moleculer/addon-context-factory.ts` → `id: addonId`);
31170
+ * - an addon co-located in hub-main gets it PREFIXED
31171
+ * (`server/backend/src/core/addon/addon-registry.service.ts` →
31172
+ * ``id: `addon:${addonId}` ``).
31173
+ *
31174
+ * Everything an addon might compare `ctx.id` AGAINST carries the bare form:
31175
+ * `DeviceBindingEntry.providerAddonId`, the `device-manager` bindings store's
31176
+ * `wrapperAddonId`, `CapabilityRegistry` provider keys, manifest ids.
31177
+ *
31178
+ * So `entry.providerAddonId === this.ctx.id` is silently, permanently false
31179
+ * for a builtin — and only for a builtin, which is why it survives a green
31180
+ * suite whose fake supplies the bare id. That is exactly how camera 615's
31181
+ * virtual doorbell latched `unbound` for twelve hours on 2026-08-07 while its
31182
+ * binding was intact ([D72](../../../../docs/decisions/adr-0072.md)).
31183
+ *
31184
+ * Route every comparison between `ctx.id` and a registry/store addon id
31185
+ * through {@link isSameAddonId}. `scripts/check-addon-id-comparison.ts`
31186
+ * enforces it in the processes where the prefix exists.
31187
+ */
31188
+ /** The prefix the hub-main context factory prepends to the manifest id. */
31189
+ var ADDON_ID_PREFIX = "addon:";
31190
+ /** The manifest id, whichever spelling of `ctx.id` you were handed. */
31191
+ function bareAddonId(id) {
31192
+ return id.startsWith(ADDON_ID_PREFIX) ? id.slice(6) : id;
31193
+ }
31194
+ /** True when both ids name the same addon, prefixed or not. */
31195
+ function isSameAddonId(a, b) {
31196
+ return bareAddonId(a) === bareAddonId(b);
31197
+ }
31198
+ /**
30321
31199
  * Decode base64 little-endian Float32 back into a vector.
30322
31200
  *
30323
31201
  * Throws on a byte length that is not a multiple of 4 — a truncated vector
@@ -30360,6 +31238,88 @@ z.object({
30360
31238
  square: false
30361
31239
  }).paddingRatio;
30362
31240
  /**
31241
+ * WHICH delivered frames the decode worker retains a native copy of.
31242
+ *
31243
+ * - `all` — every frame the worker delivered to the runner. The shipped
31244
+ * behaviour, and the only correct one if something can ask for a crop of a
31245
+ * frame the runner never sent to inference.
31246
+ * - `inferred` — only the frames the runner ADMITTED to its detection queue.
31247
+ * A native-crop request always names a `frameId` that rode an inference
31248
+ * result, so that is the only set a request can name. How much it drops is
31249
+ * the two-plane governor's admit ratio and nothing else: measured at ~50% on
31250
+ * this cluster, not the ~80% the design sketch assumed, because the governor
31251
+ * was not throttling as hard as the sketch supposed. Read
31252
+ * `leaseAdmitted`/`leaseOffered` off the metrics line for the camera in front
31253
+ * of you rather than quoting a number from here. The newest delivered frame is
31254
+ * croppable regardless — it is still the worker's reserved slot, not a lease —
31255
+ * which covers the one-frame race between a mark and the supersede that
31256
+ * consumes it.
31257
+ */
31258
+ var NativeLeaseAdmissionSchema = z.enum(["all", "inferred"]);
31259
+ z.object({
31260
+ /**
31261
+ * How long a retained native frame is served before it counts as a miss.
31262
+ *
31263
+ * Must cover the FULL late-crop horizon: detection inference + the
31264
+ * cross-process inference-result hop to hub post-analysis + tracking + the
31265
+ * tRPC crop round-trip back. Below ~500 ms the busiest cameras' subject crops
31266
+ * outrun it and fall back to the ≤640 detection frame; above ~3 s the resident
31267
+ * RAM per busy camera grows linearly with no measured hit-rate gain.
31268
+ */
31269
+ ttlMs: z.number().int().min(250).max(1e4),
31270
+ /**
31271
+ * Hard per-decode-worker RAM ceiling for retained native frames, in MB.
31272
+ *
31273
+ * Intended as a SAFETY ceiling with the TTL as the effective cap — but check
31274
+ * which one is actually binding before reasoning from that. At the shipped
31275
+ * 1024 MB and a 2 800 ms TTL, a 4K camera hits the CEILING first (~43 frames
31276
+ * at ~24 MB each) and the TTL never gets to expire anything; `leaseMb` /
31277
+ * `leaseFrames` on the metrics line say which. When the ceiling binds, a
31278
+ * change that admits fewer frames buys retention WINDOW at constant RAM
31279
+ * rather than giving RAM back — lower this knob if RAM is what you wanted.
31280
+ * `0` DISABLES the lease entirely and falls the worker back to the tiny
31281
+ * leak-prone GPU surface ring (~85% crop miss; that is what the lease exists
31282
+ * to replace).
31283
+ */
31284
+ budgetMb: z.number().int().min(0).max(4096),
31285
+ /**
31286
+ * Demand window: eager per-frame native retention runs only within this many
31287
+ * ms of the last native-crop request (or of the dial starting).
31288
+ *
31289
+ * `0` means ALWAYS ON — it disables the gate, it does not disable retention.
31290
+ * That is the legacy behaviour that saturated an N100 (24 native-4K downloads
31291
+ * per second on a camera with zero crop demand), so leave it non-zero unless
31292
+ * you are reproducing that.
31293
+ */
31294
+ activityMs: z.number().int().min(0).max(12e4),
31295
+ /**
31296
+ * Which delivered frames are retained at all — see
31297
+ * {@link NativeLeaseAdmissionSchema}. This is the only knob of the four that
31298
+ * changes WHAT is kept rather than for how long, so it is also the only one
31299
+ * that can turn a crop that used to hit into a miss. The worker counts every
31300
+ * crop request naming a frame it did NOT see marked
31301
+ * (`leaseUnmarkedCrops` on the session-decode metrics line): a non-zero value
31302
+ * there is the signal that some caller names frames outside the inference set
31303
+ * and that this must go back to `all`.
31304
+ */
31305
+ admission: NativeLeaseAdmissionSchema
31306
+ });
31307
+ /**
31308
+ * The values in force when the operator has set nothing — byte-for-byte the
31309
+ * constants the decode worker shipped with as env-var defaults, so making these
31310
+ * settings changed no behaviour on the day it landed.
31311
+ */
31312
+ var DEFAULT_NATIVE_LEASE_SETTINGS = {
31313
+ ttlMs: 1200,
31314
+ budgetMb: 1024,
31315
+ activityMs: 15e3,
31316
+ admission: "inferred"
31317
+ };
31318
+ DEFAULT_NATIVE_LEASE_SETTINGS.ttlMs;
31319
+ DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
31320
+ DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
31321
+ DEFAULT_NATIVE_LEASE_SETTINGS.admission;
31322
+ /**
30363
31323
  * Scores all applicable inference backends for the given hardware.
30364
31324
  *
30365
31325
  * Rules (hardware-driven only — no Python import probes):
@@ -30473,4 +31433,4 @@ function enumerateInferenceDevices(hw) {
30473
31433
  return out;
30474
31434
  }
30475
31435
  //#endregion
30476
- export { setByPath as $, enumerateItemArrayFields as A, nodePin as At, kebabToCamel as B, dataStoreProviderCapability as C, asString as Ct, deviceStatusCapability as D, expandCapMethods as Dt, deviceStateCapability as E, emitReadiness as Et, getByPath as F, scopeKey as Ft, looseSchema as G, localNetworkCapability as H, isArrayOutputSchema as I, sleep as It, objectInputDeclaresAddonId as J, metricsProviderCapability as K, isCollectionArrayMethod as L, EventCategory as Lt, evaluateLinkExpression as M, parseJsonUnknown as Mt, extractNestedAddonId as N, readinessKey as Nt, doorbellCapability as O, hydrateSchema as Ot, filesystemBrowseCapability as P, resolveCapMount as Pt, scoreRuntimes as Q, isObjectInput as R, coreBlocksCapability as S, asNumber as St, deviceManagerCapability as T, emitDownForOwnedCaps as Tt, logDestinationCapability as U, lifecycleJobSchema as V, logLevelAtMost as W, platformProbeCapability as X, parseStreamParamsFormPatch as Y, procedureAuthKey as Z, alertsCapability as _, DeviceType as _t, CAP_NAMES_WITH_STATUS as a, toExpressionValue as at, backupCapability as b, WELL_KNOWN_TAB_MAP as bt, METHOD_ACCESS_MAP as c, vectorDimFromBase64 as ct, ScopedTokenSchema as d, BaseAddon as dt, settingsStoreCapability as et, StorageLocationTypeSchema as f, DATAPLANE_SECRET_HEADER as ft, addonWidgetsCapability as g, DeviceRole as gt, addonSettingsCapability as h, DeviceFeature as ht, BatteryStatusSchema as i, streamQualityLabel as it, enumerateSchemaFields as j, parseJsonObject as jt, enumerateInferenceDevices as k, isDeviceConfigCap as kt, RUNTIME_DEFAULTS as l, vectorStoreCapability as lt, addonPagesCapability as m, DEVICE_STATUS_METHOD as mt, AlertSchema as n, storageCapability as nt, CoreBlockSchema as o, userManagementCapability as ot, UserRecordSchema as p, DEVICE_SETTINGS_CONTRIBUTION_METHODS as pt, normalizeUnit as q, ApiKeyRecordSchema as r, storageProviderCapability as rt, DeviceStatusSchema as s, validateExpressionSource as st, ALL_CAPABILITY_DEFINITIONS as t, snapshotCapability as tt, STREAM_PROFILE_META as u, errMsg as ut, applyTransform as v, ReadinessRegistry as vt, decodeVectorBase64 as w, createEvent as wt, buildStreamParamsConfigSchema as x, asJsonObject as xt, authProviderCapability as y, ReadinessTimeoutError as yt, isVoidInput as z };
31436
+ export { procedureAuthKey as $, enumerateInferenceDevices as A, hydrateSchema as At, isSameAddonId as B, coreBlocksCapability as C, asJsonObject as Ct, deviceStateCapability as D, emitDownForOwnedCaps as Dt, deviceManagerCapability as E, createEvent as Et, filesystemBrowseCapability as F, readinessKey as Ft, logDestinationCapability as G, kebabToCamel as H, getByPath as I, resolveCapMount as It, metricsProviderCapability as J, logLevelAtMost as K, isArrayOutputSchema as L, scopeKey as Lt, enumerateSchemaFields as M, nodePin as Mt, evaluateLinkExpression as N, parseJsonObject as Nt, deviceStatusCapability as O, emitReadiness as Ot, extractNestedAddonId as P, parseJsonUnknown as Pt, platformProbeCapability as Q, isCollectionArrayMethod as R, sleep as Rt, buildStreamParamsConfigSchema as S, WELL_KNOWN_TAB_MAP as St, decodeVectorBase64 as T, asString as Tt, lifecycleJobSchema as U, isVoidInput as V, localNetworkCapability as W, objectInputDeclaresAddonId as X, normalizeUnit as Y, parseStreamParamsFormPatch as Z, alertsCapability as _, DeviceFeature as _t, CAP_NAMES_WITH_STATUS as a, storageProviderCapability as at, backupCapability as b, ReadinessRegistry as bt, METHOD_ACCESS_MAP as c, userManagementCapability as ct, ScopedTokenSchema as d, vectorStoreCapability as dt, scoreRuntimes as et, StorageLocationTypeSchema as f, errMsg as ft, addonWidgetsCapability as g, DEVICE_STATUS_METHOD as gt, addonSettingsCapability as h, DEVICE_SETTINGS_CONTRIBUTION_METHODS as ht, BatteryStatusSchema as i, storageCapability as it, enumerateItemArrayFields as j, isDeviceConfigCap as jt, doorbellCapability as k, expandCapMethods as kt, RUNTIME_DEFAULTS as l, validateExpressionSource as lt, addonPagesCapability as m, DATAPLANE_SECRET_HEADER as mt, AlertSchema as n, settingsStoreCapability as nt, CoreBlockSchema as o, streamQualityLabel as ot, UserRecordSchema as p, BaseAddon as pt, looseSchema as q, ApiKeyRecordSchema as r, snapshotCapability as rt, DeviceStatusSchema as s, toExpressionValue as st, ALL_CAPABILITY_DEFINITIONS as t, setByPath as tt, STREAM_PROFILE_META as u, vectorDimFromBase64 as ut, applyTransform as v, DeviceRole as vt, dataStoreProviderCapability as w, asNumber as wt, bareAddonId as x, ReadinessTimeoutError as xt, authProviderCapability as y, DeviceType as yt, isObjectInput as z, EventCategory as zt };