@camstack/addon-ai 0.4.42 → 0.4.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/addon.js +259 -174
  2. package/dist/addon.mjs +259 -174
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -13123,6 +13123,114 @@ method(object({
13123
13123
  height: number$1()
13124
13124
  }), EmbeddingResultSchema, { auth: "admin" }), method(object({ text: string() }), EmbeddingResultSchema, { auth: "admin" }), method(_void(), EmbeddingInfoSchema, { auth: "admin" });
13125
13125
  /**
13126
+ * `failure-contribution` — the capability an addon reports its OWN losses
13127
+ * through, per camera, with the denominator attached. It stores nothing.
13128
+ *
13129
+ * ## The twin of `load-contribution`, and why it is a twin and not a field
13130
+ *
13131
+ * `load-contribution` answers *what did this camera COST*. This answers *what
13132
+ * did this camera LOSE*. The reporting discipline is identical and deliberately
13133
+ * copied: the contributor reports what it already knows, hub-main adds only
13134
+ * `addonId`, nothing needs global knowledge, and there is no central list for
13135
+ * somebody to forget to edit.
13136
+ *
13137
+ * They are not merged, because their invariants are opposites:
13138
+ *
13139
+ * - a `load-contribution` measurement is **absent, never zero** — a zero would
13140
+ * claim a camera cost nothing, which is a measurement nobody made;
13141
+ * - a `failure-contribution` zero is the **most valuable value on the
13142
+ * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
13143
+ * and it is exactly what an absent entry cannot say.
13144
+ *
13145
+ * Putting a loss counter on a cost entry would also break the reconciliation
13146
+ * that gives `load-contribution` its point: contributions are subtracted from
13147
+ * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
13148
+ * has no process.
13149
+ *
13150
+ * ## Why not a log line, since the counters already exist
13151
+ *
13152
+ * Several of these paths already counted themselves — `CaptureScheduler`'s
13153
+ * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
13154
+ * ends in a log line, and a log line is the thing the operator asked to stop
13155
+ * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
13156
+ * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
13157
+ * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
13158
+ * media blackout were both diagnosed. The counters stay; this is where they can
13159
+ * be READ.
13160
+ *
13161
+ * ## The rate is served with its denominator or not at all
13162
+ *
13163
+ * Every entry carries `attempts` and `succeeded`. A miss count alone is
13164
+ * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
13165
+ * than yesterday" and was **flat across twelve hours** once divided by the
13166
+ * successes on the same path. A surface that publishes only the numerator
13167
+ * reproduces that mistake on every read.
13168
+ *
13169
+ * ## Shape
13170
+ *
13171
+ * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
13172
+ * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
13173
+ * generated hooks, while `addons.listCapabilityProviders` still enumerates it
13174
+ * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
13175
+ * a forked runner's entries reach hub-main over transport that already exists.
13176
+ * No new UDS message, no second registry (D3). The operator reads the assembled
13177
+ * result through `system.getFailureContributions`.
13178
+ */
13179
+ var FailureReasonCountSchema = object({
13180
+ /**
13181
+ * Why the attempt did not land, in the contributor's own vocabulary —
13182
+ * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
13183
+ * strings that already appear in this repo's logs and, where one exists, the
13184
+ * same string the per-track `previewMissReason` records (D276): a second
13185
+ * vocabulary for the same loss would make the row and the counter
13186
+ * un-joinable.
13187
+ */
13188
+ reason: string(),
13189
+ count: number$1().int().nonnegative()
13190
+ });
13191
+ var FailureContributionSchema = object({
13192
+ /**
13193
+ * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
13194
+ * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
13195
+ * `unit` free: the families are owned by different addons and a shared enum
13196
+ * is a central list that rots invisibly.
13197
+ */
13198
+ family: string(),
13199
+ /**
13200
+ * The NUMERIC device id — the same value every log line carries as
13201
+ * `tags.deviceId`. Never nullable and never absent: a contributor that
13202
+ * cannot name the camera must not emit the entry, because a fleet total
13203
+ * cannot answer the only question anybody asks of this surface.
13204
+ */
13205
+ deviceId: number$1().int().positive(),
13206
+ /**
13207
+ * A second dimension inside the family: the model / step id for an inference
13208
+ * timeout, so "which camera AND which model" is one read. Absent when the
13209
+ * family has a single variant.
13210
+ */
13211
+ variant: string().optional(),
13212
+ /**
13213
+ * Epoch ms this counter started — the INCARNATION MARKER. A consumer
13214
+ * differencing two reads must drop the interval when it changes, because the
13215
+ * counter restarted from zero in a respawned runner. Same discipline as
13216
+ * `LoadContribution.startedAtMs`.
13217
+ */
13218
+ sinceMs: number$1(),
13219
+ /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
13220
+ atMs: number$1(),
13221
+ /**
13222
+ * THE DENOMINATOR — every attempt on this path for this camera in the
13223
+ * window. A failure count published without it is the mistake this schema
13224
+ * exists to make impossible.
13225
+ */
13226
+ attempts: number$1().int().nonnegative(),
13227
+ /** Attempts that landed. `attempts - succeeded` is the loss. */
13228
+ succeeded: number$1().int().nonnegative(),
13229
+ /** The loss, partitioned. Sums to `attempts - succeeded`. */
13230
+ reasons: array(FailureReasonCountSchema).readonly()
13231
+ });
13232
+ method(_void(), array(FailureContributionSchema).readonly());
13233
+ /**
13126
13234
  * filesystem-browse — per-node capability for browsing the node's local
13127
13235
  * filesystem. Reads are unconfined (whole filesystem, from `/` down); WRITES
13128
13236
  * are sandboxed to operator-configured allowed roots (D115). Used by the
@@ -13738,6 +13846,68 @@ var llmCapability = {
13738
13846
  })
13739
13847
  }
13740
13848
  };
13849
+ var LoadContributionSchema = object({
13850
+ role: _enum([
13851
+ "decode",
13852
+ "transcode",
13853
+ "recording",
13854
+ "streaming",
13855
+ "detection"
13856
+ ]),
13857
+ /**
13858
+ * The NUMERIC device id — the same value every log line carries as
13859
+ * `tags.deviceId`. `null` means this cost genuinely belongs to no single
13860
+ * camera (a shared pool), NOT that the contributor forgot to look it up: a
13861
+ * contributor that cannot name its camera must not emit the entry at all,
13862
+ * because an unnamed per-camera entry is indistinguishable from a shared one
13863
+ * and would quietly turn one camera's cost into everybody's.
13864
+ */
13865
+ deviceId: number$1().int().positive().nullable(),
13866
+ attribution: _enum([
13867
+ "measured",
13868
+ "accounted",
13869
+ "unattributable"
13870
+ ]),
13871
+ /**
13872
+ * What ONE entry is, in the contributor's own words — `615/high`,
13873
+ * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
13874
+ * family and inventing a common one would lose the only information that
13875
+ * makes two entries for the same camera distinguishable.
13876
+ */
13877
+ unit: string(),
13878
+ /**
13879
+ * The OS process this cost lives in, when there is one. Present so a
13880
+ * consumer can (a) tell two generations of the same unit apart across a
13881
+ * restart, and (b) subtract claimed processes from the node's process
13882
+ * snapshot to see what NOBODY claimed. Absent for an entry that owns no
13883
+ * process of its own.
13884
+ */
13885
+ pid: number$1().int().positive().optional(),
13886
+ /**
13887
+ * When this generation started. The pid's incarnation marker: a consumer
13888
+ * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
13889
+ * window when this changes, because the counter restarted from zero in a new
13890
+ * process.
13891
+ */
13892
+ startedAtMs: number$1().optional(),
13893
+ /**
13894
+ * CUMULATIVE CPU seconds this unit has consumed since it started — user +
13895
+ * system, read from the child's own `/proc/<pid>/stat` at the moment the
13896
+ * contribution is asked for.
13897
+ *
13898
+ * Cumulative and not a rate on purpose: a rate needs a window, a window
13899
+ * needs a sampler, and a new per-node sampler is the defect half of
13900
+ * `docs/architecture/load-ledger.md` documents. A counter can be differenced
13901
+ * by whoever already keeps a history; a rate cannot be un-averaged.
13902
+ *
13903
+ * Absent — never zero — on a node with no `/proc`, on a read failure, and on
13904
+ * an entry with no process.
13905
+ */
13906
+ cpuSeconds: number$1().optional(),
13907
+ /** Resident bytes of this unit's process, same source and same rules. */
13908
+ rssBytes: number$1().optional()
13909
+ });
13910
+ method(_void(), array(LoadContributionSchema).readonly());
13741
13911
  /**
13742
13912
  * `log-channels` — the capability an addon DECLARES its diagnostic channels
13743
13913
  * through. It stores nothing.
@@ -13814,176 +13984,6 @@ method(LogEntrySchema, _void(), { kind: "mutation" }), method(object({
13814
13984
  tags: record(string(), string()).optional()
13815
13985
  }), array(LogEntrySchema).readonly());
13816
13986
  /**
13817
- * `failure-contribution` — the capability an addon reports its OWN losses
13818
- * through, per camera, with the denominator attached. It stores nothing.
13819
- *
13820
- * ## The twin of `load-contribution`, and why it is a twin and not a field
13821
- *
13822
- * `load-contribution` answers *what did this camera COST*. This answers *what
13823
- * did this camera LOSE*. The reporting discipline is identical and deliberately
13824
- * copied: the contributor reports what it already knows, hub-main adds only
13825
- * `addonId`, nothing needs global knowledge, and there is no central list for
13826
- * somebody to forget to edit.
13827
- *
13828
- * They are not merged, because their invariants are opposites:
13829
- *
13830
- * - a `load-contribution` measurement is **absent, never zero** — a zero would
13831
- * claim a camera cost nothing, which is a measurement nobody made;
13832
- * - a `failure-contribution` zero is the **most valuable value on the
13833
- * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
13834
- * and it is exactly what an absent entry cannot say.
13835
- *
13836
- * Putting a loss counter on a cost entry would also break the reconciliation
13837
- * that gives `load-contribution` its point: contributions are subtracted from
13838
- * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
13839
- * has no process.
13840
- *
13841
- * ## Why not a log line, since the counters already exist
13842
- *
13843
- * Several of these paths already counted themselves — `CaptureScheduler`'s
13844
- * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
13845
- * ends in a log line, and a log line is the thing the operator asked to stop
13846
- * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
13847
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
13848
- * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
13849
- * media blackout were both diagnosed. The counters stay; this is where they can
13850
- * be READ.
13851
- *
13852
- * ## The rate is served with its denominator or not at all
13853
- *
13854
- * Every entry carries `attempts` and `succeeded`. A miss count alone is
13855
- * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
13856
- * than yesterday" and was **flat across twelve hours** once divided by the
13857
- * successes on the same path. A surface that publishes only the numerator
13858
- * reproduces that mistake on every read.
13859
- *
13860
- * ## Shape
13861
- *
13862
- * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
13863
- * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
13864
- * generated hooks, while `addons.listCapabilityProviders` still enumerates it
13865
- * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
13866
- * a forked runner's entries reach hub-main over transport that already exists.
13867
- * No new UDS message, no second registry (D3). The operator reads the assembled
13868
- * result through `system.getFailureContributions`.
13869
- */
13870
- var FailureReasonCountSchema = object({
13871
- /**
13872
- * Why the attempt did not land, in the contributor's own vocabulary —
13873
- * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
13874
- * strings that already appear in this repo's logs and, where one exists, the
13875
- * same string the per-track `previewMissReason` records (D276): a second
13876
- * vocabulary for the same loss would make the row and the counter
13877
- * un-joinable.
13878
- */
13879
- reason: string(),
13880
- count: number$1().int().nonnegative()
13881
- });
13882
- var FailureContributionSchema = object({
13883
- /**
13884
- * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
13885
- * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
13886
- * `unit` free: the families are owned by different addons and a shared enum
13887
- * is a central list that rots invisibly.
13888
- */
13889
- family: string(),
13890
- /**
13891
- * The NUMERIC device id — the same value every log line carries as
13892
- * `tags.deviceId`. Never nullable and never absent: a contributor that
13893
- * cannot name the camera must not emit the entry, because a fleet total
13894
- * cannot answer the only question anybody asks of this surface.
13895
- */
13896
- deviceId: number$1().int().positive(),
13897
- /**
13898
- * A second dimension inside the family: the model / step id for an inference
13899
- * timeout, so "which camera AND which model" is one read. Absent when the
13900
- * family has a single variant.
13901
- */
13902
- variant: string().optional(),
13903
- /**
13904
- * Epoch ms this counter started — the INCARNATION MARKER. A consumer
13905
- * differencing two reads must drop the interval when it changes, because the
13906
- * counter restarted from zero in a respawned runner. Same discipline as
13907
- * `LoadContribution.startedAtMs`.
13908
- */
13909
- sinceMs: number$1(),
13910
- /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
13911
- atMs: number$1(),
13912
- /**
13913
- * THE DENOMINATOR — every attempt on this path for this camera in the
13914
- * window. A failure count published without it is the mistake this schema
13915
- * exists to make impossible.
13916
- */
13917
- attempts: number$1().int().nonnegative(),
13918
- /** Attempts that landed. `attempts - succeeded` is the loss. */
13919
- succeeded: number$1().int().nonnegative(),
13920
- /** The loss, partitioned. Sums to `attempts - succeeded`. */
13921
- reasons: array(FailureReasonCountSchema).readonly()
13922
- });
13923
- method(_void(), array(FailureContributionSchema).readonly());
13924
- var LoadContributionSchema = object({
13925
- role: _enum([
13926
- "decode",
13927
- "transcode",
13928
- "recording",
13929
- "streaming",
13930
- "detection"
13931
- ]),
13932
- /**
13933
- * The NUMERIC device id — the same value every log line carries as
13934
- * `tags.deviceId`. `null` means this cost genuinely belongs to no single
13935
- * camera (a shared pool), NOT that the contributor forgot to look it up: a
13936
- * contributor that cannot name its camera must not emit the entry at all,
13937
- * because an unnamed per-camera entry is indistinguishable from a shared one
13938
- * and would quietly turn one camera's cost into everybody's.
13939
- */
13940
- deviceId: number$1().int().positive().nullable(),
13941
- attribution: _enum([
13942
- "measured",
13943
- "accounted",
13944
- "unattributable"
13945
- ]),
13946
- /**
13947
- * What ONE entry is, in the contributor's own words — `615/high`,
13948
- * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
13949
- * family and inventing a common one would lose the only information that
13950
- * makes two entries for the same camera distinguishable.
13951
- */
13952
- unit: string(),
13953
- /**
13954
- * The OS process this cost lives in, when there is one. Present so a
13955
- * consumer can (a) tell two generations of the same unit apart across a
13956
- * restart, and (b) subtract claimed processes from the node's process
13957
- * snapshot to see what NOBODY claimed. Absent for an entry that owns no
13958
- * process of its own.
13959
- */
13960
- pid: number$1().int().positive().optional(),
13961
- /**
13962
- * When this generation started. The pid's incarnation marker: a consumer
13963
- * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
13964
- * window when this changes, because the counter restarted from zero in a new
13965
- * process.
13966
- */
13967
- startedAtMs: number$1().optional(),
13968
- /**
13969
- * CUMULATIVE CPU seconds this unit has consumed since it started — user +
13970
- * system, read from the child's own `/proc/<pid>/stat` at the moment the
13971
- * contribution is asked for.
13972
- *
13973
- * Cumulative and not a rate on purpose: a rate needs a window, a window
13974
- * needs a sampler, and a new per-node sampler is the defect half of
13975
- * `docs/architecture/load-ledger.md` documents. A counter can be differenced
13976
- * by whoever already keeps a history; a rate cannot be un-averaged.
13977
- *
13978
- * Absent — never zero — on a node with no `/proc`, on a read failure, and on
13979
- * an entry with no process.
13980
- */
13981
- cpuSeconds: number$1().optional(),
13982
- /** Resident bytes of this unit's process, same source and same rules. */
13983
- rssBytes: number$1().optional()
13984
- });
13985
- method(_void(), array(LoadContributionSchema).readonly());
13986
- /**
13987
13987
  * `login-method` — collection cap through which auth addons contribute
13988
13988
  * their pre-auth login surfaces to the login page. This is the SINGLE,
13989
13989
  * generic mechanism that supersedes the dead `auth.listProviders` reader:
@@ -18643,12 +18643,53 @@ var MediaFileKindEnum = _enum([
18643
18643
  "keyFrameSmall",
18644
18644
  "thumbnailSmall"
18645
18645
  ]);
18646
+ /**
18647
+ * One media row ON THE WIRE: what it is, how big it is, and WHERE ITS BYTES
18648
+ * ARE — never the bytes themselves.
18649
+ *
18650
+ * ## Why `url` and not `base64`
18651
+ *
18652
+ * Measured on the live hub 2026-08-30: `getTrackMedia {trackId, deviceId}`
18653
+ * with no `kinds` returned 6 rows / **3 597 219 B**, of which `keyFrame` alone
18654
+ * was **2 824 077 B** — one full-resolution frame, base64, so +33 % on the
18655
+ * wire. Forty events is ~144 MB. Every byte of it was read off disk,
18656
+ * base64-encoded, held whole in a unary tRPC envelope, and materialised in
18657
+ * hub-main's heap on the way past — for an `<img>` that would have cached it.
18658
+ *
18659
+ * `url` points at the `event-media` data plane
18660
+ * (`/addon/<addonId>/event-media/<storedKey>`), which serves the same blob
18661
+ * with an ETag and `Cache-Control: immutable`, honours conditional GETs, can
18662
+ * render a `?variant=thumb`, and streams. The hub gate in front of it requires
18663
+ * a bearer or the session cookie (`access: 'authenticated'`), so the bytes are
18664
+ * no less protected than they were inside a `view`-level cap response — see
18665
+ * `data-plane-access.ts` for the rule and the one gap it does not close
18666
+ * (per-device scoping).
18667
+ *
18668
+ * The URL is built from the row's **stored** key, which is not always its
18669
+ * published `kind`: a track's face/plate crop is stored as `crop` under
18670
+ * `('face'|'plate', '<prefix>-<trackId>')` and published as
18671
+ * `faceCrop`/`plateCrop`. `MediaStore.getByKey` knows only the stored key.
18672
+ *
18673
+ * ## `base64` is TRANSITIONAL and is going away
18674
+ *
18675
+ * It is still populated for one reason: the deployed viewer's track-detail
18676
+ * HERO tile reads it (`use-track-media-entry.ts` → `parseMediaFiles`, which
18677
+ * REQUIRES the field), and a row without it parses as a FAILED read — the red
18678
+ * triangle — not as absence. Removing the field before that viewer ships is an
18679
+ * outage, not a cleanup. Once the viewer takes its hero bytes from `url`,
18680
+ * delete this line and the `withBytes` pass-through in
18681
+ * `analytics-query-facade.ts`; nothing else reads it.
18682
+ */
18646
18683
  var MediaFileSchema = object({
18647
18684
  key: string(),
18648
18685
  kind: MediaFileKindEnum,
18649
- base64: string(),
18650
18686
  sizeBytes: number$1(),
18651
18687
  timestamp: number$1()
18688
+ }).extend({
18689
+ /** `/addon/<addonId>/event-media/<encoded stored key>`. Always present. */
18690
+ url: string(),
18691
+ /** @deprecated Transitional — see the schema docblock. Use {@link url}. */
18692
+ base64: string()
18652
18693
  });
18653
18694
  /**
18654
18695
  * One media row WITHOUT its bytes.
@@ -18660,7 +18701,9 @@ var MediaFileSchema = object({
18660
18701
  * blocks the whole view.
18661
18702
  *
18662
18703
  * `sizeBytes` is carried because it is what lets a client decide between the
18663
- * stored blob and a `?variant=thumb` rendering without fetching either.
18704
+ * stored blob and a `?variant=thumb` rendering without fetching either, and
18705
+ * `url` because a client that had to build the plane path itself is a second
18706
+ * copy of a route — the embed, the viewer and the admin UI each grew one.
18664
18707
  */
18665
18708
  var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
18666
18709
  /**
@@ -19349,6 +19392,9 @@ DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).r
19349
19392
  }), array(MediaFileSchema).readonly()), method(object({
19350
19393
  trackId: string(),
19351
19394
  deviceId: number$1()
19395
+ }), array(MediaFileInfoSchema).readonly()), method(object({
19396
+ eventId: string(),
19397
+ deviceId: number$1()
19352
19398
  }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
19353
19399
  kind: "mutation",
19354
19400
  auth: "admin"
@@ -23609,10 +23655,24 @@ var FaceClusterSchema = object({
23609
23655
  size: number$1().int(),
23610
23656
  cohesion: number$1()
23611
23657
  });
23658
+ /**
23659
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
23660
+ * are — never the bytes.
23661
+ *
23662
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
23663
+ * track/event contract) is still populated because a deployed viewer requires
23664
+ * the field to parse a row at all; this method has no such reader. Its ONE
23665
+ * caller is the admin UI's detail modal, which was building
23666
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
23667
+ * dialog already rendering its key FRAME from the `event-media` plane.
23668
+ *
23669
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
23670
+ * media key directly, so this needed no new plane and no new access decision.
23671
+ */
23612
23672
  var MediaFileLiteSchema$1 = object({
23613
23673
  key: string(),
23614
23674
  kind: string(),
23615
- base64: string(),
23675
+ url: string(),
23616
23676
  sizeBytes: number$1(),
23617
23677
  timestamp: number$1()
23618
23678
  });
@@ -25864,10 +25924,24 @@ var PlateInfoSchema = object({
25864
25924
  */
25865
25925
  cropUrl: string().optional()
25866
25926
  });
25927
+ /**
25928
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
25929
+ * are — never the bytes.
25930
+ *
25931
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
25932
+ * track/event contract) is still populated because a deployed viewer requires
25933
+ * the field to parse a row at all; this method has no such reader. Its ONE
25934
+ * caller is the admin UI's detail modal, which was building
25935
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
25936
+ * dialog already rendering its key FRAME from the `event-media` plane.
25937
+ *
25938
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
25939
+ * media key directly, so this needed no new plane and no new access decision.
25940
+ */
25867
25941
  var MediaFileLiteSchema = object({
25868
25942
  key: string(),
25869
25943
  kind: string(),
25870
- base64: string(),
25944
+ url: string(),
25871
25945
  sizeBytes: number$1(),
25872
25946
  timestamp: number$1()
25873
25947
  });
@@ -31880,6 +31954,12 @@ Object.freeze({
31880
31954
  addonId: null,
31881
31955
  access: "view"
31882
31956
  },
31957
+ "pipelineAnalytics.listEventMedia": {
31958
+ capName: "pipeline-analytics",
31959
+ capScope: "device",
31960
+ addonId: null,
31961
+ access: "view"
31962
+ },
31883
31963
  "pipelineAnalytics.listGroups": {
31884
31964
  capName: "pipeline-analytics",
31885
31965
  capScope: "device",
@@ -35503,6 +35583,11 @@ Object.freeze({
35503
35583
  form: "array",
35504
35584
  optional: false
35505
35585
  }],
35586
+ "pipelineAnalytics.listEventMedia": [{
35587
+ name: "deviceId",
35588
+ form: "single",
35589
+ optional: false
35590
+ }],
35506
35591
  "pipelineAnalytics.listGroups": [{
35507
35592
  name: "deviceIds",
35508
35593
  form: "array",
package/dist/addon.mjs CHANGED
@@ -13150,6 +13150,114 @@ method(object({
13150
13150
  height: number$1()
13151
13151
  }), EmbeddingResultSchema, { auth: "admin" }), method(object({ text: string() }), EmbeddingResultSchema, { auth: "admin" }), method(_void(), EmbeddingInfoSchema, { auth: "admin" });
13152
13152
  /**
13153
+ * `failure-contribution` — the capability an addon reports its OWN losses
13154
+ * through, per camera, with the denominator attached. It stores nothing.
13155
+ *
13156
+ * ## The twin of `load-contribution`, and why it is a twin and not a field
13157
+ *
13158
+ * `load-contribution` answers *what did this camera COST*. This answers *what
13159
+ * did this camera LOSE*. The reporting discipline is identical and deliberately
13160
+ * copied: the contributor reports what it already knows, hub-main adds only
13161
+ * `addonId`, nothing needs global knowledge, and there is no central list for
13162
+ * somebody to forget to edit.
13163
+ *
13164
+ * They are not merged, because their invariants are opposites:
13165
+ *
13166
+ * - a `load-contribution` measurement is **absent, never zero** — a zero would
13167
+ * claim a camera cost nothing, which is a measurement nobody made;
13168
+ * - a `failure-contribution` zero is the **most valuable value on the
13169
+ * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
13170
+ * and it is exactly what an absent entry cannot say.
13171
+ *
13172
+ * Putting a loss counter on a cost entry would also break the reconciliation
13173
+ * that gives `load-contribution` its point: contributions are subtracted from
13174
+ * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
13175
+ * has no process.
13176
+ *
13177
+ * ## Why not a log line, since the counters already exist
13178
+ *
13179
+ * Several of these paths already counted themselves — `CaptureScheduler`'s
13180
+ * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
13181
+ * ends in a log line, and a log line is the thing the operator asked to stop
13182
+ * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
13183
+ * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
13184
+ * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
13185
+ * media blackout were both diagnosed. The counters stay; this is where they can
13186
+ * be READ.
13187
+ *
13188
+ * ## The rate is served with its denominator or not at all
13189
+ *
13190
+ * Every entry carries `attempts` and `succeeded`. A miss count alone is
13191
+ * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
13192
+ * than yesterday" and was **flat across twelve hours** once divided by the
13193
+ * successes on the same path. A surface that publishes only the numerator
13194
+ * reproduces that mistake on every read.
13195
+ *
13196
+ * ## Shape
13197
+ *
13198
+ * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
13199
+ * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
13200
+ * generated hooks, while `addons.listCapabilityProviders` still enumerates it
13201
+ * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
13202
+ * a forked runner's entries reach hub-main over transport that already exists.
13203
+ * No new UDS message, no second registry (D3). The operator reads the assembled
13204
+ * result through `system.getFailureContributions`.
13205
+ */
13206
+ var FailureReasonCountSchema = object({
13207
+ /**
13208
+ * Why the attempt did not land, in the contributor's own vocabulary —
13209
+ * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
13210
+ * strings that already appear in this repo's logs and, where one exists, the
13211
+ * same string the per-track `previewMissReason` records (D276): a second
13212
+ * vocabulary for the same loss would make the row and the counter
13213
+ * un-joinable.
13214
+ */
13215
+ reason: string(),
13216
+ count: number$1().int().nonnegative()
13217
+ });
13218
+ var FailureContributionSchema = object({
13219
+ /**
13220
+ * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
13221
+ * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
13222
+ * `unit` free: the families are owned by different addons and a shared enum
13223
+ * is a central list that rots invisibly.
13224
+ */
13225
+ family: string(),
13226
+ /**
13227
+ * The NUMERIC device id — the same value every log line carries as
13228
+ * `tags.deviceId`. Never nullable and never absent: a contributor that
13229
+ * cannot name the camera must not emit the entry, because a fleet total
13230
+ * cannot answer the only question anybody asks of this surface.
13231
+ */
13232
+ deviceId: number$1().int().positive(),
13233
+ /**
13234
+ * A second dimension inside the family: the model / step id for an inference
13235
+ * timeout, so "which camera AND which model" is one read. Absent when the
13236
+ * family has a single variant.
13237
+ */
13238
+ variant: string().optional(),
13239
+ /**
13240
+ * Epoch ms this counter started — the INCARNATION MARKER. A consumer
13241
+ * differencing two reads must drop the interval when it changes, because the
13242
+ * counter restarted from zero in a respawned runner. Same discipline as
13243
+ * `LoadContribution.startedAtMs`.
13244
+ */
13245
+ sinceMs: number$1(),
13246
+ /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
13247
+ atMs: number$1(),
13248
+ /**
13249
+ * THE DENOMINATOR — every attempt on this path for this camera in the
13250
+ * window. A failure count published without it is the mistake this schema
13251
+ * exists to make impossible.
13252
+ */
13253
+ attempts: number$1().int().nonnegative(),
13254
+ /** Attempts that landed. `attempts - succeeded` is the loss. */
13255
+ succeeded: number$1().int().nonnegative(),
13256
+ /** The loss, partitioned. Sums to `attempts - succeeded`. */
13257
+ reasons: array(FailureReasonCountSchema).readonly()
13258
+ });
13259
+ method(_void(), array(FailureContributionSchema).readonly());
13260
+ /**
13153
13261
  * filesystem-browse — per-node capability for browsing the node's local
13154
13262
  * filesystem. Reads are unconfined (whole filesystem, from `/` down); WRITES
13155
13263
  * are sandboxed to operator-configured allowed roots (D115). Used by the
@@ -13765,6 +13873,68 @@ var llmCapability = {
13765
13873
  })
13766
13874
  }
13767
13875
  };
13876
+ var LoadContributionSchema = object({
13877
+ role: _enum([
13878
+ "decode",
13879
+ "transcode",
13880
+ "recording",
13881
+ "streaming",
13882
+ "detection"
13883
+ ]),
13884
+ /**
13885
+ * The NUMERIC device id — the same value every log line carries as
13886
+ * `tags.deviceId`. `null` means this cost genuinely belongs to no single
13887
+ * camera (a shared pool), NOT that the contributor forgot to look it up: a
13888
+ * contributor that cannot name its camera must not emit the entry at all,
13889
+ * because an unnamed per-camera entry is indistinguishable from a shared one
13890
+ * and would quietly turn one camera's cost into everybody's.
13891
+ */
13892
+ deviceId: number$1().int().positive().nullable(),
13893
+ attribution: _enum([
13894
+ "measured",
13895
+ "accounted",
13896
+ "unattributable"
13897
+ ]),
13898
+ /**
13899
+ * What ONE entry is, in the contributor's own words — `615/high`,
13900
+ * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
13901
+ * family and inventing a common one would lose the only information that
13902
+ * makes two entries for the same camera distinguishable.
13903
+ */
13904
+ unit: string(),
13905
+ /**
13906
+ * The OS process this cost lives in, when there is one. Present so a
13907
+ * consumer can (a) tell two generations of the same unit apart across a
13908
+ * restart, and (b) subtract claimed processes from the node's process
13909
+ * snapshot to see what NOBODY claimed. Absent for an entry that owns no
13910
+ * process of its own.
13911
+ */
13912
+ pid: number$1().int().positive().optional(),
13913
+ /**
13914
+ * When this generation started. The pid's incarnation marker: a consumer
13915
+ * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
13916
+ * window when this changes, because the counter restarted from zero in a new
13917
+ * process.
13918
+ */
13919
+ startedAtMs: number$1().optional(),
13920
+ /**
13921
+ * CUMULATIVE CPU seconds this unit has consumed since it started — user +
13922
+ * system, read from the child's own `/proc/<pid>/stat` at the moment the
13923
+ * contribution is asked for.
13924
+ *
13925
+ * Cumulative and not a rate on purpose: a rate needs a window, a window
13926
+ * needs a sampler, and a new per-node sampler is the defect half of
13927
+ * `docs/architecture/load-ledger.md` documents. A counter can be differenced
13928
+ * by whoever already keeps a history; a rate cannot be un-averaged.
13929
+ *
13930
+ * Absent — never zero — on a node with no `/proc`, on a read failure, and on
13931
+ * an entry with no process.
13932
+ */
13933
+ cpuSeconds: number$1().optional(),
13934
+ /** Resident bytes of this unit's process, same source and same rules. */
13935
+ rssBytes: number$1().optional()
13936
+ });
13937
+ method(_void(), array(LoadContributionSchema).readonly());
13768
13938
  /**
13769
13939
  * `log-channels` — the capability an addon DECLARES its diagnostic channels
13770
13940
  * through. It stores nothing.
@@ -13841,176 +14011,6 @@ method(LogEntrySchema, _void(), { kind: "mutation" }), method(object({
13841
14011
  tags: record(string(), string()).optional()
13842
14012
  }), array(LogEntrySchema).readonly());
13843
14013
  /**
13844
- * `failure-contribution` — the capability an addon reports its OWN losses
13845
- * through, per camera, with the denominator attached. It stores nothing.
13846
- *
13847
- * ## The twin of `load-contribution`, and why it is a twin and not a field
13848
- *
13849
- * `load-contribution` answers *what did this camera COST*. This answers *what
13850
- * did this camera LOSE*. The reporting discipline is identical and deliberately
13851
- * copied: the contributor reports what it already knows, hub-main adds only
13852
- * `addonId`, nothing needs global knowledge, and there is no central list for
13853
- * somebody to forget to edit.
13854
- *
13855
- * They are not merged, because their invariants are opposites:
13856
- *
13857
- * - a `load-contribution` measurement is **absent, never zero** — a zero would
13858
- * claim a camera cost nothing, which is a measurement nobody made;
13859
- * - a `failure-contribution` zero is the **most valuable value on the
13860
- * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
13861
- * and it is exactly what an absent entry cannot say.
13862
- *
13863
- * Putting a loss counter on a cost entry would also break the reconciliation
13864
- * that gives `load-contribution` its point: contributions are subtracted from
13865
- * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
13866
- * has no process.
13867
- *
13868
- * ## Why not a log line, since the counters already exist
13869
- *
13870
- * Several of these paths already counted themselves — `CaptureScheduler`'s
13871
- * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
13872
- * ends in a log line, and a log line is the thing the operator asked to stop
13873
- * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
13874
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
13875
- * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
13876
- * media blackout were both diagnosed. The counters stay; this is where they can
13877
- * be READ.
13878
- *
13879
- * ## The rate is served with its denominator or not at all
13880
- *
13881
- * Every entry carries `attempts` and `succeeded`. A miss count alone is
13882
- * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
13883
- * than yesterday" and was **flat across twelve hours** once divided by the
13884
- * successes on the same path. A surface that publishes only the numerator
13885
- * reproduces that mistake on every read.
13886
- *
13887
- * ## Shape
13888
- *
13889
- * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
13890
- * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
13891
- * generated hooks, while `addons.listCapabilityProviders` still enumerates it
13892
- * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
13893
- * a forked runner's entries reach hub-main over transport that already exists.
13894
- * No new UDS message, no second registry (D3). The operator reads the assembled
13895
- * result through `system.getFailureContributions`.
13896
- */
13897
- var FailureReasonCountSchema = object({
13898
- /**
13899
- * Why the attempt did not land, in the contributor's own vocabulary —
13900
- * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
13901
- * strings that already appear in this repo's logs and, where one exists, the
13902
- * same string the per-track `previewMissReason` records (D276): a second
13903
- * vocabulary for the same loss would make the row and the counter
13904
- * un-joinable.
13905
- */
13906
- reason: string(),
13907
- count: number$1().int().nonnegative()
13908
- });
13909
- var FailureContributionSchema = object({
13910
- /**
13911
- * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
13912
- * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
13913
- * `unit` free: the families are owned by different addons and a shared enum
13914
- * is a central list that rots invisibly.
13915
- */
13916
- family: string(),
13917
- /**
13918
- * The NUMERIC device id — the same value every log line carries as
13919
- * `tags.deviceId`. Never nullable and never absent: a contributor that
13920
- * cannot name the camera must not emit the entry, because a fleet total
13921
- * cannot answer the only question anybody asks of this surface.
13922
- */
13923
- deviceId: number$1().int().positive(),
13924
- /**
13925
- * A second dimension inside the family: the model / step id for an inference
13926
- * timeout, so "which camera AND which model" is one read. Absent when the
13927
- * family has a single variant.
13928
- */
13929
- variant: string().optional(),
13930
- /**
13931
- * Epoch ms this counter started — the INCARNATION MARKER. A consumer
13932
- * differencing two reads must drop the interval when it changes, because the
13933
- * counter restarted from zero in a respawned runner. Same discipline as
13934
- * `LoadContribution.startedAtMs`.
13935
- */
13936
- sinceMs: number$1(),
13937
- /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
13938
- atMs: number$1(),
13939
- /**
13940
- * THE DENOMINATOR — every attempt on this path for this camera in the
13941
- * window. A failure count published without it is the mistake this schema
13942
- * exists to make impossible.
13943
- */
13944
- attempts: number$1().int().nonnegative(),
13945
- /** Attempts that landed. `attempts - succeeded` is the loss. */
13946
- succeeded: number$1().int().nonnegative(),
13947
- /** The loss, partitioned. Sums to `attempts - succeeded`. */
13948
- reasons: array(FailureReasonCountSchema).readonly()
13949
- });
13950
- method(_void(), array(FailureContributionSchema).readonly());
13951
- var LoadContributionSchema = object({
13952
- role: _enum([
13953
- "decode",
13954
- "transcode",
13955
- "recording",
13956
- "streaming",
13957
- "detection"
13958
- ]),
13959
- /**
13960
- * The NUMERIC device id — the same value every log line carries as
13961
- * `tags.deviceId`. `null` means this cost genuinely belongs to no single
13962
- * camera (a shared pool), NOT that the contributor forgot to look it up: a
13963
- * contributor that cannot name its camera must not emit the entry at all,
13964
- * because an unnamed per-camera entry is indistinguishable from a shared one
13965
- * and would quietly turn one camera's cost into everybody's.
13966
- */
13967
- deviceId: number$1().int().positive().nullable(),
13968
- attribution: _enum([
13969
- "measured",
13970
- "accounted",
13971
- "unattributable"
13972
- ]),
13973
- /**
13974
- * What ONE entry is, in the contributor's own words — `615/high`,
13975
- * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
13976
- * family and inventing a common one would lose the only information that
13977
- * makes two entries for the same camera distinguishable.
13978
- */
13979
- unit: string(),
13980
- /**
13981
- * The OS process this cost lives in, when there is one. Present so a
13982
- * consumer can (a) tell two generations of the same unit apart across a
13983
- * restart, and (b) subtract claimed processes from the node's process
13984
- * snapshot to see what NOBODY claimed. Absent for an entry that owns no
13985
- * process of its own.
13986
- */
13987
- pid: number$1().int().positive().optional(),
13988
- /**
13989
- * When this generation started. The pid's incarnation marker: a consumer
13990
- * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
13991
- * window when this changes, because the counter restarted from zero in a new
13992
- * process.
13993
- */
13994
- startedAtMs: number$1().optional(),
13995
- /**
13996
- * CUMULATIVE CPU seconds this unit has consumed since it started — user +
13997
- * system, read from the child's own `/proc/<pid>/stat` at the moment the
13998
- * contribution is asked for.
13999
- *
14000
- * Cumulative and not a rate on purpose: a rate needs a window, a window
14001
- * needs a sampler, and a new per-node sampler is the defect half of
14002
- * `docs/architecture/load-ledger.md` documents. A counter can be differenced
14003
- * by whoever already keeps a history; a rate cannot be un-averaged.
14004
- *
14005
- * Absent — never zero — on a node with no `/proc`, on a read failure, and on
14006
- * an entry with no process.
14007
- */
14008
- cpuSeconds: number$1().optional(),
14009
- /** Resident bytes of this unit's process, same source and same rules. */
14010
- rssBytes: number$1().optional()
14011
- });
14012
- method(_void(), array(LoadContributionSchema).readonly());
14013
- /**
14014
14014
  * `login-method` — collection cap through which auth addons contribute
14015
14015
  * their pre-auth login surfaces to the login page. This is the SINGLE,
14016
14016
  * generic mechanism that supersedes the dead `auth.listProviders` reader:
@@ -18670,12 +18670,53 @@ var MediaFileKindEnum = _enum([
18670
18670
  "keyFrameSmall",
18671
18671
  "thumbnailSmall"
18672
18672
  ]);
18673
+ /**
18674
+ * One media row ON THE WIRE: what it is, how big it is, and WHERE ITS BYTES
18675
+ * ARE — never the bytes themselves.
18676
+ *
18677
+ * ## Why `url` and not `base64`
18678
+ *
18679
+ * Measured on the live hub 2026-08-30: `getTrackMedia {trackId, deviceId}`
18680
+ * with no `kinds` returned 6 rows / **3 597 219 B**, of which `keyFrame` alone
18681
+ * was **2 824 077 B** — one full-resolution frame, base64, so +33 % on the
18682
+ * wire. Forty events is ~144 MB. Every byte of it was read off disk,
18683
+ * base64-encoded, held whole in a unary tRPC envelope, and materialised in
18684
+ * hub-main's heap on the way past — for an `<img>` that would have cached it.
18685
+ *
18686
+ * `url` points at the `event-media` data plane
18687
+ * (`/addon/<addonId>/event-media/<storedKey>`), which serves the same blob
18688
+ * with an ETag and `Cache-Control: immutable`, honours conditional GETs, can
18689
+ * render a `?variant=thumb`, and streams. The hub gate in front of it requires
18690
+ * a bearer or the session cookie (`access: 'authenticated'`), so the bytes are
18691
+ * no less protected than they were inside a `view`-level cap response — see
18692
+ * `data-plane-access.ts` for the rule and the one gap it does not close
18693
+ * (per-device scoping).
18694
+ *
18695
+ * The URL is built from the row's **stored** key, which is not always its
18696
+ * published `kind`: a track's face/plate crop is stored as `crop` under
18697
+ * `('face'|'plate', '<prefix>-<trackId>')` and published as
18698
+ * `faceCrop`/`plateCrop`. `MediaStore.getByKey` knows only the stored key.
18699
+ *
18700
+ * ## `base64` is TRANSITIONAL and is going away
18701
+ *
18702
+ * It is still populated for one reason: the deployed viewer's track-detail
18703
+ * HERO tile reads it (`use-track-media-entry.ts` → `parseMediaFiles`, which
18704
+ * REQUIRES the field), and a row without it parses as a FAILED read — the red
18705
+ * triangle — not as absence. Removing the field before that viewer ships is an
18706
+ * outage, not a cleanup. Once the viewer takes its hero bytes from `url`,
18707
+ * delete this line and the `withBytes` pass-through in
18708
+ * `analytics-query-facade.ts`; nothing else reads it.
18709
+ */
18673
18710
  var MediaFileSchema = object({
18674
18711
  key: string(),
18675
18712
  kind: MediaFileKindEnum,
18676
- base64: string(),
18677
18713
  sizeBytes: number$1(),
18678
18714
  timestamp: number$1()
18715
+ }).extend({
18716
+ /** `/addon/<addonId>/event-media/<encoded stored key>`. Always present. */
18717
+ url: string(),
18718
+ /** @deprecated Transitional — see the schema docblock. Use {@link url}. */
18719
+ base64: string()
18679
18720
  });
18680
18721
  /**
18681
18722
  * One media row WITHOUT its bytes.
@@ -18687,7 +18728,9 @@ var MediaFileSchema = object({
18687
18728
  * blocks the whole view.
18688
18729
  *
18689
18730
  * `sizeBytes` is carried because it is what lets a client decide between the
18690
- * stored blob and a `?variant=thumb` rendering without fetching either.
18731
+ * stored blob and a `?variant=thumb` rendering without fetching either, and
18732
+ * `url` because a client that had to build the plane path itself is a second
18733
+ * copy of a route — the embed, the viewer and the admin UI each grew one.
18691
18734
  */
18692
18735
  var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
18693
18736
  /**
@@ -19376,6 +19419,9 @@ DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).r
19376
19419
  }), array(MediaFileSchema).readonly()), method(object({
19377
19420
  trackId: string(),
19378
19421
  deviceId: number$1()
19422
+ }), array(MediaFileInfoSchema).readonly()), method(object({
19423
+ eventId: string(),
19424
+ deviceId: number$1()
19379
19425
  }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
19380
19426
  kind: "mutation",
19381
19427
  auth: "admin"
@@ -23636,10 +23682,24 @@ var FaceClusterSchema = object({
23636
23682
  size: number$1().int(),
23637
23683
  cohesion: number$1()
23638
23684
  });
23685
+ /**
23686
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
23687
+ * are — never the bytes.
23688
+ *
23689
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
23690
+ * track/event contract) is still populated because a deployed viewer requires
23691
+ * the field to parse a row at all; this method has no such reader. Its ONE
23692
+ * caller is the admin UI's detail modal, which was building
23693
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
23694
+ * dialog already rendering its key FRAME from the `event-media` plane.
23695
+ *
23696
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
23697
+ * media key directly, so this needed no new plane and no new access decision.
23698
+ */
23639
23699
  var MediaFileLiteSchema$1 = object({
23640
23700
  key: string(),
23641
23701
  kind: string(),
23642
- base64: string(),
23702
+ url: string(),
23643
23703
  sizeBytes: number$1(),
23644
23704
  timestamp: number$1()
23645
23705
  });
@@ -25891,10 +25951,24 @@ var PlateInfoSchema = object({
25891
25951
  */
25892
25952
  cropUrl: string().optional()
25893
25953
  });
25954
+ /**
25955
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
25956
+ * are — never the bytes.
25957
+ *
25958
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
25959
+ * track/event contract) is still populated because a deployed viewer requires
25960
+ * the field to parse a row at all; this method has no such reader. Its ONE
25961
+ * caller is the admin UI's detail modal, which was building
25962
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
25963
+ * dialog already rendering its key FRAME from the `event-media` plane.
25964
+ *
25965
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
25966
+ * media key directly, so this needed no new plane and no new access decision.
25967
+ */
25894
25968
  var MediaFileLiteSchema = object({
25895
25969
  key: string(),
25896
25970
  kind: string(),
25897
- base64: string(),
25971
+ url: string(),
25898
25972
  sizeBytes: number$1(),
25899
25973
  timestamp: number$1()
25900
25974
  });
@@ -31907,6 +31981,12 @@ Object.freeze({
31907
31981
  addonId: null,
31908
31982
  access: "view"
31909
31983
  },
31984
+ "pipelineAnalytics.listEventMedia": {
31985
+ capName: "pipeline-analytics",
31986
+ capScope: "device",
31987
+ addonId: null,
31988
+ access: "view"
31989
+ },
31910
31990
  "pipelineAnalytics.listGroups": {
31911
31991
  capName: "pipeline-analytics",
31912
31992
  capScope: "device",
@@ -35530,6 +35610,11 @@ Object.freeze({
35530
35610
  form: "array",
35531
35611
  optional: false
35532
35612
  }],
35613
+ "pipelineAnalytics.listEventMedia": [{
35614
+ name: "deviceId",
35615
+ form: "single",
35616
+ optional: false
35617
+ }],
35533
35618
  "pipelineAnalytics.listGroups": [{
35534
35619
  name: "deviceIds",
35535
35620
  form: "array",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-ai",
3
- "version": "0.4.42",
3
+ "version": "0.4.43",
4
4
  "description": "AI addon for CamStack — the `llm` collection provider (cloud, LAN, and camstack-managed local llama.cpp profiles) plus the per-node `llm-runtime` managed executor.",
5
5
  "keywords": [
6
6
  "camstack",