@camstack/addon-post-analysis 1.2.48 → 1.2.49

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.
@@ -6509,9 +6509,38 @@ var CAP_NODE_PIN_CONTEXT_KEY = "__camstackNodePin";
6509
6509
  /**
6510
6510
  * Build the tRPC request options that pin a single capability call to `nodeId`.
6511
6511
  * Pass as the second argument to `.query(input, …)` / `.mutate(input, …)`.
6512
+ *
6513
+ * ## The id is normalised here, and it has to be
6514
+ *
6515
+ * A forked addon reads its own node from `ctx.kernel.localNodeId`, and inside a
6516
+ * worker that value is a RUNNER id — `hub/export-hap`, not `hub`. Routing
6517
+ * compares a pin against real node ids, so such a pin matches nothing and the
6518
+ * call fails with `no provider registered for cap "…"`. The local-first
6519
+ * resolver already guarded against this (`localNodeId.split('/')[0]`), which
6520
+ * made the hazard invisible: unpinned calls worked, and only an explicit pin —
6521
+ * the thing you reach for when you specifically need THIS node — silently
6522
+ * addressed a node that does not exist.
6523
+ *
6524
+ * Cost of it being missing: `addon-export-hap` pinned `decoder.getInfo` to its
6525
+ * own node to read the host's hardware-decode backend. It never once answered,
6526
+ * so every HomeKit egress transcode decoded in SOFTWARE — including 4K H.265 —
6527
+ * while D67's whole premise was that the decoder addon is the authority on
6528
+ * hardware. The warn said `decoding in SOFTWARE` and read as "this node has no
6529
+ * hardware", which was false.
6530
+ *
6531
+ * Normalising in the ONE constructor fixes every caller at once, which is why
6532
+ * it is here and not at the call sites.
6512
6533
  */
6513
6534
  function nodePin(nodeId) {
6514
- return { context: { [CAP_NODE_PIN_CONTEXT_KEY]: nodeId } };
6535
+ return { context: { [CAP_NODE_PIN_CONTEXT_KEY]: toNodeId(nodeId) } };
6536
+ }
6537
+ /**
6538
+ * A runner id is `<nodeId>/<addonId>`; a node id has no slash. Taking the head
6539
+ * is idempotent, so passing an already-clean id costs nothing.
6540
+ */
6541
+ function toNodeId(idOrRunnerId) {
6542
+ const head = idOrRunnerId.split("/")[0];
6543
+ return head === void 0 || head.length === 0 ? idOrRunnerId : head;
6515
6544
  }
6516
6545
  /**
6517
6546
  * Output schema shared by the contribution + live methods.
@@ -7249,7 +7278,7 @@ object({
7249
7278
  * ## This file adds no state
7250
7279
  *
7251
7280
  * Every switch here is a VIEW onto an authority that already existed
7252
- * ([D61](../../../../docs/decisions/adr-0062.md)). The whole point of the
7281
+ * ([D62](../../../../docs/decisions/adr-0062.md)). The whole point of the
7253
7282
  * group is that there is exactly one place each function is turned off, and
7254
7283
  * the group routes to it:
7255
7284
  *
@@ -7260,6 +7289,40 @@ object({
7260
7289
  * | `audio-analysis` | `deviceManager.setWrapperActive('audio-analysis')` | `AudioSubscriptionController.subscribeAudioStream` returns `null` before opening the stream |
7261
7290
  * | `recording` | `recording.setDeviceConfig` → `RecordingConfig.enabled` | `band-decision.shouldRecord` returns false; the controller detaches the device |
7262
7291
  * | `notifications` | `notificationRules.setDeviceMuted` | `NotificationCenter.evaluateAndEnqueue` returns before any rule is evaluated |
7292
+ * | `privacy-mask` | `privacyMask.setMask({ enabled })` → the CAMERA | the camera blanks the masked regions itself; every stream and recording carries the black boxes |
7293
+ * | `device-audio` | `privacyMask.setAudioEnabled` → the CAMERA | the camera stops encoding an audio track at all; every consumer sees silent video |
7294
+ *
7295
+ * ## The two switches whose authority is not on this server
7296
+ *
7297
+ * `privacy-mask` and `device-audio` write the CAMERA. That is not a loophole
7298
+ * in "the group stores nothing" — it is the purest form of it: the camera
7299
+ * holds the fact, every read is a read-through, and there is no server-side
7300
+ * copy that could drift. Their availability therefore cannot come from
7301
+ * `listBindableCapsForDeviceType` (a device-NATIVE cap carries no wrappers and
7302
+ * is filtered out there); it comes from the cap's own camera-probed
7303
+ * `privacyMask.getOptions()`, which is strictly more honest — it answers for
7304
+ * THIS camera rather than for the device type
7305
+ * ([D74](../../../../docs/decisions/adr-0074.md)).
7306
+ *
7307
+ * ## `privacy-mask` is the one row whose ON is not "the function is working"
7308
+ *
7309
+ * Every other switch means *this camera's function is doing its job*, so
7310
+ * `enabled: false` is a thing an operator took away. `privacy-mask` means **the
7311
+ * MASK is active** — `enabled: true` is video deliberately obscured. The
7312
+ * polarity is not a choice made here: `addon-export-hap`'s privacy `Switch`
7313
+ * (`builders/privacy-switch.ts`) already mirrors `patch.enabled` verbatim, and
7314
+ * a HomeKit toggle that disagreed with the app's toggle for the same camera is
7315
+ * worse than either surface not having one.
7316
+ *
7317
+ * Two consequences follow and both are load-bearing:
7318
+ *
7319
+ * - **It never counts as `switchedOff`.** `countsAsSwitchedOff` is `false` for
7320
+ * exactly this row. With the polarity above, every camera that has NOT drawn
7321
+ * a privacy mask would otherwise report `switchedOff: ['privacy-mask']` — the
7322
+ * normal, healthy state of most cameras rendered as an operator disablement.
7323
+ * - **Its cost line names BOTH directions.** `costWhenOff` is rendered
7324
+ * unconditionally by both clients, so for this row it has to read correctly
7325
+ * whichever way the switch is sitting.
7263
7326
  *
7264
7327
  * The wrapper-binding pair is not a new idea: `legacy-migrations.ts` already
7265
7328
  * migrated the legacy `audioEnabled` / `pipelineEnabled` /
@@ -7278,14 +7341,17 @@ object({
7278
7341
  * `CameraStatus.switchedOff`.
7279
7342
  */
7280
7343
  /**
7281
- * The five functions the operator named (2026-08-05). Deliberately NOT one id
7282
- * per pipeline step: face recognition and plate/LPR are per-step toggles on
7344
+ * The functions the operator named — five on 2026-08-05, plus the camera's own
7345
+ * microphone on 2026-08-07. Deliberately NOT one id per pipeline step: face
7346
+ * recognition and plate/LPR are per-step toggles on
7283
7347
  * `pipelineOrchestrator.setCameraStepToggle` and belong in the pipeline
7284
- * editor, not in a five-button safety group.
7348
+ * editor, not in a safety group.
7285
7349
  */
7286
7350
  var CameraSwitchIdSchema = _enum([
7287
7351
  "stream-broker",
7288
7352
  "object-detection",
7353
+ "privacy-mask",
7354
+ "device-audio",
7289
7355
  "audio-analysis",
7290
7356
  "recording",
7291
7357
  "notifications"
@@ -7303,14 +7369,26 @@ var CameraSwitchAuthoritySchema = discriminatedUnion("kind", [
7303
7369
  capName: string()
7304
7370
  }),
7305
7371
  object({ kind: literal("recording-config") }),
7306
- object({ kind: literal("notification-mute") })
7372
+ object({ kind: literal("notification-mute") }),
7373
+ object({
7374
+ kind: literal("camera-audio"),
7375
+ capName: string()
7376
+ }),
7377
+ object({
7378
+ kind: literal("camera-mask"),
7379
+ capName: string()
7380
+ })
7307
7381
  ]);
7308
7382
  /**
7309
7383
  * Why a switch is not offered for this camera. Rendered instead of the
7310
7384
  * control, never as a dead control — an absent function and a broken one must
7311
7385
  * not look the same.
7312
7386
  */
7313
- var CameraSwitchUnavailableReasonSchema = _enum(["no-provider", "source-unreachable"]);
7387
+ var CameraSwitchUnavailableReasonSchema = _enum([
7388
+ "no-provider",
7389
+ "source-unreachable",
7390
+ "not-configured"
7391
+ ]);
7314
7392
  /**
7315
7393
  * One switch, resolved for one camera.
7316
7394
  *
@@ -9303,6 +9381,26 @@ var EgressTranscodeRequestSchema = object({
9303
9381
  "h264_mp4toannexb",
9304
9382
  "hevc_mp4toannexb"
9305
9383
  ]).optional(),
9384
+ /**
9385
+ * Publish the transcode as a LOCAL push cam stream, instead of leaving the
9386
+ * consumer to dial the returned url. The broker picks the id and returns it
9387
+ * as `camStreamId` — a caller-supplied one would be circular, since the
9388
+ * sharing key is computed FROM this request.
9389
+ *
9390
+ * The url is still returned and still the contract for a transcode pinned to
9391
+ * another node. But dialling it locally costs an RTSP round trip that changes
9392
+ * the transport underneath the consumer: a dialled stream is an RTP source,
9393
+ * so `isRtpSource()` is true and the session takes the RTP-passthrough +
9394
+ * repacketizer branch. The push branch — the one the derived mechanism has
9395
+ * live hours on — is never reached. Measured on Alexa: broker registered, RTP
9396
+ * arriving, key frame arriving, black screen, on a chain healthy at every
9397
+ * other point.
9398
+ *
9399
+ * Same idea the transport already applies to CALLS, where `classifyCapRoute`
9400
+ * gives priority to `hub-in-process` so a local call never leaves the node.
9401
+ * This is that rule for media.
9402
+ */
9403
+ publishLocally: boolean().optional(),
9306
9404
  pixelFormat: _enum(["yuv420p", "nv12"]).optional(),
9307
9405
  /**
9308
9406
  * Operator/consumer override for decode hardware. ABSENT is the normal case
@@ -9347,7 +9445,13 @@ var EgressTranscodeSchema = object({
9347
9445
  * Returned rather than assumed: a consumer that asked for hardware and got
9348
9446
  * software needs to be able to see that without reading the broker's logs.
9349
9447
  */
9350
- decodeHwAccel: string().nullable()
9448
+ decodeHwAccel: string().nullable(),
9449
+ /**
9450
+ * Set when `publishLocally` was honoured: attach to THIS instead of dialling
9451
+ * `url`, and the session takes the push/deframe transport rather than the
9452
+ * RTP-passthrough one. `null` means the consumer must dial.
9453
+ */
9454
+ camStreamId: string().nullable()
9351
9455
  });
9352
9456
  method(object({
9353
9457
  deviceId: number().int().nonnegative(),
@@ -18106,9 +18210,15 @@ DeviceType.Camera, method(object({
18106
18210
  * Bypass the cache freshness check and fetch directly from the
18107
18211
  * native (or stream-broker fallback). Triggered by the UI's
18108
18212
  * "refresh" button so an operator can force a fresh frame
18109
- * even when the cache is well within `snapshotMaxAgeMs`.
18110
- * On battery cams this WILL wake the camera — accept the
18111
- * cost only when the user explicitly asks for it.
18213
+ * even when the cache is well within the device's
18214
+ * `snapshotMaxAgeS` window.
18215
+ *
18216
+ * **`force` is an OPERATOR signal, not a freshness preference.** On a
18217
+ * battery camera it is the one thing that walks past the wrapper's
18218
+ * sleep gate and wakes the camera, so a background caller — a poller,
18219
+ * an event handler, a thumbnail — must NEVER set it. Every such caller
18220
+ * gets the cached frame, which on a sleeping battery camera is the
18221
+ * correct answer: stale but honest beats woken.
18112
18222
  */
18113
18223
  force: boolean().optional()
18114
18224
  }), SnapshotImageSchema.nullable()), method(object({ deviceId: number() }), _void(), {
@@ -23590,12 +23700,30 @@ var pressureSensorCapability = {
23590
23700
  runtimeState: PressureSensorStatusSchema
23591
23701
  };
23592
23702
  /**
23593
- * Privacy mask = up to `maxRegions` SHAPES the camera blanks out (NOT a
23594
- * cell grid). Reolink `<shelterList>` zones are rectangles; Hikvision
23595
- * ISAPI `<RegionCoordinatesList>` zones are free polygons (this camera:
23596
- * exactly 4 vertices, not necessarily axis-aligned). The cap composes the
23597
- * shared rect|polygon subset of the MaskShape vocabulary. All coords are
23598
- * normalized 0..1 (top-left origin).
23703
+ * PRIVACY — what the camera deliberately does not capture. Two planes:
23704
+ *
23705
+ * - **video**: up to `maxRegions` SHAPES the camera blanks out (NOT a cell
23706
+ * grid). Reolink `<shelterList>` zones are rectangles; Hikvision ISAPI
23707
+ * `<RegionCoordinatesList>` zones are free polygons (this camera: exactly
23708
+ * 4 vertices, not necessarily axis-aligned). The cap composes the shared
23709
+ * rect|polygon subset of the MaskShape vocabulary. All coords are
23710
+ * normalized 0..1 (top-left origin).
23711
+ * - **audio**: the camera's microphone. `setAudioEnabled(false)` stops the
23712
+ * camera encoding an audio track at all, so EVERY consumer — live view,
23713
+ * recording, the audio analyzer, an export — sees silent video. There is
23714
+ * no server-side copy of this fact; the camera is the store and every read
23715
+ * is a read-through, which is why a switch over it cannot drift
23716
+ * ([D62](../../../../docs/decisions/adr-0062.md)).
23717
+ *
23718
+ * Both belong here for one reason: they are the two things an operator turns
23719
+ * off when the answer to "what is this camera allowed to record" changes, and
23720
+ * both are applied ON the device, before anything leaves it.
23721
+ *
23722
+ * **The audio flag has exactly one writer.** `stream-params` used to carry a
23723
+ * per-profile `audio` in its patch schema — reachable from no UI and honoured
23724
+ * by one provider — and it was removed when this landed. A second writer onto
23725
+ * one device register is the shape of every knob this repo has shipped that
23726
+ * disagreed with the one the reader read.
23599
23727
  */
23600
23728
  /** A privacy-mask region's geometry — rectangle or free polygon. */
23601
23729
  var PrivacyMaskShapeSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
@@ -23607,21 +23735,45 @@ var PrivacyMaskRegionSchema = object({
23607
23735
  enabled: boolean(),
23608
23736
  shape: PrivacyMaskShapeSchema
23609
23737
  });
23610
- /** Current on-camera privacy-mask state — master enable + zones. */
23738
+ /** Current on-camera privacy state — mask master enable + zones + microphone. */
23611
23739
  var PrivacyMaskStatusSchema = object({
23612
23740
  enabled: boolean(),
23613
23741
  /** Active zones (normalized 0..1). Length ≤ maxRegions. */
23614
23742
  regions: array(PrivacyMaskRegionSchema),
23743
+ /**
23744
+ * Is the camera capturing sound right now? Read from the camera, never from
23745
+ * a server-side mirror.
23746
+ *
23747
+ * `null` means "no answer" — either this camera exposes no controllable
23748
+ * microphone (`getOptions().supportsAudioMute === false`) or the read
23749
+ * failed. A consumer must render `null` as UNKNOWN and never as `false`:
23750
+ * "the microphone is off" and "we could not ask" look identical to an
23751
+ * operator only until one of them is wrong.
23752
+ *
23753
+ * On a camera whose profiles carry the flag independently (Reolink writes
23754
+ * it per stream), `true` means AT LEAST ONE profile still carries audio —
23755
+ * privacy is only satisfied when every one of them is silent.
23756
+ */
23757
+ audioEnabled: boolean().nullable(),
23615
23758
  lastFetchedAt: number()
23616
23759
  });
23617
- /** Per-camera availability. */
23760
+ /** Per-camera availability. Probed, never assumed from the model name. */
23618
23761
  var PrivacyMaskOptionsSchema = object({
23619
23762
  /** Maximum number of supported zones. */
23620
23763
  maxRegions: number(),
23621
23764
  /** Shape kinds this camera accepts — Reolink: ['rect']; Hikvision: ['rect','polygon']. */
23622
23765
  supportedShapes: array(MaskShapeKindSchema),
23623
23766
  /** Polygon vertex bounds when 'polygon' is supported (Hikvision: {min:4,max:4}). */
23624
- polygonVertices: MaskPolygonVerticesSchema.optional()
23767
+ polygonVertices: MaskPolygonVerticesSchema.optional(),
23768
+ /**
23769
+ * Does this camera expose a microphone switch we can actually write?
23770
+ *
23771
+ * Camera-probed: `true` only when the firmware answered with an audio flag
23772
+ * we know how to patch. A camera that never answered is `false` — a control
23773
+ * the operator can press that changes nothing is worse than no control, and
23774
+ * the switch group renders "not available" instead.
23775
+ */
23776
+ supportsAudioMute: boolean()
23625
23777
  });
23626
23778
  /** Partial change — every field optional. */
23627
23779
  var PrivacyMaskPatchSchema = object({
@@ -23648,6 +23800,27 @@ var privacyMaskCapability = {
23648
23800
  }), _void(), {
23649
23801
  kind: "mutation",
23650
23802
  auth: "admin"
23803
+ }),
23804
+ /**
23805
+ * Turn the camera's microphone on or off, at the camera.
23806
+ *
23807
+ * Deliberately its OWN mutation rather than a field on
23808
+ * {@link PrivacyMaskPatchSchema}: `patch.enabled` already means "the video
23809
+ * mask master switch", and overloading it would make one boolean mean two
23810
+ * unrelated things on the same call. It is also the only method here whose
23811
+ * write leaves the device in a state a later `getStatus` reads back
23812
+ * verbatim, which is what makes it safe as a switch authority.
23813
+ *
23814
+ * A camera whose `getOptions().supportsAudioMute` is false must REJECT
23815
+ * this rather than silently accept it — a write nothing applies is exactly
23816
+ * what the switch group exists to remove.
23817
+ */
23818
+ setAudioEnabled: method(object({
23819
+ deviceId: number(),
23820
+ enabled: boolean()
23821
+ }), _void(), {
23822
+ kind: "mutation",
23823
+ auth: "admin"
23651
23824
  })
23652
23825
  },
23653
23826
  status: {
@@ -23930,6 +24103,21 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
23930
24103
  })]);
23931
24104
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
23932
24105
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
24106
+ /**
24107
+ * One GOP of a finalized segment, cut by byte range through the segment's own
24108
+ * `mfra` (D31 on the D42 feeder path). `data` is the `ftyp`+`moov` head plus
24109
+ * the single `moof`+`mdat` covering the requested instant — standalone-
24110
+ * demuxable, never the whole file. When the segment's index cannot be parsed
24111
+ * the provider degrades INSIDE the mechanism to the whole segment (still one
24112
+ * `data`, `gopStartMs` = the segment start) — a worse read, not another path.
24113
+ */
24114
+ var ReadGopBytesResultSchema = object({
24115
+ data: _instanceof(Uint8Array),
24116
+ /** Absolute epoch ms of the returned fragment's first sample. */
24117
+ gopStartMs: number(),
24118
+ /** Media ms the returned fragment covers. */
24119
+ gopDurMs: number()
24120
+ });
23933
24121
  method(object({
23934
24122
  deviceId: number(),
23935
24123
  fromMs: number(),
@@ -23972,6 +24160,14 @@ method(object({
23972
24160
  }), ReadSegmentBytesResultSchema, {
23973
24161
  kind: "query",
23974
24162
  auth: "admin"
24163
+ }), method(object({
24164
+ deviceId: number(),
24165
+ profile: string(),
24166
+ startMs: number(),
24167
+ epochMs: number()
24168
+ }), ReadGopBytesResultSchema, {
24169
+ kind: "query",
24170
+ auth: "admin"
23975
24171
  }), method(object({
23976
24172
  deviceId: number(),
23977
24173
  config: RecordingConfigSchema
@@ -24531,6 +24727,16 @@ var StreamProfileConfigSchema = object({
24531
24727
  "baseline"
24532
24728
  ]).optional(),
24533
24729
  gop: number().optional(),
24730
+ /**
24731
+ * Whether THIS profile currently carries an audio track. READ-ONLY here.
24732
+ *
24733
+ * There is no matching field on {@link StreamProfilePatchSchema}: the
24734
+ * camera's microphone is owned by `privacy-mask` (`setAudioEnabled`), which
24735
+ * writes every profile at once so "audio off" means silent everywhere. A
24736
+ * per-profile writer beside it would let a camera be half-muted and would be
24737
+ * a second knob onto one device register — the failure D62 exists to
24738
+ * prevent. Absent when the firmware does not report the flag.
24739
+ */
24534
24740
  audio: boolean().optional()
24535
24741
  });
24536
24742
  var StreamParamsStatusSchema = object({
@@ -24571,7 +24777,13 @@ var StreamParamsOptionsSchema = object({
24571
24777
  ext: StreamProfileOptionsSchema.optional()
24572
24778
  });
24573
24779
  /** A partial change to one profile — every field optional; a provider
24574
- * ignores fields it doesn't support. */
24780
+ * ignores fields it doesn't support.
24781
+ *
24782
+ * There is deliberately NO `audio` here. It existed until 2026-08-07,
24783
+ * reachable from no form and honoured by exactly one provider, while the
24784
+ * camera's microphone is a whole-device fact. It now has one writer,
24785
+ * `privacyMask.setAudioEnabled`, which writes every profile — see
24786
+ * `privacy-mask.cap.ts`. */
24575
24787
  var StreamProfilePatchSchema = object({
24576
24788
  width: number().optional(),
24577
24789
  height: number().optional(),
@@ -24584,8 +24796,7 @@ var StreamProfilePatchSchema = object({
24584
24796
  "main",
24585
24797
  "baseline"
24586
24798
  ]).optional(),
24587
- gop: number().optional(),
24588
- audio: boolean().optional()
24799
+ gop: number().optional()
24589
24800
  });
24590
24801
  var streamParamsCapability = {
24591
24802
  name: "stream-params",
@@ -30317,6 +30528,12 @@ Object.freeze({
30317
30528
  addonId: null,
30318
30529
  access: "view"
30319
30530
  },
30531
+ "privacyMask.setAudioEnabled": {
30532
+ capName: "privacy-mask",
30533
+ capScope: "device",
30534
+ addonId: null,
30535
+ access: "create"
30536
+ },
30320
30537
  "privacyMask.setMask": {
30321
30538
  capName: "privacy-mask",
30322
30539
  capScope: "device",
@@ -30485,6 +30702,12 @@ Object.freeze({
30485
30702
  addonId: null,
30486
30703
  access: "create"
30487
30704
  },
30705
+ "recording.readGopBytes": {
30706
+ capName: "recording",
30707
+ capScope: "system",
30708
+ addonId: null,
30709
+ access: "view"
30710
+ },
30488
30711
  "recording.readSegmentBytes": {
30489
30712
  capName: "recording",
30490
30713
  capScope: "system",
@@ -31951,5 +32174,87 @@ object({
31951
32174
  paddingRatio: .15,
31952
32175
  square: false
31953
32176
  }).paddingRatio;
32177
+ /**
32178
+ * WHICH delivered frames the decode worker retains a native copy of.
32179
+ *
32180
+ * - `all` — every frame the worker delivered to the runner. The shipped
32181
+ * behaviour, and the only correct one if something can ask for a crop of a
32182
+ * frame the runner never sent to inference.
32183
+ * - `inferred` — only the frames the runner ADMITTED to its detection queue.
32184
+ * A native-crop request always names a `frameId` that rode an inference
32185
+ * result, so that is the only set a request can name. How much it drops is
32186
+ * the two-plane governor's admit ratio and nothing else: measured at ~50% on
32187
+ * this cluster, not the ~80% the design sketch assumed, because the governor
32188
+ * was not throttling as hard as the sketch supposed. Read
32189
+ * `leaseAdmitted`/`leaseOffered` off the metrics line for the camera in front
32190
+ * of you rather than quoting a number from here. The newest delivered frame is
32191
+ * croppable regardless — it is still the worker's reserved slot, not a lease —
32192
+ * which covers the one-frame race between a mark and the supersede that
32193
+ * consumes it.
32194
+ */
32195
+ var NativeLeaseAdmissionSchema = _enum(["all", "inferred"]);
32196
+ object({
32197
+ /**
32198
+ * How long a retained native frame is served before it counts as a miss.
32199
+ *
32200
+ * Must cover the FULL late-crop horizon: detection inference + the
32201
+ * cross-process inference-result hop to hub post-analysis + tracking + the
32202
+ * tRPC crop round-trip back. Below ~500 ms the busiest cameras' subject crops
32203
+ * outrun it and fall back to the ≤640 detection frame; above ~3 s the resident
32204
+ * RAM per busy camera grows linearly with no measured hit-rate gain.
32205
+ */
32206
+ ttlMs: number().int().min(250).max(1e4),
32207
+ /**
32208
+ * Hard per-decode-worker RAM ceiling for retained native frames, in MB.
32209
+ *
32210
+ * Intended as a SAFETY ceiling with the TTL as the effective cap — but check
32211
+ * which one is actually binding before reasoning from that. At the shipped
32212
+ * 1024 MB and a 2 800 ms TTL, a 4K camera hits the CEILING first (~43 frames
32213
+ * at ~24 MB each) and the TTL never gets to expire anything; `leaseMb` /
32214
+ * `leaseFrames` on the metrics line say which. When the ceiling binds, a
32215
+ * change that admits fewer frames buys retention WINDOW at constant RAM
32216
+ * rather than giving RAM back — lower this knob if RAM is what you wanted.
32217
+ * `0` DISABLES the lease entirely and falls the worker back to the tiny
32218
+ * leak-prone GPU surface ring (~85% crop miss; that is what the lease exists
32219
+ * to replace).
32220
+ */
32221
+ budgetMb: number().int().min(0).max(4096),
32222
+ /**
32223
+ * Demand window: eager per-frame native retention runs only within this many
32224
+ * ms of the last native-crop request (or of the dial starting).
32225
+ *
32226
+ * `0` means ALWAYS ON — it disables the gate, it does not disable retention.
32227
+ * That is the legacy behaviour that saturated an N100 (24 native-4K downloads
32228
+ * per second on a camera with zero crop demand), so leave it non-zero unless
32229
+ * you are reproducing that.
32230
+ */
32231
+ activityMs: number().int().min(0).max(12e4),
32232
+ /**
32233
+ * Which delivered frames are retained at all — see
32234
+ * {@link NativeLeaseAdmissionSchema}. This is the only knob of the four that
32235
+ * changes WHAT is kept rather than for how long, so it is also the only one
32236
+ * that can turn a crop that used to hit into a miss. The worker counts every
32237
+ * crop request naming a frame it did NOT see marked
32238
+ * (`leaseUnmarkedCrops` on the session-decode metrics line): a non-zero value
32239
+ * there is the signal that some caller names frames outside the inference set
32240
+ * and that this must go back to `all`.
32241
+ */
32242
+ admission: NativeLeaseAdmissionSchema
32243
+ });
32244
+ /**
32245
+ * The values in force when the operator has set nothing — byte-for-byte the
32246
+ * constants the decode worker shipped with as env-var defaults, so making these
32247
+ * settings changed no behaviour on the day it landed.
32248
+ */
32249
+ var DEFAULT_NATIVE_LEASE_SETTINGS = {
32250
+ ttlMs: 1200,
32251
+ budgetMb: 1024,
32252
+ activityMs: 15e3,
32253
+ admission: "inferred"
32254
+ };
32255
+ DEFAULT_NATIVE_LEASE_SETTINGS.ttlMs;
32256
+ DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
32257
+ DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
32258
+ DEFAULT_NATIVE_LEASE_SETTINGS.admission;
31954
32259
  //#endregion
31955
32260
  export { object as $, hfModelUrl as A, errMsg as B, buildEventKindDescriptor as C, embeddingEncoderCapability as D, defineCustomActions as E, readDeviceStateFrom as F, isDeviceScopedCap as G, DeviceType as H, subKindsOf as I, _enum as J, nodePin as K, vectorDimFromBase64 as L, notificationRulesCapability as M, pipelineAnalyticsCapability as N, encodeVectorBase64 as O, plateGalleryCapability as P, number as Q, videoclipsCapability as R, audioMetricsCapability as S, customAction as T, createEvent as U, BaseAddon as V, hydrateSchema as W, boolean as X, array as Y, literal as Z, TimelapseRuleInputSchema as _, MACRO_LABELS as a, addonWidgetsSourceCapability as b, NcConditionDescriptorSchema as c, NcRuleSchema as d, record as et, NcSnoozeInputSchema as f, OpsLogEntrySchema as g, NcTaxonomySchema as h, EVENT_PAD_MS as i, kebabToCamel as j, faceGalleryCapability as k, NcRuleInputSchema as l, NcSnoozeSuppressedSchema as m, DEFAULT_EVENT_COLOR as n, unknown as nt, NC_CONDITION_CATALOG as o, NcSnoozeSchema as p, sleep as q, EVENT_KIND_BY_CAP as r, EventCategory as rt, NC_TAXONOMY as s, BaseDevice as t, string as tt, NcRulePatchSchema as u, TimelapseRuleSchema as v, cosineSimilarity as w, alarmPanelCapability as x, TrackSourceSchema as y, zoneAnalyticsCapability as z };
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-DKhwOB5L.js");
5
+ const require_dist = require("../dist-BaqsyWbd.js");
6
6
  let node_fs = require("node:fs");
7
7
  let node_fs$1 = require_dist.__toESM(node_fs, 1);
8
8
  node_fs = require_dist.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { A as hfModelUrl, D as embeddingEncoderCapability, V as BaseAddon } from "../dist-4NhN1aPx.mjs";
1
+ import { A as hfModelUrl, D as embeddingEncoderCapability, V as BaseAddon } from "../dist-DYOxxL_e.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import * as fs from "node:fs";
4
4
  import "node:fs";
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-DKhwOB5L.js");
1
+ const require_dist = require("./dist-BaqsyWbd.js");
2
2
  let node_fs = require("node:fs");
3
3
  node_fs = require_dist.__toESM(node_fs, 1);
4
4
  let node_path = require("node:path");
@@ -1,6 +1,6 @@
1
1
  import { a as e, i as t, n, o as r, r as i, t as a } from "./_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.js-C0AuF9av.mjs";
2
2
  import { t as o } from "./_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-B3Wx5J80.mjs";
3
- import { a as s, i as c, n as l, o as u, r as d, s as f, t as p } from "./_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BfiNBOyJ.mjs";
3
+ import { a as s, i as c, n as l, o as u, r as d, s as f, t as p } from "./_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CiIJt_s8.mjs";
4
4
  import { n as m, r as h, t as g } from "./_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-Bm-iyjmq.mjs";
5
5
  //#region ../../node_modules/lucide-react/dist/esm/shared/src/utils.js
6
6
  var _ = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(), v = (e) => e.replace(/^([A-Z])|[\s-_]+(\w)/g, (e, t, n) => n ? n.toUpperCase() : t.toLowerCase()), y = (e) => {
@@ -18,7 +18,7 @@ var e = {
18
18
  },
19
19
  "@camstack/types": {
20
20
  name: "@camstack/types",
21
- version: "1.2.42",
21
+ version: "1.2.43",
22
22
  scope: ["default"],
23
23
  loaded: !1,
24
24
  from: "addon_pipeline_analytics_widgets",
@@ -33,7 +33,7 @@ var e = {
33
33
  },
34
34
  "@camstack/ui-library": {
35
35
  name: "@camstack/ui-library",
36
- version: "1.2.30",
36
+ version: "1.2.31",
37
37
  scope: ["default"],
38
38
  loaded: !1,
39
39
  from: "addon_pipeline_analytics_widgets",