@camstack/addon-provider-dreame 0.2.46 → 0.2.47

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
@@ -63706,6 +63706,114 @@ method(object({
63706
63706
  height: number()
63707
63707
  }), EmbeddingResultSchema, { auth: "admin" }), method(object({ text: string() }), EmbeddingResultSchema, { auth: "admin" }), method(_void(), EmbeddingInfoSchema, { auth: "admin" });
63708
63708
  /**
63709
+ * `failure-contribution` — the capability an addon reports its OWN losses
63710
+ * through, per camera, with the denominator attached. It stores nothing.
63711
+ *
63712
+ * ## The twin of `load-contribution`, and why it is a twin and not a field
63713
+ *
63714
+ * `load-contribution` answers *what did this camera COST*. This answers *what
63715
+ * did this camera LOSE*. The reporting discipline is identical and deliberately
63716
+ * copied: the contributor reports what it already knows, hub-main adds only
63717
+ * `addonId`, nothing needs global knowledge, and there is no central list for
63718
+ * somebody to forget to edit.
63719
+ *
63720
+ * They are not merged, because their invariants are opposites:
63721
+ *
63722
+ * - a `load-contribution` measurement is **absent, never zero** — a zero would
63723
+ * claim a camera cost nothing, which is a measurement nobody made;
63724
+ * - a `failure-contribution` zero is the **most valuable value on the
63725
+ * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
63726
+ * and it is exactly what an absent entry cannot say.
63727
+ *
63728
+ * Putting a loss counter on a cost entry would also break the reconciliation
63729
+ * that gives `load-contribution` its point: contributions are subtracted from
63730
+ * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
63731
+ * has no process.
63732
+ *
63733
+ * ## Why not a log line, since the counters already exist
63734
+ *
63735
+ * Several of these paths already counted themselves — `CaptureScheduler`'s
63736
+ * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
63737
+ * ends in a log line, and a log line is the thing the operator asked to stop
63738
+ * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
63739
+ * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
63740
+ * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
63741
+ * media blackout were both diagnosed. The counters stay; this is where they can
63742
+ * be READ.
63743
+ *
63744
+ * ## The rate is served with its denominator or not at all
63745
+ *
63746
+ * Every entry carries `attempts` and `succeeded`. A miss count alone is
63747
+ * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
63748
+ * than yesterday" and was **flat across twelve hours** once divided by the
63749
+ * successes on the same path. A surface that publishes only the numerator
63750
+ * reproduces that mistake on every read.
63751
+ *
63752
+ * ## Shape
63753
+ *
63754
+ * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
63755
+ * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
63756
+ * generated hooks, while `addons.listCapabilityProviders` still enumerates it
63757
+ * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
63758
+ * a forked runner's entries reach hub-main over transport that already exists.
63759
+ * No new UDS message, no second registry (D3). The operator reads the assembled
63760
+ * result through `system.getFailureContributions`.
63761
+ */
63762
+ var FailureReasonCountSchema = object({
63763
+ /**
63764
+ * Why the attempt did not land, in the contributor's own vocabulary —
63765
+ * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
63766
+ * strings that already appear in this repo's logs and, where one exists, the
63767
+ * same string the per-track `previewMissReason` records (D276): a second
63768
+ * vocabulary for the same loss would make the row and the counter
63769
+ * un-joinable.
63770
+ */
63771
+ reason: string(),
63772
+ count: number().int().nonnegative()
63773
+ });
63774
+ var FailureContributionSchema = object({
63775
+ /**
63776
+ * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
63777
+ * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
63778
+ * `unit` free: the families are owned by different addons and a shared enum
63779
+ * is a central list that rots invisibly.
63780
+ */
63781
+ family: string(),
63782
+ /**
63783
+ * The NUMERIC device id — the same value every log line carries as
63784
+ * `tags.deviceId`. Never nullable and never absent: a contributor that
63785
+ * cannot name the camera must not emit the entry, because a fleet total
63786
+ * cannot answer the only question anybody asks of this surface.
63787
+ */
63788
+ deviceId: number().int().positive(),
63789
+ /**
63790
+ * A second dimension inside the family: the model / step id for an inference
63791
+ * timeout, so "which camera AND which model" is one read. Absent when the
63792
+ * family has a single variant.
63793
+ */
63794
+ variant: string().optional(),
63795
+ /**
63796
+ * Epoch ms this counter started — the INCARNATION MARKER. A consumer
63797
+ * differencing two reads must drop the interval when it changes, because the
63798
+ * counter restarted from zero in a respawned runner. Same discipline as
63799
+ * `LoadContribution.startedAtMs`.
63800
+ */
63801
+ sinceMs: number(),
63802
+ /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
63803
+ atMs: number(),
63804
+ /**
63805
+ * THE DENOMINATOR — every attempt on this path for this camera in the
63806
+ * window. A failure count published without it is the mistake this schema
63807
+ * exists to make impossible.
63808
+ */
63809
+ attempts: number().int().nonnegative(),
63810
+ /** Attempts that landed. `attempts - succeeded` is the loss. */
63811
+ succeeded: number().int().nonnegative(),
63812
+ /** The loss, partitioned. Sums to `attempts - succeeded`. */
63813
+ reasons: array(FailureReasonCountSchema).readonly()
63814
+ });
63815
+ method(_void(), array(FailureContributionSchema).readonly());
63816
+ /**
63709
63817
  * filesystem-browse — per-node capability for browsing the node's local
63710
63818
  * filesystem. Reads are unconfined (whole filesystem, from `/` down); WRITES
63711
63819
  * are sandboxed to operator-configured allowed roots (D115). Used by the
@@ -64227,6 +64335,68 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
64227
64335
  kind: "mutation",
64228
64336
  auth: "admin"
64229
64337
  });
64338
+ var LoadContributionSchema = object({
64339
+ role: _enum([
64340
+ "decode",
64341
+ "transcode",
64342
+ "recording",
64343
+ "streaming",
64344
+ "detection"
64345
+ ]),
64346
+ /**
64347
+ * The NUMERIC device id — the same value every log line carries as
64348
+ * `tags.deviceId`. `null` means this cost genuinely belongs to no single
64349
+ * camera (a shared pool), NOT that the contributor forgot to look it up: a
64350
+ * contributor that cannot name its camera must not emit the entry at all,
64351
+ * because an unnamed per-camera entry is indistinguishable from a shared one
64352
+ * and would quietly turn one camera's cost into everybody's.
64353
+ */
64354
+ deviceId: number().int().positive().nullable(),
64355
+ attribution: _enum([
64356
+ "measured",
64357
+ "accounted",
64358
+ "unattributable"
64359
+ ]),
64360
+ /**
64361
+ * What ONE entry is, in the contributor's own words — `615/high`,
64362
+ * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
64363
+ * family and inventing a common one would lose the only information that
64364
+ * makes two entries for the same camera distinguishable.
64365
+ */
64366
+ unit: string(),
64367
+ /**
64368
+ * The OS process this cost lives in, when there is one. Present so a
64369
+ * consumer can (a) tell two generations of the same unit apart across a
64370
+ * restart, and (b) subtract claimed processes from the node's process
64371
+ * snapshot to see what NOBODY claimed. Absent for an entry that owns no
64372
+ * process of its own.
64373
+ */
64374
+ pid: number().int().positive().optional(),
64375
+ /**
64376
+ * When this generation started. The pid's incarnation marker: a consumer
64377
+ * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
64378
+ * window when this changes, because the counter restarted from zero in a new
64379
+ * process.
64380
+ */
64381
+ startedAtMs: number().optional(),
64382
+ /**
64383
+ * CUMULATIVE CPU seconds this unit has consumed since it started — user +
64384
+ * system, read from the child's own `/proc/<pid>/stat` at the moment the
64385
+ * contribution is asked for.
64386
+ *
64387
+ * Cumulative and not a rate on purpose: a rate needs a window, a window
64388
+ * needs a sampler, and a new per-node sampler is the defect half of
64389
+ * `docs/architecture/load-ledger.md` documents. A counter can be differenced
64390
+ * by whoever already keeps a history; a rate cannot be un-averaged.
64391
+ *
64392
+ * Absent — never zero — on a node with no `/proc`, on a read failure, and on
64393
+ * an entry with no process.
64394
+ */
64395
+ cpuSeconds: number().optional(),
64396
+ /** Resident bytes of this unit's process, same source and same rules. */
64397
+ rssBytes: number().optional()
64398
+ });
64399
+ method(_void(), array(LoadContributionSchema).readonly());
64230
64400
  /**
64231
64401
  * `log-channels` — the capability an addon DECLARES its diagnostic channels
64232
64402
  * through. It stores nothing.
@@ -64303,176 +64473,6 @@ method(LogEntrySchema, _void(), { kind: "mutation" }), method(object({
64303
64473
  tags: record(string(), string()).optional()
64304
64474
  }), array(LogEntrySchema).readonly());
64305
64475
  /**
64306
- * `failure-contribution` — the capability an addon reports its OWN losses
64307
- * through, per camera, with the denominator attached. It stores nothing.
64308
- *
64309
- * ## The twin of `load-contribution`, and why it is a twin and not a field
64310
- *
64311
- * `load-contribution` answers *what did this camera COST*. This answers *what
64312
- * did this camera LOSE*. The reporting discipline is identical and deliberately
64313
- * copied: the contributor reports what it already knows, hub-main adds only
64314
- * `addonId`, nothing needs global knowledge, and there is no central list for
64315
- * somebody to forget to edit.
64316
- *
64317
- * They are not merged, because their invariants are opposites:
64318
- *
64319
- * - a `load-contribution` measurement is **absent, never zero** — a zero would
64320
- * claim a camera cost nothing, which is a measurement nobody made;
64321
- * - a `failure-contribution` zero is the **most valuable value on the
64322
- * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
64323
- * and it is exactly what an absent entry cannot say.
64324
- *
64325
- * Putting a loss counter on a cost entry would also break the reconciliation
64326
- * that gives `load-contribution` its point: contributions are subtracted from
64327
- * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
64328
- * has no process.
64329
- *
64330
- * ## Why not a log line, since the counters already exist
64331
- *
64332
- * Several of these paths already counted themselves — `CaptureScheduler`'s
64333
- * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
64334
- * ends in a log line, and a log line is the thing the operator asked to stop
64335
- * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
64336
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
64337
- * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
64338
- * media blackout were both diagnosed. The counters stay; this is where they can
64339
- * be READ.
64340
- *
64341
- * ## The rate is served with its denominator or not at all
64342
- *
64343
- * Every entry carries `attempts` and `succeeded`. A miss count alone is
64344
- * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
64345
- * than yesterday" and was **flat across twelve hours** once divided by the
64346
- * successes on the same path. A surface that publishes only the numerator
64347
- * reproduces that mistake on every read.
64348
- *
64349
- * ## Shape
64350
- *
64351
- * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
64352
- * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
64353
- * generated hooks, while `addons.listCapabilityProviders` still enumerates it
64354
- * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
64355
- * a forked runner's entries reach hub-main over transport that already exists.
64356
- * No new UDS message, no second registry (D3). The operator reads the assembled
64357
- * result through `system.getFailureContributions`.
64358
- */
64359
- var FailureReasonCountSchema = object({
64360
- /**
64361
- * Why the attempt did not land, in the contributor's own vocabulary —
64362
- * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
64363
- * strings that already appear in this repo's logs and, where one exists, the
64364
- * same string the per-track `previewMissReason` records (D276): a second
64365
- * vocabulary for the same loss would make the row and the counter
64366
- * un-joinable.
64367
- */
64368
- reason: string(),
64369
- count: number().int().nonnegative()
64370
- });
64371
- var FailureContributionSchema = object({
64372
- /**
64373
- * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
64374
- * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
64375
- * `unit` free: the families are owned by different addons and a shared enum
64376
- * is a central list that rots invisibly.
64377
- */
64378
- family: string(),
64379
- /**
64380
- * The NUMERIC device id — the same value every log line carries as
64381
- * `tags.deviceId`. Never nullable and never absent: a contributor that
64382
- * cannot name the camera must not emit the entry, because a fleet total
64383
- * cannot answer the only question anybody asks of this surface.
64384
- */
64385
- deviceId: number().int().positive(),
64386
- /**
64387
- * A second dimension inside the family: the model / step id for an inference
64388
- * timeout, so "which camera AND which model" is one read. Absent when the
64389
- * family has a single variant.
64390
- */
64391
- variant: string().optional(),
64392
- /**
64393
- * Epoch ms this counter started — the INCARNATION MARKER. A consumer
64394
- * differencing two reads must drop the interval when it changes, because the
64395
- * counter restarted from zero in a respawned runner. Same discipline as
64396
- * `LoadContribution.startedAtMs`.
64397
- */
64398
- sinceMs: number(),
64399
- /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
64400
- atMs: number(),
64401
- /**
64402
- * THE DENOMINATOR — every attempt on this path for this camera in the
64403
- * window. A failure count published without it is the mistake this schema
64404
- * exists to make impossible.
64405
- */
64406
- attempts: number().int().nonnegative(),
64407
- /** Attempts that landed. `attempts - succeeded` is the loss. */
64408
- succeeded: number().int().nonnegative(),
64409
- /** The loss, partitioned. Sums to `attempts - succeeded`. */
64410
- reasons: array(FailureReasonCountSchema).readonly()
64411
- });
64412
- method(_void(), array(FailureContributionSchema).readonly());
64413
- var LoadContributionSchema = object({
64414
- role: _enum([
64415
- "decode",
64416
- "transcode",
64417
- "recording",
64418
- "streaming",
64419
- "detection"
64420
- ]),
64421
- /**
64422
- * The NUMERIC device id — the same value every log line carries as
64423
- * `tags.deviceId`. `null` means this cost genuinely belongs to no single
64424
- * camera (a shared pool), NOT that the contributor forgot to look it up: a
64425
- * contributor that cannot name its camera must not emit the entry at all,
64426
- * because an unnamed per-camera entry is indistinguishable from a shared one
64427
- * and would quietly turn one camera's cost into everybody's.
64428
- */
64429
- deviceId: number().int().positive().nullable(),
64430
- attribution: _enum([
64431
- "measured",
64432
- "accounted",
64433
- "unattributable"
64434
- ]),
64435
- /**
64436
- * What ONE entry is, in the contributor's own words — `615/high`,
64437
- * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
64438
- * family and inventing a common one would lose the only information that
64439
- * makes two entries for the same camera distinguishable.
64440
- */
64441
- unit: string(),
64442
- /**
64443
- * The OS process this cost lives in, when there is one. Present so a
64444
- * consumer can (a) tell two generations of the same unit apart across a
64445
- * restart, and (b) subtract claimed processes from the node's process
64446
- * snapshot to see what NOBODY claimed. Absent for an entry that owns no
64447
- * process of its own.
64448
- */
64449
- pid: number().int().positive().optional(),
64450
- /**
64451
- * When this generation started. The pid's incarnation marker: a consumer
64452
- * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
64453
- * window when this changes, because the counter restarted from zero in a new
64454
- * process.
64455
- */
64456
- startedAtMs: number().optional(),
64457
- /**
64458
- * CUMULATIVE CPU seconds this unit has consumed since it started — user +
64459
- * system, read from the child's own `/proc/<pid>/stat` at the moment the
64460
- * contribution is asked for.
64461
- *
64462
- * Cumulative and not a rate on purpose: a rate needs a window, a window
64463
- * needs a sampler, and a new per-node sampler is the defect half of
64464
- * `docs/architecture/load-ledger.md` documents. A counter can be differenced
64465
- * by whoever already keeps a history; a rate cannot be un-averaged.
64466
- *
64467
- * Absent — never zero — on a node with no `/proc`, on a read failure, and on
64468
- * an entry with no process.
64469
- */
64470
- cpuSeconds: number().optional(),
64471
- /** Resident bytes of this unit's process, same source and same rules. */
64472
- rssBytes: number().optional()
64473
- });
64474
- method(_void(), array(LoadContributionSchema).readonly());
64475
- /**
64476
64476
  * `login-method` — collection cap through which auth addons contribute
64477
64477
  * their pre-auth login surfaces to the login page. This is the SINGLE,
64478
64478
  * generic mechanism that supersedes the dead `auth.listProviders` reader:
@@ -69210,12 +69210,53 @@ var MediaFileKindEnum = _enum([
69210
69210
  "keyFrameSmall",
69211
69211
  "thumbnailSmall"
69212
69212
  ]);
69213
+ /**
69214
+ * One media row ON THE WIRE: what it is, how big it is, and WHERE ITS BYTES
69215
+ * ARE — never the bytes themselves.
69216
+ *
69217
+ * ## Why `url` and not `base64`
69218
+ *
69219
+ * Measured on the live hub 2026-08-30: `getTrackMedia {trackId, deviceId}`
69220
+ * with no `kinds` returned 6 rows / **3 597 219 B**, of which `keyFrame` alone
69221
+ * was **2 824 077 B** — one full-resolution frame, base64, so +33 % on the
69222
+ * wire. Forty events is ~144 MB. Every byte of it was read off disk,
69223
+ * base64-encoded, held whole in a unary tRPC envelope, and materialised in
69224
+ * hub-main's heap on the way past — for an `<img>` that would have cached it.
69225
+ *
69226
+ * `url` points at the `event-media` data plane
69227
+ * (`/addon/<addonId>/event-media/<storedKey>`), which serves the same blob
69228
+ * with an ETag and `Cache-Control: immutable`, honours conditional GETs, can
69229
+ * render a `?variant=thumb`, and streams. The hub gate in front of it requires
69230
+ * a bearer or the session cookie (`access: 'authenticated'`), so the bytes are
69231
+ * no less protected than they were inside a `view`-level cap response — see
69232
+ * `data-plane-access.ts` for the rule and the one gap it does not close
69233
+ * (per-device scoping).
69234
+ *
69235
+ * The URL is built from the row's **stored** key, which is not always its
69236
+ * published `kind`: a track's face/plate crop is stored as `crop` under
69237
+ * `('face'|'plate', '<prefix>-<trackId>')` and published as
69238
+ * `faceCrop`/`plateCrop`. `MediaStore.getByKey` knows only the stored key.
69239
+ *
69240
+ * ## `base64` is TRANSITIONAL and is going away
69241
+ *
69242
+ * It is still populated for one reason: the deployed viewer's track-detail
69243
+ * HERO tile reads it (`use-track-media-entry.ts` → `parseMediaFiles`, which
69244
+ * REQUIRES the field), and a row without it parses as a FAILED read — the red
69245
+ * triangle — not as absence. Removing the field before that viewer ships is an
69246
+ * outage, not a cleanup. Once the viewer takes its hero bytes from `url`,
69247
+ * delete this line and the `withBytes` pass-through in
69248
+ * `analytics-query-facade.ts`; nothing else reads it.
69249
+ */
69213
69250
  var MediaFileSchema = object({
69214
69251
  key: string(),
69215
69252
  kind: MediaFileKindEnum,
69216
- base64: string(),
69217
69253
  sizeBytes: number(),
69218
69254
  timestamp: number()
69255
+ }).extend({
69256
+ /** `/addon/<addonId>/event-media/<encoded stored key>`. Always present. */
69257
+ url: string(),
69258
+ /** @deprecated Transitional — see the schema docblock. Use {@link url}. */
69259
+ base64: string()
69219
69260
  });
69220
69261
  /**
69221
69262
  * One media row WITHOUT its bytes.
@@ -69227,7 +69268,9 @@ var MediaFileSchema = object({
69227
69268
  * blocks the whole view.
69228
69269
  *
69229
69270
  * `sizeBytes` is carried because it is what lets a client decide between the
69230
- * stored blob and a `?variant=thumb` rendering without fetching either.
69271
+ * stored blob and a `?variant=thumb` rendering without fetching either, and
69272
+ * `url` because a client that had to build the plane path itself is a second
69273
+ * copy of a route — the embed, the viewer and the admin UI each grew one.
69231
69274
  */
69232
69275
  var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
69233
69276
  /**
@@ -69916,6 +69959,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
69916
69959
  }), array(MediaFileSchema).readonly()), method(object({
69917
69960
  trackId: string(),
69918
69961
  deviceId: number()
69962
+ }), array(MediaFileInfoSchema).readonly()), method(object({
69963
+ eventId: string(),
69964
+ deviceId: number()
69919
69965
  }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
69920
69966
  kind: "mutation",
69921
69967
  auth: "admin"
@@ -74931,10 +74977,24 @@ var FaceClusterSchema = object({
74931
74977
  size: number().int(),
74932
74978
  cohesion: number()
74933
74979
  });
74980
+ /**
74981
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
74982
+ * are — never the bytes.
74983
+ *
74984
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
74985
+ * track/event contract) is still populated because a deployed viewer requires
74986
+ * the field to parse a row at all; this method has no such reader. Its ONE
74987
+ * caller is the admin UI's detail modal, which was building
74988
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
74989
+ * dialog already rendering its key FRAME from the `event-media` plane.
74990
+ *
74991
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
74992
+ * media key directly, so this needed no new plane and no new access decision.
74993
+ */
74934
74994
  var MediaFileLiteSchema$1 = object({
74935
74995
  key: string(),
74936
74996
  kind: string(),
74937
- base64: string(),
74997
+ url: string(),
74938
74998
  sizeBytes: number(),
74939
74999
  timestamp: number()
74940
75000
  });
@@ -77956,10 +78016,24 @@ var PlateInfoSchema = object({
77956
78016
  */
77957
78017
  cropUrl: string().optional()
77958
78018
  });
78019
+ /**
78020
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
78021
+ * are — never the bytes.
78022
+ *
78023
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
78024
+ * track/event contract) is still populated because a deployed viewer requires
78025
+ * the field to parse a row at all; this method has no such reader. Its ONE
78026
+ * caller is the admin UI's detail modal, which was building
78027
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
78028
+ * dialog already rendering its key FRAME from the `event-media` plane.
78029
+ *
78030
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
78031
+ * media key directly, so this needed no new plane and no new access decision.
78032
+ */
77959
78033
  var MediaFileLiteSchema = object({
77960
78034
  key: string(),
77961
78035
  kind: string(),
77962
- base64: string(),
78036
+ url: string(),
77963
78037
  sizeBytes: number(),
77964
78038
  timestamp: number()
77965
78039
  });
@@ -85856,6 +85930,12 @@ Object.freeze({
85856
85930
  addonId: null,
85857
85931
  access: "view"
85858
85932
  },
85933
+ "pipelineAnalytics.listEventMedia": {
85934
+ capName: "pipeline-analytics",
85935
+ capScope: "device",
85936
+ addonId: null,
85937
+ access: "view"
85938
+ },
85859
85939
  "pipelineAnalytics.listGroups": {
85860
85940
  capName: "pipeline-analytics",
85861
85941
  capScope: "device",
@@ -89479,6 +89559,11 @@ Object.freeze({
89479
89559
  form: "array",
89480
89560
  optional: false
89481
89561
  }],
89562
+ "pipelineAnalytics.listEventMedia": [{
89563
+ name: "deviceId",
89564
+ form: "single",
89565
+ optional: false
89566
+ }],
89482
89567
  "pipelineAnalytics.listGroups": [{
89483
89568
  name: "deviceIds",
89484
89569
  form: "array",
package/dist/addon.mjs CHANGED
@@ -63706,6 +63706,114 @@ method(object({
63706
63706
  height: number()
63707
63707
  }), EmbeddingResultSchema, { auth: "admin" }), method(object({ text: string() }), EmbeddingResultSchema, { auth: "admin" }), method(_void(), EmbeddingInfoSchema, { auth: "admin" });
63708
63708
  /**
63709
+ * `failure-contribution` — the capability an addon reports its OWN losses
63710
+ * through, per camera, with the denominator attached. It stores nothing.
63711
+ *
63712
+ * ## The twin of `load-contribution`, and why it is a twin and not a field
63713
+ *
63714
+ * `load-contribution` answers *what did this camera COST*. This answers *what
63715
+ * did this camera LOSE*. The reporting discipline is identical and deliberately
63716
+ * copied: the contributor reports what it already knows, hub-main adds only
63717
+ * `addonId`, nothing needs global knowledge, and there is no central list for
63718
+ * somebody to forget to edit.
63719
+ *
63720
+ * They are not merged, because their invariants are opposites:
63721
+ *
63722
+ * - a `load-contribution` measurement is **absent, never zero** — a zero would
63723
+ * claim a camera cost nothing, which is a measurement nobody made;
63724
+ * - a `failure-contribution` zero is the **most valuable value on the
63725
+ * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
63726
+ * and it is exactly what an absent entry cannot say.
63727
+ *
63728
+ * Putting a loss counter on a cost entry would also break the reconciliation
63729
+ * that gives `load-contribution` its point: contributions are subtracted from
63730
+ * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
63731
+ * has no process.
63732
+ *
63733
+ * ## Why not a log line, since the counters already exist
63734
+ *
63735
+ * Several of these paths already counted themselves — `CaptureScheduler`'s
63736
+ * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
63737
+ * ends in a log line, and a log line is the thing the operator asked to stop
63738
+ * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
63739
+ * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
63740
+ * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
63741
+ * media blackout were both diagnosed. The counters stay; this is where they can
63742
+ * be READ.
63743
+ *
63744
+ * ## The rate is served with its denominator or not at all
63745
+ *
63746
+ * Every entry carries `attempts` and `succeeded`. A miss count alone is
63747
+ * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
63748
+ * than yesterday" and was **flat across twelve hours** once divided by the
63749
+ * successes on the same path. A surface that publishes only the numerator
63750
+ * reproduces that mistake on every read.
63751
+ *
63752
+ * ## Shape
63753
+ *
63754
+ * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
63755
+ * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
63756
+ * generated hooks, while `addons.listCapabilityProviders` still enumerates it
63757
+ * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
63758
+ * a forked runner's entries reach hub-main over transport that already exists.
63759
+ * No new UDS message, no second registry (D3). The operator reads the assembled
63760
+ * result through `system.getFailureContributions`.
63761
+ */
63762
+ var FailureReasonCountSchema = object({
63763
+ /**
63764
+ * Why the attempt did not land, in the contributor's own vocabulary —
63765
+ * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
63766
+ * strings that already appear in this repo's logs and, where one exists, the
63767
+ * same string the per-track `previewMissReason` records (D276): a second
63768
+ * vocabulary for the same loss would make the row and the counter
63769
+ * un-joinable.
63770
+ */
63771
+ reason: string(),
63772
+ count: number().int().nonnegative()
63773
+ });
63774
+ var FailureContributionSchema = object({
63775
+ /**
63776
+ * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
63777
+ * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
63778
+ * `unit` free: the families are owned by different addons and a shared enum
63779
+ * is a central list that rots invisibly.
63780
+ */
63781
+ family: string(),
63782
+ /**
63783
+ * The NUMERIC device id — the same value every log line carries as
63784
+ * `tags.deviceId`. Never nullable and never absent: a contributor that
63785
+ * cannot name the camera must not emit the entry, because a fleet total
63786
+ * cannot answer the only question anybody asks of this surface.
63787
+ */
63788
+ deviceId: number().int().positive(),
63789
+ /**
63790
+ * A second dimension inside the family: the model / step id for an inference
63791
+ * timeout, so "which camera AND which model" is one read. Absent when the
63792
+ * family has a single variant.
63793
+ */
63794
+ variant: string().optional(),
63795
+ /**
63796
+ * Epoch ms this counter started — the INCARNATION MARKER. A consumer
63797
+ * differencing two reads must drop the interval when it changes, because the
63798
+ * counter restarted from zero in a respawned runner. Same discipline as
63799
+ * `LoadContribution.startedAtMs`.
63800
+ */
63801
+ sinceMs: number(),
63802
+ /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
63803
+ atMs: number(),
63804
+ /**
63805
+ * THE DENOMINATOR — every attempt on this path for this camera in the
63806
+ * window. A failure count published without it is the mistake this schema
63807
+ * exists to make impossible.
63808
+ */
63809
+ attempts: number().int().nonnegative(),
63810
+ /** Attempts that landed. `attempts - succeeded` is the loss. */
63811
+ succeeded: number().int().nonnegative(),
63812
+ /** The loss, partitioned. Sums to `attempts - succeeded`. */
63813
+ reasons: array(FailureReasonCountSchema).readonly()
63814
+ });
63815
+ method(_void(), array(FailureContributionSchema).readonly());
63816
+ /**
63709
63817
  * filesystem-browse — per-node capability for browsing the node's local
63710
63818
  * filesystem. Reads are unconfined (whole filesystem, from `/` down); WRITES
63711
63819
  * are sandboxed to operator-configured allowed roots (D115). Used by the
@@ -64227,6 +64335,68 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
64227
64335
  kind: "mutation",
64228
64336
  auth: "admin"
64229
64337
  });
64338
+ var LoadContributionSchema = object({
64339
+ role: _enum([
64340
+ "decode",
64341
+ "transcode",
64342
+ "recording",
64343
+ "streaming",
64344
+ "detection"
64345
+ ]),
64346
+ /**
64347
+ * The NUMERIC device id — the same value every log line carries as
64348
+ * `tags.deviceId`. `null` means this cost genuinely belongs to no single
64349
+ * camera (a shared pool), NOT that the contributor forgot to look it up: a
64350
+ * contributor that cannot name its camera must not emit the entry at all,
64351
+ * because an unnamed per-camera entry is indistinguishable from a shared one
64352
+ * and would quietly turn one camera's cost into everybody's.
64353
+ */
64354
+ deviceId: number().int().positive().nullable(),
64355
+ attribution: _enum([
64356
+ "measured",
64357
+ "accounted",
64358
+ "unattributable"
64359
+ ]),
64360
+ /**
64361
+ * What ONE entry is, in the contributor's own words — `615/high`,
64362
+ * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
64363
+ * family and inventing a common one would lose the only information that
64364
+ * makes two entries for the same camera distinguishable.
64365
+ */
64366
+ unit: string(),
64367
+ /**
64368
+ * The OS process this cost lives in, when there is one. Present so a
64369
+ * consumer can (a) tell two generations of the same unit apart across a
64370
+ * restart, and (b) subtract claimed processes from the node's process
64371
+ * snapshot to see what NOBODY claimed. Absent for an entry that owns no
64372
+ * process of its own.
64373
+ */
64374
+ pid: number().int().positive().optional(),
64375
+ /**
64376
+ * When this generation started. The pid's incarnation marker: a consumer
64377
+ * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
64378
+ * window when this changes, because the counter restarted from zero in a new
64379
+ * process.
64380
+ */
64381
+ startedAtMs: number().optional(),
64382
+ /**
64383
+ * CUMULATIVE CPU seconds this unit has consumed since it started — user +
64384
+ * system, read from the child's own `/proc/<pid>/stat` at the moment the
64385
+ * contribution is asked for.
64386
+ *
64387
+ * Cumulative and not a rate on purpose: a rate needs a window, a window
64388
+ * needs a sampler, and a new per-node sampler is the defect half of
64389
+ * `docs/architecture/load-ledger.md` documents. A counter can be differenced
64390
+ * by whoever already keeps a history; a rate cannot be un-averaged.
64391
+ *
64392
+ * Absent — never zero — on a node with no `/proc`, on a read failure, and on
64393
+ * an entry with no process.
64394
+ */
64395
+ cpuSeconds: number().optional(),
64396
+ /** Resident bytes of this unit's process, same source and same rules. */
64397
+ rssBytes: number().optional()
64398
+ });
64399
+ method(_void(), array(LoadContributionSchema).readonly());
64230
64400
  /**
64231
64401
  * `log-channels` — the capability an addon DECLARES its diagnostic channels
64232
64402
  * through. It stores nothing.
@@ -64303,176 +64473,6 @@ method(LogEntrySchema, _void(), { kind: "mutation" }), method(object({
64303
64473
  tags: record(string(), string()).optional()
64304
64474
  }), array(LogEntrySchema).readonly());
64305
64475
  /**
64306
- * `failure-contribution` — the capability an addon reports its OWN losses
64307
- * through, per camera, with the denominator attached. It stores nothing.
64308
- *
64309
- * ## The twin of `load-contribution`, and why it is a twin and not a field
64310
- *
64311
- * `load-contribution` answers *what did this camera COST*. This answers *what
64312
- * did this camera LOSE*. The reporting discipline is identical and deliberately
64313
- * copied: the contributor reports what it already knows, hub-main adds only
64314
- * `addonId`, nothing needs global knowledge, and there is no central list for
64315
- * somebody to forget to edit.
64316
- *
64317
- * They are not merged, because their invariants are opposites:
64318
- *
64319
- * - a `load-contribution` measurement is **absent, never zero** — a zero would
64320
- * claim a camera cost nothing, which is a measurement nobody made;
64321
- * - a `failure-contribution` zero is the **most valuable value on the
64322
- * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
64323
- * and it is exactly what an absent entry cannot say.
64324
- *
64325
- * Putting a loss counter on a cost entry would also break the reconciliation
64326
- * that gives `load-contribution` its point: contributions are subtracted from
64327
- * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
64328
- * has no process.
64329
- *
64330
- * ## Why not a log line, since the counters already exist
64331
- *
64332
- * Several of these paths already counted themselves — `CaptureScheduler`'s
64333
- * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
64334
- * ends in a log line, and a log line is the thing the operator asked to stop
64335
- * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
64336
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
64337
- * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
64338
- * media blackout were both diagnosed. The counters stay; this is where they can
64339
- * be READ.
64340
- *
64341
- * ## The rate is served with its denominator or not at all
64342
- *
64343
- * Every entry carries `attempts` and `succeeded`. A miss count alone is
64344
- * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
64345
- * than yesterday" and was **flat across twelve hours** once divided by the
64346
- * successes on the same path. A surface that publishes only the numerator
64347
- * reproduces that mistake on every read.
64348
- *
64349
- * ## Shape
64350
- *
64351
- * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
64352
- * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
64353
- * generated hooks, while `addons.listCapabilityProviders` still enumerates it
64354
- * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
64355
- * a forked runner's entries reach hub-main over transport that already exists.
64356
- * No new UDS message, no second registry (D3). The operator reads the assembled
64357
- * result through `system.getFailureContributions`.
64358
- */
64359
- var FailureReasonCountSchema = object({
64360
- /**
64361
- * Why the attempt did not land, in the contributor's own vocabulary —
64362
- * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
64363
- * strings that already appear in this repo's logs and, where one exists, the
64364
- * same string the per-track `previewMissReason` records (D276): a second
64365
- * vocabulary for the same loss would make the row and the counter
64366
- * un-joinable.
64367
- */
64368
- reason: string(),
64369
- count: number().int().nonnegative()
64370
- });
64371
- var FailureContributionSchema = object({
64372
- /**
64373
- * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
64374
- * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
64375
- * `unit` free: the families are owned by different addons and a shared enum
64376
- * is a central list that rots invisibly.
64377
- */
64378
- family: string(),
64379
- /**
64380
- * The NUMERIC device id — the same value every log line carries as
64381
- * `tags.deviceId`. Never nullable and never absent: a contributor that
64382
- * cannot name the camera must not emit the entry, because a fleet total
64383
- * cannot answer the only question anybody asks of this surface.
64384
- */
64385
- deviceId: number().int().positive(),
64386
- /**
64387
- * A second dimension inside the family: the model / step id for an inference
64388
- * timeout, so "which camera AND which model" is one read. Absent when the
64389
- * family has a single variant.
64390
- */
64391
- variant: string().optional(),
64392
- /**
64393
- * Epoch ms this counter started — the INCARNATION MARKER. A consumer
64394
- * differencing two reads must drop the interval when it changes, because the
64395
- * counter restarted from zero in a respawned runner. Same discipline as
64396
- * `LoadContribution.startedAtMs`.
64397
- */
64398
- sinceMs: number(),
64399
- /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
64400
- atMs: number(),
64401
- /**
64402
- * THE DENOMINATOR — every attempt on this path for this camera in the
64403
- * window. A failure count published without it is the mistake this schema
64404
- * exists to make impossible.
64405
- */
64406
- attempts: number().int().nonnegative(),
64407
- /** Attempts that landed. `attempts - succeeded` is the loss. */
64408
- succeeded: number().int().nonnegative(),
64409
- /** The loss, partitioned. Sums to `attempts - succeeded`. */
64410
- reasons: array(FailureReasonCountSchema).readonly()
64411
- });
64412
- method(_void(), array(FailureContributionSchema).readonly());
64413
- var LoadContributionSchema = object({
64414
- role: _enum([
64415
- "decode",
64416
- "transcode",
64417
- "recording",
64418
- "streaming",
64419
- "detection"
64420
- ]),
64421
- /**
64422
- * The NUMERIC device id — the same value every log line carries as
64423
- * `tags.deviceId`. `null` means this cost genuinely belongs to no single
64424
- * camera (a shared pool), NOT that the contributor forgot to look it up: a
64425
- * contributor that cannot name its camera must not emit the entry at all,
64426
- * because an unnamed per-camera entry is indistinguishable from a shared one
64427
- * and would quietly turn one camera's cost into everybody's.
64428
- */
64429
- deviceId: number().int().positive().nullable(),
64430
- attribution: _enum([
64431
- "measured",
64432
- "accounted",
64433
- "unattributable"
64434
- ]),
64435
- /**
64436
- * What ONE entry is, in the contributor's own words — `615/high`,
64437
- * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
64438
- * family and inventing a common one would lose the only information that
64439
- * makes two entries for the same camera distinguishable.
64440
- */
64441
- unit: string(),
64442
- /**
64443
- * The OS process this cost lives in, when there is one. Present so a
64444
- * consumer can (a) tell two generations of the same unit apart across a
64445
- * restart, and (b) subtract claimed processes from the node's process
64446
- * snapshot to see what NOBODY claimed. Absent for an entry that owns no
64447
- * process of its own.
64448
- */
64449
- pid: number().int().positive().optional(),
64450
- /**
64451
- * When this generation started. The pid's incarnation marker: a consumer
64452
- * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
64453
- * window when this changes, because the counter restarted from zero in a new
64454
- * process.
64455
- */
64456
- startedAtMs: number().optional(),
64457
- /**
64458
- * CUMULATIVE CPU seconds this unit has consumed since it started — user +
64459
- * system, read from the child's own `/proc/<pid>/stat` at the moment the
64460
- * contribution is asked for.
64461
- *
64462
- * Cumulative and not a rate on purpose: a rate needs a window, a window
64463
- * needs a sampler, and a new per-node sampler is the defect half of
64464
- * `docs/architecture/load-ledger.md` documents. A counter can be differenced
64465
- * by whoever already keeps a history; a rate cannot be un-averaged.
64466
- *
64467
- * Absent — never zero — on a node with no `/proc`, on a read failure, and on
64468
- * an entry with no process.
64469
- */
64470
- cpuSeconds: number().optional(),
64471
- /** Resident bytes of this unit's process, same source and same rules. */
64472
- rssBytes: number().optional()
64473
- });
64474
- method(_void(), array(LoadContributionSchema).readonly());
64475
- /**
64476
64476
  * `login-method` — collection cap through which auth addons contribute
64477
64477
  * their pre-auth login surfaces to the login page. This is the SINGLE,
64478
64478
  * generic mechanism that supersedes the dead `auth.listProviders` reader:
@@ -69210,12 +69210,53 @@ var MediaFileKindEnum = _enum([
69210
69210
  "keyFrameSmall",
69211
69211
  "thumbnailSmall"
69212
69212
  ]);
69213
+ /**
69214
+ * One media row ON THE WIRE: what it is, how big it is, and WHERE ITS BYTES
69215
+ * ARE — never the bytes themselves.
69216
+ *
69217
+ * ## Why `url` and not `base64`
69218
+ *
69219
+ * Measured on the live hub 2026-08-30: `getTrackMedia {trackId, deviceId}`
69220
+ * with no `kinds` returned 6 rows / **3 597 219 B**, of which `keyFrame` alone
69221
+ * was **2 824 077 B** — one full-resolution frame, base64, so +33 % on the
69222
+ * wire. Forty events is ~144 MB. Every byte of it was read off disk,
69223
+ * base64-encoded, held whole in a unary tRPC envelope, and materialised in
69224
+ * hub-main's heap on the way past — for an `<img>` that would have cached it.
69225
+ *
69226
+ * `url` points at the `event-media` data plane
69227
+ * (`/addon/<addonId>/event-media/<storedKey>`), which serves the same blob
69228
+ * with an ETag and `Cache-Control: immutable`, honours conditional GETs, can
69229
+ * render a `?variant=thumb`, and streams. The hub gate in front of it requires
69230
+ * a bearer or the session cookie (`access: 'authenticated'`), so the bytes are
69231
+ * no less protected than they were inside a `view`-level cap response — see
69232
+ * `data-plane-access.ts` for the rule and the one gap it does not close
69233
+ * (per-device scoping).
69234
+ *
69235
+ * The URL is built from the row's **stored** key, which is not always its
69236
+ * published `kind`: a track's face/plate crop is stored as `crop` under
69237
+ * `('face'|'plate', '<prefix>-<trackId>')` and published as
69238
+ * `faceCrop`/`plateCrop`. `MediaStore.getByKey` knows only the stored key.
69239
+ *
69240
+ * ## `base64` is TRANSITIONAL and is going away
69241
+ *
69242
+ * It is still populated for one reason: the deployed viewer's track-detail
69243
+ * HERO tile reads it (`use-track-media-entry.ts` → `parseMediaFiles`, which
69244
+ * REQUIRES the field), and a row without it parses as a FAILED read — the red
69245
+ * triangle — not as absence. Removing the field before that viewer ships is an
69246
+ * outage, not a cleanup. Once the viewer takes its hero bytes from `url`,
69247
+ * delete this line and the `withBytes` pass-through in
69248
+ * `analytics-query-facade.ts`; nothing else reads it.
69249
+ */
69213
69250
  var MediaFileSchema = object({
69214
69251
  key: string(),
69215
69252
  kind: MediaFileKindEnum,
69216
- base64: string(),
69217
69253
  sizeBytes: number(),
69218
69254
  timestamp: number()
69255
+ }).extend({
69256
+ /** `/addon/<addonId>/event-media/<encoded stored key>`. Always present. */
69257
+ url: string(),
69258
+ /** @deprecated Transitional — see the schema docblock. Use {@link url}. */
69259
+ base64: string()
69219
69260
  });
69220
69261
  /**
69221
69262
  * One media row WITHOUT its bytes.
@@ -69227,7 +69268,9 @@ var MediaFileSchema = object({
69227
69268
  * blocks the whole view.
69228
69269
  *
69229
69270
  * `sizeBytes` is carried because it is what lets a client decide between the
69230
- * stored blob and a `?variant=thumb` rendering without fetching either.
69271
+ * stored blob and a `?variant=thumb` rendering without fetching either, and
69272
+ * `url` because a client that had to build the plane path itself is a second
69273
+ * copy of a route — the embed, the viewer and the admin UI each grew one.
69231
69274
  */
69232
69275
  var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
69233
69276
  /**
@@ -69916,6 +69959,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
69916
69959
  }), array(MediaFileSchema).readonly()), method(object({
69917
69960
  trackId: string(),
69918
69961
  deviceId: number()
69962
+ }), array(MediaFileInfoSchema).readonly()), method(object({
69963
+ eventId: string(),
69964
+ deviceId: number()
69919
69965
  }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
69920
69966
  kind: "mutation",
69921
69967
  auth: "admin"
@@ -74931,10 +74977,24 @@ var FaceClusterSchema = object({
74931
74977
  size: number().int(),
74932
74978
  cohesion: number()
74933
74979
  });
74980
+ /**
74981
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
74982
+ * are — never the bytes.
74983
+ *
74984
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
74985
+ * track/event contract) is still populated because a deployed viewer requires
74986
+ * the field to parse a row at all; this method has no such reader. Its ONE
74987
+ * caller is the admin UI's detail modal, which was building
74988
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
74989
+ * dialog already rendering its key FRAME from the `event-media` plane.
74990
+ *
74991
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
74992
+ * media key directly, so this needed no new plane and no new access decision.
74993
+ */
74934
74994
  var MediaFileLiteSchema$1 = object({
74935
74995
  key: string(),
74936
74996
  kind: string(),
74937
- base64: string(),
74997
+ url: string(),
74938
74998
  sizeBytes: number(),
74939
74999
  timestamp: number()
74940
75000
  });
@@ -77956,10 +78016,24 @@ var PlateInfoSchema = object({
77956
78016
  */
77957
78017
  cropUrl: string().optional()
77958
78018
  });
78019
+ /**
78020
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
78021
+ * are — never the bytes.
78022
+ *
78023
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
78024
+ * track/event contract) is still populated because a deployed viewer requires
78025
+ * the field to parse a row at all; this method has no such reader. Its ONE
78026
+ * caller is the admin UI's detail modal, which was building
78027
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
78028
+ * dialog already rendering its key FRAME from the `event-media` plane.
78029
+ *
78030
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
78031
+ * media key directly, so this needed no new plane and no new access decision.
78032
+ */
77959
78033
  var MediaFileLiteSchema = object({
77960
78034
  key: string(),
77961
78035
  kind: string(),
77962
- base64: string(),
78036
+ url: string(),
77963
78037
  sizeBytes: number(),
77964
78038
  timestamp: number()
77965
78039
  });
@@ -85856,6 +85930,12 @@ Object.freeze({
85856
85930
  addonId: null,
85857
85931
  access: "view"
85858
85932
  },
85933
+ "pipelineAnalytics.listEventMedia": {
85934
+ capName: "pipeline-analytics",
85935
+ capScope: "device",
85936
+ addonId: null,
85937
+ access: "view"
85938
+ },
85859
85939
  "pipelineAnalytics.listGroups": {
85860
85940
  capName: "pipeline-analytics",
85861
85941
  capScope: "device",
@@ -89479,6 +89559,11 @@ Object.freeze({
89479
89559
  form: "array",
89480
89560
  optional: false
89481
89561
  }],
89562
+ "pipelineAnalytics.listEventMedia": [{
89563
+ name: "deviceId",
89564
+ form: "single",
89565
+ optional: false
89566
+ }],
89482
89567
  "pipelineAnalytics.listGroups": [{
89483
89568
  name: "deviceIds",
89484
89569
  form: "array",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-dreame",
3
- "version": "0.2.46",
3
+ "version": "0.2.47",
4
4
  "description": "Dreame robot-vacuum / lawn-mower device-provider addon for CamStack — wraps the @apocaliss92/nodedreame Dreamehome cloud client",
5
5
  "keywords": [
6
6
  "camstack",