@camstack/addon-provider-wyze 0.2.50 → 0.2.51

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 +259 -174
  2. package/dist/addon.mjs +259 -174
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -13233,6 +13233,114 @@ method(object({
13233
13233
  height: number()
13234
13234
  }), EmbeddingResultSchema, { auth: "admin" }), method(object({ text: string() }), EmbeddingResultSchema, { auth: "admin" }), method(_void(), EmbeddingInfoSchema, { auth: "admin" });
13235
13235
  /**
13236
+ * `failure-contribution` — the capability an addon reports its OWN losses
13237
+ * through, per camera, with the denominator attached. It stores nothing.
13238
+ *
13239
+ * ## The twin of `load-contribution`, and why it is a twin and not a field
13240
+ *
13241
+ * `load-contribution` answers *what did this camera COST*. This answers *what
13242
+ * did this camera LOSE*. The reporting discipline is identical and deliberately
13243
+ * copied: the contributor reports what it already knows, hub-main adds only
13244
+ * `addonId`, nothing needs global knowledge, and there is no central list for
13245
+ * somebody to forget to edit.
13246
+ *
13247
+ * They are not merged, because their invariants are opposites:
13248
+ *
13249
+ * - a `load-contribution` measurement is **absent, never zero** — a zero would
13250
+ * claim a camera cost nothing, which is a measurement nobody made;
13251
+ * - a `failure-contribution` zero is the **most valuable value on the
13252
+ * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
13253
+ * and it is exactly what an absent entry cannot say.
13254
+ *
13255
+ * Putting a loss counter on a cost entry would also break the reconciliation
13256
+ * that gives `load-contribution` its point: contributions are subtracted from
13257
+ * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
13258
+ * has no process.
13259
+ *
13260
+ * ## Why not a log line, since the counters already exist
13261
+ *
13262
+ * Several of these paths already counted themselves — `CaptureScheduler`'s
13263
+ * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
13264
+ * ends in a log line, and a log line is the thing the operator asked to stop
13265
+ * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
13266
+ * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
13267
+ * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
13268
+ * media blackout were both diagnosed. The counters stay; this is where they can
13269
+ * be READ.
13270
+ *
13271
+ * ## The rate is served with its denominator or not at all
13272
+ *
13273
+ * Every entry carries `attempts` and `succeeded`. A miss count alone is
13274
+ * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
13275
+ * than yesterday" and was **flat across twelve hours** once divided by the
13276
+ * successes on the same path. A surface that publishes only the numerator
13277
+ * reproduces that mistake on every read.
13278
+ *
13279
+ * ## Shape
13280
+ *
13281
+ * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
13282
+ * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
13283
+ * generated hooks, while `addons.listCapabilityProviders` still enumerates it
13284
+ * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
13285
+ * a forked runner's entries reach hub-main over transport that already exists.
13286
+ * No new UDS message, no second registry (D3). The operator reads the assembled
13287
+ * result through `system.getFailureContributions`.
13288
+ */
13289
+ var FailureReasonCountSchema = object({
13290
+ /**
13291
+ * Why the attempt did not land, in the contributor's own vocabulary —
13292
+ * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
13293
+ * strings that already appear in this repo's logs and, where one exists, the
13294
+ * same string the per-track `previewMissReason` records (D276): a second
13295
+ * vocabulary for the same loss would make the row and the counter
13296
+ * un-joinable.
13297
+ */
13298
+ reason: string(),
13299
+ count: number().int().nonnegative()
13300
+ });
13301
+ var FailureContributionSchema = object({
13302
+ /**
13303
+ * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
13304
+ * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
13305
+ * `unit` free: the families are owned by different addons and a shared enum
13306
+ * is a central list that rots invisibly.
13307
+ */
13308
+ family: string(),
13309
+ /**
13310
+ * The NUMERIC device id — the same value every log line carries as
13311
+ * `tags.deviceId`. Never nullable and never absent: a contributor that
13312
+ * cannot name the camera must not emit the entry, because a fleet total
13313
+ * cannot answer the only question anybody asks of this surface.
13314
+ */
13315
+ deviceId: number().int().positive(),
13316
+ /**
13317
+ * A second dimension inside the family: the model / step id for an inference
13318
+ * timeout, so "which camera AND which model" is one read. Absent when the
13319
+ * family has a single variant.
13320
+ */
13321
+ variant: string().optional(),
13322
+ /**
13323
+ * Epoch ms this counter started — the INCARNATION MARKER. A consumer
13324
+ * differencing two reads must drop the interval when it changes, because the
13325
+ * counter restarted from zero in a respawned runner. Same discipline as
13326
+ * `LoadContribution.startedAtMs`.
13327
+ */
13328
+ sinceMs: number(),
13329
+ /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
13330
+ atMs: number(),
13331
+ /**
13332
+ * THE DENOMINATOR — every attempt on this path for this camera in the
13333
+ * window. A failure count published without it is the mistake this schema
13334
+ * exists to make impossible.
13335
+ */
13336
+ attempts: number().int().nonnegative(),
13337
+ /** Attempts that landed. `attempts - succeeded` is the loss. */
13338
+ succeeded: number().int().nonnegative(),
13339
+ /** The loss, partitioned. Sums to `attempts - succeeded`. */
13340
+ reasons: array(FailureReasonCountSchema).readonly()
13341
+ });
13342
+ method(_void(), array(FailureContributionSchema).readonly());
13343
+ /**
13236
13344
  * filesystem-browse — per-node capability for browsing the node's local
13237
13345
  * filesystem. Reads are unconfined (whole filesystem, from `/` down); WRITES
13238
13346
  * are sandboxed to operator-configured allowed roots (D115). Used by the
@@ -13754,6 +13862,68 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
13754
13862
  kind: "mutation",
13755
13863
  auth: "admin"
13756
13864
  });
13865
+ var LoadContributionSchema = object({
13866
+ role: _enum([
13867
+ "decode",
13868
+ "transcode",
13869
+ "recording",
13870
+ "streaming",
13871
+ "detection"
13872
+ ]),
13873
+ /**
13874
+ * The NUMERIC device id — the same value every log line carries as
13875
+ * `tags.deviceId`. `null` means this cost genuinely belongs to no single
13876
+ * camera (a shared pool), NOT that the contributor forgot to look it up: a
13877
+ * contributor that cannot name its camera must not emit the entry at all,
13878
+ * because an unnamed per-camera entry is indistinguishable from a shared one
13879
+ * and would quietly turn one camera's cost into everybody's.
13880
+ */
13881
+ deviceId: number().int().positive().nullable(),
13882
+ attribution: _enum([
13883
+ "measured",
13884
+ "accounted",
13885
+ "unattributable"
13886
+ ]),
13887
+ /**
13888
+ * What ONE entry is, in the contributor's own words — `615/high`,
13889
+ * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
13890
+ * family and inventing a common one would lose the only information that
13891
+ * makes two entries for the same camera distinguishable.
13892
+ */
13893
+ unit: string(),
13894
+ /**
13895
+ * The OS process this cost lives in, when there is one. Present so a
13896
+ * consumer can (a) tell two generations of the same unit apart across a
13897
+ * restart, and (b) subtract claimed processes from the node's process
13898
+ * snapshot to see what NOBODY claimed. Absent for an entry that owns no
13899
+ * process of its own.
13900
+ */
13901
+ pid: number().int().positive().optional(),
13902
+ /**
13903
+ * When this generation started. The pid's incarnation marker: a consumer
13904
+ * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
13905
+ * window when this changes, because the counter restarted from zero in a new
13906
+ * process.
13907
+ */
13908
+ startedAtMs: number().optional(),
13909
+ /**
13910
+ * CUMULATIVE CPU seconds this unit has consumed since it started — user +
13911
+ * system, read from the child's own `/proc/<pid>/stat` at the moment the
13912
+ * contribution is asked for.
13913
+ *
13914
+ * Cumulative and not a rate on purpose: a rate needs a window, a window
13915
+ * needs a sampler, and a new per-node sampler is the defect half of
13916
+ * `docs/architecture/load-ledger.md` documents. A counter can be differenced
13917
+ * by whoever already keeps a history; a rate cannot be un-averaged.
13918
+ *
13919
+ * Absent — never zero — on a node with no `/proc`, on a read failure, and on
13920
+ * an entry with no process.
13921
+ */
13922
+ cpuSeconds: number().optional(),
13923
+ /** Resident bytes of this unit's process, same source and same rules. */
13924
+ rssBytes: number().optional()
13925
+ });
13926
+ method(_void(), array(LoadContributionSchema).readonly());
13757
13927
  /**
13758
13928
  * `log-channels` — the capability an addon DECLARES its diagnostic channels
13759
13929
  * through. It stores nothing.
@@ -13830,176 +14000,6 @@ method(LogEntrySchema, _void(), { kind: "mutation" }), method(object({
13830
14000
  tags: record(string(), string()).optional()
13831
14001
  }), array(LogEntrySchema).readonly());
13832
14002
  /**
13833
- * `failure-contribution` — the capability an addon reports its OWN losses
13834
- * through, per camera, with the denominator attached. It stores nothing.
13835
- *
13836
- * ## The twin of `load-contribution`, and why it is a twin and not a field
13837
- *
13838
- * `load-contribution` answers *what did this camera COST*. This answers *what
13839
- * did this camera LOSE*. The reporting discipline is identical and deliberately
13840
- * copied: the contributor reports what it already knows, hub-main adds only
13841
- * `addonId`, nothing needs global knowledge, and there is no central list for
13842
- * somebody to forget to edit.
13843
- *
13844
- * They are not merged, because their invariants are opposites:
13845
- *
13846
- * - a `load-contribution` measurement is **absent, never zero** — a zero would
13847
- * claim a camera cost nothing, which is a measurement nobody made;
13848
- * - a `failure-contribution` zero is the **most valuable value on the
13849
- * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
13850
- * and it is exactly what an absent entry cannot say.
13851
- *
13852
- * Putting a loss counter on a cost entry would also break the reconciliation
13853
- * that gives `load-contribution` its point: contributions are subtracted from
13854
- * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
13855
- * has no process.
13856
- *
13857
- * ## Why not a log line, since the counters already exist
13858
- *
13859
- * Several of these paths already counted themselves — `CaptureScheduler`'s
13860
- * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
13861
- * ends in a log line, and a log line is the thing the operator asked to stop
13862
- * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
13863
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
13864
- * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
13865
- * media blackout were both diagnosed. The counters stay; this is where they can
13866
- * be READ.
13867
- *
13868
- * ## The rate is served with its denominator or not at all
13869
- *
13870
- * Every entry carries `attempts` and `succeeded`. A miss count alone is
13871
- * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
13872
- * than yesterday" and was **flat across twelve hours** once divided by the
13873
- * successes on the same path. A surface that publishes only the numerator
13874
- * reproduces that mistake on every read.
13875
- *
13876
- * ## Shape
13877
- *
13878
- * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
13879
- * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
13880
- * generated hooks, while `addons.listCapabilityProviders` still enumerates it
13881
- * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
13882
- * a forked runner's entries reach hub-main over transport that already exists.
13883
- * No new UDS message, no second registry (D3). The operator reads the assembled
13884
- * result through `system.getFailureContributions`.
13885
- */
13886
- var FailureReasonCountSchema = object({
13887
- /**
13888
- * Why the attempt did not land, in the contributor's own vocabulary —
13889
- * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
13890
- * strings that already appear in this repo's logs and, where one exists, the
13891
- * same string the per-track `previewMissReason` records (D276): a second
13892
- * vocabulary for the same loss would make the row and the counter
13893
- * un-joinable.
13894
- */
13895
- reason: string(),
13896
- count: number().int().nonnegative()
13897
- });
13898
- var FailureContributionSchema = object({
13899
- /**
13900
- * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
13901
- * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
13902
- * `unit` free: the families are owned by different addons and a shared enum
13903
- * is a central list that rots invisibly.
13904
- */
13905
- family: string(),
13906
- /**
13907
- * The NUMERIC device id — the same value every log line carries as
13908
- * `tags.deviceId`. Never nullable and never absent: a contributor that
13909
- * cannot name the camera must not emit the entry, because a fleet total
13910
- * cannot answer the only question anybody asks of this surface.
13911
- */
13912
- deviceId: number().int().positive(),
13913
- /**
13914
- * A second dimension inside the family: the model / step id for an inference
13915
- * timeout, so "which camera AND which model" is one read. Absent when the
13916
- * family has a single variant.
13917
- */
13918
- variant: string().optional(),
13919
- /**
13920
- * Epoch ms this counter started — the INCARNATION MARKER. A consumer
13921
- * differencing two reads must drop the interval when it changes, because the
13922
- * counter restarted from zero in a respawned runner. Same discipline as
13923
- * `LoadContribution.startedAtMs`.
13924
- */
13925
- sinceMs: number(),
13926
- /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
13927
- atMs: number(),
13928
- /**
13929
- * THE DENOMINATOR — every attempt on this path for this camera in the
13930
- * window. A failure count published without it is the mistake this schema
13931
- * exists to make impossible.
13932
- */
13933
- attempts: number().int().nonnegative(),
13934
- /** Attempts that landed. `attempts - succeeded` is the loss. */
13935
- succeeded: number().int().nonnegative(),
13936
- /** The loss, partitioned. Sums to `attempts - succeeded`. */
13937
- reasons: array(FailureReasonCountSchema).readonly()
13938
- });
13939
- method(_void(), array(FailureContributionSchema).readonly());
13940
- var LoadContributionSchema = object({
13941
- role: _enum([
13942
- "decode",
13943
- "transcode",
13944
- "recording",
13945
- "streaming",
13946
- "detection"
13947
- ]),
13948
- /**
13949
- * The NUMERIC device id — the same value every log line carries as
13950
- * `tags.deviceId`. `null` means this cost genuinely belongs to no single
13951
- * camera (a shared pool), NOT that the contributor forgot to look it up: a
13952
- * contributor that cannot name its camera must not emit the entry at all,
13953
- * because an unnamed per-camera entry is indistinguishable from a shared one
13954
- * and would quietly turn one camera's cost into everybody's.
13955
- */
13956
- deviceId: number().int().positive().nullable(),
13957
- attribution: _enum([
13958
- "measured",
13959
- "accounted",
13960
- "unattributable"
13961
- ]),
13962
- /**
13963
- * What ONE entry is, in the contributor's own words — `615/high`,
13964
- * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
13965
- * family and inventing a common one would lose the only information that
13966
- * makes two entries for the same camera distinguishable.
13967
- */
13968
- unit: string(),
13969
- /**
13970
- * The OS process this cost lives in, when there is one. Present so a
13971
- * consumer can (a) tell two generations of the same unit apart across a
13972
- * restart, and (b) subtract claimed processes from the node's process
13973
- * snapshot to see what NOBODY claimed. Absent for an entry that owns no
13974
- * process of its own.
13975
- */
13976
- pid: number().int().positive().optional(),
13977
- /**
13978
- * When this generation started. The pid's incarnation marker: a consumer
13979
- * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
13980
- * window when this changes, because the counter restarted from zero in a new
13981
- * process.
13982
- */
13983
- startedAtMs: number().optional(),
13984
- /**
13985
- * CUMULATIVE CPU seconds this unit has consumed since it started — user +
13986
- * system, read from the child's own `/proc/<pid>/stat` at the moment the
13987
- * contribution is asked for.
13988
- *
13989
- * Cumulative and not a rate on purpose: a rate needs a window, a window
13990
- * needs a sampler, and a new per-node sampler is the defect half of
13991
- * `docs/architecture/load-ledger.md` documents. A counter can be differenced
13992
- * by whoever already keeps a history; a rate cannot be un-averaged.
13993
- *
13994
- * Absent — never zero — on a node with no `/proc`, on a read failure, and on
13995
- * an entry with no process.
13996
- */
13997
- cpuSeconds: number().optional(),
13998
- /** Resident bytes of this unit's process, same source and same rules. */
13999
- rssBytes: number().optional()
14000
- });
14001
- method(_void(), array(LoadContributionSchema).readonly());
14002
- /**
14003
14003
  * `login-method` — collection cap through which auth addons contribute
14004
14004
  * their pre-auth login surfaces to the login page. This is the SINGLE,
14005
14005
  * generic mechanism that supersedes the dead `auth.listProviders` reader:
@@ -18737,12 +18737,53 @@ var MediaFileKindEnum = _enum([
18737
18737
  "keyFrameSmall",
18738
18738
  "thumbnailSmall"
18739
18739
  ]);
18740
+ /**
18741
+ * One media row ON THE WIRE: what it is, how big it is, and WHERE ITS BYTES
18742
+ * ARE — never the bytes themselves.
18743
+ *
18744
+ * ## Why `url` and not `base64`
18745
+ *
18746
+ * Measured on the live hub 2026-08-30: `getTrackMedia {trackId, deviceId}`
18747
+ * with no `kinds` returned 6 rows / **3 597 219 B**, of which `keyFrame` alone
18748
+ * was **2 824 077 B** — one full-resolution frame, base64, so +33 % on the
18749
+ * wire. Forty events is ~144 MB. Every byte of it was read off disk,
18750
+ * base64-encoded, held whole in a unary tRPC envelope, and materialised in
18751
+ * hub-main's heap on the way past — for an `<img>` that would have cached it.
18752
+ *
18753
+ * `url` points at the `event-media` data plane
18754
+ * (`/addon/<addonId>/event-media/<storedKey>`), which serves the same blob
18755
+ * with an ETag and `Cache-Control: immutable`, honours conditional GETs, can
18756
+ * render a `?variant=thumb`, and streams. The hub gate in front of it requires
18757
+ * a bearer or the session cookie (`access: 'authenticated'`), so the bytes are
18758
+ * no less protected than they were inside a `view`-level cap response — see
18759
+ * `data-plane-access.ts` for the rule and the one gap it does not close
18760
+ * (per-device scoping).
18761
+ *
18762
+ * The URL is built from the row's **stored** key, which is not always its
18763
+ * published `kind`: a track's face/plate crop is stored as `crop` under
18764
+ * `('face'|'plate', '<prefix>-<trackId>')` and published as
18765
+ * `faceCrop`/`plateCrop`. `MediaStore.getByKey` knows only the stored key.
18766
+ *
18767
+ * ## `base64` is TRANSITIONAL and is going away
18768
+ *
18769
+ * It is still populated for one reason: the deployed viewer's track-detail
18770
+ * HERO tile reads it (`use-track-media-entry.ts` → `parseMediaFiles`, which
18771
+ * REQUIRES the field), and a row without it parses as a FAILED read — the red
18772
+ * triangle — not as absence. Removing the field before that viewer ships is an
18773
+ * outage, not a cleanup. Once the viewer takes its hero bytes from `url`,
18774
+ * delete this line and the `withBytes` pass-through in
18775
+ * `analytics-query-facade.ts`; nothing else reads it.
18776
+ */
18740
18777
  var MediaFileSchema = object({
18741
18778
  key: string(),
18742
18779
  kind: MediaFileKindEnum,
18743
- base64: string(),
18744
18780
  sizeBytes: number(),
18745
18781
  timestamp: number()
18782
+ }).extend({
18783
+ /** `/addon/<addonId>/event-media/<encoded stored key>`. Always present. */
18784
+ url: string(),
18785
+ /** @deprecated Transitional — see the schema docblock. Use {@link url}. */
18786
+ base64: string()
18746
18787
  });
18747
18788
  /**
18748
18789
  * One media row WITHOUT its bytes.
@@ -18754,7 +18795,9 @@ var MediaFileSchema = object({
18754
18795
  * blocks the whole view.
18755
18796
  *
18756
18797
  * `sizeBytes` is carried because it is what lets a client decide between the
18757
- * stored blob and a `?variant=thumb` rendering without fetching either.
18798
+ * stored blob and a `?variant=thumb` rendering without fetching either, and
18799
+ * `url` because a client that had to build the plane path itself is a second
18800
+ * copy of a route — the embed, the viewer and the admin UI each grew one.
18758
18801
  */
18759
18802
  var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
18760
18803
  /**
@@ -19443,6 +19486,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19443
19486
  }), array(MediaFileSchema).readonly()), method(object({
19444
19487
  trackId: string(),
19445
19488
  deviceId: number()
19489
+ }), array(MediaFileInfoSchema).readonly()), method(object({
19490
+ eventId: string(),
19491
+ deviceId: number()
19446
19492
  }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
19447
19493
  kind: "mutation",
19448
19494
  auth: "admin"
@@ -24545,10 +24591,24 @@ var FaceClusterSchema = object({
24545
24591
  size: number().int(),
24546
24592
  cohesion: number()
24547
24593
  });
24594
+ /**
24595
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
24596
+ * are — never the bytes.
24597
+ *
24598
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
24599
+ * track/event contract) is still populated because a deployed viewer requires
24600
+ * the field to parse a row at all; this method has no such reader. Its ONE
24601
+ * caller is the admin UI's detail modal, which was building
24602
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
24603
+ * dialog already rendering its key FRAME from the `event-media` plane.
24604
+ *
24605
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
24606
+ * media key directly, so this needed no new plane and no new access decision.
24607
+ */
24548
24608
  var MediaFileLiteSchema$1 = object({
24549
24609
  key: string(),
24550
24610
  kind: string(),
24551
- base64: string(),
24611
+ url: string(),
24552
24612
  sizeBytes: number(),
24553
24613
  timestamp: number()
24554
24614
  });
@@ -27570,10 +27630,24 @@ var PlateInfoSchema = object({
27570
27630
  */
27571
27631
  cropUrl: string().optional()
27572
27632
  });
27633
+ /**
27634
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
27635
+ * are — never the bytes.
27636
+ *
27637
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
27638
+ * track/event contract) is still populated because a deployed viewer requires
27639
+ * the field to parse a row at all; this method has no such reader. Its ONE
27640
+ * caller is the admin UI's detail modal, which was building
27641
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
27642
+ * dialog already rendering its key FRAME from the `event-media` plane.
27643
+ *
27644
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
27645
+ * media key directly, so this needed no new plane and no new access decision.
27646
+ */
27573
27647
  var MediaFileLiteSchema = object({
27574
27648
  key: string(),
27575
27649
  kind: string(),
27576
- base64: string(),
27650
+ url: string(),
27577
27651
  sizeBytes: number(),
27578
27652
  timestamp: number()
27579
27653
  });
@@ -35470,6 +35544,12 @@ Object.freeze({
35470
35544
  addonId: null,
35471
35545
  access: "view"
35472
35546
  },
35547
+ "pipelineAnalytics.listEventMedia": {
35548
+ capName: "pipeline-analytics",
35549
+ capScope: "device",
35550
+ addonId: null,
35551
+ access: "view"
35552
+ },
35473
35553
  "pipelineAnalytics.listGroups": {
35474
35554
  capName: "pipeline-analytics",
35475
35555
  capScope: "device",
@@ -39093,6 +39173,11 @@ Object.freeze({
39093
39173
  form: "array",
39094
39174
  optional: false
39095
39175
  }],
39176
+ "pipelineAnalytics.listEventMedia": [{
39177
+ name: "deviceId",
39178
+ form: "single",
39179
+ optional: false
39180
+ }],
39096
39181
  "pipelineAnalytics.listGroups": [{
39097
39182
  name: "deviceIds",
39098
39183
  form: "array",
package/dist/addon.mjs CHANGED
@@ -13212,6 +13212,114 @@ method(object({
13212
13212
  height: number()
13213
13213
  }), EmbeddingResultSchema, { auth: "admin" }), method(object({ text: string() }), EmbeddingResultSchema, { auth: "admin" }), method(_void(), EmbeddingInfoSchema, { auth: "admin" });
13214
13214
  /**
13215
+ * `failure-contribution` — the capability an addon reports its OWN losses
13216
+ * through, per camera, with the denominator attached. It stores nothing.
13217
+ *
13218
+ * ## The twin of `load-contribution`, and why it is a twin and not a field
13219
+ *
13220
+ * `load-contribution` answers *what did this camera COST*. This answers *what
13221
+ * did this camera LOSE*. The reporting discipline is identical and deliberately
13222
+ * copied: the contributor reports what it already knows, hub-main adds only
13223
+ * `addonId`, nothing needs global knowledge, and there is no central list for
13224
+ * somebody to forget to edit.
13225
+ *
13226
+ * They are not merged, because their invariants are opposites:
13227
+ *
13228
+ * - a `load-contribution` measurement is **absent, never zero** — a zero would
13229
+ * claim a camera cost nothing, which is a measurement nobody made;
13230
+ * - a `failure-contribution` zero is the **most valuable value on the
13231
+ * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
13232
+ * and it is exactly what an absent entry cannot say.
13233
+ *
13234
+ * Putting a loss counter on a cost entry would also break the reconciliation
13235
+ * that gives `load-contribution` its point: contributions are subtracted from
13236
+ * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
13237
+ * has no process.
13238
+ *
13239
+ * ## Why not a log line, since the counters already exist
13240
+ *
13241
+ * Several of these paths already counted themselves — `CaptureScheduler`'s
13242
+ * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
13243
+ * ends in a log line, and a log line is the thing the operator asked to stop
13244
+ * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
13245
+ * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
13246
+ * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
13247
+ * media blackout were both diagnosed. The counters stay; this is where they can
13248
+ * be READ.
13249
+ *
13250
+ * ## The rate is served with its denominator or not at all
13251
+ *
13252
+ * Every entry carries `attempts` and `succeeded`. A miss count alone is
13253
+ * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
13254
+ * than yesterday" and was **flat across twelve hours** once divided by the
13255
+ * successes on the same path. A surface that publishes only the numerator
13256
+ * reproduces that mistake on every read.
13257
+ *
13258
+ * ## Shape
13259
+ *
13260
+ * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
13261
+ * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
13262
+ * generated hooks, while `addons.listCapabilityProviders` still enumerates it
13263
+ * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
13264
+ * a forked runner's entries reach hub-main over transport that already exists.
13265
+ * No new UDS message, no second registry (D3). The operator reads the assembled
13266
+ * result through `system.getFailureContributions`.
13267
+ */
13268
+ var FailureReasonCountSchema = object({
13269
+ /**
13270
+ * Why the attempt did not land, in the contributor's own vocabulary —
13271
+ * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
13272
+ * strings that already appear in this repo's logs and, where one exists, the
13273
+ * same string the per-track `previewMissReason` records (D276): a second
13274
+ * vocabulary for the same loss would make the row and the counter
13275
+ * un-joinable.
13276
+ */
13277
+ reason: string(),
13278
+ count: number().int().nonnegative()
13279
+ });
13280
+ var FailureContributionSchema = object({
13281
+ /**
13282
+ * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
13283
+ * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
13284
+ * `unit` free: the families are owned by different addons and a shared enum
13285
+ * is a central list that rots invisibly.
13286
+ */
13287
+ family: string(),
13288
+ /**
13289
+ * The NUMERIC device id — the same value every log line carries as
13290
+ * `tags.deviceId`. Never nullable and never absent: a contributor that
13291
+ * cannot name the camera must not emit the entry, because a fleet total
13292
+ * cannot answer the only question anybody asks of this surface.
13293
+ */
13294
+ deviceId: number().int().positive(),
13295
+ /**
13296
+ * A second dimension inside the family: the model / step id for an inference
13297
+ * timeout, so "which camera AND which model" is one read. Absent when the
13298
+ * family has a single variant.
13299
+ */
13300
+ variant: string().optional(),
13301
+ /**
13302
+ * Epoch ms this counter started — the INCARNATION MARKER. A consumer
13303
+ * differencing two reads must drop the interval when it changes, because the
13304
+ * counter restarted from zero in a respawned runner. Same discipline as
13305
+ * `LoadContribution.startedAtMs`.
13306
+ */
13307
+ sinceMs: number(),
13308
+ /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
13309
+ atMs: number(),
13310
+ /**
13311
+ * THE DENOMINATOR — every attempt on this path for this camera in the
13312
+ * window. A failure count published without it is the mistake this schema
13313
+ * exists to make impossible.
13314
+ */
13315
+ attempts: number().int().nonnegative(),
13316
+ /** Attempts that landed. `attempts - succeeded` is the loss. */
13317
+ succeeded: number().int().nonnegative(),
13318
+ /** The loss, partitioned. Sums to `attempts - succeeded`. */
13319
+ reasons: array(FailureReasonCountSchema).readonly()
13320
+ });
13321
+ method(_void(), array(FailureContributionSchema).readonly());
13322
+ /**
13215
13323
  * filesystem-browse — per-node capability for browsing the node's local
13216
13324
  * filesystem. Reads are unconfined (whole filesystem, from `/` down); WRITES
13217
13325
  * are sandboxed to operator-configured allowed roots (D115). Used by the
@@ -13733,6 +13841,68 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
13733
13841
  kind: "mutation",
13734
13842
  auth: "admin"
13735
13843
  });
13844
+ var LoadContributionSchema = object({
13845
+ role: _enum([
13846
+ "decode",
13847
+ "transcode",
13848
+ "recording",
13849
+ "streaming",
13850
+ "detection"
13851
+ ]),
13852
+ /**
13853
+ * The NUMERIC device id — the same value every log line carries as
13854
+ * `tags.deviceId`. `null` means this cost genuinely belongs to no single
13855
+ * camera (a shared pool), NOT that the contributor forgot to look it up: a
13856
+ * contributor that cannot name its camera must not emit the entry at all,
13857
+ * because an unnamed per-camera entry is indistinguishable from a shared one
13858
+ * and would quietly turn one camera's cost into everybody's.
13859
+ */
13860
+ deviceId: number().int().positive().nullable(),
13861
+ attribution: _enum([
13862
+ "measured",
13863
+ "accounted",
13864
+ "unattributable"
13865
+ ]),
13866
+ /**
13867
+ * What ONE entry is, in the contributor's own words — `615/high`,
13868
+ * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
13869
+ * family and inventing a common one would lose the only information that
13870
+ * makes two entries for the same camera distinguishable.
13871
+ */
13872
+ unit: string(),
13873
+ /**
13874
+ * The OS process this cost lives in, when there is one. Present so a
13875
+ * consumer can (a) tell two generations of the same unit apart across a
13876
+ * restart, and (b) subtract claimed processes from the node's process
13877
+ * snapshot to see what NOBODY claimed. Absent for an entry that owns no
13878
+ * process of its own.
13879
+ */
13880
+ pid: number().int().positive().optional(),
13881
+ /**
13882
+ * When this generation started. The pid's incarnation marker: a consumer
13883
+ * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
13884
+ * window when this changes, because the counter restarted from zero in a new
13885
+ * process.
13886
+ */
13887
+ startedAtMs: number().optional(),
13888
+ /**
13889
+ * CUMULATIVE CPU seconds this unit has consumed since it started — user +
13890
+ * system, read from the child's own `/proc/<pid>/stat` at the moment the
13891
+ * contribution is asked for.
13892
+ *
13893
+ * Cumulative and not a rate on purpose: a rate needs a window, a window
13894
+ * needs a sampler, and a new per-node sampler is the defect half of
13895
+ * `docs/architecture/load-ledger.md` documents. A counter can be differenced
13896
+ * by whoever already keeps a history; a rate cannot be un-averaged.
13897
+ *
13898
+ * Absent — never zero — on a node with no `/proc`, on a read failure, and on
13899
+ * an entry with no process.
13900
+ */
13901
+ cpuSeconds: number().optional(),
13902
+ /** Resident bytes of this unit's process, same source and same rules. */
13903
+ rssBytes: number().optional()
13904
+ });
13905
+ method(_void(), array(LoadContributionSchema).readonly());
13736
13906
  /**
13737
13907
  * `log-channels` — the capability an addon DECLARES its diagnostic channels
13738
13908
  * through. It stores nothing.
@@ -13809,176 +13979,6 @@ method(LogEntrySchema, _void(), { kind: "mutation" }), method(object({
13809
13979
  tags: record(string(), string()).optional()
13810
13980
  }), array(LogEntrySchema).readonly());
13811
13981
  /**
13812
- * `failure-contribution` — the capability an addon reports its OWN losses
13813
- * through, per camera, with the denominator attached. It stores nothing.
13814
- *
13815
- * ## The twin of `load-contribution`, and why it is a twin and not a field
13816
- *
13817
- * `load-contribution` answers *what did this camera COST*. This answers *what
13818
- * did this camera LOSE*. The reporting discipline is identical and deliberately
13819
- * copied: the contributor reports what it already knows, hub-main adds only
13820
- * `addonId`, nothing needs global knowledge, and there is no central list for
13821
- * somebody to forget to edit.
13822
- *
13823
- * They are not merged, because their invariants are opposites:
13824
- *
13825
- * - a `load-contribution` measurement is **absent, never zero** — a zero would
13826
- * claim a camera cost nothing, which is a measurement nobody made;
13827
- * - a `failure-contribution` zero is the **most valuable value on the
13828
- * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
13829
- * and it is exactly what an absent entry cannot say.
13830
- *
13831
- * Putting a loss counter on a cost entry would also break the reconciliation
13832
- * that gives `load-contribution` its point: contributions are subtracted from
13833
- * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
13834
- * has no process.
13835
- *
13836
- * ## Why not a log line, since the counters already exist
13837
- *
13838
- * Several of these paths already counted themselves — `CaptureScheduler`'s
13839
- * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
13840
- * ends in a log line, and a log line is the thing the operator asked to stop
13841
- * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
13842
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
13843
- * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
13844
- * media blackout were both diagnosed. The counters stay; this is where they can
13845
- * be READ.
13846
- *
13847
- * ## The rate is served with its denominator or not at all
13848
- *
13849
- * Every entry carries `attempts` and `succeeded`. A miss count alone is
13850
- * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
13851
- * than yesterday" and was **flat across twelve hours** once divided by the
13852
- * successes on the same path. A surface that publishes only the numerator
13853
- * reproduces that mistake on every read.
13854
- *
13855
- * ## Shape
13856
- *
13857
- * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
13858
- * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
13859
- * generated hooks, while `addons.listCapabilityProviders` still enumerates it
13860
- * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
13861
- * a forked runner's entries reach hub-main over transport that already exists.
13862
- * No new UDS message, no second registry (D3). The operator reads the assembled
13863
- * result through `system.getFailureContributions`.
13864
- */
13865
- var FailureReasonCountSchema = object({
13866
- /**
13867
- * Why the attempt did not land, in the contributor's own vocabulary —
13868
- * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
13869
- * strings that already appear in this repo's logs and, where one exists, the
13870
- * same string the per-track `previewMissReason` records (D276): a second
13871
- * vocabulary for the same loss would make the row and the counter
13872
- * un-joinable.
13873
- */
13874
- reason: string(),
13875
- count: number().int().nonnegative()
13876
- });
13877
- var FailureContributionSchema = object({
13878
- /**
13879
- * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
13880
- * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
13881
- * `unit` free: the families are owned by different addons and a shared enum
13882
- * is a central list that rots invisibly.
13883
- */
13884
- family: string(),
13885
- /**
13886
- * The NUMERIC device id — the same value every log line carries as
13887
- * `tags.deviceId`. Never nullable and never absent: a contributor that
13888
- * cannot name the camera must not emit the entry, because a fleet total
13889
- * cannot answer the only question anybody asks of this surface.
13890
- */
13891
- deviceId: number().int().positive(),
13892
- /**
13893
- * A second dimension inside the family: the model / step id for an inference
13894
- * timeout, so "which camera AND which model" is one read. Absent when the
13895
- * family has a single variant.
13896
- */
13897
- variant: string().optional(),
13898
- /**
13899
- * Epoch ms this counter started — the INCARNATION MARKER. A consumer
13900
- * differencing two reads must drop the interval when it changes, because the
13901
- * counter restarted from zero in a respawned runner. Same discipline as
13902
- * `LoadContribution.startedAtMs`.
13903
- */
13904
- sinceMs: number(),
13905
- /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
13906
- atMs: number(),
13907
- /**
13908
- * THE DENOMINATOR — every attempt on this path for this camera in the
13909
- * window. A failure count published without it is the mistake this schema
13910
- * exists to make impossible.
13911
- */
13912
- attempts: number().int().nonnegative(),
13913
- /** Attempts that landed. `attempts - succeeded` is the loss. */
13914
- succeeded: number().int().nonnegative(),
13915
- /** The loss, partitioned. Sums to `attempts - succeeded`. */
13916
- reasons: array(FailureReasonCountSchema).readonly()
13917
- });
13918
- method(_void(), array(FailureContributionSchema).readonly());
13919
- var LoadContributionSchema = object({
13920
- role: _enum([
13921
- "decode",
13922
- "transcode",
13923
- "recording",
13924
- "streaming",
13925
- "detection"
13926
- ]),
13927
- /**
13928
- * The NUMERIC device id — the same value every log line carries as
13929
- * `tags.deviceId`. `null` means this cost genuinely belongs to no single
13930
- * camera (a shared pool), NOT that the contributor forgot to look it up: a
13931
- * contributor that cannot name its camera must not emit the entry at all,
13932
- * because an unnamed per-camera entry is indistinguishable from a shared one
13933
- * and would quietly turn one camera's cost into everybody's.
13934
- */
13935
- deviceId: number().int().positive().nullable(),
13936
- attribution: _enum([
13937
- "measured",
13938
- "accounted",
13939
- "unattributable"
13940
- ]),
13941
- /**
13942
- * What ONE entry is, in the contributor's own words — `615/high`,
13943
- * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
13944
- * family and inventing a common one would lose the only information that
13945
- * makes two entries for the same camera distinguishable.
13946
- */
13947
- unit: string(),
13948
- /**
13949
- * The OS process this cost lives in, when there is one. Present so a
13950
- * consumer can (a) tell two generations of the same unit apart across a
13951
- * restart, and (b) subtract claimed processes from the node's process
13952
- * snapshot to see what NOBODY claimed. Absent for an entry that owns no
13953
- * process of its own.
13954
- */
13955
- pid: number().int().positive().optional(),
13956
- /**
13957
- * When this generation started. The pid's incarnation marker: a consumer
13958
- * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
13959
- * window when this changes, because the counter restarted from zero in a new
13960
- * process.
13961
- */
13962
- startedAtMs: number().optional(),
13963
- /**
13964
- * CUMULATIVE CPU seconds this unit has consumed since it started — user +
13965
- * system, read from the child's own `/proc/<pid>/stat` at the moment the
13966
- * contribution is asked for.
13967
- *
13968
- * Cumulative and not a rate on purpose: a rate needs a window, a window
13969
- * needs a sampler, and a new per-node sampler is the defect half of
13970
- * `docs/architecture/load-ledger.md` documents. A counter can be differenced
13971
- * by whoever already keeps a history; a rate cannot be un-averaged.
13972
- *
13973
- * Absent — never zero — on a node with no `/proc`, on a read failure, and on
13974
- * an entry with no process.
13975
- */
13976
- cpuSeconds: number().optional(),
13977
- /** Resident bytes of this unit's process, same source and same rules. */
13978
- rssBytes: number().optional()
13979
- });
13980
- method(_void(), array(LoadContributionSchema).readonly());
13981
- /**
13982
13982
  * `login-method` — collection cap through which auth addons contribute
13983
13983
  * their pre-auth login surfaces to the login page. This is the SINGLE,
13984
13984
  * generic mechanism that supersedes the dead `auth.listProviders` reader:
@@ -18716,12 +18716,53 @@ var MediaFileKindEnum = _enum([
18716
18716
  "keyFrameSmall",
18717
18717
  "thumbnailSmall"
18718
18718
  ]);
18719
+ /**
18720
+ * One media row ON THE WIRE: what it is, how big it is, and WHERE ITS BYTES
18721
+ * ARE — never the bytes themselves.
18722
+ *
18723
+ * ## Why `url` and not `base64`
18724
+ *
18725
+ * Measured on the live hub 2026-08-30: `getTrackMedia {trackId, deviceId}`
18726
+ * with no `kinds` returned 6 rows / **3 597 219 B**, of which `keyFrame` alone
18727
+ * was **2 824 077 B** — one full-resolution frame, base64, so +33 % on the
18728
+ * wire. Forty events is ~144 MB. Every byte of it was read off disk,
18729
+ * base64-encoded, held whole in a unary tRPC envelope, and materialised in
18730
+ * hub-main's heap on the way past — for an `<img>` that would have cached it.
18731
+ *
18732
+ * `url` points at the `event-media` data plane
18733
+ * (`/addon/<addonId>/event-media/<storedKey>`), which serves the same blob
18734
+ * with an ETag and `Cache-Control: immutable`, honours conditional GETs, can
18735
+ * render a `?variant=thumb`, and streams. The hub gate in front of it requires
18736
+ * a bearer or the session cookie (`access: 'authenticated'`), so the bytes are
18737
+ * no less protected than they were inside a `view`-level cap response — see
18738
+ * `data-plane-access.ts` for the rule and the one gap it does not close
18739
+ * (per-device scoping).
18740
+ *
18741
+ * The URL is built from the row's **stored** key, which is not always its
18742
+ * published `kind`: a track's face/plate crop is stored as `crop` under
18743
+ * `('face'|'plate', '<prefix>-<trackId>')` and published as
18744
+ * `faceCrop`/`plateCrop`. `MediaStore.getByKey` knows only the stored key.
18745
+ *
18746
+ * ## `base64` is TRANSITIONAL and is going away
18747
+ *
18748
+ * It is still populated for one reason: the deployed viewer's track-detail
18749
+ * HERO tile reads it (`use-track-media-entry.ts` → `parseMediaFiles`, which
18750
+ * REQUIRES the field), and a row without it parses as a FAILED read — the red
18751
+ * triangle — not as absence. Removing the field before that viewer ships is an
18752
+ * outage, not a cleanup. Once the viewer takes its hero bytes from `url`,
18753
+ * delete this line and the `withBytes` pass-through in
18754
+ * `analytics-query-facade.ts`; nothing else reads it.
18755
+ */
18719
18756
  var MediaFileSchema = object({
18720
18757
  key: string(),
18721
18758
  kind: MediaFileKindEnum,
18722
- base64: string(),
18723
18759
  sizeBytes: number(),
18724
18760
  timestamp: number()
18761
+ }).extend({
18762
+ /** `/addon/<addonId>/event-media/<encoded stored key>`. Always present. */
18763
+ url: string(),
18764
+ /** @deprecated Transitional — see the schema docblock. Use {@link url}. */
18765
+ base64: string()
18725
18766
  });
18726
18767
  /**
18727
18768
  * One media row WITHOUT its bytes.
@@ -18733,7 +18774,9 @@ var MediaFileSchema = object({
18733
18774
  * blocks the whole view.
18734
18775
  *
18735
18776
  * `sizeBytes` is carried because it is what lets a client decide between the
18736
- * stored blob and a `?variant=thumb` rendering without fetching either.
18777
+ * stored blob and a `?variant=thumb` rendering without fetching either, and
18778
+ * `url` because a client that had to build the plane path itself is a second
18779
+ * copy of a route — the embed, the viewer and the admin UI each grew one.
18737
18780
  */
18738
18781
  var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
18739
18782
  /**
@@ -19422,6 +19465,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19422
19465
  }), array(MediaFileSchema).readonly()), method(object({
19423
19466
  trackId: string(),
19424
19467
  deviceId: number()
19468
+ }), array(MediaFileInfoSchema).readonly()), method(object({
19469
+ eventId: string(),
19470
+ deviceId: number()
19425
19471
  }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
19426
19472
  kind: "mutation",
19427
19473
  auth: "admin"
@@ -24524,10 +24570,24 @@ var FaceClusterSchema = object({
24524
24570
  size: number().int(),
24525
24571
  cohesion: number()
24526
24572
  });
24573
+ /**
24574
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
24575
+ * are — never the bytes.
24576
+ *
24577
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
24578
+ * track/event contract) is still populated because a deployed viewer requires
24579
+ * the field to parse a row at all; this method has no such reader. Its ONE
24580
+ * caller is the admin UI's detail modal, which was building
24581
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
24582
+ * dialog already rendering its key FRAME from the `event-media` plane.
24583
+ *
24584
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
24585
+ * media key directly, so this needed no new plane and no new access decision.
24586
+ */
24527
24587
  var MediaFileLiteSchema$1 = object({
24528
24588
  key: string(),
24529
24589
  kind: string(),
24530
- base64: string(),
24590
+ url: string(),
24531
24591
  sizeBytes: number(),
24532
24592
  timestamp: number()
24533
24593
  });
@@ -27549,10 +27609,24 @@ var PlateInfoSchema = object({
27549
27609
  */
27550
27610
  cropUrl: string().optional()
27551
27611
  });
27612
+ /**
27613
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
27614
+ * are — never the bytes.
27615
+ *
27616
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
27617
+ * track/event contract) is still populated because a deployed viewer requires
27618
+ * the field to parse a row at all; this method has no such reader. Its ONE
27619
+ * caller is the admin UI's detail modal, which was building
27620
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
27621
+ * dialog already rendering its key FRAME from the `event-media` plane.
27622
+ *
27623
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
27624
+ * media key directly, so this needed no new plane and no new access decision.
27625
+ */
27552
27626
  var MediaFileLiteSchema = object({
27553
27627
  key: string(),
27554
27628
  kind: string(),
27555
- base64: string(),
27629
+ url: string(),
27556
27630
  sizeBytes: number(),
27557
27631
  timestamp: number()
27558
27632
  });
@@ -35449,6 +35523,12 @@ Object.freeze({
35449
35523
  addonId: null,
35450
35524
  access: "view"
35451
35525
  },
35526
+ "pipelineAnalytics.listEventMedia": {
35527
+ capName: "pipeline-analytics",
35528
+ capScope: "device",
35529
+ addonId: null,
35530
+ access: "view"
35531
+ },
35452
35532
  "pipelineAnalytics.listGroups": {
35453
35533
  capName: "pipeline-analytics",
35454
35534
  capScope: "device",
@@ -39072,6 +39152,11 @@ Object.freeze({
39072
39152
  form: "array",
39073
39153
  optional: false
39074
39154
  }],
39155
+ "pipelineAnalytics.listEventMedia": [{
39156
+ name: "deviceId",
39157
+ form: "single",
39158
+ optional: false
39159
+ }],
39075
39160
  "pipelineAnalytics.listGroups": [{
39076
39161
  name: "deviceIds",
39077
39162
  form: "array",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-wyze",
3
- "version": "0.2.50",
3
+ "version": "0.2.51",
4
4
  "description": "Wyze camera device-provider addon for CamStack — wraps the @apocaliss92/wyze-bridge-js P2P/DTLS client, feeding the stream-broker via the pull-rfc4571 lazy-publish path (a structural twin of addon-provider-reolink)",
5
5
  "keywords": [
6
6
  "camstack",