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