@camstack/addon-decoder-ffmpeg 1.2.46 → 1.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/index.js +259 -174
  2. package/dist/index.mjs +259 -174
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -13051,6 +13051,114 @@ method(object({
13051
13051
  height: number()
13052
13052
  }), EmbeddingResultSchema, { auth: "admin" }), method(object({ text: string() }), EmbeddingResultSchema, { auth: "admin" }), method(_void(), EmbeddingInfoSchema, { auth: "admin" });
13053
13053
  /**
13054
+ * `failure-contribution` — the capability an addon reports its OWN losses
13055
+ * through, per camera, with the denominator attached. It stores nothing.
13056
+ *
13057
+ * ## The twin of `load-contribution`, and why it is a twin and not a field
13058
+ *
13059
+ * `load-contribution` answers *what did this camera COST*. This answers *what
13060
+ * did this camera LOSE*. The reporting discipline is identical and deliberately
13061
+ * copied: the contributor reports what it already knows, hub-main adds only
13062
+ * `addonId`, nothing needs global knowledge, and there is no central list for
13063
+ * somebody to forget to edit.
13064
+ *
13065
+ * They are not merged, because their invariants are opposites:
13066
+ *
13067
+ * - a `load-contribution` measurement is **absent, never zero** — a zero would
13068
+ * claim a camera cost nothing, which is a measurement nobody made;
13069
+ * - a `failure-contribution` zero is the **most valuable value on the
13070
+ * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
13071
+ * and it is exactly what an absent entry cannot say.
13072
+ *
13073
+ * Putting a loss counter on a cost entry would also break the reconciliation
13074
+ * that gives `load-contribution` its point: contributions are subtracted from
13075
+ * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
13076
+ * has no process.
13077
+ *
13078
+ * ## Why not a log line, since the counters already exist
13079
+ *
13080
+ * Several of these paths already counted themselves — `CaptureScheduler`'s
13081
+ * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
13082
+ * ends in a log line, and a log line is the thing the operator asked to stop
13083
+ * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
13084
+ * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
13085
+ * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
13086
+ * media blackout were both diagnosed. The counters stay; this is where they can
13087
+ * be READ.
13088
+ *
13089
+ * ## The rate is served with its denominator or not at all
13090
+ *
13091
+ * Every entry carries `attempts` and `succeeded`. A miss count alone is
13092
+ * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
13093
+ * than yesterday" and was **flat across twelve hours** once divided by the
13094
+ * successes on the same path. A surface that publishes only the numerator
13095
+ * reproduces that mistake on every read.
13096
+ *
13097
+ * ## Shape
13098
+ *
13099
+ * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
13100
+ * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
13101
+ * generated hooks, while `addons.listCapabilityProviders` still enumerates it
13102
+ * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
13103
+ * a forked runner's entries reach hub-main over transport that already exists.
13104
+ * No new UDS message, no second registry (D3). The operator reads the assembled
13105
+ * result through `system.getFailureContributions`.
13106
+ */
13107
+ var FailureReasonCountSchema = object({
13108
+ /**
13109
+ * Why the attempt did not land, in the contributor's own vocabulary —
13110
+ * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
13111
+ * strings that already appear in this repo's logs and, where one exists, the
13112
+ * same string the per-track `previewMissReason` records (D276): a second
13113
+ * vocabulary for the same loss would make the row and the counter
13114
+ * un-joinable.
13115
+ */
13116
+ reason: string(),
13117
+ count: number().int().nonnegative()
13118
+ });
13119
+ var FailureContributionSchema = object({
13120
+ /**
13121
+ * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
13122
+ * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
13123
+ * `unit` free: the families are owned by different addons and a shared enum
13124
+ * is a central list that rots invisibly.
13125
+ */
13126
+ family: string(),
13127
+ /**
13128
+ * The NUMERIC device id — the same value every log line carries as
13129
+ * `tags.deviceId`. Never nullable and never absent: a contributor that
13130
+ * cannot name the camera must not emit the entry, because a fleet total
13131
+ * cannot answer the only question anybody asks of this surface.
13132
+ */
13133
+ deviceId: number().int().positive(),
13134
+ /**
13135
+ * A second dimension inside the family: the model / step id for an inference
13136
+ * timeout, so "which camera AND which model" is one read. Absent when the
13137
+ * family has a single variant.
13138
+ */
13139
+ variant: string().optional(),
13140
+ /**
13141
+ * Epoch ms this counter started — the INCARNATION MARKER. A consumer
13142
+ * differencing two reads must drop the interval when it changes, because the
13143
+ * counter restarted from zero in a respawned runner. Same discipline as
13144
+ * `LoadContribution.startedAtMs`.
13145
+ */
13146
+ sinceMs: number(),
13147
+ /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
13148
+ atMs: number(),
13149
+ /**
13150
+ * THE DENOMINATOR — every attempt on this path for this camera in the
13151
+ * window. A failure count published without it is the mistake this schema
13152
+ * exists to make impossible.
13153
+ */
13154
+ attempts: number().int().nonnegative(),
13155
+ /** Attempts that landed. `attempts - succeeded` is the loss. */
13156
+ succeeded: number().int().nonnegative(),
13157
+ /** The loss, partitioned. Sums to `attempts - succeeded`. */
13158
+ reasons: array(FailureReasonCountSchema).readonly()
13159
+ });
13160
+ method(_void(), array(FailureContributionSchema).readonly());
13161
+ /**
13054
13162
  * filesystem-browse — per-node capability for browsing the node's local
13055
13163
  * filesystem. Reads are unconfined (whole filesystem, from `/` down); WRITES
13056
13164
  * are sandboxed to operator-configured allowed roots (D115). Used by the
@@ -13572,6 +13680,68 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
13572
13680
  kind: "mutation",
13573
13681
  auth: "admin"
13574
13682
  });
13683
+ var LoadContributionSchema = object({
13684
+ role: _enum([
13685
+ "decode",
13686
+ "transcode",
13687
+ "recording",
13688
+ "streaming",
13689
+ "detection"
13690
+ ]),
13691
+ /**
13692
+ * The NUMERIC device id — the same value every log line carries as
13693
+ * `tags.deviceId`. `null` means this cost genuinely belongs to no single
13694
+ * camera (a shared pool), NOT that the contributor forgot to look it up: a
13695
+ * contributor that cannot name its camera must not emit the entry at all,
13696
+ * because an unnamed per-camera entry is indistinguishable from a shared one
13697
+ * and would quietly turn one camera's cost into everybody's.
13698
+ */
13699
+ deviceId: number().int().positive().nullable(),
13700
+ attribution: _enum([
13701
+ "measured",
13702
+ "accounted",
13703
+ "unattributable"
13704
+ ]),
13705
+ /**
13706
+ * What ONE entry is, in the contributor's own words — `615/high`,
13707
+ * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
13708
+ * family and inventing a common one would lose the only information that
13709
+ * makes two entries for the same camera distinguishable.
13710
+ */
13711
+ unit: string(),
13712
+ /**
13713
+ * The OS process this cost lives in, when there is one. Present so a
13714
+ * consumer can (a) tell two generations of the same unit apart across a
13715
+ * restart, and (b) subtract claimed processes from the node's process
13716
+ * snapshot to see what NOBODY claimed. Absent for an entry that owns no
13717
+ * process of its own.
13718
+ */
13719
+ pid: number().int().positive().optional(),
13720
+ /**
13721
+ * When this generation started. The pid's incarnation marker: a consumer
13722
+ * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
13723
+ * window when this changes, because the counter restarted from zero in a new
13724
+ * process.
13725
+ */
13726
+ startedAtMs: number().optional(),
13727
+ /**
13728
+ * CUMULATIVE CPU seconds this unit has consumed since it started — user +
13729
+ * system, read from the child's own `/proc/<pid>/stat` at the moment the
13730
+ * contribution is asked for.
13731
+ *
13732
+ * Cumulative and not a rate on purpose: a rate needs a window, a window
13733
+ * needs a sampler, and a new per-node sampler is the defect half of
13734
+ * `docs/architecture/load-ledger.md` documents. A counter can be differenced
13735
+ * by whoever already keeps a history; a rate cannot be un-averaged.
13736
+ *
13737
+ * Absent — never zero — on a node with no `/proc`, on a read failure, and on
13738
+ * an entry with no process.
13739
+ */
13740
+ cpuSeconds: number().optional(),
13741
+ /** Resident bytes of this unit's process, same source and same rules. */
13742
+ rssBytes: number().optional()
13743
+ });
13744
+ method(_void(), array(LoadContributionSchema).readonly());
13575
13745
  /**
13576
13746
  * `log-channels` — the capability an addon DECLARES its diagnostic channels
13577
13747
  * through. It stores nothing.
@@ -13648,176 +13818,6 @@ method(LogEntrySchema, _void(), { kind: "mutation" }), method(object({
13648
13818
  tags: record(string(), string()).optional()
13649
13819
  }), array(LogEntrySchema).readonly());
13650
13820
  /**
13651
- * `failure-contribution` — the capability an addon reports its OWN losses
13652
- * through, per camera, with the denominator attached. It stores nothing.
13653
- *
13654
- * ## The twin of `load-contribution`, and why it is a twin and not a field
13655
- *
13656
- * `load-contribution` answers *what did this camera COST*. This answers *what
13657
- * did this camera LOSE*. The reporting discipline is identical and deliberately
13658
- * copied: the contributor reports what it already knows, hub-main adds only
13659
- * `addonId`, nothing needs global knowledge, and there is no central list for
13660
- * somebody to forget to edit.
13661
- *
13662
- * They are not merged, because their invariants are opposites:
13663
- *
13664
- * - a `load-contribution` measurement is **absent, never zero** — a zero would
13665
- * claim a camera cost nothing, which is a measurement nobody made;
13666
- * - a `failure-contribution` zero is the **most valuable value on the
13667
- * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
13668
- * and it is exactly what an absent entry cannot say.
13669
- *
13670
- * Putting a loss counter on a cost entry would also break the reconciliation
13671
- * that gives `load-contribution` its point: contributions are subtracted from
13672
- * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
13673
- * has no process.
13674
- *
13675
- * ## Why not a log line, since the counters already exist
13676
- *
13677
- * Several of these paths already counted themselves — `CaptureScheduler`'s
13678
- * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
13679
- * ends in a log line, and a log line is the thing the operator asked to stop
13680
- * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
13681
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
13682
- * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
13683
- * media blackout were both diagnosed. The counters stay; this is where they can
13684
- * be READ.
13685
- *
13686
- * ## The rate is served with its denominator or not at all
13687
- *
13688
- * Every entry carries `attempts` and `succeeded`. A miss count alone is
13689
- * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
13690
- * than yesterday" and was **flat across twelve hours** once divided by the
13691
- * successes on the same path. A surface that publishes only the numerator
13692
- * reproduces that mistake on every read.
13693
- *
13694
- * ## Shape
13695
- *
13696
- * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
13697
- * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
13698
- * generated hooks, while `addons.listCapabilityProviders` still enumerates it
13699
- * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
13700
- * a forked runner's entries reach hub-main over transport that already exists.
13701
- * No new UDS message, no second registry (D3). The operator reads the assembled
13702
- * result through `system.getFailureContributions`.
13703
- */
13704
- var FailureReasonCountSchema = object({
13705
- /**
13706
- * Why the attempt did not land, in the contributor's own vocabulary —
13707
- * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
13708
- * strings that already appear in this repo's logs and, where one exists, the
13709
- * same string the per-track `previewMissReason` records (D276): a second
13710
- * vocabulary for the same loss would make the row and the counter
13711
- * un-joinable.
13712
- */
13713
- reason: string(),
13714
- count: number().int().nonnegative()
13715
- });
13716
- var FailureContributionSchema = object({
13717
- /**
13718
- * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
13719
- * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
13720
- * `unit` free: the families are owned by different addons and a shared enum
13721
- * is a central list that rots invisibly.
13722
- */
13723
- family: string(),
13724
- /**
13725
- * The NUMERIC device id — the same value every log line carries as
13726
- * `tags.deviceId`. Never nullable and never absent: a contributor that
13727
- * cannot name the camera must not emit the entry, because a fleet total
13728
- * cannot answer the only question anybody asks of this surface.
13729
- */
13730
- deviceId: number().int().positive(),
13731
- /**
13732
- * A second dimension inside the family: the model / step id for an inference
13733
- * timeout, so "which camera AND which model" is one read. Absent when the
13734
- * family has a single variant.
13735
- */
13736
- variant: string().optional(),
13737
- /**
13738
- * Epoch ms this counter started — the INCARNATION MARKER. A consumer
13739
- * differencing two reads must drop the interval when it changes, because the
13740
- * counter restarted from zero in a respawned runner. Same discipline as
13741
- * `LoadContribution.startedAtMs`.
13742
- */
13743
- sinceMs: number(),
13744
- /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
13745
- atMs: number(),
13746
- /**
13747
- * THE DENOMINATOR — every attempt on this path for this camera in the
13748
- * window. A failure count published without it is the mistake this schema
13749
- * exists to make impossible.
13750
- */
13751
- attempts: number().int().nonnegative(),
13752
- /** Attempts that landed. `attempts - succeeded` is the loss. */
13753
- succeeded: number().int().nonnegative(),
13754
- /** The loss, partitioned. Sums to `attempts - succeeded`. */
13755
- reasons: array(FailureReasonCountSchema).readonly()
13756
- });
13757
- method(_void(), array(FailureContributionSchema).readonly());
13758
- var LoadContributionSchema = object({
13759
- role: _enum([
13760
- "decode",
13761
- "transcode",
13762
- "recording",
13763
- "streaming",
13764
- "detection"
13765
- ]),
13766
- /**
13767
- * The NUMERIC device id — the same value every log line carries as
13768
- * `tags.deviceId`. `null` means this cost genuinely belongs to no single
13769
- * camera (a shared pool), NOT that the contributor forgot to look it up: a
13770
- * contributor that cannot name its camera must not emit the entry at all,
13771
- * because an unnamed per-camera entry is indistinguishable from a shared one
13772
- * and would quietly turn one camera's cost into everybody's.
13773
- */
13774
- deviceId: number().int().positive().nullable(),
13775
- attribution: _enum([
13776
- "measured",
13777
- "accounted",
13778
- "unattributable"
13779
- ]),
13780
- /**
13781
- * What ONE entry is, in the contributor's own words — `615/high`,
13782
- * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
13783
- * family and inventing a common one would lose the only information that
13784
- * makes two entries for the same camera distinguishable.
13785
- */
13786
- unit: string(),
13787
- /**
13788
- * The OS process this cost lives in, when there is one. Present so a
13789
- * consumer can (a) tell two generations of the same unit apart across a
13790
- * restart, and (b) subtract claimed processes from the node's process
13791
- * snapshot to see what NOBODY claimed. Absent for an entry that owns no
13792
- * process of its own.
13793
- */
13794
- pid: number().int().positive().optional(),
13795
- /**
13796
- * When this generation started. The pid's incarnation marker: a consumer
13797
- * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
13798
- * window when this changes, because the counter restarted from zero in a new
13799
- * process.
13800
- */
13801
- startedAtMs: number().optional(),
13802
- /**
13803
- * CUMULATIVE CPU seconds this unit has consumed since it started — user +
13804
- * system, read from the child's own `/proc/<pid>/stat` at the moment the
13805
- * contribution is asked for.
13806
- *
13807
- * Cumulative and not a rate on purpose: a rate needs a window, a window
13808
- * needs a sampler, and a new per-node sampler is the defect half of
13809
- * `docs/architecture/load-ledger.md` documents. A counter can be differenced
13810
- * by whoever already keeps a history; a rate cannot be un-averaged.
13811
- *
13812
- * Absent — never zero — on a node with no `/proc`, on a read failure, and on
13813
- * an entry with no process.
13814
- */
13815
- cpuSeconds: number().optional(),
13816
- /** Resident bytes of this unit's process, same source and same rules. */
13817
- rssBytes: number().optional()
13818
- });
13819
- method(_void(), array(LoadContributionSchema).readonly());
13820
- /**
13821
13821
  * `login-method` — collection cap through which auth addons contribute
13822
13822
  * their pre-auth login surfaces to the login page. This is the SINGLE,
13823
13823
  * generic mechanism that supersedes the dead `auth.listProviders` reader:
@@ -18477,12 +18477,53 @@ var MediaFileKindEnum = _enum([
18477
18477
  "keyFrameSmall",
18478
18478
  "thumbnailSmall"
18479
18479
  ]);
18480
+ /**
18481
+ * One media row ON THE WIRE: what it is, how big it is, and WHERE ITS BYTES
18482
+ * ARE — never the bytes themselves.
18483
+ *
18484
+ * ## Why `url` and not `base64`
18485
+ *
18486
+ * Measured on the live hub 2026-08-30: `getTrackMedia {trackId, deviceId}`
18487
+ * with no `kinds` returned 6 rows / **3 597 219 B**, of which `keyFrame` alone
18488
+ * was **2 824 077 B** — one full-resolution frame, base64, so +33 % on the
18489
+ * wire. Forty events is ~144 MB. Every byte of it was read off disk,
18490
+ * base64-encoded, held whole in a unary tRPC envelope, and materialised in
18491
+ * hub-main's heap on the way past — for an `<img>` that would have cached it.
18492
+ *
18493
+ * `url` points at the `event-media` data plane
18494
+ * (`/addon/<addonId>/event-media/<storedKey>`), which serves the same blob
18495
+ * with an ETag and `Cache-Control: immutable`, honours conditional GETs, can
18496
+ * render a `?variant=thumb`, and streams. The hub gate in front of it requires
18497
+ * a bearer or the session cookie (`access: 'authenticated'`), so the bytes are
18498
+ * no less protected than they were inside a `view`-level cap response — see
18499
+ * `data-plane-access.ts` for the rule and the one gap it does not close
18500
+ * (per-device scoping).
18501
+ *
18502
+ * The URL is built from the row's **stored** key, which is not always its
18503
+ * published `kind`: a track's face/plate crop is stored as `crop` under
18504
+ * `('face'|'plate', '<prefix>-<trackId>')` and published as
18505
+ * `faceCrop`/`plateCrop`. `MediaStore.getByKey` knows only the stored key.
18506
+ *
18507
+ * ## `base64` is TRANSITIONAL and is going away
18508
+ *
18509
+ * It is still populated for one reason: the deployed viewer's track-detail
18510
+ * HERO tile reads it (`use-track-media-entry.ts` → `parseMediaFiles`, which
18511
+ * REQUIRES the field), and a row without it parses as a FAILED read — the red
18512
+ * triangle — not as absence. Removing the field before that viewer ships is an
18513
+ * outage, not a cleanup. Once the viewer takes its hero bytes from `url`,
18514
+ * delete this line and the `withBytes` pass-through in
18515
+ * `analytics-query-facade.ts`; nothing else reads it.
18516
+ */
18480
18517
  var MediaFileSchema = object({
18481
18518
  key: string(),
18482
18519
  kind: MediaFileKindEnum,
18483
- base64: string(),
18484
18520
  sizeBytes: number(),
18485
18521
  timestamp: number()
18522
+ }).extend({
18523
+ /** `/addon/<addonId>/event-media/<encoded stored key>`. Always present. */
18524
+ url: string(),
18525
+ /** @deprecated Transitional — see the schema docblock. Use {@link url}. */
18526
+ base64: string()
18486
18527
  });
18487
18528
  /**
18488
18529
  * One media row WITHOUT its bytes.
@@ -18494,7 +18535,9 @@ var MediaFileSchema = object({
18494
18535
  * blocks the whole view.
18495
18536
  *
18496
18537
  * `sizeBytes` is carried because it is what lets a client decide between the
18497
- * stored blob and a `?variant=thumb` rendering without fetching either.
18538
+ * stored blob and a `?variant=thumb` rendering without fetching either, and
18539
+ * `url` because a client that had to build the plane path itself is a second
18540
+ * copy of a route — the embed, the viewer and the admin UI each grew one.
18498
18541
  */
18499
18542
  var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
18500
18543
  /**
@@ -19183,6 +19226,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19183
19226
  }), array(MediaFileSchema).readonly()), method(object({
19184
19227
  trackId: string(),
19185
19228
  deviceId: number()
19229
+ }), array(MediaFileInfoSchema).readonly()), method(object({
19230
+ eventId: string(),
19231
+ deviceId: number()
19186
19232
  }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
19187
19233
  kind: "mutation",
19188
19234
  auth: "admin"
@@ -23443,10 +23489,24 @@ var FaceClusterSchema = object({
23443
23489
  size: number().int(),
23444
23490
  cohesion: number()
23445
23491
  });
23492
+ /**
23493
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
23494
+ * are — never the bytes.
23495
+ *
23496
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
23497
+ * track/event contract) is still populated because a deployed viewer requires
23498
+ * the field to parse a row at all; this method has no such reader. Its ONE
23499
+ * caller is the admin UI's detail modal, which was building
23500
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
23501
+ * dialog already rendering its key FRAME from the `event-media` plane.
23502
+ *
23503
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
23504
+ * media key directly, so this needed no new plane and no new access decision.
23505
+ */
23446
23506
  var MediaFileLiteSchema$1 = object({
23447
23507
  key: string(),
23448
23508
  kind: string(),
23449
- base64: string(),
23509
+ url: string(),
23450
23510
  sizeBytes: number(),
23451
23511
  timestamp: number()
23452
23512
  });
@@ -25698,10 +25758,24 @@ var PlateInfoSchema = object({
25698
25758
  */
25699
25759
  cropUrl: string().optional()
25700
25760
  });
25761
+ /**
25762
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
25763
+ * are — never the bytes.
25764
+ *
25765
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
25766
+ * track/event contract) is still populated because a deployed viewer requires
25767
+ * the field to parse a row at all; this method has no such reader. Its ONE
25768
+ * caller is the admin UI's detail modal, which was building
25769
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
25770
+ * dialog already rendering its key FRAME from the `event-media` plane.
25771
+ *
25772
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
25773
+ * media key directly, so this needed no new plane and no new access decision.
25774
+ */
25701
25775
  var MediaFileLiteSchema = object({
25702
25776
  key: string(),
25703
25777
  kind: string(),
25704
- base64: string(),
25778
+ url: string(),
25705
25779
  sizeBytes: number(),
25706
25780
  timestamp: number()
25707
25781
  });
@@ -31714,6 +31788,12 @@ Object.freeze({
31714
31788
  addonId: null,
31715
31789
  access: "view"
31716
31790
  },
31791
+ "pipelineAnalytics.listEventMedia": {
31792
+ capName: "pipeline-analytics",
31793
+ capScope: "device",
31794
+ addonId: null,
31795
+ access: "view"
31796
+ },
31717
31797
  "pipelineAnalytics.listGroups": {
31718
31798
  capName: "pipeline-analytics",
31719
31799
  capScope: "device",
@@ -35337,6 +35417,11 @@ Object.freeze({
35337
35417
  form: "array",
35338
35418
  optional: false
35339
35419
  }],
35420
+ "pipelineAnalytics.listEventMedia": [{
35421
+ name: "deviceId",
35422
+ form: "single",
35423
+ optional: false
35424
+ }],
35340
35425
  "pipelineAnalytics.listGroups": [{
35341
35426
  name: "deviceIds",
35342
35427
  form: "array",
package/dist/index.mjs CHANGED
@@ -13047,6 +13047,114 @@ method(object({
13047
13047
  height: number()
13048
13048
  }), EmbeddingResultSchema, { auth: "admin" }), method(object({ text: string() }), EmbeddingResultSchema, { auth: "admin" }), method(_void(), EmbeddingInfoSchema, { auth: "admin" });
13049
13049
  /**
13050
+ * `failure-contribution` — the capability an addon reports its OWN losses
13051
+ * through, per camera, with the denominator attached. It stores nothing.
13052
+ *
13053
+ * ## The twin of `load-contribution`, and why it is a twin and not a field
13054
+ *
13055
+ * `load-contribution` answers *what did this camera COST*. This answers *what
13056
+ * did this camera LOSE*. The reporting discipline is identical and deliberately
13057
+ * copied: the contributor reports what it already knows, hub-main adds only
13058
+ * `addonId`, nothing needs global knowledge, and there is no central list for
13059
+ * somebody to forget to edit.
13060
+ *
13061
+ * They are not merged, because their invariants are opposites:
13062
+ *
13063
+ * - a `load-contribution` measurement is **absent, never zero** — a zero would
13064
+ * claim a camera cost nothing, which is a measurement nobody made;
13065
+ * - a `failure-contribution` zero is the **most valuable value on the
13066
+ * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
13067
+ * and it is exactly what an absent entry cannot say.
13068
+ *
13069
+ * Putting a loss counter on a cost entry would also break the reconciliation
13070
+ * that gives `load-contribution` its point: contributions are subtracted from
13071
+ * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
13072
+ * has no process.
13073
+ *
13074
+ * ## Why not a log line, since the counters already exist
13075
+ *
13076
+ * Several of these paths already counted themselves — `CaptureScheduler`'s
13077
+ * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
13078
+ * ends in a log line, and a log line is the thing the operator asked to stop
13079
+ * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
13080
+ * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
13081
+ * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
13082
+ * media blackout were both diagnosed. The counters stay; this is where they can
13083
+ * be READ.
13084
+ *
13085
+ * ## The rate is served with its denominator or not at all
13086
+ *
13087
+ * Every entry carries `attempts` and `succeeded`. A miss count alone is
13088
+ * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
13089
+ * than yesterday" and was **flat across twelve hours** once divided by the
13090
+ * successes on the same path. A surface that publishes only the numerator
13091
+ * reproduces that mistake on every read.
13092
+ *
13093
+ * ## Shape
13094
+ *
13095
+ * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
13096
+ * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
13097
+ * generated hooks, while `addons.listCapabilityProviders` still enumerates it
13098
+ * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
13099
+ * a forked runner's entries reach hub-main over transport that already exists.
13100
+ * No new UDS message, no second registry (D3). The operator reads the assembled
13101
+ * result through `system.getFailureContributions`.
13102
+ */
13103
+ var FailureReasonCountSchema = object({
13104
+ /**
13105
+ * Why the attempt did not land, in the contributor's own vocabulary —
13106
+ * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
13107
+ * strings that already appear in this repo's logs and, where one exists, the
13108
+ * same string the per-track `previewMissReason` records (D276): a second
13109
+ * vocabulary for the same loss would make the row and the counter
13110
+ * un-joinable.
13111
+ */
13112
+ reason: string(),
13113
+ count: number().int().nonnegative()
13114
+ });
13115
+ var FailureContributionSchema = object({
13116
+ /**
13117
+ * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
13118
+ * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
13119
+ * `unit` free: the families are owned by different addons and a shared enum
13120
+ * is a central list that rots invisibly.
13121
+ */
13122
+ family: string(),
13123
+ /**
13124
+ * The NUMERIC device id — the same value every log line carries as
13125
+ * `tags.deviceId`. Never nullable and never absent: a contributor that
13126
+ * cannot name the camera must not emit the entry, because a fleet total
13127
+ * cannot answer the only question anybody asks of this surface.
13128
+ */
13129
+ deviceId: number().int().positive(),
13130
+ /**
13131
+ * A second dimension inside the family: the model / step id for an inference
13132
+ * timeout, so "which camera AND which model" is one read. Absent when the
13133
+ * family has a single variant.
13134
+ */
13135
+ variant: string().optional(),
13136
+ /**
13137
+ * Epoch ms this counter started — the INCARNATION MARKER. A consumer
13138
+ * differencing two reads must drop the interval when it changes, because the
13139
+ * counter restarted from zero in a respawned runner. Same discipline as
13140
+ * `LoadContribution.startedAtMs`.
13141
+ */
13142
+ sinceMs: number(),
13143
+ /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
13144
+ atMs: number(),
13145
+ /**
13146
+ * THE DENOMINATOR — every attempt on this path for this camera in the
13147
+ * window. A failure count published without it is the mistake this schema
13148
+ * exists to make impossible.
13149
+ */
13150
+ attempts: number().int().nonnegative(),
13151
+ /** Attempts that landed. `attempts - succeeded` is the loss. */
13152
+ succeeded: number().int().nonnegative(),
13153
+ /** The loss, partitioned. Sums to `attempts - succeeded`. */
13154
+ reasons: array(FailureReasonCountSchema).readonly()
13155
+ });
13156
+ method(_void(), array(FailureContributionSchema).readonly());
13157
+ /**
13050
13158
  * filesystem-browse — per-node capability for browsing the node's local
13051
13159
  * filesystem. Reads are unconfined (whole filesystem, from `/` down); WRITES
13052
13160
  * are sandboxed to operator-configured allowed roots (D115). Used by the
@@ -13568,6 +13676,68 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
13568
13676
  kind: "mutation",
13569
13677
  auth: "admin"
13570
13678
  });
13679
+ var LoadContributionSchema = object({
13680
+ role: _enum([
13681
+ "decode",
13682
+ "transcode",
13683
+ "recording",
13684
+ "streaming",
13685
+ "detection"
13686
+ ]),
13687
+ /**
13688
+ * The NUMERIC device id — the same value every log line carries as
13689
+ * `tags.deviceId`. `null` means this cost genuinely belongs to no single
13690
+ * camera (a shared pool), NOT that the contributor forgot to look it up: a
13691
+ * contributor that cannot name its camera must not emit the entry at all,
13692
+ * because an unnamed per-camera entry is indistinguishable from a shared one
13693
+ * and would quietly turn one camera's cost into everybody's.
13694
+ */
13695
+ deviceId: number().int().positive().nullable(),
13696
+ attribution: _enum([
13697
+ "measured",
13698
+ "accounted",
13699
+ "unattributable"
13700
+ ]),
13701
+ /**
13702
+ * What ONE entry is, in the contributor's own words — `615/high`,
13703
+ * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
13704
+ * family and inventing a common one would lose the only information that
13705
+ * makes two entries for the same camera distinguishable.
13706
+ */
13707
+ unit: string(),
13708
+ /**
13709
+ * The OS process this cost lives in, when there is one. Present so a
13710
+ * consumer can (a) tell two generations of the same unit apart across a
13711
+ * restart, and (b) subtract claimed processes from the node's process
13712
+ * snapshot to see what NOBODY claimed. Absent for an entry that owns no
13713
+ * process of its own.
13714
+ */
13715
+ pid: number().int().positive().optional(),
13716
+ /**
13717
+ * When this generation started. The pid's incarnation marker: a consumer
13718
+ * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
13719
+ * window when this changes, because the counter restarted from zero in a new
13720
+ * process.
13721
+ */
13722
+ startedAtMs: number().optional(),
13723
+ /**
13724
+ * CUMULATIVE CPU seconds this unit has consumed since it started — user +
13725
+ * system, read from the child's own `/proc/<pid>/stat` at the moment the
13726
+ * contribution is asked for.
13727
+ *
13728
+ * Cumulative and not a rate on purpose: a rate needs a window, a window
13729
+ * needs a sampler, and a new per-node sampler is the defect half of
13730
+ * `docs/architecture/load-ledger.md` documents. A counter can be differenced
13731
+ * by whoever already keeps a history; a rate cannot be un-averaged.
13732
+ *
13733
+ * Absent — never zero — on a node with no `/proc`, on a read failure, and on
13734
+ * an entry with no process.
13735
+ */
13736
+ cpuSeconds: number().optional(),
13737
+ /** Resident bytes of this unit's process, same source and same rules. */
13738
+ rssBytes: number().optional()
13739
+ });
13740
+ method(_void(), array(LoadContributionSchema).readonly());
13571
13741
  /**
13572
13742
  * `log-channels` — the capability an addon DECLARES its diagnostic channels
13573
13743
  * through. It stores nothing.
@@ -13644,176 +13814,6 @@ method(LogEntrySchema, _void(), { kind: "mutation" }), method(object({
13644
13814
  tags: record(string(), string()).optional()
13645
13815
  }), array(LogEntrySchema).readonly());
13646
13816
  /**
13647
- * `failure-contribution` — the capability an addon reports its OWN losses
13648
- * through, per camera, with the denominator attached. It stores nothing.
13649
- *
13650
- * ## The twin of `load-contribution`, and why it is a twin and not a field
13651
- *
13652
- * `load-contribution` answers *what did this camera COST*. This answers *what
13653
- * did this camera LOSE*. The reporting discipline is identical and deliberately
13654
- * copied: the contributor reports what it already knows, hub-main adds only
13655
- * `addonId`, nothing needs global knowledge, and there is no central list for
13656
- * somebody to forget to edit.
13657
- *
13658
- * They are not merged, because their invariants are opposites:
13659
- *
13660
- * - a `load-contribution` measurement is **absent, never zero** — a zero would
13661
- * claim a camera cost nothing, which is a measurement nobody made;
13662
- * - a `failure-contribution` zero is the **most valuable value on the
13663
- * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
13664
- * and it is exactly what an absent entry cannot say.
13665
- *
13666
- * Putting a loss counter on a cost entry would also break the reconciliation
13667
- * that gives `load-contribution` its point: contributions are subtracted from
13668
- * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
13669
- * has no process.
13670
- *
13671
- * ## Why not a log line, since the counters already exist
13672
- *
13673
- * Several of these paths already counted themselves — `CaptureScheduler`'s
13674
- * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
13675
- * ends in a log line, and a log line is the thing the operator asked to stop
13676
- * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
13677
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
13678
- * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
13679
- * media blackout were both diagnosed. The counters stay; this is where they can
13680
- * be READ.
13681
- *
13682
- * ## The rate is served with its denominator or not at all
13683
- *
13684
- * Every entry carries `attempts` and `succeeded`. A miss count alone is
13685
- * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
13686
- * than yesterday" and was **flat across twelve hours** once divided by the
13687
- * successes on the same path. A surface that publishes only the numerator
13688
- * reproduces that mistake on every read.
13689
- *
13690
- * ## Shape
13691
- *
13692
- * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
13693
- * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
13694
- * generated hooks, while `addons.listCapabilityProviders` still enumerates it
13695
- * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
13696
- * a forked runner's entries reach hub-main over transport that already exists.
13697
- * No new UDS message, no second registry (D3). The operator reads the assembled
13698
- * result through `system.getFailureContributions`.
13699
- */
13700
- var FailureReasonCountSchema = object({
13701
- /**
13702
- * Why the attempt did not land, in the contributor's own vocabulary —
13703
- * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
13704
- * strings that already appear in this repo's logs and, where one exists, the
13705
- * same string the per-track `previewMissReason` records (D276): a second
13706
- * vocabulary for the same loss would make the row and the counter
13707
- * un-joinable.
13708
- */
13709
- reason: string(),
13710
- count: number().int().nonnegative()
13711
- });
13712
- var FailureContributionSchema = object({
13713
- /**
13714
- * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
13715
- * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
13716
- * `unit` free: the families are owned by different addons and a shared enum
13717
- * is a central list that rots invisibly.
13718
- */
13719
- family: string(),
13720
- /**
13721
- * The NUMERIC device id — the same value every log line carries as
13722
- * `tags.deviceId`. Never nullable and never absent: a contributor that
13723
- * cannot name the camera must not emit the entry, because a fleet total
13724
- * cannot answer the only question anybody asks of this surface.
13725
- */
13726
- deviceId: number().int().positive(),
13727
- /**
13728
- * A second dimension inside the family: the model / step id for an inference
13729
- * timeout, so "which camera AND which model" is one read. Absent when the
13730
- * family has a single variant.
13731
- */
13732
- variant: string().optional(),
13733
- /**
13734
- * Epoch ms this counter started — the INCARNATION MARKER. A consumer
13735
- * differencing two reads must drop the interval when it changes, because the
13736
- * counter restarted from zero in a respawned runner. Same discipline as
13737
- * `LoadContribution.startedAtMs`.
13738
- */
13739
- sinceMs: number(),
13740
- /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
13741
- atMs: number(),
13742
- /**
13743
- * THE DENOMINATOR — every attempt on this path for this camera in the
13744
- * window. A failure count published without it is the mistake this schema
13745
- * exists to make impossible.
13746
- */
13747
- attempts: number().int().nonnegative(),
13748
- /** Attempts that landed. `attempts - succeeded` is the loss. */
13749
- succeeded: number().int().nonnegative(),
13750
- /** The loss, partitioned. Sums to `attempts - succeeded`. */
13751
- reasons: array(FailureReasonCountSchema).readonly()
13752
- });
13753
- method(_void(), array(FailureContributionSchema).readonly());
13754
- var LoadContributionSchema = object({
13755
- role: _enum([
13756
- "decode",
13757
- "transcode",
13758
- "recording",
13759
- "streaming",
13760
- "detection"
13761
- ]),
13762
- /**
13763
- * The NUMERIC device id — the same value every log line carries as
13764
- * `tags.deviceId`. `null` means this cost genuinely belongs to no single
13765
- * camera (a shared pool), NOT that the contributor forgot to look it up: a
13766
- * contributor that cannot name its camera must not emit the entry at all,
13767
- * because an unnamed per-camera entry is indistinguishable from a shared one
13768
- * and would quietly turn one camera's cost into everybody's.
13769
- */
13770
- deviceId: number().int().positive().nullable(),
13771
- attribution: _enum([
13772
- "measured",
13773
- "accounted",
13774
- "unattributable"
13775
- ]),
13776
- /**
13777
- * What ONE entry is, in the contributor's own words — `615/high`,
13778
- * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
13779
- * family and inventing a common one would lose the only information that
13780
- * makes two entries for the same camera distinguishable.
13781
- */
13782
- unit: string(),
13783
- /**
13784
- * The OS process this cost lives in, when there is one. Present so a
13785
- * consumer can (a) tell two generations of the same unit apart across a
13786
- * restart, and (b) subtract claimed processes from the node's process
13787
- * snapshot to see what NOBODY claimed. Absent for an entry that owns no
13788
- * process of its own.
13789
- */
13790
- pid: number().int().positive().optional(),
13791
- /**
13792
- * When this generation started. The pid's incarnation marker: a consumer
13793
- * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
13794
- * window when this changes, because the counter restarted from zero in a new
13795
- * process.
13796
- */
13797
- startedAtMs: number().optional(),
13798
- /**
13799
- * CUMULATIVE CPU seconds this unit has consumed since it started — user +
13800
- * system, read from the child's own `/proc/<pid>/stat` at the moment the
13801
- * contribution is asked for.
13802
- *
13803
- * Cumulative and not a rate on purpose: a rate needs a window, a window
13804
- * needs a sampler, and a new per-node sampler is the defect half of
13805
- * `docs/architecture/load-ledger.md` documents. A counter can be differenced
13806
- * by whoever already keeps a history; a rate cannot be un-averaged.
13807
- *
13808
- * Absent — never zero — on a node with no `/proc`, on a read failure, and on
13809
- * an entry with no process.
13810
- */
13811
- cpuSeconds: number().optional(),
13812
- /** Resident bytes of this unit's process, same source and same rules. */
13813
- rssBytes: number().optional()
13814
- });
13815
- method(_void(), array(LoadContributionSchema).readonly());
13816
- /**
13817
13817
  * `login-method` — collection cap through which auth addons contribute
13818
13818
  * their pre-auth login surfaces to the login page. This is the SINGLE,
13819
13819
  * generic mechanism that supersedes the dead `auth.listProviders` reader:
@@ -18473,12 +18473,53 @@ var MediaFileKindEnum = _enum([
18473
18473
  "keyFrameSmall",
18474
18474
  "thumbnailSmall"
18475
18475
  ]);
18476
+ /**
18477
+ * One media row ON THE WIRE: what it is, how big it is, and WHERE ITS BYTES
18478
+ * ARE — never the bytes themselves.
18479
+ *
18480
+ * ## Why `url` and not `base64`
18481
+ *
18482
+ * Measured on the live hub 2026-08-30: `getTrackMedia {trackId, deviceId}`
18483
+ * with no `kinds` returned 6 rows / **3 597 219 B**, of which `keyFrame` alone
18484
+ * was **2 824 077 B** — one full-resolution frame, base64, so +33 % on the
18485
+ * wire. Forty events is ~144 MB. Every byte of it was read off disk,
18486
+ * base64-encoded, held whole in a unary tRPC envelope, and materialised in
18487
+ * hub-main's heap on the way past — for an `<img>` that would have cached it.
18488
+ *
18489
+ * `url` points at the `event-media` data plane
18490
+ * (`/addon/<addonId>/event-media/<storedKey>`), which serves the same blob
18491
+ * with an ETag and `Cache-Control: immutable`, honours conditional GETs, can
18492
+ * render a `?variant=thumb`, and streams. The hub gate in front of it requires
18493
+ * a bearer or the session cookie (`access: 'authenticated'`), so the bytes are
18494
+ * no less protected than they were inside a `view`-level cap response — see
18495
+ * `data-plane-access.ts` for the rule and the one gap it does not close
18496
+ * (per-device scoping).
18497
+ *
18498
+ * The URL is built from the row's **stored** key, which is not always its
18499
+ * published `kind`: a track's face/plate crop is stored as `crop` under
18500
+ * `('face'|'plate', '<prefix>-<trackId>')` and published as
18501
+ * `faceCrop`/`plateCrop`. `MediaStore.getByKey` knows only the stored key.
18502
+ *
18503
+ * ## `base64` is TRANSITIONAL and is going away
18504
+ *
18505
+ * It is still populated for one reason: the deployed viewer's track-detail
18506
+ * HERO tile reads it (`use-track-media-entry.ts` → `parseMediaFiles`, which
18507
+ * REQUIRES the field), and a row without it parses as a FAILED read — the red
18508
+ * triangle — not as absence. Removing the field before that viewer ships is an
18509
+ * outage, not a cleanup. Once the viewer takes its hero bytes from `url`,
18510
+ * delete this line and the `withBytes` pass-through in
18511
+ * `analytics-query-facade.ts`; nothing else reads it.
18512
+ */
18476
18513
  var MediaFileSchema = object({
18477
18514
  key: string(),
18478
18515
  kind: MediaFileKindEnum,
18479
- base64: string(),
18480
18516
  sizeBytes: number(),
18481
18517
  timestamp: number()
18518
+ }).extend({
18519
+ /** `/addon/<addonId>/event-media/<encoded stored key>`. Always present. */
18520
+ url: string(),
18521
+ /** @deprecated Transitional — see the schema docblock. Use {@link url}. */
18522
+ base64: string()
18482
18523
  });
18483
18524
  /**
18484
18525
  * One media row WITHOUT its bytes.
@@ -18490,7 +18531,9 @@ var MediaFileSchema = object({
18490
18531
  * blocks the whole view.
18491
18532
  *
18492
18533
  * `sizeBytes` is carried because it is what lets a client decide between the
18493
- * stored blob and a `?variant=thumb` rendering without fetching either.
18534
+ * stored blob and a `?variant=thumb` rendering without fetching either, and
18535
+ * `url` because a client that had to build the plane path itself is a second
18536
+ * copy of a route — the embed, the viewer and the admin UI each grew one.
18494
18537
  */
18495
18538
  var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
18496
18539
  /**
@@ -19179,6 +19222,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19179
19222
  }), array(MediaFileSchema).readonly()), method(object({
19180
19223
  trackId: string(),
19181
19224
  deviceId: number()
19225
+ }), array(MediaFileInfoSchema).readonly()), method(object({
19226
+ eventId: string(),
19227
+ deviceId: number()
19182
19228
  }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
19183
19229
  kind: "mutation",
19184
19230
  auth: "admin"
@@ -23439,10 +23485,24 @@ var FaceClusterSchema = object({
23439
23485
  size: number().int(),
23440
23486
  cohesion: number()
23441
23487
  });
23488
+ /**
23489
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
23490
+ * are — never the bytes.
23491
+ *
23492
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
23493
+ * track/event contract) is still populated because a deployed viewer requires
23494
+ * the field to parse a row at all; this method has no such reader. Its ONE
23495
+ * caller is the admin UI's detail modal, which was building
23496
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
23497
+ * dialog already rendering its key FRAME from the `event-media` plane.
23498
+ *
23499
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
23500
+ * media key directly, so this needed no new plane and no new access decision.
23501
+ */
23442
23502
  var MediaFileLiteSchema$1 = object({
23443
23503
  key: string(),
23444
23504
  kind: string(),
23445
- base64: string(),
23505
+ url: string(),
23446
23506
  sizeBytes: number(),
23447
23507
  timestamp: number()
23448
23508
  });
@@ -25694,10 +25754,24 @@ var PlateInfoSchema = object({
25694
25754
  */
25695
25755
  cropUrl: string().optional()
25696
25756
  });
25757
+ /**
25758
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
25759
+ * are — never the bytes.
25760
+ *
25761
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
25762
+ * track/event contract) is still populated because a deployed viewer requires
25763
+ * the field to parse a row at all; this method has no such reader. Its ONE
25764
+ * caller is the admin UI's detail modal, which was building
25765
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
25766
+ * dialog already rendering its key FRAME from the `event-media` plane.
25767
+ *
25768
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
25769
+ * media key directly, so this needed no new plane and no new access decision.
25770
+ */
25697
25771
  var MediaFileLiteSchema = object({
25698
25772
  key: string(),
25699
25773
  kind: string(),
25700
- base64: string(),
25774
+ url: string(),
25701
25775
  sizeBytes: number(),
25702
25776
  timestamp: number()
25703
25777
  });
@@ -31710,6 +31784,12 @@ Object.freeze({
31710
31784
  addonId: null,
31711
31785
  access: "view"
31712
31786
  },
31787
+ "pipelineAnalytics.listEventMedia": {
31788
+ capName: "pipeline-analytics",
31789
+ capScope: "device",
31790
+ addonId: null,
31791
+ access: "view"
31792
+ },
31713
31793
  "pipelineAnalytics.listGroups": {
31714
31794
  capName: "pipeline-analytics",
31715
31795
  capScope: "device",
@@ -35333,6 +35413,11 @@ Object.freeze({
35333
35413
  form: "array",
35334
35414
  optional: false
35335
35415
  }],
35416
+ "pipelineAnalytics.listEventMedia": [{
35417
+ name: "deviceId",
35418
+ form: "single",
35419
+ optional: false
35420
+ }],
35336
35421
  "pipelineAnalytics.listGroups": [{
35337
35422
  name: "deviceIds",
35338
35423
  form: "array",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-decoder-ffmpeg",
3
- "version": "1.2.46",
3
+ "version": "1.2.47",
4
4
  "description": "Standalone ffmpeg-subprocess decoder fallback addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",