@camstack/addon-terminal 0.1.25 → 0.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/addon.js +167 -59
  2. package/dist/addon.mjs +167 -59
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -7665,7 +7665,7 @@ var RecordingBandSchema = object({
7665
7665
  postBufferSec: number().min(0).optional()
7666
7666
  });
7667
7667
  ({
7668
- preBufferSec: 10,
7668
+ preBufferSec: 15,
7669
7669
  postBufferSec: 30
7670
7670
  }).postBufferSec * 1e3;
7671
7671
  /**
@@ -13813,128 +13813,103 @@ var COCO_TO_MACRO = {
13813
13813
  var AUDIO_MACRO_LABELS = [
13814
13814
  {
13815
13815
  id: "speech",
13816
- name: "Speech",
13817
- icon: "🗣️"
13816
+ name: "Speech"
13818
13817
  },
13819
13818
  {
13820
13819
  id: "scream",
13821
- name: "Scream / Shout",
13822
- icon: "😱"
13820
+ name: "Scream / Shout"
13823
13821
  },
13824
13822
  {
13825
13823
  id: "crying",
13826
- name: "Crying / Baby",
13827
- icon: "😢"
13824
+ name: "Crying / Baby"
13828
13825
  },
13829
13826
  {
13830
13827
  id: "laughter",
13831
- name: "Laughter",
13832
- icon: "😂"
13828
+ name: "Laughter"
13833
13829
  },
13834
13830
  {
13835
13831
  id: "music",
13836
- name: "Music",
13837
- icon: "🎵"
13832
+ name: "Music"
13838
13833
  },
13839
13834
  {
13840
13835
  id: "dog",
13841
- name: "Dog",
13842
- icon: "🐕"
13836
+ name: "Dog"
13843
13837
  },
13844
13838
  {
13845
13839
  id: "cat",
13846
- name: "Cat",
13847
- icon: "🐈"
13840
+ name: "Cat"
13848
13841
  },
13849
13842
  {
13850
13843
  id: "bird",
13851
- name: "Bird",
13852
- icon: "🐦"
13844
+ name: "Bird"
13853
13845
  },
13854
13846
  {
13855
13847
  id: "animal",
13856
- name: "Animal (other)",
13857
- icon: "🐾"
13848
+ name: "Animal (other)"
13858
13849
  },
13859
13850
  {
13860
13851
  id: "alarm",
13861
- name: "Alarm / Siren",
13862
- icon: "🚨"
13852
+ name: "Alarm / Siren"
13863
13853
  },
13864
13854
  {
13865
13855
  id: "doorbell",
13866
- name: "Doorbell / Knock",
13867
- icon: "🔔"
13856
+ name: "Doorbell / Knock"
13868
13857
  },
13869
13858
  {
13870
13859
  id: "glass_breaking",
13871
- name: "Glass Breaking",
13872
- icon: "💥"
13860
+ name: "Glass Breaking"
13873
13861
  },
13874
13862
  {
13875
13863
  id: "gunshot",
13876
- name: "Gunshot / Explosion",
13877
- icon: "💣"
13864
+ name: "Gunshot / Explosion"
13878
13865
  },
13879
13866
  {
13880
13867
  id: "vehicle",
13881
- name: "Vehicle",
13882
- icon: "🚗"
13868
+ name: "Vehicle"
13883
13869
  },
13884
13870
  {
13885
13871
  id: "siren",
13886
- name: "Emergency Siren",
13887
- icon: "🚑"
13872
+ name: "Emergency Siren"
13888
13873
  },
13889
13874
  {
13890
13875
  id: "fire",
13891
- name: "Fire / Smoke",
13892
- icon: "🔥"
13876
+ name: "Fire / Smoke"
13893
13877
  },
13894
13878
  {
13895
13879
  id: "water",
13896
- name: "Water",
13897
- icon: "💧"
13880
+ name: "Water"
13898
13881
  },
13899
13882
  {
13900
13883
  id: "wind",
13901
- name: "Wind / Weather",
13902
- icon: "🌬️"
13884
+ name: "Wind / Weather"
13903
13885
  },
13904
13886
  {
13905
13887
  id: "door",
13906
- name: "Door",
13907
- icon: "🚪"
13888
+ name: "Door"
13908
13889
  },
13909
13890
  {
13910
13891
  id: "footsteps",
13911
- name: "Footsteps",
13912
- icon: "👣"
13892
+ name: "Footsteps"
13913
13893
  },
13914
13894
  {
13915
13895
  id: "crowd",
13916
- name: "Crowd / Chatter",
13917
- icon: "👥"
13896
+ name: "Crowd / Chatter"
13918
13897
  },
13919
13898
  {
13920
13899
  id: "telephone",
13921
- name: "Telephone",
13922
- icon: "📞"
13900
+ name: "Telephone"
13923
13901
  },
13924
13902
  {
13925
13903
  id: "engine",
13926
- name: "Engine / Motor",
13927
- icon: "⚙️"
13904
+ name: "Engine / Motor"
13928
13905
  },
13929
13906
  {
13930
13907
  id: "tools",
13931
- name: "Tools / Construction",
13932
- icon: "🔨"
13908
+ name: "Tools / Construction"
13933
13909
  },
13934
13910
  {
13935
13911
  id: "silence",
13936
- name: "Silence",
13937
- icon: "🤫"
13912
+ name: "Silence"
13938
13913
  }
13939
13914
  ];
13940
13915
  var YAMNET_TO_MACRO = {
@@ -16436,7 +16411,7 @@ var TrackEnvelopeSchema = object({
16436
16411
  * `snapshots[]` references — megabytes across a page of tracks. `slim`
16437
16412
  * keeps every scalar the list surfaces actually render (ids, class(es),
16438
16413
  * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
16439
- * zonesVisited, bestEventId, envelope, hasFace, hasRider) and returns `positions` /
16414
+ * zonesVisited, bestEventId, envelope, hasFace, hasEmbeddedFace, hasRider) and returns `positions` /
16440
16415
  * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
16441
16416
  * `getTrack`. Mirrors the event-store `projection` convention
16442
16417
  * (`getObjectEvents` et al.).
@@ -16731,6 +16706,35 @@ var TrackSchema = object({
16731
16706
  */
16732
16707
  hasFace: boolean().optional(),
16733
16708
  /**
16709
+ * This track has a face row IN THE GALLERY: a crop **and** an embedding — a
16710
+ * face an operator could ASSIGN to an identity.
16711
+ *
16712
+ * The STRICT twin of {@link hasFace}, and the pair only earns its keep
16713
+ * because the two disagree. `hasFace` is stamped at the TOP of the face
16714
+ * branch, before every gate, and means no more than "a face detector produced
16715
+ * a face detail". This one is stamped at the single moment the gallery row
16716
+ * LANDS — after `FaceRecognizer.onTrackEnd` successfully persists it, i.e.
16717
+ * past the embedding-magnitude verdict, the `minFacePx` detection gate, the
16718
+ * candidate gate, the imageless-track drop (no crop was ever captured) and
16719
+ * the crop-store drop. Everything between the detector and that insert can
16720
+ * legitimately refuse the face, so a flag written any earlier promises the
16721
+ * operator something to assign and delivers nothing.
16722
+ *
16723
+ * **Independent of recognition.** A face collected but never auto-matched is
16724
+ * still assignable — it is in fact the face an operator most wants to reach —
16725
+ * so this is NOT gated on `recognizedIdentityId`. Recognition lands in
16726
+ * `subLabel`; this says only that the raw material exists.
16727
+ *
16728
+ * **Set once, never cleared.** A track that produced a gallery row produced
16729
+ * one; deleting the row later is the gallery's business, not this flag's.
16730
+ *
16731
+ * **Absent ≠ false**, the same rule as {@link hasFace}: every row written
16732
+ * before the column omits it, and so does every server that predates the
16733
+ * field. A consumer must test `=== true` and render nothing otherwise —
16734
+ * never infer "no assignable face".
16735
+ */
16736
+ hasEmbeddedFace: boolean().optional(),
16737
+ /**
16734
16738
  * This subject CONTAINS a folded rider — a person the rider-pairing step
16735
16739
  * ([D34](../decisions/adr-0034.md)) removed from the frame BEFORE the tracker,
16736
16740
  * so the passage is tracked once and as a VEHICLE.
@@ -18120,6 +18124,32 @@ var DetailResultSchema = object({
18120
18124
  /** FRAME-space bbox (already mapped back from crop space). */
18121
18125
  bbox: NativeCropBboxSchema.optional(),
18122
18126
  embedding: string().optional(),
18127
+ /**
18128
+ * L2 magnitude of the RAW pre-normalization embedding. Additive and optional
18129
+ * so an older runner that does not send it degrades to "unmeasurable", which
18130
+ * the consuming gate treats as accept — never as reject.
18131
+ */
18132
+ embeddingMagnitude: number().optional(),
18133
+ /**
18134
+ * Model that produced `embedding` — the SAME contract `labelModelId` has for
18135
+ * `label`, and for the same reason: the producing node is the only side that
18136
+ * knows which model actually ran, and re-deriving it downstream is exactly
18137
+ * the inference this model exists to forbid.
18138
+ *
18139
+ * Additive and optional, so a runner that predates it degrades to
18140
+ * "unmeasurable" rather than to a wrong answer — the consumer falls back to
18141
+ * its previous assumption instead of refusing.
18142
+ *
18143
+ * Its absence was not cosmetic. `FaceRecognizer` needs the probe's model id
18144
+ * to use `face-matcher`'s same-model gate (`sample.modelId !== probe.modelId`);
18145
+ * with nothing on the wire it took the id from the GALLERY, so the gate
18146
+ * compared the gallery against itself and could never fire. That turned the
18147
+ * one guard protecting the face feature space into a tautology, and it is
18148
+ * why swapping `face-embedding`'s model would NOT have stranded the enrolled
18149
+ * gallery as the catalog comment predicts — it would have scored the new
18150
+ * model's vectors against the old model's samples in one cosine space.
18151
+ */
18152
+ embeddingModelId: string().optional(),
18123
18153
  label: string().optional(),
18124
18154
  /**
18125
18155
  * The tier `label` occupies, copied VERBATIM from the producing step's
@@ -19584,6 +19614,7 @@ var snapshotCapability = {
19584
19614
  isBattery: boolean(),
19585
19615
  reason: _enum([
19586
19616
  "disabled",
19617
+ "offline",
19587
19618
  "sleeping",
19588
19619
  "unreachable",
19589
19620
  "waking"
@@ -19673,9 +19704,17 @@ targets: array(object({
19673
19704
  sleeping: boolean(),
19674
19705
  /** Current device state rendered over the cached frame. State images
19675
19706
  * remain authoritative even when their photographic background is
19676
- * old; null means the link must carry a current camera frame. */
19707
+ * old; null means the link must carry a current camera frame.
19708
+ *
19709
+ * **This is the ONLY source a surface may derive an inactive tile
19710
+ * from.** A client that reads a device's `online`/`disabled` flags and
19711
+ * draws its own verdict will disagree with the pixels it is painting
19712
+ * — the frame comes from here, and so must the sentence over it
19713
+ * (D190). Pair with `ageMs` for the age of the photograph behind the
19714
+ * state. */
19677
19715
  stateReason: _enum([
19678
19716
  "disabled",
19717
+ "offline",
19679
19718
  "sleeping",
19680
19719
  "unreachable",
19681
19720
  "waking"
@@ -26443,9 +26482,21 @@ var RecordingLocationUsageSchema = object({
26443
26482
  * previously rendered as two identical "disks" with a nonsensical used
26444
26483
  * split — hydrate attribution across aliases is arbitrary by nature. */
26445
26484
  locationIds: array(string()).optional(),
26446
- /** Bytes of recordings stored on this PHYSICAL volume (all aliases). */
26485
+ /**
26486
+ * Filesystem root of the physical volume this row accounts — the only
26487
+ * operator-legible identity when a location has no `displayName` yet, and
26488
+ * what a "which disk is full" alert has to name. OPTIONAL for the same
26489
+ * train-skew reason as `oldestMs`: a recorder that predates the field omits
26490
+ * it, and this schema must keep validating that older provider's payload.
26491
+ */
26492
+ root: string().optional(),
26493
+ /** Bytes of recordings stored on this PHYSICAL volume (all aliases). This is
26494
+ * the footage's SHARE of the volume, not how full the volume is — the disk
26495
+ * also holds everything that is not recordings. Volume fill is
26496
+ * `(totalBytes - availableBytes) / totalBytes`. */
26447
26497
  usedBytes: number(),
26448
- /** Free bytes on the location's volume; null when capacity is unknown (remote). */
26498
+ /** Free bytes on the location's volume; null when capacity is unknown
26499
+ * (remote/unstattable — the recorder logs the statfs failure with the root). */
26449
26500
  availableBytes: number().nullable(),
26450
26501
  /** Total bytes of the location's volume; null when unknown. */
26451
26502
  totalBytes: number().nullable()
@@ -26457,7 +26508,15 @@ var RecordingStorageUsageSchema = object({
26457
26508
  nodeId: string(),
26458
26509
  totalUsedBytes: number(),
26459
26510
  devices: array(RecordingDeviceUsageSchema),
26460
- locations: array(RecordingLocationUsageSchema)
26511
+ /**
26512
+ * Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
26513
+ * of the payload without it: the hub serves this method through a BAKED
26514
+ * router, so a hub whose framework train predates a change to this array
26515
+ * strips it out of an answer a newer recorder happily produced. A UI that
26516
+ * dereferences it unconditionally crashes the whole Storage page over a
26517
+ * missing disk bar.
26518
+ */
26519
+ locations: array(RecordingLocationUsageSchema).optional()
26461
26520
  });
26462
26521
  /**
26463
26522
  * The OPERATOR-ARMED half of multi-location recordings (D116).
@@ -37240,8 +37299,13 @@ Object.freeze({
37240
37299
  "network-access": "ingress",
37241
37300
  "smtp-provider": "email"
37242
37301
  });
37243
- new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
37302
+ /** Schema defaults an untouched sub-field must author exactly these. */
37303
+ var NC_AUDIO_DEFAULTS = {
37304
+ hitPercent: 60,
37305
+ samplingSeconds: 10
37306
+ };
37244
37307
  new Set(["devices", "classes"]);
37308
+ NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
37245
37309
  /**
37246
37310
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
37247
37311
  *
@@ -37473,6 +37537,39 @@ TimelapseRuleInputSchema.extend({
37473
37537
  createdAt: number(),
37474
37538
  updatedAt: number()
37475
37539
  });
37540
+ Object.freeze(Object.fromEntries([{
37541
+ stepId: "face-embedding",
37542
+ label: "Face recognition model",
37543
+ defaultModelId: "arcface-r100",
37544
+ indexName: "the enrolled face gallery",
37545
+ options: [
37546
+ {
37547
+ id: "arcface-r100",
37548
+ label: "ArcFace (ResNet34)"
37549
+ },
37550
+ {
37551
+ id: "auraface-r100",
37552
+ label: "AuraFace R100"
37553
+ },
37554
+ {
37555
+ id: "inception-resnet-v1",
37556
+ label: "Inception ResNet V1"
37557
+ }
37558
+ ]
37559
+ }, {
37560
+ stepId: "clip-embedding",
37561
+ label: "Semantic search model",
37562
+ defaultModelId: "mobileclip-s1",
37563
+ indexName: "the object semantic-search index",
37564
+ options: [{
37565
+ id: "mobileclip-s1",
37566
+ label: "MobileCLIP S1"
37567
+ }, {
37568
+ id: "mobileclip-s2",
37569
+ label: "MobileCLIP S2"
37570
+ }]
37571
+ }].map((s) => [s.stepId, s.defaultModelId])));
37572
+ string().min(1);
37476
37573
  object({
37477
37574
  /**
37478
37575
  * Fraction of the box's own size added on EACH side before cutting.
@@ -37493,10 +37590,21 @@ object({
37493
37590
  */
37494
37591
  square: boolean()
37495
37592
  });
37496
- ({
37593
+ /**
37594
+ * The convention in force when nobody has configured one — byte-for-byte the
37595
+ * behaviour of the pre-unification LIVE path (`DETAIL_CROP_PADDING_RATIO`).
37596
+ */
37597
+ var DEFAULT_DETAIL_CROP_CONVENTION = {
37497
37598
  paddingRatio: .15,
37498
37599
  square: false
37499
- }).paddingRatio;
37600
+ };
37601
+ Object.freeze({
37602
+ x: 0,
37603
+ y: 0,
37604
+ w: 1,
37605
+ h: 1
37606
+ });
37607
+ DEFAULT_DETAIL_CROP_CONVENTION.paddingRatio;
37500
37608
  /**
37501
37609
  * WHICH delivered frames the decode worker retains a native copy of.
37502
37610
  *
package/dist/addon.mjs CHANGED
@@ -7642,7 +7642,7 @@ var RecordingBandSchema = object({
7642
7642
  postBufferSec: number().min(0).optional()
7643
7643
  });
7644
7644
  ({
7645
- preBufferSec: 10,
7645
+ preBufferSec: 15,
7646
7646
  postBufferSec: 30
7647
7647
  }).postBufferSec * 1e3;
7648
7648
  /**
@@ -13790,128 +13790,103 @@ var COCO_TO_MACRO = {
13790
13790
  var AUDIO_MACRO_LABELS = [
13791
13791
  {
13792
13792
  id: "speech",
13793
- name: "Speech",
13794
- icon: "🗣️"
13793
+ name: "Speech"
13795
13794
  },
13796
13795
  {
13797
13796
  id: "scream",
13798
- name: "Scream / Shout",
13799
- icon: "😱"
13797
+ name: "Scream / Shout"
13800
13798
  },
13801
13799
  {
13802
13800
  id: "crying",
13803
- name: "Crying / Baby",
13804
- icon: "😢"
13801
+ name: "Crying / Baby"
13805
13802
  },
13806
13803
  {
13807
13804
  id: "laughter",
13808
- name: "Laughter",
13809
- icon: "😂"
13805
+ name: "Laughter"
13810
13806
  },
13811
13807
  {
13812
13808
  id: "music",
13813
- name: "Music",
13814
- icon: "🎵"
13809
+ name: "Music"
13815
13810
  },
13816
13811
  {
13817
13812
  id: "dog",
13818
- name: "Dog",
13819
- icon: "🐕"
13813
+ name: "Dog"
13820
13814
  },
13821
13815
  {
13822
13816
  id: "cat",
13823
- name: "Cat",
13824
- icon: "🐈"
13817
+ name: "Cat"
13825
13818
  },
13826
13819
  {
13827
13820
  id: "bird",
13828
- name: "Bird",
13829
- icon: "🐦"
13821
+ name: "Bird"
13830
13822
  },
13831
13823
  {
13832
13824
  id: "animal",
13833
- name: "Animal (other)",
13834
- icon: "🐾"
13825
+ name: "Animal (other)"
13835
13826
  },
13836
13827
  {
13837
13828
  id: "alarm",
13838
- name: "Alarm / Siren",
13839
- icon: "🚨"
13829
+ name: "Alarm / Siren"
13840
13830
  },
13841
13831
  {
13842
13832
  id: "doorbell",
13843
- name: "Doorbell / Knock",
13844
- icon: "🔔"
13833
+ name: "Doorbell / Knock"
13845
13834
  },
13846
13835
  {
13847
13836
  id: "glass_breaking",
13848
- name: "Glass Breaking",
13849
- icon: "💥"
13837
+ name: "Glass Breaking"
13850
13838
  },
13851
13839
  {
13852
13840
  id: "gunshot",
13853
- name: "Gunshot / Explosion",
13854
- icon: "💣"
13841
+ name: "Gunshot / Explosion"
13855
13842
  },
13856
13843
  {
13857
13844
  id: "vehicle",
13858
- name: "Vehicle",
13859
- icon: "🚗"
13845
+ name: "Vehicle"
13860
13846
  },
13861
13847
  {
13862
13848
  id: "siren",
13863
- name: "Emergency Siren",
13864
- icon: "🚑"
13849
+ name: "Emergency Siren"
13865
13850
  },
13866
13851
  {
13867
13852
  id: "fire",
13868
- name: "Fire / Smoke",
13869
- icon: "🔥"
13853
+ name: "Fire / Smoke"
13870
13854
  },
13871
13855
  {
13872
13856
  id: "water",
13873
- name: "Water",
13874
- icon: "💧"
13857
+ name: "Water"
13875
13858
  },
13876
13859
  {
13877
13860
  id: "wind",
13878
- name: "Wind / Weather",
13879
- icon: "🌬️"
13861
+ name: "Wind / Weather"
13880
13862
  },
13881
13863
  {
13882
13864
  id: "door",
13883
- name: "Door",
13884
- icon: "🚪"
13865
+ name: "Door"
13885
13866
  },
13886
13867
  {
13887
13868
  id: "footsteps",
13888
- name: "Footsteps",
13889
- icon: "👣"
13869
+ name: "Footsteps"
13890
13870
  },
13891
13871
  {
13892
13872
  id: "crowd",
13893
- name: "Crowd / Chatter",
13894
- icon: "👥"
13873
+ name: "Crowd / Chatter"
13895
13874
  },
13896
13875
  {
13897
13876
  id: "telephone",
13898
- name: "Telephone",
13899
- icon: "📞"
13877
+ name: "Telephone"
13900
13878
  },
13901
13879
  {
13902
13880
  id: "engine",
13903
- name: "Engine / Motor",
13904
- icon: "⚙️"
13881
+ name: "Engine / Motor"
13905
13882
  },
13906
13883
  {
13907
13884
  id: "tools",
13908
- name: "Tools / Construction",
13909
- icon: "🔨"
13885
+ name: "Tools / Construction"
13910
13886
  },
13911
13887
  {
13912
13888
  id: "silence",
13913
- name: "Silence",
13914
- icon: "🤫"
13889
+ name: "Silence"
13915
13890
  }
13916
13891
  ];
13917
13892
  var YAMNET_TO_MACRO = {
@@ -16413,7 +16388,7 @@ var TrackEnvelopeSchema = object({
16413
16388
  * `snapshots[]` references — megabytes across a page of tracks. `slim`
16414
16389
  * keeps every scalar the list surfaces actually render (ids, class(es),
16415
16390
  * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
16416
- * zonesVisited, bestEventId, envelope, hasFace, hasRider) and returns `positions` /
16391
+ * zonesVisited, bestEventId, envelope, hasFace, hasEmbeddedFace, hasRider) and returns `positions` /
16417
16392
  * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
16418
16393
  * `getTrack`. Mirrors the event-store `projection` convention
16419
16394
  * (`getObjectEvents` et al.).
@@ -16708,6 +16683,35 @@ var TrackSchema = object({
16708
16683
  */
16709
16684
  hasFace: boolean().optional(),
16710
16685
  /**
16686
+ * This track has a face row IN THE GALLERY: a crop **and** an embedding — a
16687
+ * face an operator could ASSIGN to an identity.
16688
+ *
16689
+ * The STRICT twin of {@link hasFace}, and the pair only earns its keep
16690
+ * because the two disagree. `hasFace` is stamped at the TOP of the face
16691
+ * branch, before every gate, and means no more than "a face detector produced
16692
+ * a face detail". This one is stamped at the single moment the gallery row
16693
+ * LANDS — after `FaceRecognizer.onTrackEnd` successfully persists it, i.e.
16694
+ * past the embedding-magnitude verdict, the `minFacePx` detection gate, the
16695
+ * candidate gate, the imageless-track drop (no crop was ever captured) and
16696
+ * the crop-store drop. Everything between the detector and that insert can
16697
+ * legitimately refuse the face, so a flag written any earlier promises the
16698
+ * operator something to assign and delivers nothing.
16699
+ *
16700
+ * **Independent of recognition.** A face collected but never auto-matched is
16701
+ * still assignable — it is in fact the face an operator most wants to reach —
16702
+ * so this is NOT gated on `recognizedIdentityId`. Recognition lands in
16703
+ * `subLabel`; this says only that the raw material exists.
16704
+ *
16705
+ * **Set once, never cleared.** A track that produced a gallery row produced
16706
+ * one; deleting the row later is the gallery's business, not this flag's.
16707
+ *
16708
+ * **Absent ≠ false**, the same rule as {@link hasFace}: every row written
16709
+ * before the column omits it, and so does every server that predates the
16710
+ * field. A consumer must test `=== true` and render nothing otherwise —
16711
+ * never infer "no assignable face".
16712
+ */
16713
+ hasEmbeddedFace: boolean().optional(),
16714
+ /**
16711
16715
  * This subject CONTAINS a folded rider — a person the rider-pairing step
16712
16716
  * ([D34](../decisions/adr-0034.md)) removed from the frame BEFORE the tracker,
16713
16717
  * so the passage is tracked once and as a VEHICLE.
@@ -18097,6 +18101,32 @@ var DetailResultSchema = object({
18097
18101
  /** FRAME-space bbox (already mapped back from crop space). */
18098
18102
  bbox: NativeCropBboxSchema.optional(),
18099
18103
  embedding: string().optional(),
18104
+ /**
18105
+ * L2 magnitude of the RAW pre-normalization embedding. Additive and optional
18106
+ * so an older runner that does not send it degrades to "unmeasurable", which
18107
+ * the consuming gate treats as accept — never as reject.
18108
+ */
18109
+ embeddingMagnitude: number().optional(),
18110
+ /**
18111
+ * Model that produced `embedding` — the SAME contract `labelModelId` has for
18112
+ * `label`, and for the same reason: the producing node is the only side that
18113
+ * knows which model actually ran, and re-deriving it downstream is exactly
18114
+ * the inference this model exists to forbid.
18115
+ *
18116
+ * Additive and optional, so a runner that predates it degrades to
18117
+ * "unmeasurable" rather than to a wrong answer — the consumer falls back to
18118
+ * its previous assumption instead of refusing.
18119
+ *
18120
+ * Its absence was not cosmetic. `FaceRecognizer` needs the probe's model id
18121
+ * to use `face-matcher`'s same-model gate (`sample.modelId !== probe.modelId`);
18122
+ * with nothing on the wire it took the id from the GALLERY, so the gate
18123
+ * compared the gallery against itself and could never fire. That turned the
18124
+ * one guard protecting the face feature space into a tautology, and it is
18125
+ * why swapping `face-embedding`'s model would NOT have stranded the enrolled
18126
+ * gallery as the catalog comment predicts — it would have scored the new
18127
+ * model's vectors against the old model's samples in one cosine space.
18128
+ */
18129
+ embeddingModelId: string().optional(),
18100
18130
  label: string().optional(),
18101
18131
  /**
18102
18132
  * The tier `label` occupies, copied VERBATIM from the producing step's
@@ -19561,6 +19591,7 @@ var snapshotCapability = {
19561
19591
  isBattery: boolean(),
19562
19592
  reason: _enum([
19563
19593
  "disabled",
19594
+ "offline",
19564
19595
  "sleeping",
19565
19596
  "unreachable",
19566
19597
  "waking"
@@ -19650,9 +19681,17 @@ targets: array(object({
19650
19681
  sleeping: boolean(),
19651
19682
  /** Current device state rendered over the cached frame. State images
19652
19683
  * remain authoritative even when their photographic background is
19653
- * old; null means the link must carry a current camera frame. */
19684
+ * old; null means the link must carry a current camera frame.
19685
+ *
19686
+ * **This is the ONLY source a surface may derive an inactive tile
19687
+ * from.** A client that reads a device's `online`/`disabled` flags and
19688
+ * draws its own verdict will disagree with the pixels it is painting
19689
+ * — the frame comes from here, and so must the sentence over it
19690
+ * (D190). Pair with `ageMs` for the age of the photograph behind the
19691
+ * state. */
19654
19692
  stateReason: _enum([
19655
19693
  "disabled",
19694
+ "offline",
19656
19695
  "sleeping",
19657
19696
  "unreachable",
19658
19697
  "waking"
@@ -26420,9 +26459,21 @@ var RecordingLocationUsageSchema = object({
26420
26459
  * previously rendered as two identical "disks" with a nonsensical used
26421
26460
  * split — hydrate attribution across aliases is arbitrary by nature. */
26422
26461
  locationIds: array(string()).optional(),
26423
- /** Bytes of recordings stored on this PHYSICAL volume (all aliases). */
26462
+ /**
26463
+ * Filesystem root of the physical volume this row accounts — the only
26464
+ * operator-legible identity when a location has no `displayName` yet, and
26465
+ * what a "which disk is full" alert has to name. OPTIONAL for the same
26466
+ * train-skew reason as `oldestMs`: a recorder that predates the field omits
26467
+ * it, and this schema must keep validating that older provider's payload.
26468
+ */
26469
+ root: string().optional(),
26470
+ /** Bytes of recordings stored on this PHYSICAL volume (all aliases). This is
26471
+ * the footage's SHARE of the volume, not how full the volume is — the disk
26472
+ * also holds everything that is not recordings. Volume fill is
26473
+ * `(totalBytes - availableBytes) / totalBytes`. */
26424
26474
  usedBytes: number(),
26425
- /** Free bytes on the location's volume; null when capacity is unknown (remote). */
26475
+ /** Free bytes on the location's volume; null when capacity is unknown
26476
+ * (remote/unstattable — the recorder logs the statfs failure with the root). */
26426
26477
  availableBytes: number().nullable(),
26427
26478
  /** Total bytes of the location's volume; null when unknown. */
26428
26479
  totalBytes: number().nullable()
@@ -26434,7 +26485,15 @@ var RecordingStorageUsageSchema = object({
26434
26485
  nodeId: string(),
26435
26486
  totalUsedBytes: number(),
26436
26487
  devices: array(RecordingDeviceUsageSchema),
26437
- locations: array(RecordingLocationUsageSchema)
26488
+ /**
26489
+ * Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
26490
+ * of the payload without it: the hub serves this method through a BAKED
26491
+ * router, so a hub whose framework train predates a change to this array
26492
+ * strips it out of an answer a newer recorder happily produced. A UI that
26493
+ * dereferences it unconditionally crashes the whole Storage page over a
26494
+ * missing disk bar.
26495
+ */
26496
+ locations: array(RecordingLocationUsageSchema).optional()
26438
26497
  });
26439
26498
  /**
26440
26499
  * The OPERATOR-ARMED half of multi-location recordings (D116).
@@ -37217,8 +37276,13 @@ Object.freeze({
37217
37276
  "network-access": "ingress",
37218
37277
  "smtp-provider": "email"
37219
37278
  });
37220
- new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
37279
+ /** Schema defaults an untouched sub-field must author exactly these. */
37280
+ var NC_AUDIO_DEFAULTS = {
37281
+ hitPercent: 60,
37282
+ samplingSeconds: 10
37283
+ };
37221
37284
  new Set(["devices", "classes"]);
37285
+ NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
37222
37286
  /**
37223
37287
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
37224
37288
  *
@@ -37450,6 +37514,39 @@ TimelapseRuleInputSchema.extend({
37450
37514
  createdAt: number(),
37451
37515
  updatedAt: number()
37452
37516
  });
37517
+ Object.freeze(Object.fromEntries([{
37518
+ stepId: "face-embedding",
37519
+ label: "Face recognition model",
37520
+ defaultModelId: "arcface-r100",
37521
+ indexName: "the enrolled face gallery",
37522
+ options: [
37523
+ {
37524
+ id: "arcface-r100",
37525
+ label: "ArcFace (ResNet34)"
37526
+ },
37527
+ {
37528
+ id: "auraface-r100",
37529
+ label: "AuraFace R100"
37530
+ },
37531
+ {
37532
+ id: "inception-resnet-v1",
37533
+ label: "Inception ResNet V1"
37534
+ }
37535
+ ]
37536
+ }, {
37537
+ stepId: "clip-embedding",
37538
+ label: "Semantic search model",
37539
+ defaultModelId: "mobileclip-s1",
37540
+ indexName: "the object semantic-search index",
37541
+ options: [{
37542
+ id: "mobileclip-s1",
37543
+ label: "MobileCLIP S1"
37544
+ }, {
37545
+ id: "mobileclip-s2",
37546
+ label: "MobileCLIP S2"
37547
+ }]
37548
+ }].map((s) => [s.stepId, s.defaultModelId])));
37549
+ string().min(1);
37453
37550
  object({
37454
37551
  /**
37455
37552
  * Fraction of the box's own size added on EACH side before cutting.
@@ -37470,10 +37567,21 @@ object({
37470
37567
  */
37471
37568
  square: boolean()
37472
37569
  });
37473
- ({
37570
+ /**
37571
+ * The convention in force when nobody has configured one — byte-for-byte the
37572
+ * behaviour of the pre-unification LIVE path (`DETAIL_CROP_PADDING_RATIO`).
37573
+ */
37574
+ var DEFAULT_DETAIL_CROP_CONVENTION = {
37474
37575
  paddingRatio: .15,
37475
37576
  square: false
37476
- }).paddingRatio;
37577
+ };
37578
+ Object.freeze({
37579
+ x: 0,
37580
+ y: 0,
37581
+ w: 1,
37582
+ h: 1
37583
+ });
37584
+ DEFAULT_DETAIL_CROP_CONVENTION.paddingRatio;
37477
37585
  /**
37478
37586
  * WHICH delivered frames the decode worker retains a native copy of.
37479
37587
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-terminal",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "description": "Interactive terminal sessions (pty + xterm) as a CamStack addon",
5
5
  "keywords": [
6
6
  "camstack",