@camstack/addon-tailscale 1.2.22 → 1.2.24

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.
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  enumerable: true
25
25
  }) : target, mod));
26
26
  //#endregion
27
- const require_dist = require("../dist-B_l6CtRR.js");
27
+ const require_dist = require("../dist-Of54y7eC.js");
28
28
  let node_child_process = require("node:child_process");
29
29
  let node_util = require("node:util");
30
30
  let node_fs = require("node:fs");
@@ -1,4 +1,4 @@
1
- import { a as EventCategory, i as BaseAddon, n as networkAccessCapability, r as BOOT_RECOVERY_BACKOFF_MS, t as meshNetworkCapability } from "../dist-LUO_d3xF.mjs";
1
+ import { a as EventCategory, i as BaseAddon, n as networkAccessCapability, r as BOOT_RECOVERY_BACKOFF_MS, t as meshNetworkCapability } from "../dist-9AnjWRPW.mjs";
2
2
  import { execFile, spawn } from "node:child_process";
3
3
  import { promisify } from "node:util";
4
4
  import * as fs from "node:fs";
@@ -7583,7 +7583,7 @@ var RecordingBandSchema = object({
7583
7583
  postBufferSec: number().min(0).optional()
7584
7584
  });
7585
7585
  ({
7586
- preBufferSec: 10,
7586
+ preBufferSec: 15,
7587
7587
  postBufferSec: 30
7588
7588
  }).postBufferSec * 1e3;
7589
7589
  /**
@@ -13539,128 +13539,103 @@ var COCO_TO_MACRO = {
13539
13539
  var AUDIO_MACRO_LABELS = [
13540
13540
  {
13541
13541
  id: "speech",
13542
- name: "Speech",
13543
- icon: "🗣️"
13542
+ name: "Speech"
13544
13543
  },
13545
13544
  {
13546
13545
  id: "scream",
13547
- name: "Scream / Shout",
13548
- icon: "😱"
13546
+ name: "Scream / Shout"
13549
13547
  },
13550
13548
  {
13551
13549
  id: "crying",
13552
- name: "Crying / Baby",
13553
- icon: "😢"
13550
+ name: "Crying / Baby"
13554
13551
  },
13555
13552
  {
13556
13553
  id: "laughter",
13557
- name: "Laughter",
13558
- icon: "😂"
13554
+ name: "Laughter"
13559
13555
  },
13560
13556
  {
13561
13557
  id: "music",
13562
- name: "Music",
13563
- icon: "🎵"
13558
+ name: "Music"
13564
13559
  },
13565
13560
  {
13566
13561
  id: "dog",
13567
- name: "Dog",
13568
- icon: "🐕"
13562
+ name: "Dog"
13569
13563
  },
13570
13564
  {
13571
13565
  id: "cat",
13572
- name: "Cat",
13573
- icon: "🐈"
13566
+ name: "Cat"
13574
13567
  },
13575
13568
  {
13576
13569
  id: "bird",
13577
- name: "Bird",
13578
- icon: "🐦"
13570
+ name: "Bird"
13579
13571
  },
13580
13572
  {
13581
13573
  id: "animal",
13582
- name: "Animal (other)",
13583
- icon: "🐾"
13574
+ name: "Animal (other)"
13584
13575
  },
13585
13576
  {
13586
13577
  id: "alarm",
13587
- name: "Alarm / Siren",
13588
- icon: "🚨"
13578
+ name: "Alarm / Siren"
13589
13579
  },
13590
13580
  {
13591
13581
  id: "doorbell",
13592
- name: "Doorbell / Knock",
13593
- icon: "🔔"
13582
+ name: "Doorbell / Knock"
13594
13583
  },
13595
13584
  {
13596
13585
  id: "glass_breaking",
13597
- name: "Glass Breaking",
13598
- icon: "💥"
13586
+ name: "Glass Breaking"
13599
13587
  },
13600
13588
  {
13601
13589
  id: "gunshot",
13602
- name: "Gunshot / Explosion",
13603
- icon: "💣"
13590
+ name: "Gunshot / Explosion"
13604
13591
  },
13605
13592
  {
13606
13593
  id: "vehicle",
13607
- name: "Vehicle",
13608
- icon: "🚗"
13594
+ name: "Vehicle"
13609
13595
  },
13610
13596
  {
13611
13597
  id: "siren",
13612
- name: "Emergency Siren",
13613
- icon: "🚑"
13598
+ name: "Emergency Siren"
13614
13599
  },
13615
13600
  {
13616
13601
  id: "fire",
13617
- name: "Fire / Smoke",
13618
- icon: "🔥"
13602
+ name: "Fire / Smoke"
13619
13603
  },
13620
13604
  {
13621
13605
  id: "water",
13622
- name: "Water",
13623
- icon: "💧"
13606
+ name: "Water"
13624
13607
  },
13625
13608
  {
13626
13609
  id: "wind",
13627
- name: "Wind / Weather",
13628
- icon: "🌬️"
13610
+ name: "Wind / Weather"
13629
13611
  },
13630
13612
  {
13631
13613
  id: "door",
13632
- name: "Door",
13633
- icon: "🚪"
13614
+ name: "Door"
13634
13615
  },
13635
13616
  {
13636
13617
  id: "footsteps",
13637
- name: "Footsteps",
13638
- icon: "👣"
13618
+ name: "Footsteps"
13639
13619
  },
13640
13620
  {
13641
13621
  id: "crowd",
13642
- name: "Crowd / Chatter",
13643
- icon: "👥"
13622
+ name: "Crowd / Chatter"
13644
13623
  },
13645
13624
  {
13646
13625
  id: "telephone",
13647
- name: "Telephone",
13648
- icon: "📞"
13626
+ name: "Telephone"
13649
13627
  },
13650
13628
  {
13651
13629
  id: "engine",
13652
- name: "Engine / Motor",
13653
- icon: "⚙️"
13630
+ name: "Engine / Motor"
13654
13631
  },
13655
13632
  {
13656
13633
  id: "tools",
13657
- name: "Tools / Construction",
13658
- icon: "🔨"
13634
+ name: "Tools / Construction"
13659
13635
  },
13660
13636
  {
13661
13637
  id: "silence",
13662
- name: "Silence",
13663
- icon: "🤫"
13638
+ name: "Silence"
13664
13639
  }
13665
13640
  ];
13666
13641
  var YAMNET_TO_MACRO = {
@@ -16124,7 +16099,7 @@ var TrackEnvelopeSchema = object({
16124
16099
  * `snapshots[]` references — megabytes across a page of tracks. `slim`
16125
16100
  * keeps every scalar the list surfaces actually render (ids, class(es),
16126
16101
  * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
16127
- * zonesVisited, bestEventId, envelope, hasFace, hasRider) and returns `positions` /
16102
+ * zonesVisited, bestEventId, envelope, hasFace, hasEmbeddedFace, hasRider) and returns `positions` /
16128
16103
  * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
16129
16104
  * `getTrack`. Mirrors the event-store `projection` convention
16130
16105
  * (`getObjectEvents` et al.).
@@ -16419,6 +16394,35 @@ var TrackSchema = object({
16419
16394
  */
16420
16395
  hasFace: boolean().optional(),
16421
16396
  /**
16397
+ * This track has a face row IN THE GALLERY: a crop **and** an embedding — a
16398
+ * face an operator could ASSIGN to an identity.
16399
+ *
16400
+ * The STRICT twin of {@link hasFace}, and the pair only earns its keep
16401
+ * because the two disagree. `hasFace` is stamped at the TOP of the face
16402
+ * branch, before every gate, and means no more than "a face detector produced
16403
+ * a face detail". This one is stamped at the single moment the gallery row
16404
+ * LANDS — after `FaceRecognizer.onTrackEnd` successfully persists it, i.e.
16405
+ * past the embedding-magnitude verdict, the `minFacePx` detection gate, the
16406
+ * candidate gate, the imageless-track drop (no crop was ever captured) and
16407
+ * the crop-store drop. Everything between the detector and that insert can
16408
+ * legitimately refuse the face, so a flag written any earlier promises the
16409
+ * operator something to assign and delivers nothing.
16410
+ *
16411
+ * **Independent of recognition.** A face collected but never auto-matched is
16412
+ * still assignable — it is in fact the face an operator most wants to reach —
16413
+ * so this is NOT gated on `recognizedIdentityId`. Recognition lands in
16414
+ * `subLabel`; this says only that the raw material exists.
16415
+ *
16416
+ * **Set once, never cleared.** A track that produced a gallery row produced
16417
+ * one; deleting the row later is the gallery's business, not this flag's.
16418
+ *
16419
+ * **Absent ≠ false**, the same rule as {@link hasFace}: every row written
16420
+ * before the column omits it, and so does every server that predates the
16421
+ * field. A consumer must test `=== true` and render nothing otherwise —
16422
+ * never infer "no assignable face".
16423
+ */
16424
+ hasEmbeddedFace: boolean().optional(),
16425
+ /**
16422
16426
  * This subject CONTAINS a folded rider — a person the rider-pairing step
16423
16427
  * ([D34](../decisions/adr-0034.md)) removed from the frame BEFORE the tracker,
16424
16428
  * so the passage is tracked once and as a VEHICLE.
@@ -17768,6 +17772,32 @@ var DetailResultSchema = object({
17768
17772
  /** FRAME-space bbox (already mapped back from crop space). */
17769
17773
  bbox: NativeCropBboxSchema.optional(),
17770
17774
  embedding: string().optional(),
17775
+ /**
17776
+ * L2 magnitude of the RAW pre-normalization embedding. Additive and optional
17777
+ * so an older runner that does not send it degrades to "unmeasurable", which
17778
+ * the consuming gate treats as accept — never as reject.
17779
+ */
17780
+ embeddingMagnitude: number().optional(),
17781
+ /**
17782
+ * Model that produced `embedding` — the SAME contract `labelModelId` has for
17783
+ * `label`, and for the same reason: the producing node is the only side that
17784
+ * knows which model actually ran, and re-deriving it downstream is exactly
17785
+ * the inference this model exists to forbid.
17786
+ *
17787
+ * Additive and optional, so a runner that predates it degrades to
17788
+ * "unmeasurable" rather than to a wrong answer — the consumer falls back to
17789
+ * its previous assumption instead of refusing.
17790
+ *
17791
+ * Its absence was not cosmetic. `FaceRecognizer` needs the probe's model id
17792
+ * to use `face-matcher`'s same-model gate (`sample.modelId !== probe.modelId`);
17793
+ * with nothing on the wire it took the id from the GALLERY, so the gate
17794
+ * compared the gallery against itself and could never fire. That turned the
17795
+ * one guard protecting the face feature space into a tautology, and it is
17796
+ * why swapping `face-embedding`'s model would NOT have stranded the enrolled
17797
+ * gallery as the catalog comment predicts — it would have scored the new
17798
+ * model's vectors against the old model's samples in one cosine space.
17799
+ */
17800
+ embeddingModelId: string().optional(),
17771
17801
  label: string().optional(),
17772
17802
  /**
17773
17803
  * The tier `label` occupies, copied VERBATIM from the producing step's
@@ -19178,6 +19208,7 @@ DeviceType.Camera, method(object({
19178
19208
  isBattery: boolean(),
19179
19209
  reason: _enum([
19180
19210
  "disabled",
19211
+ "offline",
19181
19212
  "sleeping",
19182
19213
  "unreachable",
19183
19214
  "waking"
@@ -19223,9 +19254,17 @@ targets: array(object({
19223
19254
  sleeping: boolean(),
19224
19255
  /** Current device state rendered over the cached frame. State images
19225
19256
  * remain authoritative even when their photographic background is
19226
- * old; null means the link must carry a current camera frame. */
19257
+ * old; null means the link must carry a current camera frame.
19258
+ *
19259
+ * **This is the ONLY source a surface may derive an inactive tile
19260
+ * from.** A client that reads a device's `online`/`disabled` flags and
19261
+ * draws its own verdict will disagree with the pixels it is painting
19262
+ * — the frame comes from here, and so must the sentence over it
19263
+ * (D190). Pair with `ageMs` for the age of the photograph behind the
19264
+ * state. */
19227
19265
  stateReason: _enum([
19228
19266
  "disabled",
19267
+ "offline",
19229
19268
  "sleeping",
19230
19269
  "unreachable",
19231
19270
  "waking"
@@ -24317,9 +24356,21 @@ var RecordingLocationUsageSchema = object({
24317
24356
  * previously rendered as two identical "disks" with a nonsensical used
24318
24357
  * split — hydrate attribution across aliases is arbitrary by nature. */
24319
24358
  locationIds: array(string()).optional(),
24320
- /** Bytes of recordings stored on this PHYSICAL volume (all aliases). */
24359
+ /**
24360
+ * Filesystem root of the physical volume this row accounts — the only
24361
+ * operator-legible identity when a location has no `displayName` yet, and
24362
+ * what a "which disk is full" alert has to name. OPTIONAL for the same
24363
+ * train-skew reason as `oldestMs`: a recorder that predates the field omits
24364
+ * it, and this schema must keep validating that older provider's payload.
24365
+ */
24366
+ root: string().optional(),
24367
+ /** Bytes of recordings stored on this PHYSICAL volume (all aliases). This is
24368
+ * the footage's SHARE of the volume, not how full the volume is — the disk
24369
+ * also holds everything that is not recordings. Volume fill is
24370
+ * `(totalBytes - availableBytes) / totalBytes`. */
24321
24371
  usedBytes: number(),
24322
- /** Free bytes on the location's volume; null when capacity is unknown (remote). */
24372
+ /** Free bytes on the location's volume; null when capacity is unknown
24373
+ * (remote/unstattable — the recorder logs the statfs failure with the root). */
24323
24374
  availableBytes: number().nullable(),
24324
24375
  /** Total bytes of the location's volume; null when unknown. */
24325
24376
  totalBytes: number().nullable()
@@ -24331,7 +24382,15 @@ var RecordingStorageUsageSchema = object({
24331
24382
  nodeId: string(),
24332
24383
  totalUsedBytes: number(),
24333
24384
  devices: array(RecordingDeviceUsageSchema),
24334
- locations: array(RecordingLocationUsageSchema)
24385
+ /**
24386
+ * Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
24387
+ * of the payload without it: the hub serves this method through a BAKED
24388
+ * router, so a hub whose framework train predates a change to this array
24389
+ * strips it out of an answer a newer recorder happily produced. A UI that
24390
+ * dereferences it unconditionally crashes the whole Storage page over a
24391
+ * missing disk bar.
24392
+ */
24393
+ locations: array(RecordingLocationUsageSchema).optional()
24335
24394
  });
24336
24395
  /**
24337
24396
  * The OPERATOR-ARMED half of multi-location recordings (D116).
@@ -33447,8 +33506,13 @@ Object.freeze({
33447
33506
  "network-access": "ingress",
33448
33507
  "smtp-provider": "email"
33449
33508
  });
33450
- new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
33509
+ /** Schema defaults an untouched sub-field must author exactly these. */
33510
+ var NC_AUDIO_DEFAULTS = {
33511
+ hitPercent: 60,
33512
+ samplingSeconds: 10
33513
+ };
33451
33514
  new Set(["devices", "classes"]);
33515
+ NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
33452
33516
  /**
33453
33517
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
33454
33518
  *
@@ -33680,6 +33744,39 @@ TimelapseRuleInputSchema.extend({
33680
33744
  createdAt: number(),
33681
33745
  updatedAt: number()
33682
33746
  });
33747
+ Object.freeze(Object.fromEntries([{
33748
+ stepId: "face-embedding",
33749
+ label: "Face recognition model",
33750
+ defaultModelId: "arcface-r100",
33751
+ indexName: "the enrolled face gallery",
33752
+ options: [
33753
+ {
33754
+ id: "arcface-r100",
33755
+ label: "ArcFace (ResNet34)"
33756
+ },
33757
+ {
33758
+ id: "auraface-r100",
33759
+ label: "AuraFace R100"
33760
+ },
33761
+ {
33762
+ id: "inception-resnet-v1",
33763
+ label: "Inception ResNet V1"
33764
+ }
33765
+ ]
33766
+ }, {
33767
+ stepId: "clip-embedding",
33768
+ label: "Semantic search model",
33769
+ defaultModelId: "mobileclip-s1",
33770
+ indexName: "the object semantic-search index",
33771
+ options: [{
33772
+ id: "mobileclip-s1",
33773
+ label: "MobileCLIP S1"
33774
+ }, {
33775
+ id: "mobileclip-s2",
33776
+ label: "MobileCLIP S2"
33777
+ }]
33778
+ }].map((s) => [s.stepId, s.defaultModelId])));
33779
+ string().min(1);
33683
33780
  object({
33684
33781
  /**
33685
33782
  * Fraction of the box's own size added on EACH side before cutting.
@@ -33700,10 +33797,21 @@ object({
33700
33797
  */
33701
33798
  square: boolean()
33702
33799
  });
33703
- ({
33800
+ /**
33801
+ * The convention in force when nobody has configured one — byte-for-byte the
33802
+ * behaviour of the pre-unification LIVE path (`DETAIL_CROP_PADDING_RATIO`).
33803
+ */
33804
+ var DEFAULT_DETAIL_CROP_CONVENTION = {
33704
33805
  paddingRatio: .15,
33705
33806
  square: false
33706
- }).paddingRatio;
33807
+ };
33808
+ Object.freeze({
33809
+ x: 0,
33810
+ y: 0,
33811
+ w: 1,
33812
+ h: 1
33813
+ });
33814
+ DEFAULT_DETAIL_CROP_CONVENTION.paddingRatio;
33707
33815
  /**
33708
33816
  * WHICH delivered frames the decode worker retains a native copy of.
33709
33817
  *
@@ -7583,7 +7583,7 @@ var RecordingBandSchema = object({
7583
7583
  postBufferSec: number().min(0).optional()
7584
7584
  });
7585
7585
  ({
7586
- preBufferSec: 10,
7586
+ preBufferSec: 15,
7587
7587
  postBufferSec: 30
7588
7588
  }).postBufferSec * 1e3;
7589
7589
  /**
@@ -13539,128 +13539,103 @@ var COCO_TO_MACRO = {
13539
13539
  var AUDIO_MACRO_LABELS = [
13540
13540
  {
13541
13541
  id: "speech",
13542
- name: "Speech",
13543
- icon: "🗣️"
13542
+ name: "Speech"
13544
13543
  },
13545
13544
  {
13546
13545
  id: "scream",
13547
- name: "Scream / Shout",
13548
- icon: "😱"
13546
+ name: "Scream / Shout"
13549
13547
  },
13550
13548
  {
13551
13549
  id: "crying",
13552
- name: "Crying / Baby",
13553
- icon: "😢"
13550
+ name: "Crying / Baby"
13554
13551
  },
13555
13552
  {
13556
13553
  id: "laughter",
13557
- name: "Laughter",
13558
- icon: "😂"
13554
+ name: "Laughter"
13559
13555
  },
13560
13556
  {
13561
13557
  id: "music",
13562
- name: "Music",
13563
- icon: "🎵"
13558
+ name: "Music"
13564
13559
  },
13565
13560
  {
13566
13561
  id: "dog",
13567
- name: "Dog",
13568
- icon: "🐕"
13562
+ name: "Dog"
13569
13563
  },
13570
13564
  {
13571
13565
  id: "cat",
13572
- name: "Cat",
13573
- icon: "🐈"
13566
+ name: "Cat"
13574
13567
  },
13575
13568
  {
13576
13569
  id: "bird",
13577
- name: "Bird",
13578
- icon: "🐦"
13570
+ name: "Bird"
13579
13571
  },
13580
13572
  {
13581
13573
  id: "animal",
13582
- name: "Animal (other)",
13583
- icon: "🐾"
13574
+ name: "Animal (other)"
13584
13575
  },
13585
13576
  {
13586
13577
  id: "alarm",
13587
- name: "Alarm / Siren",
13588
- icon: "🚨"
13578
+ name: "Alarm / Siren"
13589
13579
  },
13590
13580
  {
13591
13581
  id: "doorbell",
13592
- name: "Doorbell / Knock",
13593
- icon: "🔔"
13582
+ name: "Doorbell / Knock"
13594
13583
  },
13595
13584
  {
13596
13585
  id: "glass_breaking",
13597
- name: "Glass Breaking",
13598
- icon: "💥"
13586
+ name: "Glass Breaking"
13599
13587
  },
13600
13588
  {
13601
13589
  id: "gunshot",
13602
- name: "Gunshot / Explosion",
13603
- icon: "💣"
13590
+ name: "Gunshot / Explosion"
13604
13591
  },
13605
13592
  {
13606
13593
  id: "vehicle",
13607
- name: "Vehicle",
13608
- icon: "🚗"
13594
+ name: "Vehicle"
13609
13595
  },
13610
13596
  {
13611
13597
  id: "siren",
13612
- name: "Emergency Siren",
13613
- icon: "🚑"
13598
+ name: "Emergency Siren"
13614
13599
  },
13615
13600
  {
13616
13601
  id: "fire",
13617
- name: "Fire / Smoke",
13618
- icon: "🔥"
13602
+ name: "Fire / Smoke"
13619
13603
  },
13620
13604
  {
13621
13605
  id: "water",
13622
- name: "Water",
13623
- icon: "💧"
13606
+ name: "Water"
13624
13607
  },
13625
13608
  {
13626
13609
  id: "wind",
13627
- name: "Wind / Weather",
13628
- icon: "🌬️"
13610
+ name: "Wind / Weather"
13629
13611
  },
13630
13612
  {
13631
13613
  id: "door",
13632
- name: "Door",
13633
- icon: "🚪"
13614
+ name: "Door"
13634
13615
  },
13635
13616
  {
13636
13617
  id: "footsteps",
13637
- name: "Footsteps",
13638
- icon: "👣"
13618
+ name: "Footsteps"
13639
13619
  },
13640
13620
  {
13641
13621
  id: "crowd",
13642
- name: "Crowd / Chatter",
13643
- icon: "👥"
13622
+ name: "Crowd / Chatter"
13644
13623
  },
13645
13624
  {
13646
13625
  id: "telephone",
13647
- name: "Telephone",
13648
- icon: "📞"
13626
+ name: "Telephone"
13649
13627
  },
13650
13628
  {
13651
13629
  id: "engine",
13652
- name: "Engine / Motor",
13653
- icon: "⚙️"
13630
+ name: "Engine / Motor"
13654
13631
  },
13655
13632
  {
13656
13633
  id: "tools",
13657
- name: "Tools / Construction",
13658
- icon: "🔨"
13634
+ name: "Tools / Construction"
13659
13635
  },
13660
13636
  {
13661
13637
  id: "silence",
13662
- name: "Silence",
13663
- icon: "🤫"
13638
+ name: "Silence"
13664
13639
  }
13665
13640
  ];
13666
13641
  var YAMNET_TO_MACRO = {
@@ -16124,7 +16099,7 @@ var TrackEnvelopeSchema = object({
16124
16099
  * `snapshots[]` references — megabytes across a page of tracks. `slim`
16125
16100
  * keeps every scalar the list surfaces actually render (ids, class(es),
16126
16101
  * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
16127
- * zonesVisited, bestEventId, envelope, hasFace, hasRider) and returns `positions` /
16102
+ * zonesVisited, bestEventId, envelope, hasFace, hasEmbeddedFace, hasRider) and returns `positions` /
16128
16103
  * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
16129
16104
  * `getTrack`. Mirrors the event-store `projection` convention
16130
16105
  * (`getObjectEvents` et al.).
@@ -16419,6 +16394,35 @@ var TrackSchema = object({
16419
16394
  */
16420
16395
  hasFace: boolean().optional(),
16421
16396
  /**
16397
+ * This track has a face row IN THE GALLERY: a crop **and** an embedding — a
16398
+ * face an operator could ASSIGN to an identity.
16399
+ *
16400
+ * The STRICT twin of {@link hasFace}, and the pair only earns its keep
16401
+ * because the two disagree. `hasFace` is stamped at the TOP of the face
16402
+ * branch, before every gate, and means no more than "a face detector produced
16403
+ * a face detail". This one is stamped at the single moment the gallery row
16404
+ * LANDS — after `FaceRecognizer.onTrackEnd` successfully persists it, i.e.
16405
+ * past the embedding-magnitude verdict, the `minFacePx` detection gate, the
16406
+ * candidate gate, the imageless-track drop (no crop was ever captured) and
16407
+ * the crop-store drop. Everything between the detector and that insert can
16408
+ * legitimately refuse the face, so a flag written any earlier promises the
16409
+ * operator something to assign and delivers nothing.
16410
+ *
16411
+ * **Independent of recognition.** A face collected but never auto-matched is
16412
+ * still assignable — it is in fact the face an operator most wants to reach —
16413
+ * so this is NOT gated on `recognizedIdentityId`. Recognition lands in
16414
+ * `subLabel`; this says only that the raw material exists.
16415
+ *
16416
+ * **Set once, never cleared.** A track that produced a gallery row produced
16417
+ * one; deleting the row later is the gallery's business, not this flag's.
16418
+ *
16419
+ * **Absent ≠ false**, the same rule as {@link hasFace}: every row written
16420
+ * before the column omits it, and so does every server that predates the
16421
+ * field. A consumer must test `=== true` and render nothing otherwise —
16422
+ * never infer "no assignable face".
16423
+ */
16424
+ hasEmbeddedFace: boolean().optional(),
16425
+ /**
16422
16426
  * This subject CONTAINS a folded rider — a person the rider-pairing step
16423
16427
  * ([D34](../decisions/adr-0034.md)) removed from the frame BEFORE the tracker,
16424
16428
  * so the passage is tracked once and as a VEHICLE.
@@ -17768,6 +17772,32 @@ var DetailResultSchema = object({
17768
17772
  /** FRAME-space bbox (already mapped back from crop space). */
17769
17773
  bbox: NativeCropBboxSchema.optional(),
17770
17774
  embedding: string().optional(),
17775
+ /**
17776
+ * L2 magnitude of the RAW pre-normalization embedding. Additive and optional
17777
+ * so an older runner that does not send it degrades to "unmeasurable", which
17778
+ * the consuming gate treats as accept — never as reject.
17779
+ */
17780
+ embeddingMagnitude: number().optional(),
17781
+ /**
17782
+ * Model that produced `embedding` — the SAME contract `labelModelId` has for
17783
+ * `label`, and for the same reason: the producing node is the only side that
17784
+ * knows which model actually ran, and re-deriving it downstream is exactly
17785
+ * the inference this model exists to forbid.
17786
+ *
17787
+ * Additive and optional, so a runner that predates it degrades to
17788
+ * "unmeasurable" rather than to a wrong answer — the consumer falls back to
17789
+ * its previous assumption instead of refusing.
17790
+ *
17791
+ * Its absence was not cosmetic. `FaceRecognizer` needs the probe's model id
17792
+ * to use `face-matcher`'s same-model gate (`sample.modelId !== probe.modelId`);
17793
+ * with nothing on the wire it took the id from the GALLERY, so the gate
17794
+ * compared the gallery against itself and could never fire. That turned the
17795
+ * one guard protecting the face feature space into a tautology, and it is
17796
+ * why swapping `face-embedding`'s model would NOT have stranded the enrolled
17797
+ * gallery as the catalog comment predicts — it would have scored the new
17798
+ * model's vectors against the old model's samples in one cosine space.
17799
+ */
17800
+ embeddingModelId: string().optional(),
17771
17801
  label: string().optional(),
17772
17802
  /**
17773
17803
  * The tier `label` occupies, copied VERBATIM from the producing step's
@@ -19178,6 +19208,7 @@ DeviceType.Camera, method(object({
19178
19208
  isBattery: boolean(),
19179
19209
  reason: _enum([
19180
19210
  "disabled",
19211
+ "offline",
19181
19212
  "sleeping",
19182
19213
  "unreachable",
19183
19214
  "waking"
@@ -19223,9 +19254,17 @@ targets: array(object({
19223
19254
  sleeping: boolean(),
19224
19255
  /** Current device state rendered over the cached frame. State images
19225
19256
  * remain authoritative even when their photographic background is
19226
- * old; null means the link must carry a current camera frame. */
19257
+ * old; null means the link must carry a current camera frame.
19258
+ *
19259
+ * **This is the ONLY source a surface may derive an inactive tile
19260
+ * from.** A client that reads a device's `online`/`disabled` flags and
19261
+ * draws its own verdict will disagree with the pixels it is painting
19262
+ * — the frame comes from here, and so must the sentence over it
19263
+ * (D190). Pair with `ageMs` for the age of the photograph behind the
19264
+ * state. */
19227
19265
  stateReason: _enum([
19228
19266
  "disabled",
19267
+ "offline",
19229
19268
  "sleeping",
19230
19269
  "unreachable",
19231
19270
  "waking"
@@ -24317,9 +24356,21 @@ var RecordingLocationUsageSchema = object({
24317
24356
  * previously rendered as two identical "disks" with a nonsensical used
24318
24357
  * split — hydrate attribution across aliases is arbitrary by nature. */
24319
24358
  locationIds: array(string()).optional(),
24320
- /** Bytes of recordings stored on this PHYSICAL volume (all aliases). */
24359
+ /**
24360
+ * Filesystem root of the physical volume this row accounts — the only
24361
+ * operator-legible identity when a location has no `displayName` yet, and
24362
+ * what a "which disk is full" alert has to name. OPTIONAL for the same
24363
+ * train-skew reason as `oldestMs`: a recorder that predates the field omits
24364
+ * it, and this schema must keep validating that older provider's payload.
24365
+ */
24366
+ root: string().optional(),
24367
+ /** Bytes of recordings stored on this PHYSICAL volume (all aliases). This is
24368
+ * the footage's SHARE of the volume, not how full the volume is — the disk
24369
+ * also holds everything that is not recordings. Volume fill is
24370
+ * `(totalBytes - availableBytes) / totalBytes`. */
24321
24371
  usedBytes: number(),
24322
- /** Free bytes on the location's volume; null when capacity is unknown (remote). */
24372
+ /** Free bytes on the location's volume; null when capacity is unknown
24373
+ * (remote/unstattable — the recorder logs the statfs failure with the root). */
24323
24374
  availableBytes: number().nullable(),
24324
24375
  /** Total bytes of the location's volume; null when unknown. */
24325
24376
  totalBytes: number().nullable()
@@ -24331,7 +24382,15 @@ var RecordingStorageUsageSchema = object({
24331
24382
  nodeId: string(),
24332
24383
  totalUsedBytes: number(),
24333
24384
  devices: array(RecordingDeviceUsageSchema),
24334
- locations: array(RecordingLocationUsageSchema)
24385
+ /**
24386
+ * Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
24387
+ * of the payload without it: the hub serves this method through a BAKED
24388
+ * router, so a hub whose framework train predates a change to this array
24389
+ * strips it out of an answer a newer recorder happily produced. A UI that
24390
+ * dereferences it unconditionally crashes the whole Storage page over a
24391
+ * missing disk bar.
24392
+ */
24393
+ locations: array(RecordingLocationUsageSchema).optional()
24335
24394
  });
24336
24395
  /**
24337
24396
  * The OPERATOR-ARMED half of multi-location recordings (D116).
@@ -33447,8 +33506,13 @@ Object.freeze({
33447
33506
  "network-access": "ingress",
33448
33507
  "smtp-provider": "email"
33449
33508
  });
33450
- new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
33509
+ /** Schema defaults an untouched sub-field must author exactly these. */
33510
+ var NC_AUDIO_DEFAULTS = {
33511
+ hitPercent: 60,
33512
+ samplingSeconds: 10
33513
+ };
33451
33514
  new Set(["devices", "classes"]);
33515
+ NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
33452
33516
  /**
33453
33517
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
33454
33518
  *
@@ -33680,6 +33744,39 @@ TimelapseRuleInputSchema.extend({
33680
33744
  createdAt: number(),
33681
33745
  updatedAt: number()
33682
33746
  });
33747
+ Object.freeze(Object.fromEntries([{
33748
+ stepId: "face-embedding",
33749
+ label: "Face recognition model",
33750
+ defaultModelId: "arcface-r100",
33751
+ indexName: "the enrolled face gallery",
33752
+ options: [
33753
+ {
33754
+ id: "arcface-r100",
33755
+ label: "ArcFace (ResNet34)"
33756
+ },
33757
+ {
33758
+ id: "auraface-r100",
33759
+ label: "AuraFace R100"
33760
+ },
33761
+ {
33762
+ id: "inception-resnet-v1",
33763
+ label: "Inception ResNet V1"
33764
+ }
33765
+ ]
33766
+ }, {
33767
+ stepId: "clip-embedding",
33768
+ label: "Semantic search model",
33769
+ defaultModelId: "mobileclip-s1",
33770
+ indexName: "the object semantic-search index",
33771
+ options: [{
33772
+ id: "mobileclip-s1",
33773
+ label: "MobileCLIP S1"
33774
+ }, {
33775
+ id: "mobileclip-s2",
33776
+ label: "MobileCLIP S2"
33777
+ }]
33778
+ }].map((s) => [s.stepId, s.defaultModelId])));
33779
+ string().min(1);
33683
33780
  object({
33684
33781
  /**
33685
33782
  * Fraction of the box's own size added on EACH side before cutting.
@@ -33700,10 +33797,21 @@ object({
33700
33797
  */
33701
33798
  square: boolean()
33702
33799
  });
33703
- ({
33800
+ /**
33801
+ * The convention in force when nobody has configured one — byte-for-byte the
33802
+ * behaviour of the pre-unification LIVE path (`DETAIL_CROP_PADDING_RATIO`).
33803
+ */
33804
+ var DEFAULT_DETAIL_CROP_CONVENTION = {
33704
33805
  paddingRatio: .15,
33705
33806
  square: false
33706
- }).paddingRatio;
33807
+ };
33808
+ Object.freeze({
33809
+ x: 0,
33810
+ y: 0,
33811
+ w: 1,
33812
+ h: 1
33813
+ });
33814
+ DEFAULT_DETAIL_CROP_CONVENTION.paddingRatio;
33707
33815
  /**
33708
33816
  * WHICH delivered frames the decode worker retains a native copy of.
33709
33817
  *
@@ -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-B_l6CtRR.js");
5
+ const require_dist = require("../dist-Of54y7eC.js");
6
6
  let node_crypto = require("node:crypto");
7
7
  let node_child_process = require("node:child_process");
8
8
  //#region src/ingress/tailscale-cli.ts
@@ -1,4 +1,4 @@
1
- import { a as EventCategory, i as BaseAddon, n as networkAccessCapability } from "../dist-LUO_d3xF.mjs";
1
+ import { a as EventCategory, i as BaseAddon, n as networkAccessCapability } from "../dist-9AnjWRPW.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { execFile } from "node:child_process";
4
4
  import { promisify } from "node:util";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-tailscale",
3
- "version": "1.2.22",
3
+ "version": "1.2.24",
4
4
  "description": "Tailscale bundle for CamStack — joins the host to a tailnet (client) and exposes the hub via Serve/Funnel (ingress). Multi-entry npm package shipping 2 addons under a single bundle.",
5
5
  "keywords": [
6
6
  "camstack",