@camstack/addon-provider-petkit 0.2.22 → 0.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.
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
@@ -8668,7 +8668,7 @@ var RecordingBandSchema = object({
8668
8668
  postBufferSec: number().min(0).optional()
8669
8669
  });
8670
8670
  ({
8671
- preBufferSec: 10,
8671
+ preBufferSec: 15,
8672
8672
  postBufferSec: 30
8673
8673
  }).postBufferSec * 1e3;
8674
8674
  /**
@@ -14888,128 +14888,103 @@ var COCO_TO_MACRO = {
14888
14888
  var AUDIO_MACRO_LABELS = [
14889
14889
  {
14890
14890
  id: "speech",
14891
- name: "Speech",
14892
- icon: "🗣️"
14891
+ name: "Speech"
14893
14892
  },
14894
14893
  {
14895
14894
  id: "scream",
14896
- name: "Scream / Shout",
14897
- icon: "😱"
14895
+ name: "Scream / Shout"
14898
14896
  },
14899
14897
  {
14900
14898
  id: "crying",
14901
- name: "Crying / Baby",
14902
- icon: "😢"
14899
+ name: "Crying / Baby"
14903
14900
  },
14904
14901
  {
14905
14902
  id: "laughter",
14906
- name: "Laughter",
14907
- icon: "😂"
14903
+ name: "Laughter"
14908
14904
  },
14909
14905
  {
14910
14906
  id: "music",
14911
- name: "Music",
14912
- icon: "🎵"
14907
+ name: "Music"
14913
14908
  },
14914
14909
  {
14915
14910
  id: "dog",
14916
- name: "Dog",
14917
- icon: "🐕"
14911
+ name: "Dog"
14918
14912
  },
14919
14913
  {
14920
14914
  id: "cat",
14921
- name: "Cat",
14922
- icon: "🐈"
14915
+ name: "Cat"
14923
14916
  },
14924
14917
  {
14925
14918
  id: "bird",
14926
- name: "Bird",
14927
- icon: "🐦"
14919
+ name: "Bird"
14928
14920
  },
14929
14921
  {
14930
14922
  id: "animal",
14931
- name: "Animal (other)",
14932
- icon: "🐾"
14923
+ name: "Animal (other)"
14933
14924
  },
14934
14925
  {
14935
14926
  id: "alarm",
14936
- name: "Alarm / Siren",
14937
- icon: "🚨"
14927
+ name: "Alarm / Siren"
14938
14928
  },
14939
14929
  {
14940
14930
  id: "doorbell",
14941
- name: "Doorbell / Knock",
14942
- icon: "🔔"
14931
+ name: "Doorbell / Knock"
14943
14932
  },
14944
14933
  {
14945
14934
  id: "glass_breaking",
14946
- name: "Glass Breaking",
14947
- icon: "💥"
14935
+ name: "Glass Breaking"
14948
14936
  },
14949
14937
  {
14950
14938
  id: "gunshot",
14951
- name: "Gunshot / Explosion",
14952
- icon: "💣"
14939
+ name: "Gunshot / Explosion"
14953
14940
  },
14954
14941
  {
14955
14942
  id: "vehicle",
14956
- name: "Vehicle",
14957
- icon: "🚗"
14943
+ name: "Vehicle"
14958
14944
  },
14959
14945
  {
14960
14946
  id: "siren",
14961
- name: "Emergency Siren",
14962
- icon: "🚑"
14947
+ name: "Emergency Siren"
14963
14948
  },
14964
14949
  {
14965
14950
  id: "fire",
14966
- name: "Fire / Smoke",
14967
- icon: "🔥"
14951
+ name: "Fire / Smoke"
14968
14952
  },
14969
14953
  {
14970
14954
  id: "water",
14971
- name: "Water",
14972
- icon: "💧"
14955
+ name: "Water"
14973
14956
  },
14974
14957
  {
14975
14958
  id: "wind",
14976
- name: "Wind / Weather",
14977
- icon: "🌬️"
14959
+ name: "Wind / Weather"
14978
14960
  },
14979
14961
  {
14980
14962
  id: "door",
14981
- name: "Door",
14982
- icon: "🚪"
14963
+ name: "Door"
14983
14964
  },
14984
14965
  {
14985
14966
  id: "footsteps",
14986
- name: "Footsteps",
14987
- icon: "👣"
14967
+ name: "Footsteps"
14988
14968
  },
14989
14969
  {
14990
14970
  id: "crowd",
14991
- name: "Crowd / Chatter",
14992
- icon: "👥"
14971
+ name: "Crowd / Chatter"
14993
14972
  },
14994
14973
  {
14995
14974
  id: "telephone",
14996
- name: "Telephone",
14997
- icon: "📞"
14975
+ name: "Telephone"
14998
14976
  },
14999
14977
  {
15000
14978
  id: "engine",
15001
- name: "Engine / Motor",
15002
- icon: "⚙️"
14979
+ name: "Engine / Motor"
15003
14980
  },
15004
14981
  {
15005
14982
  id: "tools",
15006
- name: "Tools / Construction",
15007
- icon: "🔨"
14983
+ name: "Tools / Construction"
15008
14984
  },
15009
14985
  {
15010
14986
  id: "silence",
15011
- name: "Silence",
15012
- icon: "🤫"
14987
+ name: "Silence"
15013
14988
  }
15014
14989
  ];
15015
14990
  var YAMNET_TO_MACRO = {
@@ -17511,7 +17486,7 @@ var TrackEnvelopeSchema = object({
17511
17486
  * `snapshots[]` references — megabytes across a page of tracks. `slim`
17512
17487
  * keeps every scalar the list surfaces actually render (ids, class(es),
17513
17488
  * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
17514
- * zonesVisited, bestEventId, envelope, hasFace, hasRider) and returns `positions` /
17489
+ * zonesVisited, bestEventId, envelope, hasFace, hasEmbeddedFace, hasRider) and returns `positions` /
17515
17490
  * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
17516
17491
  * `getTrack`. Mirrors the event-store `projection` convention
17517
17492
  * (`getObjectEvents` et al.).
@@ -17806,6 +17781,35 @@ var TrackSchema = object({
17806
17781
  */
17807
17782
  hasFace: boolean().optional(),
17808
17783
  /**
17784
+ * This track has a face row IN THE GALLERY: a crop **and** an embedding — a
17785
+ * face an operator could ASSIGN to an identity.
17786
+ *
17787
+ * The STRICT twin of {@link hasFace}, and the pair only earns its keep
17788
+ * because the two disagree. `hasFace` is stamped at the TOP of the face
17789
+ * branch, before every gate, and means no more than "a face detector produced
17790
+ * a face detail". This one is stamped at the single moment the gallery row
17791
+ * LANDS — after `FaceRecognizer.onTrackEnd` successfully persists it, i.e.
17792
+ * past the embedding-magnitude verdict, the `minFacePx` detection gate, the
17793
+ * candidate gate, the imageless-track drop (no crop was ever captured) and
17794
+ * the crop-store drop. Everything between the detector and that insert can
17795
+ * legitimately refuse the face, so a flag written any earlier promises the
17796
+ * operator something to assign and delivers nothing.
17797
+ *
17798
+ * **Independent of recognition.** A face collected but never auto-matched is
17799
+ * still assignable — it is in fact the face an operator most wants to reach —
17800
+ * so this is NOT gated on `recognizedIdentityId`. Recognition lands in
17801
+ * `subLabel`; this says only that the raw material exists.
17802
+ *
17803
+ * **Set once, never cleared.** A track that produced a gallery row produced
17804
+ * one; deleting the row later is the gallery's business, not this flag's.
17805
+ *
17806
+ * **Absent ≠ false**, the same rule as {@link hasFace}: every row written
17807
+ * before the column omits it, and so does every server that predates the
17808
+ * field. A consumer must test `=== true` and render nothing otherwise —
17809
+ * never infer "no assignable face".
17810
+ */
17811
+ hasEmbeddedFace: boolean().optional(),
17812
+ /**
17809
17813
  * This subject CONTAINS a folded rider — a person the rider-pairing step
17810
17814
  * ([D34](../decisions/adr-0034.md)) removed from the frame BEFORE the tracker,
17811
17815
  * so the passage is tracked once and as a VEHICLE.
@@ -19195,6 +19199,32 @@ var DetailResultSchema = object({
19195
19199
  /** FRAME-space bbox (already mapped back from crop space). */
19196
19200
  bbox: NativeCropBboxSchema.optional(),
19197
19201
  embedding: string().optional(),
19202
+ /**
19203
+ * L2 magnitude of the RAW pre-normalization embedding. Additive and optional
19204
+ * so an older runner that does not send it degrades to "unmeasurable", which
19205
+ * the consuming gate treats as accept — never as reject.
19206
+ */
19207
+ embeddingMagnitude: number().optional(),
19208
+ /**
19209
+ * Model that produced `embedding` — the SAME contract `labelModelId` has for
19210
+ * `label`, and for the same reason: the producing node is the only side that
19211
+ * knows which model actually ran, and re-deriving it downstream is exactly
19212
+ * the inference this model exists to forbid.
19213
+ *
19214
+ * Additive and optional, so a runner that predates it degrades to
19215
+ * "unmeasurable" rather than to a wrong answer — the consumer falls back to
19216
+ * its previous assumption instead of refusing.
19217
+ *
19218
+ * Its absence was not cosmetic. `FaceRecognizer` needs the probe's model id
19219
+ * to use `face-matcher`'s same-model gate (`sample.modelId !== probe.modelId`);
19220
+ * with nothing on the wire it took the id from the GALLERY, so the gate
19221
+ * compared the gallery against itself and could never fire. That turned the
19222
+ * one guard protecting the face feature space into a tautology, and it is
19223
+ * why swapping `face-embedding`'s model would NOT have stranded the enrolled
19224
+ * gallery as the catalog comment predicts — it would have scored the new
19225
+ * model's vectors against the old model's samples in one cosine space.
19226
+ */
19227
+ embeddingModelId: string().optional(),
19198
19228
  label: string().optional(),
19199
19229
  /**
19200
19230
  * The tier `label` occupies, copied VERBATIM from the producing step's
@@ -20605,6 +20635,7 @@ DeviceType.Camera, method(object({
20605
20635
  isBattery: boolean(),
20606
20636
  reason: _enum([
20607
20637
  "disabled",
20638
+ "offline",
20608
20639
  "sleeping",
20609
20640
  "unreachable",
20610
20641
  "waking"
@@ -20650,9 +20681,17 @@ targets: array(object({
20650
20681
  sleeping: boolean(),
20651
20682
  /** Current device state rendered over the cached frame. State images
20652
20683
  * remain authoritative even when their photographic background is
20653
- * old; null means the link must carry a current camera frame. */
20684
+ * old; null means the link must carry a current camera frame.
20685
+ *
20686
+ * **This is the ONLY source a surface may derive an inactive tile
20687
+ * from.** A client that reads a device's `online`/`disabled` flags and
20688
+ * draws its own verdict will disagree with the pixels it is painting
20689
+ * — the frame comes from here, and so must the sentence over it
20690
+ * (D190). Pair with `ageMs` for the age of the photograph behind the
20691
+ * state. */
20654
20692
  stateReason: _enum([
20655
20693
  "disabled",
20694
+ "offline",
20656
20695
  "sleeping",
20657
20696
  "unreachable",
20658
20697
  "waking"
@@ -27379,9 +27418,21 @@ var RecordingLocationUsageSchema = object({
27379
27418
  * previously rendered as two identical "disks" with a nonsensical used
27380
27419
  * split — hydrate attribution across aliases is arbitrary by nature. */
27381
27420
  locationIds: array(string()).optional(),
27382
- /** Bytes of recordings stored on this PHYSICAL volume (all aliases). */
27421
+ /**
27422
+ * Filesystem root of the physical volume this row accounts — the only
27423
+ * operator-legible identity when a location has no `displayName` yet, and
27424
+ * what a "which disk is full" alert has to name. OPTIONAL for the same
27425
+ * train-skew reason as `oldestMs`: a recorder that predates the field omits
27426
+ * it, and this schema must keep validating that older provider's payload.
27427
+ */
27428
+ root: string().optional(),
27429
+ /** Bytes of recordings stored on this PHYSICAL volume (all aliases). This is
27430
+ * the footage's SHARE of the volume, not how full the volume is — the disk
27431
+ * also holds everything that is not recordings. Volume fill is
27432
+ * `(totalBytes - availableBytes) / totalBytes`. */
27383
27433
  usedBytes: number(),
27384
- /** Free bytes on the location's volume; null when capacity is unknown (remote). */
27434
+ /** Free bytes on the location's volume; null when capacity is unknown
27435
+ * (remote/unstattable — the recorder logs the statfs failure with the root). */
27385
27436
  availableBytes: number().nullable(),
27386
27437
  /** Total bytes of the location's volume; null when unknown. */
27387
27438
  totalBytes: number().nullable()
@@ -27393,7 +27444,15 @@ var RecordingStorageUsageSchema = object({
27393
27444
  nodeId: string(),
27394
27445
  totalUsedBytes: number(),
27395
27446
  devices: array(RecordingDeviceUsageSchema),
27396
- locations: array(RecordingLocationUsageSchema)
27447
+ /**
27448
+ * Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
27449
+ * of the payload without it: the hub serves this method through a BAKED
27450
+ * router, so a hub whose framework train predates a change to this array
27451
+ * strips it out of an answer a newer recorder happily produced. A UI that
27452
+ * dereferences it unconditionally crashes the whole Storage page over a
27453
+ * missing disk bar.
27454
+ */
27455
+ locations: array(RecordingLocationUsageSchema).optional()
27397
27456
  });
27398
27457
  /**
27399
27458
  * The OPERATOR-ARMED half of multi-location recordings (D116).
@@ -38178,8 +38237,13 @@ Object.freeze({
38178
38237
  "network-access": "ingress",
38179
38238
  "smtp-provider": "email"
38180
38239
  });
38181
- new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
38240
+ /** Schema defaults an untouched sub-field must author exactly these. */
38241
+ var NC_AUDIO_DEFAULTS = {
38242
+ hitPercent: 60,
38243
+ samplingSeconds: 10
38244
+ };
38182
38245
  new Set(["devices", "classes"]);
38246
+ NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
38183
38247
  /**
38184
38248
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
38185
38249
  *
@@ -38411,6 +38475,39 @@ TimelapseRuleInputSchema.extend({
38411
38475
  createdAt: number(),
38412
38476
  updatedAt: number()
38413
38477
  });
38478
+ Object.freeze(Object.fromEntries([{
38479
+ stepId: "face-embedding",
38480
+ label: "Face recognition model",
38481
+ defaultModelId: "arcface-r100",
38482
+ indexName: "the enrolled face gallery",
38483
+ options: [
38484
+ {
38485
+ id: "arcface-r100",
38486
+ label: "ArcFace (ResNet34)"
38487
+ },
38488
+ {
38489
+ id: "auraface-r100",
38490
+ label: "AuraFace R100"
38491
+ },
38492
+ {
38493
+ id: "inception-resnet-v1",
38494
+ label: "Inception ResNet V1"
38495
+ }
38496
+ ]
38497
+ }, {
38498
+ stepId: "clip-embedding",
38499
+ label: "Semantic search model",
38500
+ defaultModelId: "mobileclip-s1",
38501
+ indexName: "the object semantic-search index",
38502
+ options: [{
38503
+ id: "mobileclip-s1",
38504
+ label: "MobileCLIP S1"
38505
+ }, {
38506
+ id: "mobileclip-s2",
38507
+ label: "MobileCLIP S2"
38508
+ }]
38509
+ }].map((s) => [s.stepId, s.defaultModelId])));
38510
+ string().min(1);
38414
38511
  object({
38415
38512
  /**
38416
38513
  * Fraction of the box's own size added on EACH side before cutting.
@@ -38431,10 +38528,21 @@ object({
38431
38528
  */
38432
38529
  square: boolean()
38433
38530
  });
38434
- ({
38531
+ /**
38532
+ * The convention in force when nobody has configured one — byte-for-byte the
38533
+ * behaviour of the pre-unification LIVE path (`DETAIL_CROP_PADDING_RATIO`).
38534
+ */
38535
+ var DEFAULT_DETAIL_CROP_CONVENTION = {
38435
38536
  paddingRatio: .15,
38436
38537
  square: false
38437
- }).paddingRatio;
38538
+ };
38539
+ Object.freeze({
38540
+ x: 0,
38541
+ y: 0,
38542
+ w: 1,
38543
+ h: 1
38544
+ });
38545
+ DEFAULT_DETAIL_CROP_CONVENTION.paddingRatio;
38438
38546
  /**
38439
38547
  * WHICH delivered frames the decode worker retains a native copy of.
38440
38548
  *
package/dist/addon.mjs CHANGED
@@ -8667,7 +8667,7 @@ var RecordingBandSchema = object({
8667
8667
  postBufferSec: number().min(0).optional()
8668
8668
  });
8669
8669
  ({
8670
- preBufferSec: 10,
8670
+ preBufferSec: 15,
8671
8671
  postBufferSec: 30
8672
8672
  }).postBufferSec * 1e3;
8673
8673
  /**
@@ -14887,128 +14887,103 @@ var COCO_TO_MACRO = {
14887
14887
  var AUDIO_MACRO_LABELS = [
14888
14888
  {
14889
14889
  id: "speech",
14890
- name: "Speech",
14891
- icon: "🗣️"
14890
+ name: "Speech"
14892
14891
  },
14893
14892
  {
14894
14893
  id: "scream",
14895
- name: "Scream / Shout",
14896
- icon: "😱"
14894
+ name: "Scream / Shout"
14897
14895
  },
14898
14896
  {
14899
14897
  id: "crying",
14900
- name: "Crying / Baby",
14901
- icon: "😢"
14898
+ name: "Crying / Baby"
14902
14899
  },
14903
14900
  {
14904
14901
  id: "laughter",
14905
- name: "Laughter",
14906
- icon: "😂"
14902
+ name: "Laughter"
14907
14903
  },
14908
14904
  {
14909
14905
  id: "music",
14910
- name: "Music",
14911
- icon: "🎵"
14906
+ name: "Music"
14912
14907
  },
14913
14908
  {
14914
14909
  id: "dog",
14915
- name: "Dog",
14916
- icon: "🐕"
14910
+ name: "Dog"
14917
14911
  },
14918
14912
  {
14919
14913
  id: "cat",
14920
- name: "Cat",
14921
- icon: "🐈"
14914
+ name: "Cat"
14922
14915
  },
14923
14916
  {
14924
14917
  id: "bird",
14925
- name: "Bird",
14926
- icon: "🐦"
14918
+ name: "Bird"
14927
14919
  },
14928
14920
  {
14929
14921
  id: "animal",
14930
- name: "Animal (other)",
14931
- icon: "🐾"
14922
+ name: "Animal (other)"
14932
14923
  },
14933
14924
  {
14934
14925
  id: "alarm",
14935
- name: "Alarm / Siren",
14936
- icon: "🚨"
14926
+ name: "Alarm / Siren"
14937
14927
  },
14938
14928
  {
14939
14929
  id: "doorbell",
14940
- name: "Doorbell / Knock",
14941
- icon: "🔔"
14930
+ name: "Doorbell / Knock"
14942
14931
  },
14943
14932
  {
14944
14933
  id: "glass_breaking",
14945
- name: "Glass Breaking",
14946
- icon: "💥"
14934
+ name: "Glass Breaking"
14947
14935
  },
14948
14936
  {
14949
14937
  id: "gunshot",
14950
- name: "Gunshot / Explosion",
14951
- icon: "💣"
14938
+ name: "Gunshot / Explosion"
14952
14939
  },
14953
14940
  {
14954
14941
  id: "vehicle",
14955
- name: "Vehicle",
14956
- icon: "🚗"
14942
+ name: "Vehicle"
14957
14943
  },
14958
14944
  {
14959
14945
  id: "siren",
14960
- name: "Emergency Siren",
14961
- icon: "🚑"
14946
+ name: "Emergency Siren"
14962
14947
  },
14963
14948
  {
14964
14949
  id: "fire",
14965
- name: "Fire / Smoke",
14966
- icon: "🔥"
14950
+ name: "Fire / Smoke"
14967
14951
  },
14968
14952
  {
14969
14953
  id: "water",
14970
- name: "Water",
14971
- icon: "💧"
14954
+ name: "Water"
14972
14955
  },
14973
14956
  {
14974
14957
  id: "wind",
14975
- name: "Wind / Weather",
14976
- icon: "🌬️"
14958
+ name: "Wind / Weather"
14977
14959
  },
14978
14960
  {
14979
14961
  id: "door",
14980
- name: "Door",
14981
- icon: "🚪"
14962
+ name: "Door"
14982
14963
  },
14983
14964
  {
14984
14965
  id: "footsteps",
14985
- name: "Footsteps",
14986
- icon: "👣"
14966
+ name: "Footsteps"
14987
14967
  },
14988
14968
  {
14989
14969
  id: "crowd",
14990
- name: "Crowd / Chatter",
14991
- icon: "👥"
14970
+ name: "Crowd / Chatter"
14992
14971
  },
14993
14972
  {
14994
14973
  id: "telephone",
14995
- name: "Telephone",
14996
- icon: "📞"
14974
+ name: "Telephone"
14997
14975
  },
14998
14976
  {
14999
14977
  id: "engine",
15000
- name: "Engine / Motor",
15001
- icon: "⚙️"
14978
+ name: "Engine / Motor"
15002
14979
  },
15003
14980
  {
15004
14981
  id: "tools",
15005
- name: "Tools / Construction",
15006
- icon: "🔨"
14982
+ name: "Tools / Construction"
15007
14983
  },
15008
14984
  {
15009
14985
  id: "silence",
15010
- name: "Silence",
15011
- icon: "🤫"
14986
+ name: "Silence"
15012
14987
  }
15013
14988
  ];
15014
14989
  var YAMNET_TO_MACRO = {
@@ -17510,7 +17485,7 @@ var TrackEnvelopeSchema = object({
17510
17485
  * `snapshots[]` references — megabytes across a page of tracks. `slim`
17511
17486
  * keeps every scalar the list surfaces actually render (ids, class(es),
17512
17487
  * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
17513
- * zonesVisited, bestEventId, envelope, hasFace, hasRider) and returns `positions` /
17488
+ * zonesVisited, bestEventId, envelope, hasFace, hasEmbeddedFace, hasRider) and returns `positions` /
17514
17489
  * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
17515
17490
  * `getTrack`. Mirrors the event-store `projection` convention
17516
17491
  * (`getObjectEvents` et al.).
@@ -17805,6 +17780,35 @@ var TrackSchema = object({
17805
17780
  */
17806
17781
  hasFace: boolean().optional(),
17807
17782
  /**
17783
+ * This track has a face row IN THE GALLERY: a crop **and** an embedding — a
17784
+ * face an operator could ASSIGN to an identity.
17785
+ *
17786
+ * The STRICT twin of {@link hasFace}, and the pair only earns its keep
17787
+ * because the two disagree. `hasFace` is stamped at the TOP of the face
17788
+ * branch, before every gate, and means no more than "a face detector produced
17789
+ * a face detail". This one is stamped at the single moment the gallery row
17790
+ * LANDS — after `FaceRecognizer.onTrackEnd` successfully persists it, i.e.
17791
+ * past the embedding-magnitude verdict, the `minFacePx` detection gate, the
17792
+ * candidate gate, the imageless-track drop (no crop was ever captured) and
17793
+ * the crop-store drop. Everything between the detector and that insert can
17794
+ * legitimately refuse the face, so a flag written any earlier promises the
17795
+ * operator something to assign and delivers nothing.
17796
+ *
17797
+ * **Independent of recognition.** A face collected but never auto-matched is
17798
+ * still assignable — it is in fact the face an operator most wants to reach —
17799
+ * so this is NOT gated on `recognizedIdentityId`. Recognition lands in
17800
+ * `subLabel`; this says only that the raw material exists.
17801
+ *
17802
+ * **Set once, never cleared.** A track that produced a gallery row produced
17803
+ * one; deleting the row later is the gallery's business, not this flag's.
17804
+ *
17805
+ * **Absent ≠ false**, the same rule as {@link hasFace}: every row written
17806
+ * before the column omits it, and so does every server that predates the
17807
+ * field. A consumer must test `=== true` and render nothing otherwise —
17808
+ * never infer "no assignable face".
17809
+ */
17810
+ hasEmbeddedFace: boolean().optional(),
17811
+ /**
17808
17812
  * This subject CONTAINS a folded rider — a person the rider-pairing step
17809
17813
  * ([D34](../decisions/adr-0034.md)) removed from the frame BEFORE the tracker,
17810
17814
  * so the passage is tracked once and as a VEHICLE.
@@ -19194,6 +19198,32 @@ var DetailResultSchema = object({
19194
19198
  /** FRAME-space bbox (already mapped back from crop space). */
19195
19199
  bbox: NativeCropBboxSchema.optional(),
19196
19200
  embedding: string().optional(),
19201
+ /**
19202
+ * L2 magnitude of the RAW pre-normalization embedding. Additive and optional
19203
+ * so an older runner that does not send it degrades to "unmeasurable", which
19204
+ * the consuming gate treats as accept — never as reject.
19205
+ */
19206
+ embeddingMagnitude: number().optional(),
19207
+ /**
19208
+ * Model that produced `embedding` — the SAME contract `labelModelId` has for
19209
+ * `label`, and for the same reason: the producing node is the only side that
19210
+ * knows which model actually ran, and re-deriving it downstream is exactly
19211
+ * the inference this model exists to forbid.
19212
+ *
19213
+ * Additive and optional, so a runner that predates it degrades to
19214
+ * "unmeasurable" rather than to a wrong answer — the consumer falls back to
19215
+ * its previous assumption instead of refusing.
19216
+ *
19217
+ * Its absence was not cosmetic. `FaceRecognizer` needs the probe's model id
19218
+ * to use `face-matcher`'s same-model gate (`sample.modelId !== probe.modelId`);
19219
+ * with nothing on the wire it took the id from the GALLERY, so the gate
19220
+ * compared the gallery against itself and could never fire. That turned the
19221
+ * one guard protecting the face feature space into a tautology, and it is
19222
+ * why swapping `face-embedding`'s model would NOT have stranded the enrolled
19223
+ * gallery as the catalog comment predicts — it would have scored the new
19224
+ * model's vectors against the old model's samples in one cosine space.
19225
+ */
19226
+ embeddingModelId: string().optional(),
19197
19227
  label: string().optional(),
19198
19228
  /**
19199
19229
  * The tier `label` occupies, copied VERBATIM from the producing step's
@@ -20604,6 +20634,7 @@ DeviceType.Camera, method(object({
20604
20634
  isBattery: boolean(),
20605
20635
  reason: _enum([
20606
20636
  "disabled",
20637
+ "offline",
20607
20638
  "sleeping",
20608
20639
  "unreachable",
20609
20640
  "waking"
@@ -20649,9 +20680,17 @@ targets: array(object({
20649
20680
  sleeping: boolean(),
20650
20681
  /** Current device state rendered over the cached frame. State images
20651
20682
  * remain authoritative even when their photographic background is
20652
- * old; null means the link must carry a current camera frame. */
20683
+ * old; null means the link must carry a current camera frame.
20684
+ *
20685
+ * **This is the ONLY source a surface may derive an inactive tile
20686
+ * from.** A client that reads a device's `online`/`disabled` flags and
20687
+ * draws its own verdict will disagree with the pixels it is painting
20688
+ * — the frame comes from here, and so must the sentence over it
20689
+ * (D190). Pair with `ageMs` for the age of the photograph behind the
20690
+ * state. */
20653
20691
  stateReason: _enum([
20654
20692
  "disabled",
20693
+ "offline",
20655
20694
  "sleeping",
20656
20695
  "unreachable",
20657
20696
  "waking"
@@ -27378,9 +27417,21 @@ var RecordingLocationUsageSchema = object({
27378
27417
  * previously rendered as two identical "disks" with a nonsensical used
27379
27418
  * split — hydrate attribution across aliases is arbitrary by nature. */
27380
27419
  locationIds: array(string()).optional(),
27381
- /** Bytes of recordings stored on this PHYSICAL volume (all aliases). */
27420
+ /**
27421
+ * Filesystem root of the physical volume this row accounts — the only
27422
+ * operator-legible identity when a location has no `displayName` yet, and
27423
+ * what a "which disk is full" alert has to name. OPTIONAL for the same
27424
+ * train-skew reason as `oldestMs`: a recorder that predates the field omits
27425
+ * it, and this schema must keep validating that older provider's payload.
27426
+ */
27427
+ root: string().optional(),
27428
+ /** Bytes of recordings stored on this PHYSICAL volume (all aliases). This is
27429
+ * the footage's SHARE of the volume, not how full the volume is — the disk
27430
+ * also holds everything that is not recordings. Volume fill is
27431
+ * `(totalBytes - availableBytes) / totalBytes`. */
27382
27432
  usedBytes: number(),
27383
- /** Free bytes on the location's volume; null when capacity is unknown (remote). */
27433
+ /** Free bytes on the location's volume; null when capacity is unknown
27434
+ * (remote/unstattable — the recorder logs the statfs failure with the root). */
27384
27435
  availableBytes: number().nullable(),
27385
27436
  /** Total bytes of the location's volume; null when unknown. */
27386
27437
  totalBytes: number().nullable()
@@ -27392,7 +27443,15 @@ var RecordingStorageUsageSchema = object({
27392
27443
  nodeId: string(),
27393
27444
  totalUsedBytes: number(),
27394
27445
  devices: array(RecordingDeviceUsageSchema),
27395
- locations: array(RecordingLocationUsageSchema)
27446
+ /**
27447
+ * Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
27448
+ * of the payload without it: the hub serves this method through a BAKED
27449
+ * router, so a hub whose framework train predates a change to this array
27450
+ * strips it out of an answer a newer recorder happily produced. A UI that
27451
+ * dereferences it unconditionally crashes the whole Storage page over a
27452
+ * missing disk bar.
27453
+ */
27454
+ locations: array(RecordingLocationUsageSchema).optional()
27396
27455
  });
27397
27456
  /**
27398
27457
  * The OPERATOR-ARMED half of multi-location recordings (D116).
@@ -38177,8 +38236,13 @@ Object.freeze({
38177
38236
  "network-access": "ingress",
38178
38237
  "smtp-provider": "email"
38179
38238
  });
38180
- new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
38239
+ /** Schema defaults an untouched sub-field must author exactly these. */
38240
+ var NC_AUDIO_DEFAULTS = {
38241
+ hitPercent: 60,
38242
+ samplingSeconds: 10
38243
+ };
38181
38244
  new Set(["devices", "classes"]);
38245
+ NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
38182
38246
  /**
38183
38247
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
38184
38248
  *
@@ -38410,6 +38474,39 @@ TimelapseRuleInputSchema.extend({
38410
38474
  createdAt: number(),
38411
38475
  updatedAt: number()
38412
38476
  });
38477
+ Object.freeze(Object.fromEntries([{
38478
+ stepId: "face-embedding",
38479
+ label: "Face recognition model",
38480
+ defaultModelId: "arcface-r100",
38481
+ indexName: "the enrolled face gallery",
38482
+ options: [
38483
+ {
38484
+ id: "arcface-r100",
38485
+ label: "ArcFace (ResNet34)"
38486
+ },
38487
+ {
38488
+ id: "auraface-r100",
38489
+ label: "AuraFace R100"
38490
+ },
38491
+ {
38492
+ id: "inception-resnet-v1",
38493
+ label: "Inception ResNet V1"
38494
+ }
38495
+ ]
38496
+ }, {
38497
+ stepId: "clip-embedding",
38498
+ label: "Semantic search model",
38499
+ defaultModelId: "mobileclip-s1",
38500
+ indexName: "the object semantic-search index",
38501
+ options: [{
38502
+ id: "mobileclip-s1",
38503
+ label: "MobileCLIP S1"
38504
+ }, {
38505
+ id: "mobileclip-s2",
38506
+ label: "MobileCLIP S2"
38507
+ }]
38508
+ }].map((s) => [s.stepId, s.defaultModelId])));
38509
+ string().min(1);
38413
38510
  object({
38414
38511
  /**
38415
38512
  * Fraction of the box's own size added on EACH side before cutting.
@@ -38430,10 +38527,21 @@ object({
38430
38527
  */
38431
38528
  square: boolean()
38432
38529
  });
38433
- ({
38530
+ /**
38531
+ * The convention in force when nobody has configured one — byte-for-byte the
38532
+ * behaviour of the pre-unification LIVE path (`DETAIL_CROP_PADDING_RATIO`).
38533
+ */
38534
+ var DEFAULT_DETAIL_CROP_CONVENTION = {
38434
38535
  paddingRatio: .15,
38435
38536
  square: false
38436
- }).paddingRatio;
38537
+ };
38538
+ Object.freeze({
38539
+ x: 0,
38540
+ y: 0,
38541
+ w: 1,
38542
+ h: 1
38543
+ });
38544
+ DEFAULT_DETAIL_CROP_CONVENTION.paddingRatio;
38437
38545
  /**
38438
38546
  * WHICH delivered frames the decode worker retains a native copy of.
38439
38547
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-petkit",
3
- "version": "0.2.22",
3
+ "version": "0.2.24",
4
4
  "description": "PetKit smart-feeder device-provider addon for CamStack — wraps the @apocaliss92/nodepetkit PetKit cloud client",
5
5
  "keywords": [
6
6
  "camstack",