@camstack/addon-provider-rademacher 0.2.45 → 0.2.46

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
@@ -14169,6 +14169,114 @@ method(object({
14169
14169
  height: number()
14170
14170
  }), EmbeddingResultSchema, { auth: "admin" }), method(object({ text: string() }), EmbeddingResultSchema, { auth: "admin" }), method(_void(), EmbeddingInfoSchema, { auth: "admin" });
14171
14171
  /**
14172
+ * `failure-contribution` — the capability an addon reports its OWN losses
14173
+ * through, per camera, with the denominator attached. It stores nothing.
14174
+ *
14175
+ * ## The twin of `load-contribution`, and why it is a twin and not a field
14176
+ *
14177
+ * `load-contribution` answers *what did this camera COST*. This answers *what
14178
+ * did this camera LOSE*. The reporting discipline is identical and deliberately
14179
+ * copied: the contributor reports what it already knows, hub-main adds only
14180
+ * `addonId`, nothing needs global knowledge, and there is no central list for
14181
+ * somebody to forget to edit.
14182
+ *
14183
+ * They are not merged, because their invariants are opposites:
14184
+ *
14185
+ * - a `load-contribution` measurement is **absent, never zero** — a zero would
14186
+ * claim a camera cost nothing, which is a measurement nobody made;
14187
+ * - a `failure-contribution` zero is the **most valuable value on the
14188
+ * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
14189
+ * and it is exactly what an absent entry cannot say.
14190
+ *
14191
+ * Putting a loss counter on a cost entry would also break the reconciliation
14192
+ * that gives `load-contribution` its point: contributions are subtracted from
14193
+ * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
14194
+ * has no process.
14195
+ *
14196
+ * ## Why not a log line, since the counters already exist
14197
+ *
14198
+ * Several of these paths already counted themselves — `CaptureScheduler`'s
14199
+ * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
14200
+ * ends in a log line, and a log line is the thing the operator asked to stop
14201
+ * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
14202
+ * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
14203
+ * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
14204
+ * media blackout were both diagnosed. The counters stay; this is where they can
14205
+ * be READ.
14206
+ *
14207
+ * ## The rate is served with its denominator or not at all
14208
+ *
14209
+ * Every entry carries `attempts` and `succeeded`. A miss count alone is
14210
+ * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
14211
+ * than yesterday" and was **flat across twelve hours** once divided by the
14212
+ * successes on the same path. A surface that publishes only the numerator
14213
+ * reproduces that mistake on every read.
14214
+ *
14215
+ * ## Shape
14216
+ *
14217
+ * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
14218
+ * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
14219
+ * generated hooks, while `addons.listCapabilityProviders` still enumerates it
14220
+ * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
14221
+ * a forked runner's entries reach hub-main over transport that already exists.
14222
+ * No new UDS message, no second registry (D3). The operator reads the assembled
14223
+ * result through `system.getFailureContributions`.
14224
+ */
14225
+ var FailureReasonCountSchema = object({
14226
+ /**
14227
+ * Why the attempt did not land, in the contributor's own vocabulary —
14228
+ * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
14229
+ * strings that already appear in this repo's logs and, where one exists, the
14230
+ * same string the per-track `previewMissReason` records (D276): a second
14231
+ * vocabulary for the same loss would make the row and the counter
14232
+ * un-joinable.
14233
+ */
14234
+ reason: string(),
14235
+ count: number().int().nonnegative()
14236
+ });
14237
+ var FailureContributionSchema = object({
14238
+ /**
14239
+ * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
14240
+ * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
14241
+ * `unit` free: the families are owned by different addons and a shared enum
14242
+ * is a central list that rots invisibly.
14243
+ */
14244
+ family: string(),
14245
+ /**
14246
+ * The NUMERIC device id — the same value every log line carries as
14247
+ * `tags.deviceId`. Never nullable and never absent: a contributor that
14248
+ * cannot name the camera must not emit the entry, because a fleet total
14249
+ * cannot answer the only question anybody asks of this surface.
14250
+ */
14251
+ deviceId: number().int().positive(),
14252
+ /**
14253
+ * A second dimension inside the family: the model / step id for an inference
14254
+ * timeout, so "which camera AND which model" is one read. Absent when the
14255
+ * family has a single variant.
14256
+ */
14257
+ variant: string().optional(),
14258
+ /**
14259
+ * Epoch ms this counter started — the INCARNATION MARKER. A consumer
14260
+ * differencing two reads must drop the interval when it changes, because the
14261
+ * counter restarted from zero in a respawned runner. Same discipline as
14262
+ * `LoadContribution.startedAtMs`.
14263
+ */
14264
+ sinceMs: number(),
14265
+ /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
14266
+ atMs: number(),
14267
+ /**
14268
+ * THE DENOMINATOR — every attempt on this path for this camera in the
14269
+ * window. A failure count published without it is the mistake this schema
14270
+ * exists to make impossible.
14271
+ */
14272
+ attempts: number().int().nonnegative(),
14273
+ /** Attempts that landed. `attempts - succeeded` is the loss. */
14274
+ succeeded: number().int().nonnegative(),
14275
+ /** The loss, partitioned. Sums to `attempts - succeeded`. */
14276
+ reasons: array(FailureReasonCountSchema).readonly()
14277
+ });
14278
+ method(_void(), array(FailureContributionSchema).readonly());
14279
+ /**
14172
14280
  * filesystem-browse — per-node capability for browsing the node's local
14173
14281
  * filesystem. Reads are unconfined (whole filesystem, from `/` down); WRITES
14174
14282
  * are sandboxed to operator-configured allowed roots (D115). Used by the
@@ -14690,6 +14798,68 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
14690
14798
  kind: "mutation",
14691
14799
  auth: "admin"
14692
14800
  });
14801
+ var LoadContributionSchema = object({
14802
+ role: _enum([
14803
+ "decode",
14804
+ "transcode",
14805
+ "recording",
14806
+ "streaming",
14807
+ "detection"
14808
+ ]),
14809
+ /**
14810
+ * The NUMERIC device id — the same value every log line carries as
14811
+ * `tags.deviceId`. `null` means this cost genuinely belongs to no single
14812
+ * camera (a shared pool), NOT that the contributor forgot to look it up: a
14813
+ * contributor that cannot name its camera must not emit the entry at all,
14814
+ * because an unnamed per-camera entry is indistinguishable from a shared one
14815
+ * and would quietly turn one camera's cost into everybody's.
14816
+ */
14817
+ deviceId: number().int().positive().nullable(),
14818
+ attribution: _enum([
14819
+ "measured",
14820
+ "accounted",
14821
+ "unattributable"
14822
+ ]),
14823
+ /**
14824
+ * What ONE entry is, in the contributor's own words — `615/high`,
14825
+ * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
14826
+ * family and inventing a common one would lose the only information that
14827
+ * makes two entries for the same camera distinguishable.
14828
+ */
14829
+ unit: string(),
14830
+ /**
14831
+ * The OS process this cost lives in, when there is one. Present so a
14832
+ * consumer can (a) tell two generations of the same unit apart across a
14833
+ * restart, and (b) subtract claimed processes from the node's process
14834
+ * snapshot to see what NOBODY claimed. Absent for an entry that owns no
14835
+ * process of its own.
14836
+ */
14837
+ pid: number().int().positive().optional(),
14838
+ /**
14839
+ * When this generation started. The pid's incarnation marker: a consumer
14840
+ * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
14841
+ * window when this changes, because the counter restarted from zero in a new
14842
+ * process.
14843
+ */
14844
+ startedAtMs: number().optional(),
14845
+ /**
14846
+ * CUMULATIVE CPU seconds this unit has consumed since it started — user +
14847
+ * system, read from the child's own `/proc/<pid>/stat` at the moment the
14848
+ * contribution is asked for.
14849
+ *
14850
+ * Cumulative and not a rate on purpose: a rate needs a window, a window
14851
+ * needs a sampler, and a new per-node sampler is the defect half of
14852
+ * `docs/architecture/load-ledger.md` documents. A counter can be differenced
14853
+ * by whoever already keeps a history; a rate cannot be un-averaged.
14854
+ *
14855
+ * Absent — never zero — on a node with no `/proc`, on a read failure, and on
14856
+ * an entry with no process.
14857
+ */
14858
+ cpuSeconds: number().optional(),
14859
+ /** Resident bytes of this unit's process, same source and same rules. */
14860
+ rssBytes: number().optional()
14861
+ });
14862
+ method(_void(), array(LoadContributionSchema).readonly());
14693
14863
  /**
14694
14864
  * `log-channels` — the capability an addon DECLARES its diagnostic channels
14695
14865
  * through. It stores nothing.
@@ -14766,176 +14936,6 @@ method(LogEntrySchema, _void(), { kind: "mutation" }), method(object({
14766
14936
  tags: record(string(), string()).optional()
14767
14937
  }), array(LogEntrySchema).readonly());
14768
14938
  /**
14769
- * `failure-contribution` — the capability an addon reports its OWN losses
14770
- * through, per camera, with the denominator attached. It stores nothing.
14771
- *
14772
- * ## The twin of `load-contribution`, and why it is a twin and not a field
14773
- *
14774
- * `load-contribution` answers *what did this camera COST*. This answers *what
14775
- * did this camera LOSE*. The reporting discipline is identical and deliberately
14776
- * copied: the contributor reports what it already knows, hub-main adds only
14777
- * `addonId`, nothing needs global knowledge, and there is no central list for
14778
- * somebody to forget to edit.
14779
- *
14780
- * They are not merged, because their invariants are opposites:
14781
- *
14782
- * - a `load-contribution` measurement is **absent, never zero** — a zero would
14783
- * claim a camera cost nothing, which is a measurement nobody made;
14784
- * - a `failure-contribution` zero is the **most valuable value on the
14785
- * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
14786
- * and it is exactly what an absent entry cannot say.
14787
- *
14788
- * Putting a loss counter on a cost entry would also break the reconciliation
14789
- * that gives `load-contribution` its point: contributions are subtracted from
14790
- * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
14791
- * has no process.
14792
- *
14793
- * ## Why not a log line, since the counters already exist
14794
- *
14795
- * Several of these paths already counted themselves — `CaptureScheduler`'s
14796
- * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
14797
- * ends in a log line, and a log line is the thing the operator asked to stop
14798
- * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
14799
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
14800
- * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
14801
- * media blackout were both diagnosed. The counters stay; this is where they can
14802
- * be READ.
14803
- *
14804
- * ## The rate is served with its denominator or not at all
14805
- *
14806
- * Every entry carries `attempts` and `succeeded`. A miss count alone is
14807
- * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
14808
- * than yesterday" and was **flat across twelve hours** once divided by the
14809
- * successes on the same path. A surface that publishes only the numerator
14810
- * reproduces that mistake on every read.
14811
- *
14812
- * ## Shape
14813
- *
14814
- * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
14815
- * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
14816
- * generated hooks, while `addons.listCapabilityProviders` still enumerates it
14817
- * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
14818
- * a forked runner's entries reach hub-main over transport that already exists.
14819
- * No new UDS message, no second registry (D3). The operator reads the assembled
14820
- * result through `system.getFailureContributions`.
14821
- */
14822
- var FailureReasonCountSchema = object({
14823
- /**
14824
- * Why the attempt did not land, in the contributor's own vocabulary —
14825
- * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
14826
- * strings that already appear in this repo's logs and, where one exists, the
14827
- * same string the per-track `previewMissReason` records (D276): a second
14828
- * vocabulary for the same loss would make the row and the counter
14829
- * un-joinable.
14830
- */
14831
- reason: string(),
14832
- count: number().int().nonnegative()
14833
- });
14834
- var FailureContributionSchema = object({
14835
- /**
14836
- * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
14837
- * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
14838
- * `unit` free: the families are owned by different addons and a shared enum
14839
- * is a central list that rots invisibly.
14840
- */
14841
- family: string(),
14842
- /**
14843
- * The NUMERIC device id — the same value every log line carries as
14844
- * `tags.deviceId`. Never nullable and never absent: a contributor that
14845
- * cannot name the camera must not emit the entry, because a fleet total
14846
- * cannot answer the only question anybody asks of this surface.
14847
- */
14848
- deviceId: number().int().positive(),
14849
- /**
14850
- * A second dimension inside the family: the model / step id for an inference
14851
- * timeout, so "which camera AND which model" is one read. Absent when the
14852
- * family has a single variant.
14853
- */
14854
- variant: string().optional(),
14855
- /**
14856
- * Epoch ms this counter started — the INCARNATION MARKER. A consumer
14857
- * differencing two reads must drop the interval when it changes, because the
14858
- * counter restarted from zero in a respawned runner. Same discipline as
14859
- * `LoadContribution.startedAtMs`.
14860
- */
14861
- sinceMs: number(),
14862
- /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
14863
- atMs: number(),
14864
- /**
14865
- * THE DENOMINATOR — every attempt on this path for this camera in the
14866
- * window. A failure count published without it is the mistake this schema
14867
- * exists to make impossible.
14868
- */
14869
- attempts: number().int().nonnegative(),
14870
- /** Attempts that landed. `attempts - succeeded` is the loss. */
14871
- succeeded: number().int().nonnegative(),
14872
- /** The loss, partitioned. Sums to `attempts - succeeded`. */
14873
- reasons: array(FailureReasonCountSchema).readonly()
14874
- });
14875
- method(_void(), array(FailureContributionSchema).readonly());
14876
- var LoadContributionSchema = object({
14877
- role: _enum([
14878
- "decode",
14879
- "transcode",
14880
- "recording",
14881
- "streaming",
14882
- "detection"
14883
- ]),
14884
- /**
14885
- * The NUMERIC device id — the same value every log line carries as
14886
- * `tags.deviceId`. `null` means this cost genuinely belongs to no single
14887
- * camera (a shared pool), NOT that the contributor forgot to look it up: a
14888
- * contributor that cannot name its camera must not emit the entry at all,
14889
- * because an unnamed per-camera entry is indistinguishable from a shared one
14890
- * and would quietly turn one camera's cost into everybody's.
14891
- */
14892
- deviceId: number().int().positive().nullable(),
14893
- attribution: _enum([
14894
- "measured",
14895
- "accounted",
14896
- "unattributable"
14897
- ]),
14898
- /**
14899
- * What ONE entry is, in the contributor's own words — `615/high`,
14900
- * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
14901
- * family and inventing a common one would lose the only information that
14902
- * makes two entries for the same camera distinguishable.
14903
- */
14904
- unit: string(),
14905
- /**
14906
- * The OS process this cost lives in, when there is one. Present so a
14907
- * consumer can (a) tell two generations of the same unit apart across a
14908
- * restart, and (b) subtract claimed processes from the node's process
14909
- * snapshot to see what NOBODY claimed. Absent for an entry that owns no
14910
- * process of its own.
14911
- */
14912
- pid: number().int().positive().optional(),
14913
- /**
14914
- * When this generation started. The pid's incarnation marker: a consumer
14915
- * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
14916
- * window when this changes, because the counter restarted from zero in a new
14917
- * process.
14918
- */
14919
- startedAtMs: number().optional(),
14920
- /**
14921
- * CUMULATIVE CPU seconds this unit has consumed since it started — user +
14922
- * system, read from the child's own `/proc/<pid>/stat` at the moment the
14923
- * contribution is asked for.
14924
- *
14925
- * Cumulative and not a rate on purpose: a rate needs a window, a window
14926
- * needs a sampler, and a new per-node sampler is the defect half of
14927
- * `docs/architecture/load-ledger.md` documents. A counter can be differenced
14928
- * by whoever already keeps a history; a rate cannot be un-averaged.
14929
- *
14930
- * Absent — never zero — on a node with no `/proc`, on a read failure, and on
14931
- * an entry with no process.
14932
- */
14933
- cpuSeconds: number().optional(),
14934
- /** Resident bytes of this unit's process, same source and same rules. */
14935
- rssBytes: number().optional()
14936
- });
14937
- method(_void(), array(LoadContributionSchema).readonly());
14938
- /**
14939
14939
  * `login-method` — collection cap through which auth addons contribute
14940
14940
  * their pre-auth login surfaces to the login page. This is the SINGLE,
14941
14941
  * generic mechanism that supersedes the dead `auth.listProviders` reader:
@@ -19673,12 +19673,53 @@ var MediaFileKindEnum = _enum([
19673
19673
  "keyFrameSmall",
19674
19674
  "thumbnailSmall"
19675
19675
  ]);
19676
+ /**
19677
+ * One media row ON THE WIRE: what it is, how big it is, and WHERE ITS BYTES
19678
+ * ARE — never the bytes themselves.
19679
+ *
19680
+ * ## Why `url` and not `base64`
19681
+ *
19682
+ * Measured on the live hub 2026-08-30: `getTrackMedia {trackId, deviceId}`
19683
+ * with no `kinds` returned 6 rows / **3 597 219 B**, of which `keyFrame` alone
19684
+ * was **2 824 077 B** — one full-resolution frame, base64, so +33 % on the
19685
+ * wire. Forty events is ~144 MB. Every byte of it was read off disk,
19686
+ * base64-encoded, held whole in a unary tRPC envelope, and materialised in
19687
+ * hub-main's heap on the way past — for an `<img>` that would have cached it.
19688
+ *
19689
+ * `url` points at the `event-media` data plane
19690
+ * (`/addon/<addonId>/event-media/<storedKey>`), which serves the same blob
19691
+ * with an ETag and `Cache-Control: immutable`, honours conditional GETs, can
19692
+ * render a `?variant=thumb`, and streams. The hub gate in front of it requires
19693
+ * a bearer or the session cookie (`access: 'authenticated'`), so the bytes are
19694
+ * no less protected than they were inside a `view`-level cap response — see
19695
+ * `data-plane-access.ts` for the rule and the one gap it does not close
19696
+ * (per-device scoping).
19697
+ *
19698
+ * The URL is built from the row's **stored** key, which is not always its
19699
+ * published `kind`: a track's face/plate crop is stored as `crop` under
19700
+ * `('face'|'plate', '<prefix>-<trackId>')` and published as
19701
+ * `faceCrop`/`plateCrop`. `MediaStore.getByKey` knows only the stored key.
19702
+ *
19703
+ * ## `base64` is TRANSITIONAL and is going away
19704
+ *
19705
+ * It is still populated for one reason: the deployed viewer's track-detail
19706
+ * HERO tile reads it (`use-track-media-entry.ts` → `parseMediaFiles`, which
19707
+ * REQUIRES the field), and a row without it parses as a FAILED read — the red
19708
+ * triangle — not as absence. Removing the field before that viewer ships is an
19709
+ * outage, not a cleanup. Once the viewer takes its hero bytes from `url`,
19710
+ * delete this line and the `withBytes` pass-through in
19711
+ * `analytics-query-facade.ts`; nothing else reads it.
19712
+ */
19676
19713
  var MediaFileSchema = object({
19677
19714
  key: string(),
19678
19715
  kind: MediaFileKindEnum,
19679
- base64: string(),
19680
19716
  sizeBytes: number(),
19681
19717
  timestamp: number()
19718
+ }).extend({
19719
+ /** `/addon/<addonId>/event-media/<encoded stored key>`. Always present. */
19720
+ url: string(),
19721
+ /** @deprecated Transitional — see the schema docblock. Use {@link url}. */
19722
+ base64: string()
19682
19723
  });
19683
19724
  /**
19684
19725
  * One media row WITHOUT its bytes.
@@ -19690,7 +19731,9 @@ var MediaFileSchema = object({
19690
19731
  * blocks the whole view.
19691
19732
  *
19692
19733
  * `sizeBytes` is carried because it is what lets a client decide between the
19693
- * stored blob and a `?variant=thumb` rendering without fetching either.
19734
+ * stored blob and a `?variant=thumb` rendering without fetching either, and
19735
+ * `url` because a client that had to build the plane path itself is a second
19736
+ * copy of a route — the embed, the viewer and the admin UI each grew one.
19694
19737
  */
19695
19738
  var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
19696
19739
  /**
@@ -20379,6 +20422,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20379
20422
  }), array(MediaFileSchema).readonly()), method(object({
20380
20423
  trackId: string(),
20381
20424
  deviceId: number()
20425
+ }), array(MediaFileInfoSchema).readonly()), method(object({
20426
+ eventId: string(),
20427
+ deviceId: number()
20382
20428
  }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
20383
20429
  kind: "mutation",
20384
20430
  auth: "admin"
@@ -25369,10 +25415,24 @@ var FaceClusterSchema = object({
25369
25415
  size: number().int(),
25370
25416
  cohesion: number()
25371
25417
  });
25418
+ /**
25419
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
25420
+ * are — never the bytes.
25421
+ *
25422
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
25423
+ * track/event contract) is still populated because a deployed viewer requires
25424
+ * the field to parse a row at all; this method has no such reader. Its ONE
25425
+ * caller is the admin UI's detail modal, which was building
25426
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
25427
+ * dialog already rendering its key FRAME from the `event-media` plane.
25428
+ *
25429
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
25430
+ * media key directly, so this needed no new plane and no new access decision.
25431
+ */
25372
25432
  var MediaFileLiteSchema$1 = object({
25373
25433
  key: string(),
25374
25434
  kind: string(),
25375
- base64: string(),
25435
+ url: string(),
25376
25436
  sizeBytes: number(),
25377
25437
  timestamp: number()
25378
25438
  });
@@ -28394,10 +28454,24 @@ var PlateInfoSchema = object({
28394
28454
  */
28395
28455
  cropUrl: string().optional()
28396
28456
  });
28457
+ /**
28458
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
28459
+ * are — never the bytes.
28460
+ *
28461
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
28462
+ * track/event contract) is still populated because a deployed viewer requires
28463
+ * the field to parse a row at all; this method has no such reader. Its ONE
28464
+ * caller is the admin UI's detail modal, which was building
28465
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
28466
+ * dialog already rendering its key FRAME from the `event-media` plane.
28467
+ *
28468
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
28469
+ * media key directly, so this needed no new plane and no new access decision.
28470
+ */
28397
28471
  var MediaFileLiteSchema = object({
28398
28472
  key: string(),
28399
28473
  kind: string(),
28400
- base64: string(),
28474
+ url: string(),
28401
28475
  sizeBytes: number(),
28402
28476
  timestamp: number()
28403
28477
  });
@@ -36294,6 +36368,12 @@ Object.freeze({
36294
36368
  addonId: null,
36295
36369
  access: "view"
36296
36370
  },
36371
+ "pipelineAnalytics.listEventMedia": {
36372
+ capName: "pipeline-analytics",
36373
+ capScope: "device",
36374
+ addonId: null,
36375
+ access: "view"
36376
+ },
36297
36377
  "pipelineAnalytics.listGroups": {
36298
36378
  capName: "pipeline-analytics",
36299
36379
  capScope: "device",
@@ -39917,6 +39997,11 @@ Object.freeze({
39917
39997
  form: "array",
39918
39998
  optional: false
39919
39999
  }],
40000
+ "pipelineAnalytics.listEventMedia": [{
40001
+ name: "deviceId",
40002
+ form: "single",
40003
+ optional: false
40004
+ }],
39920
40005
  "pipelineAnalytics.listGroups": [{
39921
40006
  name: "deviceIds",
39922
40007
  form: "array",
package/dist/addon.mjs CHANGED
@@ -14168,6 +14168,114 @@ method(object({
14168
14168
  height: number()
14169
14169
  }), EmbeddingResultSchema, { auth: "admin" }), method(object({ text: string() }), EmbeddingResultSchema, { auth: "admin" }), method(_void(), EmbeddingInfoSchema, { auth: "admin" });
14170
14170
  /**
14171
+ * `failure-contribution` — the capability an addon reports its OWN losses
14172
+ * through, per camera, with the denominator attached. It stores nothing.
14173
+ *
14174
+ * ## The twin of `load-contribution`, and why it is a twin and not a field
14175
+ *
14176
+ * `load-contribution` answers *what did this camera COST*. This answers *what
14177
+ * did this camera LOSE*. The reporting discipline is identical and deliberately
14178
+ * copied: the contributor reports what it already knows, hub-main adds only
14179
+ * `addonId`, nothing needs global knowledge, and there is no central list for
14180
+ * somebody to forget to edit.
14181
+ *
14182
+ * They are not merged, because their invariants are opposites:
14183
+ *
14184
+ * - a `load-contribution` measurement is **absent, never zero** — a zero would
14185
+ * claim a camera cost nothing, which is a measurement nobody made;
14186
+ * - a `failure-contribution` zero is the **most valuable value on the
14187
+ * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
14188
+ * and it is exactly what an absent entry cannot say.
14189
+ *
14190
+ * Putting a loss counter on a cost entry would also break the reconciliation
14191
+ * that gives `load-contribution` its point: contributions are subtracted from
14192
+ * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
14193
+ * has no process.
14194
+ *
14195
+ * ## Why not a log line, since the counters already exist
14196
+ *
14197
+ * Several of these paths already counted themselves — `CaptureScheduler`'s
14198
+ * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
14199
+ * ends in a log line, and a log line is the thing the operator asked to stop
14200
+ * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
14201
+ * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
14202
+ * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
14203
+ * media blackout were both diagnosed. The counters stay; this is where they can
14204
+ * be READ.
14205
+ *
14206
+ * ## The rate is served with its denominator or not at all
14207
+ *
14208
+ * Every entry carries `attempts` and `succeeded`. A miss count alone is
14209
+ * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
14210
+ * than yesterday" and was **flat across twelve hours** once divided by the
14211
+ * successes on the same path. A surface that publishes only the numerator
14212
+ * reproduces that mistake on every read.
14213
+ *
14214
+ * ## Shape
14215
+ *
14216
+ * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
14217
+ * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
14218
+ * generated hooks, while `addons.listCapabilityProviders` still enumerates it
14219
+ * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
14220
+ * a forked runner's entries reach hub-main over transport that already exists.
14221
+ * No new UDS message, no second registry (D3). The operator reads the assembled
14222
+ * result through `system.getFailureContributions`.
14223
+ */
14224
+ var FailureReasonCountSchema = object({
14225
+ /**
14226
+ * Why the attempt did not land, in the contributor's own vocabulary —
14227
+ * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
14228
+ * strings that already appear in this repo's logs and, where one exists, the
14229
+ * same string the per-track `previewMissReason` records (D276): a second
14230
+ * vocabulary for the same loss would make the row and the counter
14231
+ * un-joinable.
14232
+ */
14233
+ reason: string(),
14234
+ count: number().int().nonnegative()
14235
+ });
14236
+ var FailureContributionSchema = object({
14237
+ /**
14238
+ * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
14239
+ * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
14240
+ * `unit` free: the families are owned by different addons and a shared enum
14241
+ * is a central list that rots invisibly.
14242
+ */
14243
+ family: string(),
14244
+ /**
14245
+ * The NUMERIC device id — the same value every log line carries as
14246
+ * `tags.deviceId`. Never nullable and never absent: a contributor that
14247
+ * cannot name the camera must not emit the entry, because a fleet total
14248
+ * cannot answer the only question anybody asks of this surface.
14249
+ */
14250
+ deviceId: number().int().positive(),
14251
+ /**
14252
+ * A second dimension inside the family: the model / step id for an inference
14253
+ * timeout, so "which camera AND which model" is one read. Absent when the
14254
+ * family has a single variant.
14255
+ */
14256
+ variant: string().optional(),
14257
+ /**
14258
+ * Epoch ms this counter started — the INCARNATION MARKER. A consumer
14259
+ * differencing two reads must drop the interval when it changes, because the
14260
+ * counter restarted from zero in a respawned runner. Same discipline as
14261
+ * `LoadContribution.startedAtMs`.
14262
+ */
14263
+ sinceMs: number(),
14264
+ /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
14265
+ atMs: number(),
14266
+ /**
14267
+ * THE DENOMINATOR — every attempt on this path for this camera in the
14268
+ * window. A failure count published without it is the mistake this schema
14269
+ * exists to make impossible.
14270
+ */
14271
+ attempts: number().int().nonnegative(),
14272
+ /** Attempts that landed. `attempts - succeeded` is the loss. */
14273
+ succeeded: number().int().nonnegative(),
14274
+ /** The loss, partitioned. Sums to `attempts - succeeded`. */
14275
+ reasons: array(FailureReasonCountSchema).readonly()
14276
+ });
14277
+ method(_void(), array(FailureContributionSchema).readonly());
14278
+ /**
14171
14279
  * filesystem-browse — per-node capability for browsing the node's local
14172
14280
  * filesystem. Reads are unconfined (whole filesystem, from `/` down); WRITES
14173
14281
  * are sandboxed to operator-configured allowed roots (D115). Used by the
@@ -14689,6 +14797,68 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
14689
14797
  kind: "mutation",
14690
14798
  auth: "admin"
14691
14799
  });
14800
+ var LoadContributionSchema = object({
14801
+ role: _enum([
14802
+ "decode",
14803
+ "transcode",
14804
+ "recording",
14805
+ "streaming",
14806
+ "detection"
14807
+ ]),
14808
+ /**
14809
+ * The NUMERIC device id — the same value every log line carries as
14810
+ * `tags.deviceId`. `null` means this cost genuinely belongs to no single
14811
+ * camera (a shared pool), NOT that the contributor forgot to look it up: a
14812
+ * contributor that cannot name its camera must not emit the entry at all,
14813
+ * because an unnamed per-camera entry is indistinguishable from a shared one
14814
+ * and would quietly turn one camera's cost into everybody's.
14815
+ */
14816
+ deviceId: number().int().positive().nullable(),
14817
+ attribution: _enum([
14818
+ "measured",
14819
+ "accounted",
14820
+ "unattributable"
14821
+ ]),
14822
+ /**
14823
+ * What ONE entry is, in the contributor's own words — `615/high`,
14824
+ * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
14825
+ * family and inventing a common one would lose the only information that
14826
+ * makes two entries for the same camera distinguishable.
14827
+ */
14828
+ unit: string(),
14829
+ /**
14830
+ * The OS process this cost lives in, when there is one. Present so a
14831
+ * consumer can (a) tell two generations of the same unit apart across a
14832
+ * restart, and (b) subtract claimed processes from the node's process
14833
+ * snapshot to see what NOBODY claimed. Absent for an entry that owns no
14834
+ * process of its own.
14835
+ */
14836
+ pid: number().int().positive().optional(),
14837
+ /**
14838
+ * When this generation started. The pid's incarnation marker: a consumer
14839
+ * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
14840
+ * window when this changes, because the counter restarted from zero in a new
14841
+ * process.
14842
+ */
14843
+ startedAtMs: number().optional(),
14844
+ /**
14845
+ * CUMULATIVE CPU seconds this unit has consumed since it started — user +
14846
+ * system, read from the child's own `/proc/<pid>/stat` at the moment the
14847
+ * contribution is asked for.
14848
+ *
14849
+ * Cumulative and not a rate on purpose: a rate needs a window, a window
14850
+ * needs a sampler, and a new per-node sampler is the defect half of
14851
+ * `docs/architecture/load-ledger.md` documents. A counter can be differenced
14852
+ * by whoever already keeps a history; a rate cannot be un-averaged.
14853
+ *
14854
+ * Absent — never zero — on a node with no `/proc`, on a read failure, and on
14855
+ * an entry with no process.
14856
+ */
14857
+ cpuSeconds: number().optional(),
14858
+ /** Resident bytes of this unit's process, same source and same rules. */
14859
+ rssBytes: number().optional()
14860
+ });
14861
+ method(_void(), array(LoadContributionSchema).readonly());
14692
14862
  /**
14693
14863
  * `log-channels` — the capability an addon DECLARES its diagnostic channels
14694
14864
  * through. It stores nothing.
@@ -14765,176 +14935,6 @@ method(LogEntrySchema, _void(), { kind: "mutation" }), method(object({
14765
14935
  tags: record(string(), string()).optional()
14766
14936
  }), array(LogEntrySchema).readonly());
14767
14937
  /**
14768
- * `failure-contribution` — the capability an addon reports its OWN losses
14769
- * through, per camera, with the denominator attached. It stores nothing.
14770
- *
14771
- * ## The twin of `load-contribution`, and why it is a twin and not a field
14772
- *
14773
- * `load-contribution` answers *what did this camera COST*. This answers *what
14774
- * did this camera LOSE*. The reporting discipline is identical and deliberately
14775
- * copied: the contributor reports what it already knows, hub-main adds only
14776
- * `addonId`, nothing needs global knowledge, and there is no central list for
14777
- * somebody to forget to edit.
14778
- *
14779
- * They are not merged, because their invariants are opposites:
14780
- *
14781
- * - a `load-contribution` measurement is **absent, never zero** — a zero would
14782
- * claim a camera cost nothing, which is a measurement nobody made;
14783
- * - a `failure-contribution` zero is the **most valuable value on the
14784
- * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
14785
- * and it is exactly what an absent entry cannot say.
14786
- *
14787
- * Putting a loss counter on a cost entry would also break the reconciliation
14788
- * that gives `load-contribution` its point: contributions are subtracted from
14789
- * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
14790
- * has no process.
14791
- *
14792
- * ## Why not a log line, since the counters already exist
14793
- *
14794
- * Several of these paths already counted themselves — `CaptureScheduler`'s
14795
- * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
14796
- * ends in a log line, and a log line is the thing the operator asked to stop
14797
- * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
14798
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
14799
- * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
14800
- * media blackout were both diagnosed. The counters stay; this is where they can
14801
- * be READ.
14802
- *
14803
- * ## The rate is served with its denominator or not at all
14804
- *
14805
- * Every entry carries `attempts` and `succeeded`. A miss count alone is
14806
- * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
14807
- * than yesterday" and was **flat across twelve hours** once divided by the
14808
- * successes on the same path. A surface that publishes only the numerator
14809
- * reproduces that mistake on every read.
14810
- *
14811
- * ## Shape
14812
- *
14813
- * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
14814
- * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
14815
- * generated hooks, while `addons.listCapabilityProviders` still enumerates it
14816
- * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
14817
- * a forked runner's entries reach hub-main over transport that already exists.
14818
- * No new UDS message, no second registry (D3). The operator reads the assembled
14819
- * result through `system.getFailureContributions`.
14820
- */
14821
- var FailureReasonCountSchema = object({
14822
- /**
14823
- * Why the attempt did not land, in the contributor's own vocabulary —
14824
- * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
14825
- * strings that already appear in this repo's logs and, where one exists, the
14826
- * same string the per-track `previewMissReason` records (D276): a second
14827
- * vocabulary for the same loss would make the row and the counter
14828
- * un-joinable.
14829
- */
14830
- reason: string(),
14831
- count: number().int().nonnegative()
14832
- });
14833
- var FailureContributionSchema = object({
14834
- /**
14835
- * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
14836
- * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
14837
- * `unit` free: the families are owned by different addons and a shared enum
14838
- * is a central list that rots invisibly.
14839
- */
14840
- family: string(),
14841
- /**
14842
- * The NUMERIC device id — the same value every log line carries as
14843
- * `tags.deviceId`. Never nullable and never absent: a contributor that
14844
- * cannot name the camera must not emit the entry, because a fleet total
14845
- * cannot answer the only question anybody asks of this surface.
14846
- */
14847
- deviceId: number().int().positive(),
14848
- /**
14849
- * A second dimension inside the family: the model / step id for an inference
14850
- * timeout, so "which camera AND which model" is one read. Absent when the
14851
- * family has a single variant.
14852
- */
14853
- variant: string().optional(),
14854
- /**
14855
- * Epoch ms this counter started — the INCARNATION MARKER. A consumer
14856
- * differencing two reads must drop the interval when it changes, because the
14857
- * counter restarted from zero in a respawned runner. Same discipline as
14858
- * `LoadContribution.startedAtMs`.
14859
- */
14860
- sinceMs: number(),
14861
- /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
14862
- atMs: number(),
14863
- /**
14864
- * THE DENOMINATOR — every attempt on this path for this camera in the
14865
- * window. A failure count published without it is the mistake this schema
14866
- * exists to make impossible.
14867
- */
14868
- attempts: number().int().nonnegative(),
14869
- /** Attempts that landed. `attempts - succeeded` is the loss. */
14870
- succeeded: number().int().nonnegative(),
14871
- /** The loss, partitioned. Sums to `attempts - succeeded`. */
14872
- reasons: array(FailureReasonCountSchema).readonly()
14873
- });
14874
- method(_void(), array(FailureContributionSchema).readonly());
14875
- var LoadContributionSchema = object({
14876
- role: _enum([
14877
- "decode",
14878
- "transcode",
14879
- "recording",
14880
- "streaming",
14881
- "detection"
14882
- ]),
14883
- /**
14884
- * The NUMERIC device id — the same value every log line carries as
14885
- * `tags.deviceId`. `null` means this cost genuinely belongs to no single
14886
- * camera (a shared pool), NOT that the contributor forgot to look it up: a
14887
- * contributor that cannot name its camera must not emit the entry at all,
14888
- * because an unnamed per-camera entry is indistinguishable from a shared one
14889
- * and would quietly turn one camera's cost into everybody's.
14890
- */
14891
- deviceId: number().int().positive().nullable(),
14892
- attribution: _enum([
14893
- "measured",
14894
- "accounted",
14895
- "unattributable"
14896
- ]),
14897
- /**
14898
- * What ONE entry is, in the contributor's own words — `615/high`,
14899
- * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
14900
- * family and inventing a common one would lose the only information that
14901
- * makes two entries for the same camera distinguishable.
14902
- */
14903
- unit: string(),
14904
- /**
14905
- * The OS process this cost lives in, when there is one. Present so a
14906
- * consumer can (a) tell two generations of the same unit apart across a
14907
- * restart, and (b) subtract claimed processes from the node's process
14908
- * snapshot to see what NOBODY claimed. Absent for an entry that owns no
14909
- * process of its own.
14910
- */
14911
- pid: number().int().positive().optional(),
14912
- /**
14913
- * When this generation started. The pid's incarnation marker: a consumer
14914
- * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
14915
- * window when this changes, because the counter restarted from zero in a new
14916
- * process.
14917
- */
14918
- startedAtMs: number().optional(),
14919
- /**
14920
- * CUMULATIVE CPU seconds this unit has consumed since it started — user +
14921
- * system, read from the child's own `/proc/<pid>/stat` at the moment the
14922
- * contribution is asked for.
14923
- *
14924
- * Cumulative and not a rate on purpose: a rate needs a window, a window
14925
- * needs a sampler, and a new per-node sampler is the defect half of
14926
- * `docs/architecture/load-ledger.md` documents. A counter can be differenced
14927
- * by whoever already keeps a history; a rate cannot be un-averaged.
14928
- *
14929
- * Absent — never zero — on a node with no `/proc`, on a read failure, and on
14930
- * an entry with no process.
14931
- */
14932
- cpuSeconds: number().optional(),
14933
- /** Resident bytes of this unit's process, same source and same rules. */
14934
- rssBytes: number().optional()
14935
- });
14936
- method(_void(), array(LoadContributionSchema).readonly());
14937
- /**
14938
14938
  * `login-method` — collection cap through which auth addons contribute
14939
14939
  * their pre-auth login surfaces to the login page. This is the SINGLE,
14940
14940
  * generic mechanism that supersedes the dead `auth.listProviders` reader:
@@ -19672,12 +19672,53 @@ var MediaFileKindEnum = _enum([
19672
19672
  "keyFrameSmall",
19673
19673
  "thumbnailSmall"
19674
19674
  ]);
19675
+ /**
19676
+ * One media row ON THE WIRE: what it is, how big it is, and WHERE ITS BYTES
19677
+ * ARE — never the bytes themselves.
19678
+ *
19679
+ * ## Why `url` and not `base64`
19680
+ *
19681
+ * Measured on the live hub 2026-08-30: `getTrackMedia {trackId, deviceId}`
19682
+ * with no `kinds` returned 6 rows / **3 597 219 B**, of which `keyFrame` alone
19683
+ * was **2 824 077 B** — one full-resolution frame, base64, so +33 % on the
19684
+ * wire. Forty events is ~144 MB. Every byte of it was read off disk,
19685
+ * base64-encoded, held whole in a unary tRPC envelope, and materialised in
19686
+ * hub-main's heap on the way past — for an `<img>` that would have cached it.
19687
+ *
19688
+ * `url` points at the `event-media` data plane
19689
+ * (`/addon/<addonId>/event-media/<storedKey>`), which serves the same blob
19690
+ * with an ETag and `Cache-Control: immutable`, honours conditional GETs, can
19691
+ * render a `?variant=thumb`, and streams. The hub gate in front of it requires
19692
+ * a bearer or the session cookie (`access: 'authenticated'`), so the bytes are
19693
+ * no less protected than they were inside a `view`-level cap response — see
19694
+ * `data-plane-access.ts` for the rule and the one gap it does not close
19695
+ * (per-device scoping).
19696
+ *
19697
+ * The URL is built from the row's **stored** key, which is not always its
19698
+ * published `kind`: a track's face/plate crop is stored as `crop` under
19699
+ * `('face'|'plate', '<prefix>-<trackId>')` and published as
19700
+ * `faceCrop`/`plateCrop`. `MediaStore.getByKey` knows only the stored key.
19701
+ *
19702
+ * ## `base64` is TRANSITIONAL and is going away
19703
+ *
19704
+ * It is still populated for one reason: the deployed viewer's track-detail
19705
+ * HERO tile reads it (`use-track-media-entry.ts` → `parseMediaFiles`, which
19706
+ * REQUIRES the field), and a row without it parses as a FAILED read — the red
19707
+ * triangle — not as absence. Removing the field before that viewer ships is an
19708
+ * outage, not a cleanup. Once the viewer takes its hero bytes from `url`,
19709
+ * delete this line and the `withBytes` pass-through in
19710
+ * `analytics-query-facade.ts`; nothing else reads it.
19711
+ */
19675
19712
  var MediaFileSchema = object({
19676
19713
  key: string(),
19677
19714
  kind: MediaFileKindEnum,
19678
- base64: string(),
19679
19715
  sizeBytes: number(),
19680
19716
  timestamp: number()
19717
+ }).extend({
19718
+ /** `/addon/<addonId>/event-media/<encoded stored key>`. Always present. */
19719
+ url: string(),
19720
+ /** @deprecated Transitional — see the schema docblock. Use {@link url}. */
19721
+ base64: string()
19681
19722
  });
19682
19723
  /**
19683
19724
  * One media row WITHOUT its bytes.
@@ -19689,7 +19730,9 @@ var MediaFileSchema = object({
19689
19730
  * blocks the whole view.
19690
19731
  *
19691
19732
  * `sizeBytes` is carried because it is what lets a client decide between the
19692
- * stored blob and a `?variant=thumb` rendering without fetching either.
19733
+ * stored blob and a `?variant=thumb` rendering without fetching either, and
19734
+ * `url` because a client that had to build the plane path itself is a second
19735
+ * copy of a route — the embed, the viewer and the admin UI each grew one.
19693
19736
  */
19694
19737
  var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
19695
19738
  /**
@@ -20378,6 +20421,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20378
20421
  }), array(MediaFileSchema).readonly()), method(object({
20379
20422
  trackId: string(),
20380
20423
  deviceId: number()
20424
+ }), array(MediaFileInfoSchema).readonly()), method(object({
20425
+ eventId: string(),
20426
+ deviceId: number()
20381
20427
  }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
20382
20428
  kind: "mutation",
20383
20429
  auth: "admin"
@@ -25368,10 +25414,24 @@ var FaceClusterSchema = object({
25368
25414
  size: number().int(),
25369
25415
  cohesion: number()
25370
25416
  });
25417
+ /**
25418
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
25419
+ * are — never the bytes.
25420
+ *
25421
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
25422
+ * track/event contract) is still populated because a deployed viewer requires
25423
+ * the field to parse a row at all; this method has no such reader. Its ONE
25424
+ * caller is the admin UI's detail modal, which was building
25425
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
25426
+ * dialog already rendering its key FRAME from the `event-media` plane.
25427
+ *
25428
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
25429
+ * media key directly, so this needed no new plane and no new access decision.
25430
+ */
25371
25431
  var MediaFileLiteSchema$1 = object({
25372
25432
  key: string(),
25373
25433
  kind: string(),
25374
- base64: string(),
25434
+ url: string(),
25375
25435
  sizeBytes: number(),
25376
25436
  timestamp: number()
25377
25437
  });
@@ -28393,10 +28453,24 @@ var PlateInfoSchema = object({
28393
28453
  */
28394
28454
  cropUrl: string().optional()
28395
28455
  });
28456
+ /**
28457
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
28458
+ * are — never the bytes.
28459
+ *
28460
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
28461
+ * track/event contract) is still populated because a deployed viewer requires
28462
+ * the field to parse a row at all; this method has no such reader. Its ONE
28463
+ * caller is the admin UI's detail modal, which was building
28464
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
28465
+ * dialog already rendering its key FRAME from the `event-media` plane.
28466
+ *
28467
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
28468
+ * media key directly, so this needed no new plane and no new access decision.
28469
+ */
28396
28470
  var MediaFileLiteSchema = object({
28397
28471
  key: string(),
28398
28472
  kind: string(),
28399
- base64: string(),
28473
+ url: string(),
28400
28474
  sizeBytes: number(),
28401
28475
  timestamp: number()
28402
28476
  });
@@ -36293,6 +36367,12 @@ Object.freeze({
36293
36367
  addonId: null,
36294
36368
  access: "view"
36295
36369
  },
36370
+ "pipelineAnalytics.listEventMedia": {
36371
+ capName: "pipeline-analytics",
36372
+ capScope: "device",
36373
+ addonId: null,
36374
+ access: "view"
36375
+ },
36296
36376
  "pipelineAnalytics.listGroups": {
36297
36377
  capName: "pipeline-analytics",
36298
36378
  capScope: "device",
@@ -39916,6 +39996,11 @@ Object.freeze({
39916
39996
  form: "array",
39917
39997
  optional: false
39918
39998
  }],
39999
+ "pipelineAnalytics.listEventMedia": [{
40000
+ name: "deviceId",
40001
+ form: "single",
40002
+ optional: false
40003
+ }],
39919
40004
  "pipelineAnalytics.listGroups": [{
39920
40005
  name: "deviceIds",
39921
40006
  form: "array",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-rademacher",
3
- "version": "0.2.45",
3
+ "version": "0.2.46",
4
4
  "description": "Rademacher HomePilot device-provider addon for CamStack — wraps the @apocaliss92/noderademacher local-hub client (roller shutters over the cover cap)",
5
5
  "keywords": [
6
6
  "camstack",