@camstack/addon-terminal 0.1.51 → 0.1.52

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
@@ -13228,6 +13228,114 @@ method(object({
13228
13228
  height: number()
13229
13229
  }), EmbeddingResultSchema, { auth: "admin" }), method(object({ text: string() }), EmbeddingResultSchema, { auth: "admin" }), method(_void(), EmbeddingInfoSchema, { auth: "admin" });
13230
13230
  /**
13231
+ * `failure-contribution` — the capability an addon reports its OWN losses
13232
+ * through, per camera, with the denominator attached. It stores nothing.
13233
+ *
13234
+ * ## The twin of `load-contribution`, and why it is a twin and not a field
13235
+ *
13236
+ * `load-contribution` answers *what did this camera COST*. This answers *what
13237
+ * did this camera LOSE*. The reporting discipline is identical and deliberately
13238
+ * copied: the contributor reports what it already knows, hub-main adds only
13239
+ * `addonId`, nothing needs global knowledge, and there is no central list for
13240
+ * somebody to forget to edit.
13241
+ *
13242
+ * They are not merged, because their invariants are opposites:
13243
+ *
13244
+ * - a `load-contribution` measurement is **absent, never zero** — a zero would
13245
+ * claim a camera cost nothing, which is a measurement nobody made;
13246
+ * - a `failure-contribution` zero is the **most valuable value on the
13247
+ * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
13248
+ * and it is exactly what an absent entry cannot say.
13249
+ *
13250
+ * Putting a loss counter on a cost entry would also break the reconciliation
13251
+ * that gives `load-contribution` its point: contributions are subtracted from
13252
+ * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
13253
+ * has no process.
13254
+ *
13255
+ * ## Why not a log line, since the counters already exist
13256
+ *
13257
+ * Several of these paths already counted themselves — `CaptureScheduler`'s
13258
+ * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
13259
+ * ends in a log line, and a log line is the thing the operator asked to stop
13260
+ * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
13261
+ * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
13262
+ * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
13263
+ * media blackout were both diagnosed. The counters stay; this is where they can
13264
+ * be READ.
13265
+ *
13266
+ * ## The rate is served with its denominator or not at all
13267
+ *
13268
+ * Every entry carries `attempts` and `succeeded`. A miss count alone is
13269
+ * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
13270
+ * than yesterday" and was **flat across twelve hours** once divided by the
13271
+ * successes on the same path. A surface that publishes only the numerator
13272
+ * reproduces that mistake on every read.
13273
+ *
13274
+ * ## Shape
13275
+ *
13276
+ * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
13277
+ * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
13278
+ * generated hooks, while `addons.listCapabilityProviders` still enumerates it
13279
+ * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
13280
+ * a forked runner's entries reach hub-main over transport that already exists.
13281
+ * No new UDS message, no second registry (D3). The operator reads the assembled
13282
+ * result through `system.getFailureContributions`.
13283
+ */
13284
+ var FailureReasonCountSchema = object({
13285
+ /**
13286
+ * Why the attempt did not land, in the contributor's own vocabulary —
13287
+ * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
13288
+ * strings that already appear in this repo's logs and, where one exists, the
13289
+ * same string the per-track `previewMissReason` records (D276): a second
13290
+ * vocabulary for the same loss would make the row and the counter
13291
+ * un-joinable.
13292
+ */
13293
+ reason: string(),
13294
+ count: number().int().nonnegative()
13295
+ });
13296
+ var FailureContributionSchema = object({
13297
+ /**
13298
+ * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
13299
+ * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
13300
+ * `unit` free: the families are owned by different addons and a shared enum
13301
+ * is a central list that rots invisibly.
13302
+ */
13303
+ family: string(),
13304
+ /**
13305
+ * The NUMERIC device id — the same value every log line carries as
13306
+ * `tags.deviceId`. Never nullable and never absent: a contributor that
13307
+ * cannot name the camera must not emit the entry, because a fleet total
13308
+ * cannot answer the only question anybody asks of this surface.
13309
+ */
13310
+ deviceId: number().int().positive(),
13311
+ /**
13312
+ * A second dimension inside the family: the model / step id for an inference
13313
+ * timeout, so "which camera AND which model" is one read. Absent when the
13314
+ * family has a single variant.
13315
+ */
13316
+ variant: string().optional(),
13317
+ /**
13318
+ * Epoch ms this counter started — the INCARNATION MARKER. A consumer
13319
+ * differencing two reads must drop the interval when it changes, because the
13320
+ * counter restarted from zero in a respawned runner. Same discipline as
13321
+ * `LoadContribution.startedAtMs`.
13322
+ */
13323
+ sinceMs: number(),
13324
+ /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
13325
+ atMs: number(),
13326
+ /**
13327
+ * THE DENOMINATOR — every attempt on this path for this camera in the
13328
+ * window. A failure count published without it is the mistake this schema
13329
+ * exists to make impossible.
13330
+ */
13331
+ attempts: number().int().nonnegative(),
13332
+ /** Attempts that landed. `attempts - succeeded` is the loss. */
13333
+ succeeded: number().int().nonnegative(),
13334
+ /** The loss, partitioned. Sums to `attempts - succeeded`. */
13335
+ reasons: array(FailureReasonCountSchema).readonly()
13336
+ });
13337
+ method(_void(), array(FailureContributionSchema).readonly());
13338
+ /**
13231
13339
  * filesystem-browse — per-node capability for browsing the node's local
13232
13340
  * filesystem. Reads are unconfined (whole filesystem, from `/` down); WRITES
13233
13341
  * are sandboxed to operator-configured allowed roots (D115). Used by the
@@ -13749,6 +13857,68 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
13749
13857
  kind: "mutation",
13750
13858
  auth: "admin"
13751
13859
  });
13860
+ var LoadContributionSchema = object({
13861
+ role: _enum([
13862
+ "decode",
13863
+ "transcode",
13864
+ "recording",
13865
+ "streaming",
13866
+ "detection"
13867
+ ]),
13868
+ /**
13869
+ * The NUMERIC device id — the same value every log line carries as
13870
+ * `tags.deviceId`. `null` means this cost genuinely belongs to no single
13871
+ * camera (a shared pool), NOT that the contributor forgot to look it up: a
13872
+ * contributor that cannot name its camera must not emit the entry at all,
13873
+ * because an unnamed per-camera entry is indistinguishable from a shared one
13874
+ * and would quietly turn one camera's cost into everybody's.
13875
+ */
13876
+ deviceId: number().int().positive().nullable(),
13877
+ attribution: _enum([
13878
+ "measured",
13879
+ "accounted",
13880
+ "unattributable"
13881
+ ]),
13882
+ /**
13883
+ * What ONE entry is, in the contributor's own words — `615/high`,
13884
+ * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
13885
+ * family and inventing a common one would lose the only information that
13886
+ * makes two entries for the same camera distinguishable.
13887
+ */
13888
+ unit: string(),
13889
+ /**
13890
+ * The OS process this cost lives in, when there is one. Present so a
13891
+ * consumer can (a) tell two generations of the same unit apart across a
13892
+ * restart, and (b) subtract claimed processes from the node's process
13893
+ * snapshot to see what NOBODY claimed. Absent for an entry that owns no
13894
+ * process of its own.
13895
+ */
13896
+ pid: number().int().positive().optional(),
13897
+ /**
13898
+ * When this generation started. The pid's incarnation marker: a consumer
13899
+ * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
13900
+ * window when this changes, because the counter restarted from zero in a new
13901
+ * process.
13902
+ */
13903
+ startedAtMs: number().optional(),
13904
+ /**
13905
+ * CUMULATIVE CPU seconds this unit has consumed since it started — user +
13906
+ * system, read from the child's own `/proc/<pid>/stat` at the moment the
13907
+ * contribution is asked for.
13908
+ *
13909
+ * Cumulative and not a rate on purpose: a rate needs a window, a window
13910
+ * needs a sampler, and a new per-node sampler is the defect half of
13911
+ * `docs/architecture/load-ledger.md` documents. A counter can be differenced
13912
+ * by whoever already keeps a history; a rate cannot be un-averaged.
13913
+ *
13914
+ * Absent — never zero — on a node with no `/proc`, on a read failure, and on
13915
+ * an entry with no process.
13916
+ */
13917
+ cpuSeconds: number().optional(),
13918
+ /** Resident bytes of this unit's process, same source and same rules. */
13919
+ rssBytes: number().optional()
13920
+ });
13921
+ method(_void(), array(LoadContributionSchema).readonly());
13752
13922
  /**
13753
13923
  * `log-channels` — the capability an addon DECLARES its diagnostic channels
13754
13924
  * through. It stores nothing.
@@ -13825,176 +13995,6 @@ method(LogEntrySchema, _void(), { kind: "mutation" }), method(object({
13825
13995
  tags: record(string(), string()).optional()
13826
13996
  }), array(LogEntrySchema).readonly());
13827
13997
  /**
13828
- * `failure-contribution` — the capability an addon reports its OWN losses
13829
- * through, per camera, with the denominator attached. It stores nothing.
13830
- *
13831
- * ## The twin of `load-contribution`, and why it is a twin and not a field
13832
- *
13833
- * `load-contribution` answers *what did this camera COST*. This answers *what
13834
- * did this camera LOSE*. The reporting discipline is identical and deliberately
13835
- * copied: the contributor reports what it already knows, hub-main adds only
13836
- * `addonId`, nothing needs global knowledge, and there is no central list for
13837
- * somebody to forget to edit.
13838
- *
13839
- * They are not merged, because their invariants are opposites:
13840
- *
13841
- * - a `load-contribution` measurement is **absent, never zero** — a zero would
13842
- * claim a camera cost nothing, which is a measurement nobody made;
13843
- * - a `failure-contribution` zero is the **most valuable value on the
13844
- * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
13845
- * and it is exactly what an absent entry cannot say.
13846
- *
13847
- * Putting a loss counter on a cost entry would also break the reconciliation
13848
- * that gives `load-contribution` its point: contributions are subtracted from
13849
- * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
13850
- * has no process.
13851
- *
13852
- * ## Why not a log line, since the counters already exist
13853
- *
13854
- * Several of these paths already counted themselves — `CaptureScheduler`'s
13855
- * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
13856
- * ends in a log line, and a log line is the thing the operator asked to stop
13857
- * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
13858
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
13859
- * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
13860
- * media blackout were both diagnosed. The counters stay; this is where they can
13861
- * be READ.
13862
- *
13863
- * ## The rate is served with its denominator or not at all
13864
- *
13865
- * Every entry carries `attempts` and `succeeded`. A miss count alone is
13866
- * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
13867
- * than yesterday" and was **flat across twelve hours** once divided by the
13868
- * successes on the same path. A surface that publishes only the numerator
13869
- * reproduces that mistake on every read.
13870
- *
13871
- * ## Shape
13872
- *
13873
- * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
13874
- * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
13875
- * generated hooks, while `addons.listCapabilityProviders` still enumerates it
13876
- * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
13877
- * a forked runner's entries reach hub-main over transport that already exists.
13878
- * No new UDS message, no second registry (D3). The operator reads the assembled
13879
- * result through `system.getFailureContributions`.
13880
- */
13881
- var FailureReasonCountSchema = object({
13882
- /**
13883
- * Why the attempt did not land, in the contributor's own vocabulary —
13884
- * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
13885
- * strings that already appear in this repo's logs and, where one exists, the
13886
- * same string the per-track `previewMissReason` records (D276): a second
13887
- * vocabulary for the same loss would make the row and the counter
13888
- * un-joinable.
13889
- */
13890
- reason: string(),
13891
- count: number().int().nonnegative()
13892
- });
13893
- var FailureContributionSchema = object({
13894
- /**
13895
- * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
13896
- * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
13897
- * `unit` free: the families are owned by different addons and a shared enum
13898
- * is a central list that rots invisibly.
13899
- */
13900
- family: string(),
13901
- /**
13902
- * The NUMERIC device id — the same value every log line carries as
13903
- * `tags.deviceId`. Never nullable and never absent: a contributor that
13904
- * cannot name the camera must not emit the entry, because a fleet total
13905
- * cannot answer the only question anybody asks of this surface.
13906
- */
13907
- deviceId: number().int().positive(),
13908
- /**
13909
- * A second dimension inside the family: the model / step id for an inference
13910
- * timeout, so "which camera AND which model" is one read. Absent when the
13911
- * family has a single variant.
13912
- */
13913
- variant: string().optional(),
13914
- /**
13915
- * Epoch ms this counter started — the INCARNATION MARKER. A consumer
13916
- * differencing two reads must drop the interval when it changes, because the
13917
- * counter restarted from zero in a respawned runner. Same discipline as
13918
- * `LoadContribution.startedAtMs`.
13919
- */
13920
- sinceMs: number(),
13921
- /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
13922
- atMs: number(),
13923
- /**
13924
- * THE DENOMINATOR — every attempt on this path for this camera in the
13925
- * window. A failure count published without it is the mistake this schema
13926
- * exists to make impossible.
13927
- */
13928
- attempts: number().int().nonnegative(),
13929
- /** Attempts that landed. `attempts - succeeded` is the loss. */
13930
- succeeded: number().int().nonnegative(),
13931
- /** The loss, partitioned. Sums to `attempts - succeeded`. */
13932
- reasons: array(FailureReasonCountSchema).readonly()
13933
- });
13934
- method(_void(), array(FailureContributionSchema).readonly());
13935
- var LoadContributionSchema = object({
13936
- role: _enum([
13937
- "decode",
13938
- "transcode",
13939
- "recording",
13940
- "streaming",
13941
- "detection"
13942
- ]),
13943
- /**
13944
- * The NUMERIC device id — the same value every log line carries as
13945
- * `tags.deviceId`. `null` means this cost genuinely belongs to no single
13946
- * camera (a shared pool), NOT that the contributor forgot to look it up: a
13947
- * contributor that cannot name its camera must not emit the entry at all,
13948
- * because an unnamed per-camera entry is indistinguishable from a shared one
13949
- * and would quietly turn one camera's cost into everybody's.
13950
- */
13951
- deviceId: number().int().positive().nullable(),
13952
- attribution: _enum([
13953
- "measured",
13954
- "accounted",
13955
- "unattributable"
13956
- ]),
13957
- /**
13958
- * What ONE entry is, in the contributor's own words — `615/high`,
13959
- * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
13960
- * family and inventing a common one would lose the only information that
13961
- * makes two entries for the same camera distinguishable.
13962
- */
13963
- unit: string(),
13964
- /**
13965
- * The OS process this cost lives in, when there is one. Present so a
13966
- * consumer can (a) tell two generations of the same unit apart across a
13967
- * restart, and (b) subtract claimed processes from the node's process
13968
- * snapshot to see what NOBODY claimed. Absent for an entry that owns no
13969
- * process of its own.
13970
- */
13971
- pid: number().int().positive().optional(),
13972
- /**
13973
- * When this generation started. The pid's incarnation marker: a consumer
13974
- * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
13975
- * window when this changes, because the counter restarted from zero in a new
13976
- * process.
13977
- */
13978
- startedAtMs: number().optional(),
13979
- /**
13980
- * CUMULATIVE CPU seconds this unit has consumed since it started — user +
13981
- * system, read from the child's own `/proc/<pid>/stat` at the moment the
13982
- * contribution is asked for.
13983
- *
13984
- * Cumulative and not a rate on purpose: a rate needs a window, a window
13985
- * needs a sampler, and a new per-node sampler is the defect half of
13986
- * `docs/architecture/load-ledger.md` documents. A counter can be differenced
13987
- * by whoever already keeps a history; a rate cannot be un-averaged.
13988
- *
13989
- * Absent — never zero — on a node with no `/proc`, on a read failure, and on
13990
- * an entry with no process.
13991
- */
13992
- cpuSeconds: number().optional(),
13993
- /** Resident bytes of this unit's process, same source and same rules. */
13994
- rssBytes: number().optional()
13995
- });
13996
- method(_void(), array(LoadContributionSchema).readonly());
13997
- /**
13998
13998
  * `login-method` — collection cap through which auth addons contribute
13999
13999
  * their pre-auth login surfaces to the login page. This is the SINGLE,
14000
14000
  * generic mechanism that supersedes the dead `auth.listProviders` reader:
@@ -18732,12 +18732,53 @@ var MediaFileKindEnum = _enum([
18732
18732
  "keyFrameSmall",
18733
18733
  "thumbnailSmall"
18734
18734
  ]);
18735
+ /**
18736
+ * One media row ON THE WIRE: what it is, how big it is, and WHERE ITS BYTES
18737
+ * ARE — never the bytes themselves.
18738
+ *
18739
+ * ## Why `url` and not `base64`
18740
+ *
18741
+ * Measured on the live hub 2026-08-30: `getTrackMedia {trackId, deviceId}`
18742
+ * with no `kinds` returned 6 rows / **3 597 219 B**, of which `keyFrame` alone
18743
+ * was **2 824 077 B** — one full-resolution frame, base64, so +33 % on the
18744
+ * wire. Forty events is ~144 MB. Every byte of it was read off disk,
18745
+ * base64-encoded, held whole in a unary tRPC envelope, and materialised in
18746
+ * hub-main's heap on the way past — for an `<img>` that would have cached it.
18747
+ *
18748
+ * `url` points at the `event-media` data plane
18749
+ * (`/addon/<addonId>/event-media/<storedKey>`), which serves the same blob
18750
+ * with an ETag and `Cache-Control: immutable`, honours conditional GETs, can
18751
+ * render a `?variant=thumb`, and streams. The hub gate in front of it requires
18752
+ * a bearer or the session cookie (`access: 'authenticated'`), so the bytes are
18753
+ * no less protected than they were inside a `view`-level cap response — see
18754
+ * `data-plane-access.ts` for the rule and the one gap it does not close
18755
+ * (per-device scoping).
18756
+ *
18757
+ * The URL is built from the row's **stored** key, which is not always its
18758
+ * published `kind`: a track's face/plate crop is stored as `crop` under
18759
+ * `('face'|'plate', '<prefix>-<trackId>')` and published as
18760
+ * `faceCrop`/`plateCrop`. `MediaStore.getByKey` knows only the stored key.
18761
+ *
18762
+ * ## `base64` is TRANSITIONAL and is going away
18763
+ *
18764
+ * It is still populated for one reason: the deployed viewer's track-detail
18765
+ * HERO tile reads it (`use-track-media-entry.ts` → `parseMediaFiles`, which
18766
+ * REQUIRES the field), and a row without it parses as a FAILED read — the red
18767
+ * triangle — not as absence. Removing the field before that viewer ships is an
18768
+ * outage, not a cleanup. Once the viewer takes its hero bytes from `url`,
18769
+ * delete this line and the `withBytes` pass-through in
18770
+ * `analytics-query-facade.ts`; nothing else reads it.
18771
+ */
18735
18772
  var MediaFileSchema = object({
18736
18773
  key: string(),
18737
18774
  kind: MediaFileKindEnum,
18738
- base64: string(),
18739
18775
  sizeBytes: number(),
18740
18776
  timestamp: number()
18777
+ }).extend({
18778
+ /** `/addon/<addonId>/event-media/<encoded stored key>`. Always present. */
18779
+ url: string(),
18780
+ /** @deprecated Transitional — see the schema docblock. Use {@link url}. */
18781
+ base64: string()
18741
18782
  });
18742
18783
  /**
18743
18784
  * One media row WITHOUT its bytes.
@@ -18749,7 +18790,9 @@ var MediaFileSchema = object({
18749
18790
  * blocks the whole view.
18750
18791
  *
18751
18792
  * `sizeBytes` is carried because it is what lets a client decide between the
18752
- * stored blob and a `?variant=thumb` rendering without fetching either.
18793
+ * stored blob and a `?variant=thumb` rendering without fetching either, and
18794
+ * `url` because a client that had to build the plane path itself is a second
18795
+ * copy of a route — the embed, the viewer and the admin UI each grew one.
18753
18796
  */
18754
18797
  var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
18755
18798
  /**
@@ -19438,6 +19481,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19438
19481
  }), array(MediaFileSchema).readonly()), method(object({
19439
19482
  trackId: string(),
19440
19483
  deviceId: number()
19484
+ }), array(MediaFileInfoSchema).readonly()), method(object({
19485
+ eventId: string(),
19486
+ deviceId: number()
19441
19487
  }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
19442
19488
  kind: "mutation",
19443
19489
  auth: "admin"
@@ -24576,10 +24622,24 @@ var FaceClusterSchema = object({
24576
24622
  size: number().int(),
24577
24623
  cohesion: number()
24578
24624
  });
24625
+ /**
24626
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
24627
+ * are — never the bytes.
24628
+ *
24629
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
24630
+ * track/event contract) is still populated because a deployed viewer requires
24631
+ * the field to parse a row at all; this method has no such reader. Its ONE
24632
+ * caller is the admin UI's detail modal, which was building
24633
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
24634
+ * dialog already rendering its key FRAME from the `event-media` plane.
24635
+ *
24636
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
24637
+ * media key directly, so this needed no new plane and no new access decision.
24638
+ */
24579
24639
  var MediaFileLiteSchema$1 = object({
24580
24640
  key: string(),
24581
24641
  kind: string(),
24582
- base64: string(),
24642
+ url: string(),
24583
24643
  sizeBytes: number(),
24584
24644
  timestamp: number()
24585
24645
  });
@@ -27601,10 +27661,24 @@ var PlateInfoSchema = object({
27601
27661
  */
27602
27662
  cropUrl: string().optional()
27603
27663
  });
27664
+ /**
27665
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
27666
+ * are — never the bytes.
27667
+ *
27668
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
27669
+ * track/event contract) is still populated because a deployed viewer requires
27670
+ * the field to parse a row at all; this method has no such reader. Its ONE
27671
+ * caller is the admin UI's detail modal, which was building
27672
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
27673
+ * dialog already rendering its key FRAME from the `event-media` plane.
27674
+ *
27675
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
27676
+ * media key directly, so this needed no new plane and no new access decision.
27677
+ */
27604
27678
  var MediaFileLiteSchema = object({
27605
27679
  key: string(),
27606
27680
  kind: string(),
27607
- base64: string(),
27681
+ url: string(),
27608
27682
  sizeBytes: number(),
27609
27683
  timestamp: number()
27610
27684
  });
@@ -35499,6 +35573,12 @@ Object.freeze({
35499
35573
  addonId: null,
35500
35574
  access: "view"
35501
35575
  },
35576
+ "pipelineAnalytics.listEventMedia": {
35577
+ capName: "pipeline-analytics",
35578
+ capScope: "device",
35579
+ addonId: null,
35580
+ access: "view"
35581
+ },
35502
35582
  "pipelineAnalytics.listGroups": {
35503
35583
  capName: "pipeline-analytics",
35504
35584
  capScope: "device",
@@ -39122,6 +39202,11 @@ Object.freeze({
39122
39202
  form: "array",
39123
39203
  optional: false
39124
39204
  }],
39205
+ "pipelineAnalytics.listEventMedia": [{
39206
+ name: "deviceId",
39207
+ form: "single",
39208
+ optional: false
39209
+ }],
39125
39210
  "pipelineAnalytics.listGroups": [{
39126
39211
  name: "deviceIds",
39127
39212
  form: "array",
package/dist/addon.mjs CHANGED
@@ -13205,6 +13205,114 @@ method(object({
13205
13205
  height: number()
13206
13206
  }), EmbeddingResultSchema, { auth: "admin" }), method(object({ text: string() }), EmbeddingResultSchema, { auth: "admin" }), method(_void(), EmbeddingInfoSchema, { auth: "admin" });
13207
13207
  /**
13208
+ * `failure-contribution` — the capability an addon reports its OWN losses
13209
+ * through, per camera, with the denominator attached. It stores nothing.
13210
+ *
13211
+ * ## The twin of `load-contribution`, and why it is a twin and not a field
13212
+ *
13213
+ * `load-contribution` answers *what did this camera COST*. This answers *what
13214
+ * did this camera LOSE*. The reporting discipline is identical and deliberately
13215
+ * copied: the contributor reports what it already knows, hub-main adds only
13216
+ * `addonId`, nothing needs global knowledge, and there is no central list for
13217
+ * somebody to forget to edit.
13218
+ *
13219
+ * They are not merged, because their invariants are opposites:
13220
+ *
13221
+ * - a `load-contribution` measurement is **absent, never zero** — a zero would
13222
+ * claim a camera cost nothing, which is a measurement nobody made;
13223
+ * - a `failure-contribution` zero is the **most valuable value on the
13224
+ * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
13225
+ * and it is exactly what an absent entry cannot say.
13226
+ *
13227
+ * Putting a loss counter on a cost entry would also break the reconciliation
13228
+ * that gives `load-contribution` its point: contributions are subtracted from
13229
+ * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
13230
+ * has no process.
13231
+ *
13232
+ * ## Why not a log line, since the counters already exist
13233
+ *
13234
+ * Several of these paths already counted themselves — `CaptureScheduler`'s
13235
+ * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
13236
+ * ends in a log line, and a log line is the thing the operator asked to stop
13237
+ * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
13238
+ * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
13239
+ * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
13240
+ * media blackout were both diagnosed. The counters stay; this is where they can
13241
+ * be READ.
13242
+ *
13243
+ * ## The rate is served with its denominator or not at all
13244
+ *
13245
+ * Every entry carries `attempts` and `succeeded`. A miss count alone is
13246
+ * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
13247
+ * than yesterday" and was **flat across twelve hours** once divided by the
13248
+ * successes on the same path. A surface that publishes only the numerator
13249
+ * reproduces that mistake on every read.
13250
+ *
13251
+ * ## Shape
13252
+ *
13253
+ * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
13254
+ * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
13255
+ * generated hooks, while `addons.listCapabilityProviders` still enumerates it
13256
+ * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
13257
+ * a forked runner's entries reach hub-main over transport that already exists.
13258
+ * No new UDS message, no second registry (D3). The operator reads the assembled
13259
+ * result through `system.getFailureContributions`.
13260
+ */
13261
+ var FailureReasonCountSchema = object({
13262
+ /**
13263
+ * Why the attempt did not land, in the contributor's own vocabulary —
13264
+ * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
13265
+ * strings that already appear in this repo's logs and, where one exists, the
13266
+ * same string the per-track `previewMissReason` records (D276): a second
13267
+ * vocabulary for the same loss would make the row and the counter
13268
+ * un-joinable.
13269
+ */
13270
+ reason: string(),
13271
+ count: number().int().nonnegative()
13272
+ });
13273
+ var FailureContributionSchema = object({
13274
+ /**
13275
+ * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
13276
+ * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
13277
+ * `unit` free: the families are owned by different addons and a shared enum
13278
+ * is a central list that rots invisibly.
13279
+ */
13280
+ family: string(),
13281
+ /**
13282
+ * The NUMERIC device id — the same value every log line carries as
13283
+ * `tags.deviceId`. Never nullable and never absent: a contributor that
13284
+ * cannot name the camera must not emit the entry, because a fleet total
13285
+ * cannot answer the only question anybody asks of this surface.
13286
+ */
13287
+ deviceId: number().int().positive(),
13288
+ /**
13289
+ * A second dimension inside the family: the model / step id for an inference
13290
+ * timeout, so "which camera AND which model" is one read. Absent when the
13291
+ * family has a single variant.
13292
+ */
13293
+ variant: string().optional(),
13294
+ /**
13295
+ * Epoch ms this counter started — the INCARNATION MARKER. A consumer
13296
+ * differencing two reads must drop the interval when it changes, because the
13297
+ * counter restarted from zero in a respawned runner. Same discipline as
13298
+ * `LoadContribution.startedAtMs`.
13299
+ */
13300
+ sinceMs: number(),
13301
+ /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
13302
+ atMs: number(),
13303
+ /**
13304
+ * THE DENOMINATOR — every attempt on this path for this camera in the
13305
+ * window. A failure count published without it is the mistake this schema
13306
+ * exists to make impossible.
13307
+ */
13308
+ attempts: number().int().nonnegative(),
13309
+ /** Attempts that landed. `attempts - succeeded` is the loss. */
13310
+ succeeded: number().int().nonnegative(),
13311
+ /** The loss, partitioned. Sums to `attempts - succeeded`. */
13312
+ reasons: array(FailureReasonCountSchema).readonly()
13313
+ });
13314
+ method(_void(), array(FailureContributionSchema).readonly());
13315
+ /**
13208
13316
  * filesystem-browse — per-node capability for browsing the node's local
13209
13317
  * filesystem. Reads are unconfined (whole filesystem, from `/` down); WRITES
13210
13318
  * are sandboxed to operator-configured allowed roots (D115). Used by the
@@ -13726,6 +13834,68 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
13726
13834
  kind: "mutation",
13727
13835
  auth: "admin"
13728
13836
  });
13837
+ var LoadContributionSchema = object({
13838
+ role: _enum([
13839
+ "decode",
13840
+ "transcode",
13841
+ "recording",
13842
+ "streaming",
13843
+ "detection"
13844
+ ]),
13845
+ /**
13846
+ * The NUMERIC device id — the same value every log line carries as
13847
+ * `tags.deviceId`. `null` means this cost genuinely belongs to no single
13848
+ * camera (a shared pool), NOT that the contributor forgot to look it up: a
13849
+ * contributor that cannot name its camera must not emit the entry at all,
13850
+ * because an unnamed per-camera entry is indistinguishable from a shared one
13851
+ * and would quietly turn one camera's cost into everybody's.
13852
+ */
13853
+ deviceId: number().int().positive().nullable(),
13854
+ attribution: _enum([
13855
+ "measured",
13856
+ "accounted",
13857
+ "unattributable"
13858
+ ]),
13859
+ /**
13860
+ * What ONE entry is, in the contributor's own words — `615/high`,
13861
+ * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
13862
+ * family and inventing a common one would lose the only information that
13863
+ * makes two entries for the same camera distinguishable.
13864
+ */
13865
+ unit: string(),
13866
+ /**
13867
+ * The OS process this cost lives in, when there is one. Present so a
13868
+ * consumer can (a) tell two generations of the same unit apart across a
13869
+ * restart, and (b) subtract claimed processes from the node's process
13870
+ * snapshot to see what NOBODY claimed. Absent for an entry that owns no
13871
+ * process of its own.
13872
+ */
13873
+ pid: number().int().positive().optional(),
13874
+ /**
13875
+ * When this generation started. The pid's incarnation marker: a consumer
13876
+ * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
13877
+ * window when this changes, because the counter restarted from zero in a new
13878
+ * process.
13879
+ */
13880
+ startedAtMs: number().optional(),
13881
+ /**
13882
+ * CUMULATIVE CPU seconds this unit has consumed since it started — user +
13883
+ * system, read from the child's own `/proc/<pid>/stat` at the moment the
13884
+ * contribution is asked for.
13885
+ *
13886
+ * Cumulative and not a rate on purpose: a rate needs a window, a window
13887
+ * needs a sampler, and a new per-node sampler is the defect half of
13888
+ * `docs/architecture/load-ledger.md` documents. A counter can be differenced
13889
+ * by whoever already keeps a history; a rate cannot be un-averaged.
13890
+ *
13891
+ * Absent — never zero — on a node with no `/proc`, on a read failure, and on
13892
+ * an entry with no process.
13893
+ */
13894
+ cpuSeconds: number().optional(),
13895
+ /** Resident bytes of this unit's process, same source and same rules. */
13896
+ rssBytes: number().optional()
13897
+ });
13898
+ method(_void(), array(LoadContributionSchema).readonly());
13729
13899
  /**
13730
13900
  * `log-channels` — the capability an addon DECLARES its diagnostic channels
13731
13901
  * through. It stores nothing.
@@ -13802,176 +13972,6 @@ method(LogEntrySchema, _void(), { kind: "mutation" }), method(object({
13802
13972
  tags: record(string(), string()).optional()
13803
13973
  }), array(LogEntrySchema).readonly());
13804
13974
  /**
13805
- * `failure-contribution` — the capability an addon reports its OWN losses
13806
- * through, per camera, with the denominator attached. It stores nothing.
13807
- *
13808
- * ## The twin of `load-contribution`, and why it is a twin and not a field
13809
- *
13810
- * `load-contribution` answers *what did this camera COST*. This answers *what
13811
- * did this camera LOSE*. The reporting discipline is identical and deliberately
13812
- * copied: the contributor reports what it already knows, hub-main adds only
13813
- * `addonId`, nothing needs global knowledge, and there is no central list for
13814
- * somebody to forget to edit.
13815
- *
13816
- * They are not merged, because their invariants are opposites:
13817
- *
13818
- * - a `load-contribution` measurement is **absent, never zero** — a zero would
13819
- * claim a camera cost nothing, which is a measurement nobody made;
13820
- * - a `failure-contribution` zero is the **most valuable value on the
13821
- * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
13822
- * and it is exactly what an absent entry cannot say.
13823
- *
13824
- * Putting a loss counter on a cost entry would also break the reconciliation
13825
- * that gives `load-contribution` its point: contributions are subtracted from
13826
- * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
13827
- * has no process.
13828
- *
13829
- * ## Why not a log line, since the counters already exist
13830
- *
13831
- * Several of these paths already counted themselves — `CaptureScheduler`'s
13832
- * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
13833
- * ends in a log line, and a log line is the thing the operator asked to stop
13834
- * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
13835
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
13836
- * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
13837
- * media blackout were both diagnosed. The counters stay; this is where they can
13838
- * be READ.
13839
- *
13840
- * ## The rate is served with its denominator or not at all
13841
- *
13842
- * Every entry carries `attempts` and `succeeded`. A miss count alone is
13843
- * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
13844
- * than yesterday" and was **flat across twelve hours** once divided by the
13845
- * successes on the same path. A surface that publishes only the numerator
13846
- * reproduces that mistake on every read.
13847
- *
13848
- * ## Shape
13849
- *
13850
- * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
13851
- * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
13852
- * generated hooks, while `addons.listCapabilityProviders` still enumerates it
13853
- * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
13854
- * a forked runner's entries reach hub-main over transport that already exists.
13855
- * No new UDS message, no second registry (D3). The operator reads the assembled
13856
- * result through `system.getFailureContributions`.
13857
- */
13858
- var FailureReasonCountSchema = object({
13859
- /**
13860
- * Why the attempt did not land, in the contributor's own vocabulary —
13861
- * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
13862
- * strings that already appear in this repo's logs and, where one exists, the
13863
- * same string the per-track `previewMissReason` records (D276): a second
13864
- * vocabulary for the same loss would make the row and the counter
13865
- * un-joinable.
13866
- */
13867
- reason: string(),
13868
- count: number().int().nonnegative()
13869
- });
13870
- var FailureContributionSchema = object({
13871
- /**
13872
- * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
13873
- * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
13874
- * `unit` free: the families are owned by different addons and a shared enum
13875
- * is a central list that rots invisibly.
13876
- */
13877
- family: string(),
13878
- /**
13879
- * The NUMERIC device id — the same value every log line carries as
13880
- * `tags.deviceId`. Never nullable and never absent: a contributor that
13881
- * cannot name the camera must not emit the entry, because a fleet total
13882
- * cannot answer the only question anybody asks of this surface.
13883
- */
13884
- deviceId: number().int().positive(),
13885
- /**
13886
- * A second dimension inside the family: the model / step id for an inference
13887
- * timeout, so "which camera AND which model" is one read. Absent when the
13888
- * family has a single variant.
13889
- */
13890
- variant: string().optional(),
13891
- /**
13892
- * Epoch ms this counter started — the INCARNATION MARKER. A consumer
13893
- * differencing two reads must drop the interval when it changes, because the
13894
- * counter restarted from zero in a respawned runner. Same discipline as
13895
- * `LoadContribution.startedAtMs`.
13896
- */
13897
- sinceMs: number(),
13898
- /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
13899
- atMs: number(),
13900
- /**
13901
- * THE DENOMINATOR — every attempt on this path for this camera in the
13902
- * window. A failure count published without it is the mistake this schema
13903
- * exists to make impossible.
13904
- */
13905
- attempts: number().int().nonnegative(),
13906
- /** Attempts that landed. `attempts - succeeded` is the loss. */
13907
- succeeded: number().int().nonnegative(),
13908
- /** The loss, partitioned. Sums to `attempts - succeeded`. */
13909
- reasons: array(FailureReasonCountSchema).readonly()
13910
- });
13911
- method(_void(), array(FailureContributionSchema).readonly());
13912
- var LoadContributionSchema = object({
13913
- role: _enum([
13914
- "decode",
13915
- "transcode",
13916
- "recording",
13917
- "streaming",
13918
- "detection"
13919
- ]),
13920
- /**
13921
- * The NUMERIC device id — the same value every log line carries as
13922
- * `tags.deviceId`. `null` means this cost genuinely belongs to no single
13923
- * camera (a shared pool), NOT that the contributor forgot to look it up: a
13924
- * contributor that cannot name its camera must not emit the entry at all,
13925
- * because an unnamed per-camera entry is indistinguishable from a shared one
13926
- * and would quietly turn one camera's cost into everybody's.
13927
- */
13928
- deviceId: number().int().positive().nullable(),
13929
- attribution: _enum([
13930
- "measured",
13931
- "accounted",
13932
- "unattributable"
13933
- ]),
13934
- /**
13935
- * What ONE entry is, in the contributor's own words — `615/high`,
13936
- * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
13937
- * family and inventing a common one would lose the only information that
13938
- * makes two entries for the same camera distinguishable.
13939
- */
13940
- unit: string(),
13941
- /**
13942
- * The OS process this cost lives in, when there is one. Present so a
13943
- * consumer can (a) tell two generations of the same unit apart across a
13944
- * restart, and (b) subtract claimed processes from the node's process
13945
- * snapshot to see what NOBODY claimed. Absent for an entry that owns no
13946
- * process of its own.
13947
- */
13948
- pid: number().int().positive().optional(),
13949
- /**
13950
- * When this generation started. The pid's incarnation marker: a consumer
13951
- * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
13952
- * window when this changes, because the counter restarted from zero in a new
13953
- * process.
13954
- */
13955
- startedAtMs: number().optional(),
13956
- /**
13957
- * CUMULATIVE CPU seconds this unit has consumed since it started — user +
13958
- * system, read from the child's own `/proc/<pid>/stat` at the moment the
13959
- * contribution is asked for.
13960
- *
13961
- * Cumulative and not a rate on purpose: a rate needs a window, a window
13962
- * needs a sampler, and a new per-node sampler is the defect half of
13963
- * `docs/architecture/load-ledger.md` documents. A counter can be differenced
13964
- * by whoever already keeps a history; a rate cannot be un-averaged.
13965
- *
13966
- * Absent — never zero — on a node with no `/proc`, on a read failure, and on
13967
- * an entry with no process.
13968
- */
13969
- cpuSeconds: number().optional(),
13970
- /** Resident bytes of this unit's process, same source and same rules. */
13971
- rssBytes: number().optional()
13972
- });
13973
- method(_void(), array(LoadContributionSchema).readonly());
13974
- /**
13975
13975
  * `login-method` — collection cap through which auth addons contribute
13976
13976
  * their pre-auth login surfaces to the login page. This is the SINGLE,
13977
13977
  * generic mechanism that supersedes the dead `auth.listProviders` reader:
@@ -18709,12 +18709,53 @@ var MediaFileKindEnum = _enum([
18709
18709
  "keyFrameSmall",
18710
18710
  "thumbnailSmall"
18711
18711
  ]);
18712
+ /**
18713
+ * One media row ON THE WIRE: what it is, how big it is, and WHERE ITS BYTES
18714
+ * ARE — never the bytes themselves.
18715
+ *
18716
+ * ## Why `url` and not `base64`
18717
+ *
18718
+ * Measured on the live hub 2026-08-30: `getTrackMedia {trackId, deviceId}`
18719
+ * with no `kinds` returned 6 rows / **3 597 219 B**, of which `keyFrame` alone
18720
+ * was **2 824 077 B** — one full-resolution frame, base64, so +33 % on the
18721
+ * wire. Forty events is ~144 MB. Every byte of it was read off disk,
18722
+ * base64-encoded, held whole in a unary tRPC envelope, and materialised in
18723
+ * hub-main's heap on the way past — for an `<img>` that would have cached it.
18724
+ *
18725
+ * `url` points at the `event-media` data plane
18726
+ * (`/addon/<addonId>/event-media/<storedKey>`), which serves the same blob
18727
+ * with an ETag and `Cache-Control: immutable`, honours conditional GETs, can
18728
+ * render a `?variant=thumb`, and streams. The hub gate in front of it requires
18729
+ * a bearer or the session cookie (`access: 'authenticated'`), so the bytes are
18730
+ * no less protected than they were inside a `view`-level cap response — see
18731
+ * `data-plane-access.ts` for the rule and the one gap it does not close
18732
+ * (per-device scoping).
18733
+ *
18734
+ * The URL is built from the row's **stored** key, which is not always its
18735
+ * published `kind`: a track's face/plate crop is stored as `crop` under
18736
+ * `('face'|'plate', '<prefix>-<trackId>')` and published as
18737
+ * `faceCrop`/`plateCrop`. `MediaStore.getByKey` knows only the stored key.
18738
+ *
18739
+ * ## `base64` is TRANSITIONAL and is going away
18740
+ *
18741
+ * It is still populated for one reason: the deployed viewer's track-detail
18742
+ * HERO tile reads it (`use-track-media-entry.ts` → `parseMediaFiles`, which
18743
+ * REQUIRES the field), and a row without it parses as a FAILED read — the red
18744
+ * triangle — not as absence. Removing the field before that viewer ships is an
18745
+ * outage, not a cleanup. Once the viewer takes its hero bytes from `url`,
18746
+ * delete this line and the `withBytes` pass-through in
18747
+ * `analytics-query-facade.ts`; nothing else reads it.
18748
+ */
18712
18749
  var MediaFileSchema = object({
18713
18750
  key: string(),
18714
18751
  kind: MediaFileKindEnum,
18715
- base64: string(),
18716
18752
  sizeBytes: number(),
18717
18753
  timestamp: number()
18754
+ }).extend({
18755
+ /** `/addon/<addonId>/event-media/<encoded stored key>`. Always present. */
18756
+ url: string(),
18757
+ /** @deprecated Transitional — see the schema docblock. Use {@link url}. */
18758
+ base64: string()
18718
18759
  });
18719
18760
  /**
18720
18761
  * One media row WITHOUT its bytes.
@@ -18726,7 +18767,9 @@ var MediaFileSchema = object({
18726
18767
  * blocks the whole view.
18727
18768
  *
18728
18769
  * `sizeBytes` is carried because it is what lets a client decide between the
18729
- * stored blob and a `?variant=thumb` rendering without fetching either.
18770
+ * stored blob and a `?variant=thumb` rendering without fetching either, and
18771
+ * `url` because a client that had to build the plane path itself is a second
18772
+ * copy of a route — the embed, the viewer and the admin UI each grew one.
18730
18773
  */
18731
18774
  var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
18732
18775
  /**
@@ -19415,6 +19458,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19415
19458
  }), array(MediaFileSchema).readonly()), method(object({
19416
19459
  trackId: string(),
19417
19460
  deviceId: number()
19461
+ }), array(MediaFileInfoSchema).readonly()), method(object({
19462
+ eventId: string(),
19463
+ deviceId: number()
19418
19464
  }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
19419
19465
  kind: "mutation",
19420
19466
  auth: "admin"
@@ -24553,10 +24599,24 @@ var FaceClusterSchema = object({
24553
24599
  size: number().int(),
24554
24600
  cohesion: number()
24555
24601
  });
24602
+ /**
24603
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
24604
+ * are — never the bytes.
24605
+ *
24606
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
24607
+ * track/event contract) is still populated because a deployed viewer requires
24608
+ * the field to parse a row at all; this method has no such reader. Its ONE
24609
+ * caller is the admin UI's detail modal, which was building
24610
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
24611
+ * dialog already rendering its key FRAME from the `event-media` plane.
24612
+ *
24613
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
24614
+ * media key directly, so this needed no new plane and no new access decision.
24615
+ */
24556
24616
  var MediaFileLiteSchema$1 = object({
24557
24617
  key: string(),
24558
24618
  kind: string(),
24559
- base64: string(),
24619
+ url: string(),
24560
24620
  sizeBytes: number(),
24561
24621
  timestamp: number()
24562
24622
  });
@@ -27578,10 +27638,24 @@ var PlateInfoSchema = object({
27578
27638
  */
27579
27639
  cropUrl: string().optional()
27580
27640
  });
27641
+ /**
27642
+ * One gallery media row: what the crop is, how big it is, and WHERE its bytes
27643
+ * are — never the bytes.
27644
+ *
27645
+ * `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
27646
+ * track/event contract) is still populated because a deployed viewer requires
27647
+ * the field to parse a row at all; this method has no such reader. Its ONE
27648
+ * caller is the admin UI's detail modal, which was building
27649
+ * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
27650
+ * dialog already rendering its key FRAME from the `event-media` plane.
27651
+ *
27652
+ * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
27653
+ * media key directly, so this needed no new plane and no new access decision.
27654
+ */
27581
27655
  var MediaFileLiteSchema = object({
27582
27656
  key: string(),
27583
27657
  kind: string(),
27584
- base64: string(),
27658
+ url: string(),
27585
27659
  sizeBytes: number(),
27586
27660
  timestamp: number()
27587
27661
  });
@@ -35476,6 +35550,12 @@ Object.freeze({
35476
35550
  addonId: null,
35477
35551
  access: "view"
35478
35552
  },
35553
+ "pipelineAnalytics.listEventMedia": {
35554
+ capName: "pipeline-analytics",
35555
+ capScope: "device",
35556
+ addonId: null,
35557
+ access: "view"
35558
+ },
35479
35559
  "pipelineAnalytics.listGroups": {
35480
35560
  capName: "pipeline-analytics",
35481
35561
  capScope: "device",
@@ -39099,6 +39179,11 @@ Object.freeze({
39099
39179
  form: "array",
39100
39180
  optional: false
39101
39181
  }],
39182
+ "pipelineAnalytics.listEventMedia": [{
39183
+ name: "deviceId",
39184
+ form: "single",
39185
+ optional: false
39186
+ }],
39102
39187
  "pipelineAnalytics.listGroups": [{
39103
39188
  name: "deviceIds",
39104
39189
  form: "array",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-terminal",
3
- "version": "0.1.51",
3
+ "version": "0.1.52",
4
4
  "description": "Interactive terminal sessions (pty + xterm) as a CamStack addon",
5
5
  "keywords": [
6
6
  "camstack",