@camstack/addon-static-turn 1.2.21 → 1.2.23

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.
@@ -7550,7 +7550,7 @@ var RecordingBandSchema = object({
7550
7550
  postBufferSec: number().min(0).optional()
7551
7551
  });
7552
7552
  ({
7553
- preBufferSec: 10,
7553
+ preBufferSec: 15,
7554
7554
  postBufferSec: 30
7555
7555
  }).postBufferSec * 1e3;
7556
7556
  /**
@@ -13489,128 +13489,103 @@ var COCO_TO_MACRO = {
13489
13489
  var AUDIO_MACRO_LABELS = [
13490
13490
  {
13491
13491
  id: "speech",
13492
- name: "Speech",
13493
- icon: "🗣️"
13492
+ name: "Speech"
13494
13493
  },
13495
13494
  {
13496
13495
  id: "scream",
13497
- name: "Scream / Shout",
13498
- icon: "😱"
13496
+ name: "Scream / Shout"
13499
13497
  },
13500
13498
  {
13501
13499
  id: "crying",
13502
- name: "Crying / Baby",
13503
- icon: "😢"
13500
+ name: "Crying / Baby"
13504
13501
  },
13505
13502
  {
13506
13503
  id: "laughter",
13507
- name: "Laughter",
13508
- icon: "😂"
13504
+ name: "Laughter"
13509
13505
  },
13510
13506
  {
13511
13507
  id: "music",
13512
- name: "Music",
13513
- icon: "🎵"
13508
+ name: "Music"
13514
13509
  },
13515
13510
  {
13516
13511
  id: "dog",
13517
- name: "Dog",
13518
- icon: "🐕"
13512
+ name: "Dog"
13519
13513
  },
13520
13514
  {
13521
13515
  id: "cat",
13522
- name: "Cat",
13523
- icon: "🐈"
13516
+ name: "Cat"
13524
13517
  },
13525
13518
  {
13526
13519
  id: "bird",
13527
- name: "Bird",
13528
- icon: "🐦"
13520
+ name: "Bird"
13529
13521
  },
13530
13522
  {
13531
13523
  id: "animal",
13532
- name: "Animal (other)",
13533
- icon: "🐾"
13524
+ name: "Animal (other)"
13534
13525
  },
13535
13526
  {
13536
13527
  id: "alarm",
13537
- name: "Alarm / Siren",
13538
- icon: "🚨"
13528
+ name: "Alarm / Siren"
13539
13529
  },
13540
13530
  {
13541
13531
  id: "doorbell",
13542
- name: "Doorbell / Knock",
13543
- icon: "🔔"
13532
+ name: "Doorbell / Knock"
13544
13533
  },
13545
13534
  {
13546
13535
  id: "glass_breaking",
13547
- name: "Glass Breaking",
13548
- icon: "💥"
13536
+ name: "Glass Breaking"
13549
13537
  },
13550
13538
  {
13551
13539
  id: "gunshot",
13552
- name: "Gunshot / Explosion",
13553
- icon: "💣"
13540
+ name: "Gunshot / Explosion"
13554
13541
  },
13555
13542
  {
13556
13543
  id: "vehicle",
13557
- name: "Vehicle",
13558
- icon: "🚗"
13544
+ name: "Vehicle"
13559
13545
  },
13560
13546
  {
13561
13547
  id: "siren",
13562
- name: "Emergency Siren",
13563
- icon: "🚑"
13548
+ name: "Emergency Siren"
13564
13549
  },
13565
13550
  {
13566
13551
  id: "fire",
13567
- name: "Fire / Smoke",
13568
- icon: "🔥"
13552
+ name: "Fire / Smoke"
13569
13553
  },
13570
13554
  {
13571
13555
  id: "water",
13572
- name: "Water",
13573
- icon: "💧"
13556
+ name: "Water"
13574
13557
  },
13575
13558
  {
13576
13559
  id: "wind",
13577
- name: "Wind / Weather",
13578
- icon: "🌬️"
13560
+ name: "Wind / Weather"
13579
13561
  },
13580
13562
  {
13581
13563
  id: "door",
13582
- name: "Door",
13583
- icon: "🚪"
13564
+ name: "Door"
13584
13565
  },
13585
13566
  {
13586
13567
  id: "footsteps",
13587
- name: "Footsteps",
13588
- icon: "👣"
13568
+ name: "Footsteps"
13589
13569
  },
13590
13570
  {
13591
13571
  id: "crowd",
13592
- name: "Crowd / Chatter",
13593
- icon: "👥"
13572
+ name: "Crowd / Chatter"
13594
13573
  },
13595
13574
  {
13596
13575
  id: "telephone",
13597
- name: "Telephone",
13598
- icon: "📞"
13576
+ name: "Telephone"
13599
13577
  },
13600
13578
  {
13601
13579
  id: "engine",
13602
- name: "Engine / Motor",
13603
- icon: "⚙️"
13580
+ name: "Engine / Motor"
13604
13581
  },
13605
13582
  {
13606
13583
  id: "tools",
13607
- name: "Tools / Construction",
13608
- icon: "🔨"
13584
+ name: "Tools / Construction"
13609
13585
  },
13610
13586
  {
13611
13587
  id: "silence",
13612
- name: "Silence",
13613
- icon: "🤫"
13588
+ name: "Silence"
13614
13589
  }
13615
13590
  ];
13616
13591
  var YAMNET_TO_MACRO = {
@@ -16074,7 +16049,7 @@ var TrackEnvelopeSchema = object({
16074
16049
  * `snapshots[]` references — megabytes across a page of tracks. `slim`
16075
16050
  * keeps every scalar the list surfaces actually render (ids, class(es),
16076
16051
  * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
16077
- * zonesVisited, bestEventId, envelope, hasFace, hasRider) and returns `positions` /
16052
+ * zonesVisited, bestEventId, envelope, hasFace, hasEmbeddedFace, hasRider) and returns `positions` /
16078
16053
  * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
16079
16054
  * `getTrack`. Mirrors the event-store `projection` convention
16080
16055
  * (`getObjectEvents` et al.).
@@ -16369,6 +16344,35 @@ var TrackSchema = object({
16369
16344
  */
16370
16345
  hasFace: boolean().optional(),
16371
16346
  /**
16347
+ * This track has a face row IN THE GALLERY: a crop **and** an embedding — a
16348
+ * face an operator could ASSIGN to an identity.
16349
+ *
16350
+ * The STRICT twin of {@link hasFace}, and the pair only earns its keep
16351
+ * because the two disagree. `hasFace` is stamped at the TOP of the face
16352
+ * branch, before every gate, and means no more than "a face detector produced
16353
+ * a face detail". This one is stamped at the single moment the gallery row
16354
+ * LANDS — after `FaceRecognizer.onTrackEnd` successfully persists it, i.e.
16355
+ * past the embedding-magnitude verdict, the `minFacePx` detection gate, the
16356
+ * candidate gate, the imageless-track drop (no crop was ever captured) and
16357
+ * the crop-store drop. Everything between the detector and that insert can
16358
+ * legitimately refuse the face, so a flag written any earlier promises the
16359
+ * operator something to assign and delivers nothing.
16360
+ *
16361
+ * **Independent of recognition.** A face collected but never auto-matched is
16362
+ * still assignable — it is in fact the face an operator most wants to reach —
16363
+ * so this is NOT gated on `recognizedIdentityId`. Recognition lands in
16364
+ * `subLabel`; this says only that the raw material exists.
16365
+ *
16366
+ * **Set once, never cleared.** A track that produced a gallery row produced
16367
+ * one; deleting the row later is the gallery's business, not this flag's.
16368
+ *
16369
+ * **Absent ≠ false**, the same rule as {@link hasFace}: every row written
16370
+ * before the column omits it, and so does every server that predates the
16371
+ * field. A consumer must test `=== true` and render nothing otherwise —
16372
+ * never infer "no assignable face".
16373
+ */
16374
+ hasEmbeddedFace: boolean().optional(),
16375
+ /**
16372
16376
  * This subject CONTAINS a folded rider — a person the rider-pairing step
16373
16377
  * ([D34](../decisions/adr-0034.md)) removed from the frame BEFORE the tracker,
16374
16378
  * so the passage is tracked once and as a VEHICLE.
@@ -17718,6 +17722,32 @@ var DetailResultSchema = object({
17718
17722
  /** FRAME-space bbox (already mapped back from crop space). */
17719
17723
  bbox: NativeCropBboxSchema.optional(),
17720
17724
  embedding: string().optional(),
17725
+ /**
17726
+ * L2 magnitude of the RAW pre-normalization embedding. Additive and optional
17727
+ * so an older runner that does not send it degrades to "unmeasurable", which
17728
+ * the consuming gate treats as accept — never as reject.
17729
+ */
17730
+ embeddingMagnitude: number().optional(),
17731
+ /**
17732
+ * Model that produced `embedding` — the SAME contract `labelModelId` has for
17733
+ * `label`, and for the same reason: the producing node is the only side that
17734
+ * knows which model actually ran, and re-deriving it downstream is exactly
17735
+ * the inference this model exists to forbid.
17736
+ *
17737
+ * Additive and optional, so a runner that predates it degrades to
17738
+ * "unmeasurable" rather than to a wrong answer — the consumer falls back to
17739
+ * its previous assumption instead of refusing.
17740
+ *
17741
+ * Its absence was not cosmetic. `FaceRecognizer` needs the probe's model id
17742
+ * to use `face-matcher`'s same-model gate (`sample.modelId !== probe.modelId`);
17743
+ * with nothing on the wire it took the id from the GALLERY, so the gate
17744
+ * compared the gallery against itself and could never fire. That turned the
17745
+ * one guard protecting the face feature space into a tautology, and it is
17746
+ * why swapping `face-embedding`'s model would NOT have stranded the enrolled
17747
+ * gallery as the catalog comment predicts — it would have scored the new
17748
+ * model's vectors against the old model's samples in one cosine space.
17749
+ */
17750
+ embeddingModelId: string().optional(),
17721
17751
  label: string().optional(),
17722
17752
  /**
17723
17753
  * The tier `label` occupies, copied VERBATIM from the producing step's
@@ -19128,6 +19158,7 @@ DeviceType.Camera, method(object({
19128
19158
  isBattery: boolean(),
19129
19159
  reason: _enum([
19130
19160
  "disabled",
19161
+ "offline",
19131
19162
  "sleeping",
19132
19163
  "unreachable",
19133
19164
  "waking"
@@ -19173,9 +19204,17 @@ targets: array(object({
19173
19204
  sleeping: boolean(),
19174
19205
  /** Current device state rendered over the cached frame. State images
19175
19206
  * remain authoritative even when their photographic background is
19176
- * old; null means the link must carry a current camera frame. */
19207
+ * old; null means the link must carry a current camera frame.
19208
+ *
19209
+ * **This is the ONLY source a surface may derive an inactive tile
19210
+ * from.** A client that reads a device's `online`/`disabled` flags and
19211
+ * draws its own verdict will disagree with the pixels it is painting
19212
+ * — the frame comes from here, and so must the sentence over it
19213
+ * (D190). Pair with `ageMs` for the age of the photograph behind the
19214
+ * state. */
19177
19215
  stateReason: _enum([
19178
19216
  "disabled",
19217
+ "offline",
19179
19218
  "sleeping",
19180
19219
  "unreachable",
19181
19220
  "waking"
@@ -24229,9 +24268,21 @@ var RecordingLocationUsageSchema = object({
24229
24268
  * previously rendered as two identical "disks" with a nonsensical used
24230
24269
  * split — hydrate attribution across aliases is arbitrary by nature. */
24231
24270
  locationIds: array(string()).optional(),
24232
- /** Bytes of recordings stored on this PHYSICAL volume (all aliases). */
24271
+ /**
24272
+ * Filesystem root of the physical volume this row accounts — the only
24273
+ * operator-legible identity when a location has no `displayName` yet, and
24274
+ * what a "which disk is full" alert has to name. OPTIONAL for the same
24275
+ * train-skew reason as `oldestMs`: a recorder that predates the field omits
24276
+ * it, and this schema must keep validating that older provider's payload.
24277
+ */
24278
+ root: string().optional(),
24279
+ /** Bytes of recordings stored on this PHYSICAL volume (all aliases). This is
24280
+ * the footage's SHARE of the volume, not how full the volume is — the disk
24281
+ * also holds everything that is not recordings. Volume fill is
24282
+ * `(totalBytes - availableBytes) / totalBytes`. */
24233
24283
  usedBytes: number(),
24234
- /** Free bytes on the location's volume; null when capacity is unknown (remote). */
24284
+ /** Free bytes on the location's volume; null when capacity is unknown
24285
+ * (remote/unstattable — the recorder logs the statfs failure with the root). */
24235
24286
  availableBytes: number().nullable(),
24236
24287
  /** Total bytes of the location's volume; null when unknown. */
24237
24288
  totalBytes: number().nullable()
@@ -24243,7 +24294,15 @@ var RecordingStorageUsageSchema = object({
24243
24294
  nodeId: string(),
24244
24295
  totalUsedBytes: number(),
24245
24296
  devices: array(RecordingDeviceUsageSchema),
24246
- locations: array(RecordingLocationUsageSchema)
24297
+ /**
24298
+ * Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
24299
+ * of the payload without it: the hub serves this method through a BAKED
24300
+ * router, so a hub whose framework train predates a change to this array
24301
+ * strips it out of an answer a newer recorder happily produced. A UI that
24302
+ * dereferences it unconditionally crashes the whole Storage page over a
24303
+ * missing disk bar.
24304
+ */
24305
+ locations: array(RecordingLocationUsageSchema).optional()
24247
24306
  });
24248
24307
  /**
24249
24308
  * The OPERATOR-ARMED half of multi-location recordings (D116).
@@ -33359,8 +33418,13 @@ Object.freeze({
33359
33418
  "network-access": "ingress",
33360
33419
  "smtp-provider": "email"
33361
33420
  });
33362
- new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
33421
+ /** Schema defaults an untouched sub-field must author exactly these. */
33422
+ var NC_AUDIO_DEFAULTS = {
33423
+ hitPercent: 60,
33424
+ samplingSeconds: 10
33425
+ };
33363
33426
  new Set(["devices", "classes"]);
33427
+ NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
33364
33428
  /**
33365
33429
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
33366
33430
  *
@@ -33592,6 +33656,39 @@ TimelapseRuleInputSchema.extend({
33592
33656
  createdAt: number(),
33593
33657
  updatedAt: number()
33594
33658
  });
33659
+ Object.freeze(Object.fromEntries([{
33660
+ stepId: "face-embedding",
33661
+ label: "Face recognition model",
33662
+ defaultModelId: "arcface-r100",
33663
+ indexName: "the enrolled face gallery",
33664
+ options: [
33665
+ {
33666
+ id: "arcface-r100",
33667
+ label: "ArcFace (ResNet34)"
33668
+ },
33669
+ {
33670
+ id: "auraface-r100",
33671
+ label: "AuraFace R100"
33672
+ },
33673
+ {
33674
+ id: "inception-resnet-v1",
33675
+ label: "Inception ResNet V1"
33676
+ }
33677
+ ]
33678
+ }, {
33679
+ stepId: "clip-embedding",
33680
+ label: "Semantic search model",
33681
+ defaultModelId: "mobileclip-s1",
33682
+ indexName: "the object semantic-search index",
33683
+ options: [{
33684
+ id: "mobileclip-s1",
33685
+ label: "MobileCLIP S1"
33686
+ }, {
33687
+ id: "mobileclip-s2",
33688
+ label: "MobileCLIP S2"
33689
+ }]
33690
+ }].map((s) => [s.stepId, s.defaultModelId])));
33691
+ string().min(1);
33595
33692
  object({
33596
33693
  /**
33597
33694
  * Fraction of the box's own size added on EACH side before cutting.
@@ -33612,10 +33709,21 @@ object({
33612
33709
  */
33613
33710
  square: boolean()
33614
33711
  });
33615
- ({
33712
+ /**
33713
+ * The convention in force when nobody has configured one — byte-for-byte the
33714
+ * behaviour of the pre-unification LIVE path (`DETAIL_CROP_PADDING_RATIO`).
33715
+ */
33716
+ var DEFAULT_DETAIL_CROP_CONVENTION = {
33616
33717
  paddingRatio: .15,
33617
33718
  square: false
33618
- }).paddingRatio;
33719
+ };
33720
+ Object.freeze({
33721
+ x: 0,
33722
+ y: 0,
33723
+ w: 1,
33724
+ h: 1
33725
+ });
33726
+ DEFAULT_DETAIL_CROP_CONVENTION.paddingRatio;
33619
33727
  /**
33620
33728
  * WHICH delivered frames the decode worker retains a native copy of.
33621
33729
  *
@@ -7549,7 +7549,7 @@ var RecordingBandSchema = object({
7549
7549
  postBufferSec: number().min(0).optional()
7550
7550
  });
7551
7551
  ({
7552
- preBufferSec: 10,
7552
+ preBufferSec: 15,
7553
7553
  postBufferSec: 30
7554
7554
  }).postBufferSec * 1e3;
7555
7555
  /**
@@ -13488,128 +13488,103 @@ var COCO_TO_MACRO = {
13488
13488
  var AUDIO_MACRO_LABELS = [
13489
13489
  {
13490
13490
  id: "speech",
13491
- name: "Speech",
13492
- icon: "🗣️"
13491
+ name: "Speech"
13493
13492
  },
13494
13493
  {
13495
13494
  id: "scream",
13496
- name: "Scream / Shout",
13497
- icon: "😱"
13495
+ name: "Scream / Shout"
13498
13496
  },
13499
13497
  {
13500
13498
  id: "crying",
13501
- name: "Crying / Baby",
13502
- icon: "😢"
13499
+ name: "Crying / Baby"
13503
13500
  },
13504
13501
  {
13505
13502
  id: "laughter",
13506
- name: "Laughter",
13507
- icon: "😂"
13503
+ name: "Laughter"
13508
13504
  },
13509
13505
  {
13510
13506
  id: "music",
13511
- name: "Music",
13512
- icon: "🎵"
13507
+ name: "Music"
13513
13508
  },
13514
13509
  {
13515
13510
  id: "dog",
13516
- name: "Dog",
13517
- icon: "🐕"
13511
+ name: "Dog"
13518
13512
  },
13519
13513
  {
13520
13514
  id: "cat",
13521
- name: "Cat",
13522
- icon: "🐈"
13515
+ name: "Cat"
13523
13516
  },
13524
13517
  {
13525
13518
  id: "bird",
13526
- name: "Bird",
13527
- icon: "🐦"
13519
+ name: "Bird"
13528
13520
  },
13529
13521
  {
13530
13522
  id: "animal",
13531
- name: "Animal (other)",
13532
- icon: "🐾"
13523
+ name: "Animal (other)"
13533
13524
  },
13534
13525
  {
13535
13526
  id: "alarm",
13536
- name: "Alarm / Siren",
13537
- icon: "🚨"
13527
+ name: "Alarm / Siren"
13538
13528
  },
13539
13529
  {
13540
13530
  id: "doorbell",
13541
- name: "Doorbell / Knock",
13542
- icon: "🔔"
13531
+ name: "Doorbell / Knock"
13543
13532
  },
13544
13533
  {
13545
13534
  id: "glass_breaking",
13546
- name: "Glass Breaking",
13547
- icon: "💥"
13535
+ name: "Glass Breaking"
13548
13536
  },
13549
13537
  {
13550
13538
  id: "gunshot",
13551
- name: "Gunshot / Explosion",
13552
- icon: "💣"
13539
+ name: "Gunshot / Explosion"
13553
13540
  },
13554
13541
  {
13555
13542
  id: "vehicle",
13556
- name: "Vehicle",
13557
- icon: "🚗"
13543
+ name: "Vehicle"
13558
13544
  },
13559
13545
  {
13560
13546
  id: "siren",
13561
- name: "Emergency Siren",
13562
- icon: "🚑"
13547
+ name: "Emergency Siren"
13563
13548
  },
13564
13549
  {
13565
13550
  id: "fire",
13566
- name: "Fire / Smoke",
13567
- icon: "🔥"
13551
+ name: "Fire / Smoke"
13568
13552
  },
13569
13553
  {
13570
13554
  id: "water",
13571
- name: "Water",
13572
- icon: "💧"
13555
+ name: "Water"
13573
13556
  },
13574
13557
  {
13575
13558
  id: "wind",
13576
- name: "Wind / Weather",
13577
- icon: "🌬️"
13559
+ name: "Wind / Weather"
13578
13560
  },
13579
13561
  {
13580
13562
  id: "door",
13581
- name: "Door",
13582
- icon: "🚪"
13563
+ name: "Door"
13583
13564
  },
13584
13565
  {
13585
13566
  id: "footsteps",
13586
- name: "Footsteps",
13587
- icon: "👣"
13567
+ name: "Footsteps"
13588
13568
  },
13589
13569
  {
13590
13570
  id: "crowd",
13591
- name: "Crowd / Chatter",
13592
- icon: "👥"
13571
+ name: "Crowd / Chatter"
13593
13572
  },
13594
13573
  {
13595
13574
  id: "telephone",
13596
- name: "Telephone",
13597
- icon: "📞"
13575
+ name: "Telephone"
13598
13576
  },
13599
13577
  {
13600
13578
  id: "engine",
13601
- name: "Engine / Motor",
13602
- icon: "⚙️"
13579
+ name: "Engine / Motor"
13603
13580
  },
13604
13581
  {
13605
13582
  id: "tools",
13606
- name: "Tools / Construction",
13607
- icon: "🔨"
13583
+ name: "Tools / Construction"
13608
13584
  },
13609
13585
  {
13610
13586
  id: "silence",
13611
- name: "Silence",
13612
- icon: "🤫"
13587
+ name: "Silence"
13613
13588
  }
13614
13589
  ];
13615
13590
  var YAMNET_TO_MACRO = {
@@ -16073,7 +16048,7 @@ var TrackEnvelopeSchema = object({
16073
16048
  * `snapshots[]` references — megabytes across a page of tracks. `slim`
16074
16049
  * keeps every scalar the list surfaces actually render (ids, class(es),
16075
16050
  * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
16076
- * zonesVisited, bestEventId, envelope, hasFace, hasRider) and returns `positions` /
16051
+ * zonesVisited, bestEventId, envelope, hasFace, hasEmbeddedFace, hasRider) and returns `positions` /
16077
16052
  * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
16078
16053
  * `getTrack`. Mirrors the event-store `projection` convention
16079
16054
  * (`getObjectEvents` et al.).
@@ -16368,6 +16343,35 @@ var TrackSchema = object({
16368
16343
  */
16369
16344
  hasFace: boolean().optional(),
16370
16345
  /**
16346
+ * This track has a face row IN THE GALLERY: a crop **and** an embedding — a
16347
+ * face an operator could ASSIGN to an identity.
16348
+ *
16349
+ * The STRICT twin of {@link hasFace}, and the pair only earns its keep
16350
+ * because the two disagree. `hasFace` is stamped at the TOP of the face
16351
+ * branch, before every gate, and means no more than "a face detector produced
16352
+ * a face detail". This one is stamped at the single moment the gallery row
16353
+ * LANDS — after `FaceRecognizer.onTrackEnd` successfully persists it, i.e.
16354
+ * past the embedding-magnitude verdict, the `minFacePx` detection gate, the
16355
+ * candidate gate, the imageless-track drop (no crop was ever captured) and
16356
+ * the crop-store drop. Everything between the detector and that insert can
16357
+ * legitimately refuse the face, so a flag written any earlier promises the
16358
+ * operator something to assign and delivers nothing.
16359
+ *
16360
+ * **Independent of recognition.** A face collected but never auto-matched is
16361
+ * still assignable — it is in fact the face an operator most wants to reach —
16362
+ * so this is NOT gated on `recognizedIdentityId`. Recognition lands in
16363
+ * `subLabel`; this says only that the raw material exists.
16364
+ *
16365
+ * **Set once, never cleared.** A track that produced a gallery row produced
16366
+ * one; deleting the row later is the gallery's business, not this flag's.
16367
+ *
16368
+ * **Absent ≠ false**, the same rule as {@link hasFace}: every row written
16369
+ * before the column omits it, and so does every server that predates the
16370
+ * field. A consumer must test `=== true` and render nothing otherwise —
16371
+ * never infer "no assignable face".
16372
+ */
16373
+ hasEmbeddedFace: boolean().optional(),
16374
+ /**
16371
16375
  * This subject CONTAINS a folded rider — a person the rider-pairing step
16372
16376
  * ([D34](../decisions/adr-0034.md)) removed from the frame BEFORE the tracker,
16373
16377
  * so the passage is tracked once and as a VEHICLE.
@@ -17717,6 +17721,32 @@ var DetailResultSchema = object({
17717
17721
  /** FRAME-space bbox (already mapped back from crop space). */
17718
17722
  bbox: NativeCropBboxSchema.optional(),
17719
17723
  embedding: string().optional(),
17724
+ /**
17725
+ * L2 magnitude of the RAW pre-normalization embedding. Additive and optional
17726
+ * so an older runner that does not send it degrades to "unmeasurable", which
17727
+ * the consuming gate treats as accept — never as reject.
17728
+ */
17729
+ embeddingMagnitude: number().optional(),
17730
+ /**
17731
+ * Model that produced `embedding` — the SAME contract `labelModelId` has for
17732
+ * `label`, and for the same reason: the producing node is the only side that
17733
+ * knows which model actually ran, and re-deriving it downstream is exactly
17734
+ * the inference this model exists to forbid.
17735
+ *
17736
+ * Additive and optional, so a runner that predates it degrades to
17737
+ * "unmeasurable" rather than to a wrong answer — the consumer falls back to
17738
+ * its previous assumption instead of refusing.
17739
+ *
17740
+ * Its absence was not cosmetic. `FaceRecognizer` needs the probe's model id
17741
+ * to use `face-matcher`'s same-model gate (`sample.modelId !== probe.modelId`);
17742
+ * with nothing on the wire it took the id from the GALLERY, so the gate
17743
+ * compared the gallery against itself and could never fire. That turned the
17744
+ * one guard protecting the face feature space into a tautology, and it is
17745
+ * why swapping `face-embedding`'s model would NOT have stranded the enrolled
17746
+ * gallery as the catalog comment predicts — it would have scored the new
17747
+ * model's vectors against the old model's samples in one cosine space.
17748
+ */
17749
+ embeddingModelId: string().optional(),
17720
17750
  label: string().optional(),
17721
17751
  /**
17722
17752
  * The tier `label` occupies, copied VERBATIM from the producing step's
@@ -19127,6 +19157,7 @@ DeviceType.Camera, method(object({
19127
19157
  isBattery: boolean(),
19128
19158
  reason: _enum([
19129
19159
  "disabled",
19160
+ "offline",
19130
19161
  "sleeping",
19131
19162
  "unreachable",
19132
19163
  "waking"
@@ -19172,9 +19203,17 @@ targets: array(object({
19172
19203
  sleeping: boolean(),
19173
19204
  /** Current device state rendered over the cached frame. State images
19174
19205
  * remain authoritative even when their photographic background is
19175
- * old; null means the link must carry a current camera frame. */
19206
+ * old; null means the link must carry a current camera frame.
19207
+ *
19208
+ * **This is the ONLY source a surface may derive an inactive tile
19209
+ * from.** A client that reads a device's `online`/`disabled` flags and
19210
+ * draws its own verdict will disagree with the pixels it is painting
19211
+ * — the frame comes from here, and so must the sentence over it
19212
+ * (D190). Pair with `ageMs` for the age of the photograph behind the
19213
+ * state. */
19176
19214
  stateReason: _enum([
19177
19215
  "disabled",
19216
+ "offline",
19178
19217
  "sleeping",
19179
19218
  "unreachable",
19180
19219
  "waking"
@@ -24228,9 +24267,21 @@ var RecordingLocationUsageSchema = object({
24228
24267
  * previously rendered as two identical "disks" with a nonsensical used
24229
24268
  * split — hydrate attribution across aliases is arbitrary by nature. */
24230
24269
  locationIds: array(string()).optional(),
24231
- /** Bytes of recordings stored on this PHYSICAL volume (all aliases). */
24270
+ /**
24271
+ * Filesystem root of the physical volume this row accounts — the only
24272
+ * operator-legible identity when a location has no `displayName` yet, and
24273
+ * what a "which disk is full" alert has to name. OPTIONAL for the same
24274
+ * train-skew reason as `oldestMs`: a recorder that predates the field omits
24275
+ * it, and this schema must keep validating that older provider's payload.
24276
+ */
24277
+ root: string().optional(),
24278
+ /** Bytes of recordings stored on this PHYSICAL volume (all aliases). This is
24279
+ * the footage's SHARE of the volume, not how full the volume is — the disk
24280
+ * also holds everything that is not recordings. Volume fill is
24281
+ * `(totalBytes - availableBytes) / totalBytes`. */
24232
24282
  usedBytes: number(),
24233
- /** Free bytes on the location's volume; null when capacity is unknown (remote). */
24283
+ /** Free bytes on the location's volume; null when capacity is unknown
24284
+ * (remote/unstattable — the recorder logs the statfs failure with the root). */
24234
24285
  availableBytes: number().nullable(),
24235
24286
  /** Total bytes of the location's volume; null when unknown. */
24236
24287
  totalBytes: number().nullable()
@@ -24242,7 +24293,15 @@ var RecordingStorageUsageSchema = object({
24242
24293
  nodeId: string(),
24243
24294
  totalUsedBytes: number(),
24244
24295
  devices: array(RecordingDeviceUsageSchema),
24245
- locations: array(RecordingLocationUsageSchema)
24296
+ /**
24297
+ * Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
24298
+ * of the payload without it: the hub serves this method through a BAKED
24299
+ * router, so a hub whose framework train predates a change to this array
24300
+ * strips it out of an answer a newer recorder happily produced. A UI that
24301
+ * dereferences it unconditionally crashes the whole Storage page over a
24302
+ * missing disk bar.
24303
+ */
24304
+ locations: array(RecordingLocationUsageSchema).optional()
24246
24305
  });
24247
24306
  /**
24248
24307
  * The OPERATOR-ARMED half of multi-location recordings (D116).
@@ -33358,8 +33417,13 @@ Object.freeze({
33358
33417
  "network-access": "ingress",
33359
33418
  "smtp-provider": "email"
33360
33419
  });
33361
- new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
33420
+ /** Schema defaults an untouched sub-field must author exactly these. */
33421
+ var NC_AUDIO_DEFAULTS = {
33422
+ hitPercent: 60,
33423
+ samplingSeconds: 10
33424
+ };
33362
33425
  new Set(["devices", "classes"]);
33426
+ NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
33363
33427
  /**
33364
33428
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
33365
33429
  *
@@ -33591,6 +33655,39 @@ TimelapseRuleInputSchema.extend({
33591
33655
  createdAt: number(),
33592
33656
  updatedAt: number()
33593
33657
  });
33658
+ Object.freeze(Object.fromEntries([{
33659
+ stepId: "face-embedding",
33660
+ label: "Face recognition model",
33661
+ defaultModelId: "arcface-r100",
33662
+ indexName: "the enrolled face gallery",
33663
+ options: [
33664
+ {
33665
+ id: "arcface-r100",
33666
+ label: "ArcFace (ResNet34)"
33667
+ },
33668
+ {
33669
+ id: "auraface-r100",
33670
+ label: "AuraFace R100"
33671
+ },
33672
+ {
33673
+ id: "inception-resnet-v1",
33674
+ label: "Inception ResNet V1"
33675
+ }
33676
+ ]
33677
+ }, {
33678
+ stepId: "clip-embedding",
33679
+ label: "Semantic search model",
33680
+ defaultModelId: "mobileclip-s1",
33681
+ indexName: "the object semantic-search index",
33682
+ options: [{
33683
+ id: "mobileclip-s1",
33684
+ label: "MobileCLIP S1"
33685
+ }, {
33686
+ id: "mobileclip-s2",
33687
+ label: "MobileCLIP S2"
33688
+ }]
33689
+ }].map((s) => [s.stepId, s.defaultModelId])));
33690
+ string().min(1);
33594
33691
  object({
33595
33692
  /**
33596
33693
  * Fraction of the box's own size added on EACH side before cutting.
@@ -33611,10 +33708,21 @@ object({
33611
33708
  */
33612
33709
  square: boolean()
33613
33710
  });
33614
- ({
33711
+ /**
33712
+ * The convention in force when nobody has configured one — byte-for-byte the
33713
+ * behaviour of the pre-unification LIVE path (`DETAIL_CROP_PADDING_RATIO`).
33714
+ */
33715
+ var DEFAULT_DETAIL_CROP_CONVENTION = {
33615
33716
  paddingRatio: .15,
33616
33717
  square: false
33617
- }).paddingRatio;
33718
+ };
33719
+ Object.freeze({
33720
+ x: 0,
33721
+ y: 0,
33722
+ w: 1,
33723
+ h: 1
33724
+ });
33725
+ DEFAULT_DETAIL_CROP_CONVENTION.paddingRatio;
33618
33726
  /**
33619
33727
  * WHICH delivered frames the decode worker retains a native copy of.
33620
33728
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-static-turn",
3
- "version": "1.2.21",
3
+ "version": "1.2.23",
4
4
  "description": "Static / self-hosted (coturn) TURN provider for CamStack",
5
5
  "keywords": [
6
6
  "camstack",