@camstack/addon-smtp-nodemailer 1.2.44 → 1.2.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/smtp.addon.js +313 -181
- package/dist/smtp.addon.mjs +313 -181
- package/package.json +1 -1
package/dist/smtp.addon.js
CHANGED
|
@@ -8024,6 +8024,21 @@ var RelocateJobSchema = object({
|
|
|
8024
8024
|
bytesMoved: number().int(),
|
|
8025
8025
|
/** Total files discovered up front; null while (or when) unknown. */
|
|
8026
8026
|
filesTotal: number().int().nullable(),
|
|
8027
|
+
/**
|
|
8028
|
+
* Rows this run CORRECTED while moving them — a durable mutation the move
|
|
8029
|
+
* made that nobody asked for, so it is reported where the operator reads the
|
|
8030
|
+
* job rather than only in a log line.
|
|
8031
|
+
*
|
|
8032
|
+
* A footage segment records its byte count in its own NAME, and the durable
|
|
8033
|
+
* hour row derives its aggregates from those names. A file that does not
|
|
8034
|
+
* match its name therefore makes the ledger's sums — and with them quota and
|
|
8035
|
+
* pressure eviction — wrong by the difference, and only a rename can fix it.
|
|
8036
|
+
* On 2026-08-30 one such row also stalled a 110 749-file drain permanently.
|
|
8037
|
+
*
|
|
8038
|
+
* Absent on lanes where the question has no meaning: a media blob's size is
|
|
8039
|
+
* in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
|
|
8040
|
+
*/
|
|
8041
|
+
rowsReconciled: number().int().nonnegative().optional(),
|
|
8027
8042
|
startedAt: number(),
|
|
8028
8043
|
finishedAt: number().nullable(),
|
|
8029
8044
|
error: string().nullable()
|
|
@@ -8092,14 +8107,42 @@ var RelocateMediaInputSchema = object({
|
|
|
8092
8107
|
/** Omitted = `move`, the pre-existing behaviour. */
|
|
8093
8108
|
mode: MediaRelocateModeSchema.optional()
|
|
8094
8109
|
});
|
|
8095
|
-
/**
|
|
8096
|
-
*
|
|
8097
|
-
*
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8110
|
+
/**
|
|
8111
|
+
* The unstamped population of ONE collection — split, because the gate and the
|
|
8112
|
+
* operator ask two different questions and only one of them has to be cheap.
|
|
8113
|
+
*
|
|
8114
|
+
* `present` is the GATE: "is there at least one row that would be orphaned by a
|
|
8115
|
+
* repoint". It is a single indexed seek to the first matching row, so it stays
|
|
8116
|
+
* answerable on a saturated disk and answers in O(log n) precisely in the state
|
|
8117
|
+
* that matters — after a seal, when the population is empty.
|
|
8118
|
+
*
|
|
8119
|
+
* `rows` is the NUMBER, for the refusal message and the operator's sense of
|
|
8120
|
+
* scale. It is a second, indexed `COUNT(*)`, and `null` means **not
|
|
8121
|
+
* measurable** — never zero. `{ present: true, rows: null }` is a legitimate
|
|
8122
|
+
* and useful answer: "there are some, and this read could not say how many"
|
|
8123
|
+
* still refuses the cutover, which is the whole job.
|
|
8124
|
+
*/
|
|
8125
|
+
var UnstampedRowsSchema = object({
|
|
8126
|
+
present: boolean(),
|
|
8127
|
+
rows: number().int().nonnegative().nullable()
|
|
8102
8128
|
});
|
|
8129
|
+
/**
|
|
8130
|
+
* How many rows still carry NO `locationId` — the population a repoint would
|
|
8131
|
+
* silently re-aim at a disk that does not hold their bytes.
|
|
8132
|
+
*
|
|
8133
|
+
* **`null` = the count could not be taken**, and it is NOT permission to cut
|
|
8134
|
+
* over. The gate opens on a measured absence and on nothing else; an unread
|
|
8135
|
+
* collection and an empty one are different facts, and this repo has already
|
|
8136
|
+
* paid for conflating them (`RelocateResidueSchema`, D295).
|
|
8137
|
+
*/
|
|
8138
|
+
var UnstampedEventMediaCountSchema = object({
|
|
8139
|
+
media: UnstampedRowsSchema,
|
|
8140
|
+
retrainFrames: UnstampedRowsSchema,
|
|
8141
|
+
/** True when EITHER collection holds one. The refusal reads this. */
|
|
8142
|
+
anyPresent: boolean(),
|
|
8143
|
+
/** Sum across both, or `null` when either lane could not be counted. */
|
|
8144
|
+
total: number().int().nonnegative().nullable()
|
|
8145
|
+
}).nullable();
|
|
8103
8146
|
var StorageMigrationMediaMoveInputSchema = RelocateMediaInputSchema.extend({ leaseId: string().min(1) });
|
|
8104
8147
|
/** The independently selectable logical storage classes — every class
|
|
8105
8148
|
* `storage.listLocationDeclarations` reports, so an operator never meets a
|
|
@@ -8210,6 +8253,10 @@ var StorageMigrationMoveProgressSchema = object({
|
|
|
8210
8253
|
/** The archive census — the **M** of "N of M" (D295). `null` = unknowable. */
|
|
8211
8254
|
filesTotal: number().int().nonnegative().nullable(),
|
|
8212
8255
|
bytesMoved: number().int().nonnegative(),
|
|
8256
|
+
/** Rows the mover corrected while moving them — see `RelocateJob`. Absent on
|
|
8257
|
+
* a lane that cannot reconcile. A migration that silently rewrote durable
|
|
8258
|
+
* rows would be the same failure as one that silently skipped them. */
|
|
8259
|
+
rowsReconciled: number().int().nonnegative().optional(),
|
|
8213
8260
|
/** The MOVER's start, not the migration's: a drain restarted after an addon
|
|
8214
8261
|
* crash gets a new mover, and a rate computed from the migration's start
|
|
8215
8262
|
* would silently average in the time nothing was running. */
|
|
@@ -12894,6 +12941,114 @@ method(object({
|
|
|
12894
12941
|
height: number()
|
|
12895
12942
|
}), EmbeddingResultSchema, { auth: "admin" }), method(object({ text: string() }), EmbeddingResultSchema, { auth: "admin" }), method(_void(), EmbeddingInfoSchema, { auth: "admin" });
|
|
12896
12943
|
/**
|
|
12944
|
+
* `failure-contribution` — the capability an addon reports its OWN losses
|
|
12945
|
+
* through, per camera, with the denominator attached. It stores nothing.
|
|
12946
|
+
*
|
|
12947
|
+
* ## The twin of `load-contribution`, and why it is a twin and not a field
|
|
12948
|
+
*
|
|
12949
|
+
* `load-contribution` answers *what did this camera COST*. This answers *what
|
|
12950
|
+
* did this camera LOSE*. The reporting discipline is identical and deliberately
|
|
12951
|
+
* copied: the contributor reports what it already knows, hub-main adds only
|
|
12952
|
+
* `addonId`, nothing needs global knowledge, and there is no central list for
|
|
12953
|
+
* somebody to forget to edit.
|
|
12954
|
+
*
|
|
12955
|
+
* They are not merged, because their invariants are opposites:
|
|
12956
|
+
*
|
|
12957
|
+
* - a `load-contribution` measurement is **absent, never zero** — a zero would
|
|
12958
|
+
* claim a camera cost nothing, which is a measurement nobody made;
|
|
12959
|
+
* - a `failure-contribution` zero is the **most valuable value on the
|
|
12960
|
+
* surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
|
|
12961
|
+
* and it is exactly what an absent entry cannot say.
|
|
12962
|
+
*
|
|
12963
|
+
* Putting a loss counter on a cost entry would also break the reconciliation
|
|
12964
|
+
* that gives `load-contribution` its point: contributions are subtracted from
|
|
12965
|
+
* `metrics.node-processes-snapshot` to find processes nobody claims. A failure
|
|
12966
|
+
* has no process.
|
|
12967
|
+
*
|
|
12968
|
+
* ## Why not a log line, since the counters already exist
|
|
12969
|
+
*
|
|
12970
|
+
* Several of these paths already counted themselves — `CaptureScheduler`'s
|
|
12971
|
+
* per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
|
|
12972
|
+
* ends in a log line, and a log line is the thing the operator asked to stop
|
|
12973
|
+
* needing: *"possiamo armare questi errori intanto? Così al prossimo giro
|
|
12974
|
+
* ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
|
|
12975
|
+
* hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
|
|
12976
|
+
* media blackout were both diagnosed. The counters stay; this is where they can
|
|
12977
|
+
* be READ.
|
|
12978
|
+
*
|
|
12979
|
+
* ## The rate is served with its denominator or not at all
|
|
12980
|
+
*
|
|
12981
|
+
* Every entry carries `attempts` and `succeeded`. A miss count alone is
|
|
12982
|
+
* unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
|
|
12983
|
+
* than yesterday" and was **flat across twelve hours** once divided by the
|
|
12984
|
+
* successes on the same path. A surface that publishes only the numerator
|
|
12985
|
+
* reproduces that mistake on every read.
|
|
12986
|
+
*
|
|
12987
|
+
* ## Shape
|
|
12988
|
+
*
|
|
12989
|
+
* Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
|
|
12990
|
+
* `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
|
|
12991
|
+
* generated hooks, while `addons.listCapabilityProviders` still enumerates it
|
|
12992
|
+
* and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
|
|
12993
|
+
* a forked runner's entries reach hub-main over transport that already exists.
|
|
12994
|
+
* No new UDS message, no second registry (D3). The operator reads the assembled
|
|
12995
|
+
* result through `system.getFailureContributions`.
|
|
12996
|
+
*/
|
|
12997
|
+
var FailureReasonCountSchema = object({
|
|
12998
|
+
/**
|
|
12999
|
+
* Why the attempt did not land, in the contributor's own vocabulary —
|
|
13000
|
+
* `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
|
|
13001
|
+
* strings that already appear in this repo's logs and, where one exists, the
|
|
13002
|
+
* same string the per-track `previewMissReason` records (D276): a second
|
|
13003
|
+
* vocabulary for the same loss would make the row and the counter
|
|
13004
|
+
* un-joinable.
|
|
13005
|
+
*/
|
|
13006
|
+
reason: string(),
|
|
13007
|
+
count: number().int().nonnegative()
|
|
13008
|
+
});
|
|
13009
|
+
var FailureContributionSchema = object({
|
|
13010
|
+
/**
|
|
13011
|
+
* The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
|
|
13012
|
+
* `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
|
|
13013
|
+
* `unit` free: the families are owned by different addons and a shared enum
|
|
13014
|
+
* is a central list that rots invisibly.
|
|
13015
|
+
*/
|
|
13016
|
+
family: string(),
|
|
13017
|
+
/**
|
|
13018
|
+
* The NUMERIC device id — the same value every log line carries as
|
|
13019
|
+
* `tags.deviceId`. Never nullable and never absent: a contributor that
|
|
13020
|
+
* cannot name the camera must not emit the entry, because a fleet total
|
|
13021
|
+
* cannot answer the only question anybody asks of this surface.
|
|
13022
|
+
*/
|
|
13023
|
+
deviceId: number().int().positive(),
|
|
13024
|
+
/**
|
|
13025
|
+
* A second dimension inside the family: the model / step id for an inference
|
|
13026
|
+
* timeout, so "which camera AND which model" is one read. Absent when the
|
|
13027
|
+
* family has a single variant.
|
|
13028
|
+
*/
|
|
13029
|
+
variant: string().optional(),
|
|
13030
|
+
/**
|
|
13031
|
+
* Epoch ms this counter started — the INCARNATION MARKER. A consumer
|
|
13032
|
+
* differencing two reads must drop the interval when it changes, because the
|
|
13033
|
+
* counter restarted from zero in a respawned runner. Same discipline as
|
|
13034
|
+
* `LoadContribution.startedAtMs`.
|
|
13035
|
+
*/
|
|
13036
|
+
sinceMs: number(),
|
|
13037
|
+
/** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
|
|
13038
|
+
atMs: number(),
|
|
13039
|
+
/**
|
|
13040
|
+
* THE DENOMINATOR — every attempt on this path for this camera in the
|
|
13041
|
+
* window. A failure count published without it is the mistake this schema
|
|
13042
|
+
* exists to make impossible.
|
|
13043
|
+
*/
|
|
13044
|
+
attempts: number().int().nonnegative(),
|
|
13045
|
+
/** Attempts that landed. `attempts - succeeded` is the loss. */
|
|
13046
|
+
succeeded: number().int().nonnegative(),
|
|
13047
|
+
/** The loss, partitioned. Sums to `attempts - succeeded`. */
|
|
13048
|
+
reasons: array(FailureReasonCountSchema).readonly()
|
|
13049
|
+
});
|
|
13050
|
+
method(_void(), array(FailureContributionSchema).readonly());
|
|
13051
|
+
/**
|
|
12897
13052
|
* filesystem-browse — per-node capability for browsing the node's local
|
|
12898
13053
|
* filesystem. Reads are unconfined (whole filesystem, from `/` down); WRITES
|
|
12899
13054
|
* are sandboxed to operator-configured allowed roots (D115). Used by the
|
|
@@ -13415,6 +13570,68 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
|
|
|
13415
13570
|
kind: "mutation",
|
|
13416
13571
|
auth: "admin"
|
|
13417
13572
|
});
|
|
13573
|
+
var LoadContributionSchema = object({
|
|
13574
|
+
role: _enum([
|
|
13575
|
+
"decode",
|
|
13576
|
+
"transcode",
|
|
13577
|
+
"recording",
|
|
13578
|
+
"streaming",
|
|
13579
|
+
"detection"
|
|
13580
|
+
]),
|
|
13581
|
+
/**
|
|
13582
|
+
* The NUMERIC device id — the same value every log line carries as
|
|
13583
|
+
* `tags.deviceId`. `null` means this cost genuinely belongs to no single
|
|
13584
|
+
* camera (a shared pool), NOT that the contributor forgot to look it up: a
|
|
13585
|
+
* contributor that cannot name its camera must not emit the entry at all,
|
|
13586
|
+
* because an unnamed per-camera entry is indistinguishable from a shared one
|
|
13587
|
+
* and would quietly turn one camera's cost into everybody's.
|
|
13588
|
+
*/
|
|
13589
|
+
deviceId: number().int().positive().nullable(),
|
|
13590
|
+
attribution: _enum([
|
|
13591
|
+
"measured",
|
|
13592
|
+
"accounted",
|
|
13593
|
+
"unattributable"
|
|
13594
|
+
]),
|
|
13595
|
+
/**
|
|
13596
|
+
* What ONE entry is, in the contributor's own words — `615/high`,
|
|
13597
|
+
* `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
|
|
13598
|
+
* family and inventing a common one would lose the only information that
|
|
13599
|
+
* makes two entries for the same camera distinguishable.
|
|
13600
|
+
*/
|
|
13601
|
+
unit: string(),
|
|
13602
|
+
/**
|
|
13603
|
+
* The OS process this cost lives in, when there is one. Present so a
|
|
13604
|
+
* consumer can (a) tell two generations of the same unit apart across a
|
|
13605
|
+
* restart, and (b) subtract claimed processes from the node's process
|
|
13606
|
+
* snapshot to see what NOBODY claimed. Absent for an entry that owns no
|
|
13607
|
+
* process of its own.
|
|
13608
|
+
*/
|
|
13609
|
+
pid: number().int().positive().optional(),
|
|
13610
|
+
/**
|
|
13611
|
+
* When this generation started. The pid's incarnation marker: a consumer
|
|
13612
|
+
* differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
|
|
13613
|
+
* window when this changes, because the counter restarted from zero in a new
|
|
13614
|
+
* process.
|
|
13615
|
+
*/
|
|
13616
|
+
startedAtMs: number().optional(),
|
|
13617
|
+
/**
|
|
13618
|
+
* CUMULATIVE CPU seconds this unit has consumed since it started — user +
|
|
13619
|
+
* system, read from the child's own `/proc/<pid>/stat` at the moment the
|
|
13620
|
+
* contribution is asked for.
|
|
13621
|
+
*
|
|
13622
|
+
* Cumulative and not a rate on purpose: a rate needs a window, a window
|
|
13623
|
+
* needs a sampler, and a new per-node sampler is the defect half of
|
|
13624
|
+
* `docs/architecture/load-ledger.md` documents. A counter can be differenced
|
|
13625
|
+
* by whoever already keeps a history; a rate cannot be un-averaged.
|
|
13626
|
+
*
|
|
13627
|
+
* Absent — never zero — on a node with no `/proc`, on a read failure, and on
|
|
13628
|
+
* an entry with no process.
|
|
13629
|
+
*/
|
|
13630
|
+
cpuSeconds: number().optional(),
|
|
13631
|
+
/** Resident bytes of this unit's process, same source and same rules. */
|
|
13632
|
+
rssBytes: number().optional()
|
|
13633
|
+
});
|
|
13634
|
+
method(_void(), array(LoadContributionSchema).readonly());
|
|
13418
13635
|
/**
|
|
13419
13636
|
* `log-channels` — the capability an addon DECLARES its diagnostic channels
|
|
13420
13637
|
* through. It stores nothing.
|
|
@@ -13491,176 +13708,6 @@ method(LogEntrySchema, _void(), { kind: "mutation" }), method(object({
|
|
|
13491
13708
|
tags: record(string(), string()).optional()
|
|
13492
13709
|
}), array(LogEntrySchema).readonly());
|
|
13493
13710
|
/**
|
|
13494
|
-
* `failure-contribution` — the capability an addon reports its OWN losses
|
|
13495
|
-
* through, per camera, with the denominator attached. It stores nothing.
|
|
13496
|
-
*
|
|
13497
|
-
* ## The twin of `load-contribution`, and why it is a twin and not a field
|
|
13498
|
-
*
|
|
13499
|
-
* `load-contribution` answers *what did this camera COST*. This answers *what
|
|
13500
|
-
* did this camera LOSE*. The reporting discipline is identical and deliberately
|
|
13501
|
-
* copied: the contributor reports what it already knows, hub-main adds only
|
|
13502
|
-
* `addonId`, nothing needs global knowledge, and there is no central list for
|
|
13503
|
-
* somebody to forget to edit.
|
|
13504
|
-
*
|
|
13505
|
-
* They are not merged, because their invariants are opposites:
|
|
13506
|
-
*
|
|
13507
|
-
* - a `load-contribution` measurement is **absent, never zero** — a zero would
|
|
13508
|
-
* claim a camera cost nothing, which is a measurement nobody made;
|
|
13509
|
-
* - a `failure-contribution` zero is the **most valuable value on the
|
|
13510
|
-
* surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
|
|
13511
|
-
* and it is exactly what an absent entry cannot say.
|
|
13512
|
-
*
|
|
13513
|
-
* Putting a loss counter on a cost entry would also break the reconciliation
|
|
13514
|
-
* that gives `load-contribution` its point: contributions are subtracted from
|
|
13515
|
-
* `metrics.node-processes-snapshot` to find processes nobody claims. A failure
|
|
13516
|
-
* has no process.
|
|
13517
|
-
*
|
|
13518
|
-
* ## Why not a log line, since the counters already exist
|
|
13519
|
-
*
|
|
13520
|
-
* Several of these paths already counted themselves — `CaptureScheduler`'s
|
|
13521
|
-
* per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
|
|
13522
|
-
* ends in a log line, and a log line is the thing the operator asked to stop
|
|
13523
|
-
* needing: *"possiamo armare questi errori intanto? Così al prossimo giro
|
|
13524
|
-
* ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
|
|
13525
|
-
* hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
|
|
13526
|
-
* media blackout were both diagnosed. The counters stay; this is where they can
|
|
13527
|
-
* be READ.
|
|
13528
|
-
*
|
|
13529
|
-
* ## The rate is served with its denominator or not at all
|
|
13530
|
-
*
|
|
13531
|
-
* Every entry carries `attempts` and `succeeded`. A miss count alone is
|
|
13532
|
-
* unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
|
|
13533
|
-
* than yesterday" and was **flat across twelve hours** once divided by the
|
|
13534
|
-
* successes on the same path. A surface that publishes only the numerator
|
|
13535
|
-
* reproduces that mistake on every read.
|
|
13536
|
-
*
|
|
13537
|
-
* ## Shape
|
|
13538
|
-
*
|
|
13539
|
-
* Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
|
|
13540
|
-
* `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
|
|
13541
|
-
* generated hooks, while `addons.listCapabilityProviders` still enumerates it
|
|
13542
|
-
* and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
|
|
13543
|
-
* a forked runner's entries reach hub-main over transport that already exists.
|
|
13544
|
-
* No new UDS message, no second registry (D3). The operator reads the assembled
|
|
13545
|
-
* result through `system.getFailureContributions`.
|
|
13546
|
-
*/
|
|
13547
|
-
var FailureReasonCountSchema = object({
|
|
13548
|
-
/**
|
|
13549
|
-
* Why the attempt did not land, in the contributor's own vocabulary —
|
|
13550
|
-
* `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
|
|
13551
|
-
* strings that already appear in this repo's logs and, where one exists, the
|
|
13552
|
-
* same string the per-track `previewMissReason` records (D276): a second
|
|
13553
|
-
* vocabulary for the same loss would make the row and the counter
|
|
13554
|
-
* un-joinable.
|
|
13555
|
-
*/
|
|
13556
|
-
reason: string(),
|
|
13557
|
-
count: number().int().nonnegative()
|
|
13558
|
-
});
|
|
13559
|
-
var FailureContributionSchema = object({
|
|
13560
|
-
/**
|
|
13561
|
-
* The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
|
|
13562
|
-
* `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
|
|
13563
|
-
* `unit` free: the families are owned by different addons and a shared enum
|
|
13564
|
-
* is a central list that rots invisibly.
|
|
13565
|
-
*/
|
|
13566
|
-
family: string(),
|
|
13567
|
-
/**
|
|
13568
|
-
* The NUMERIC device id — the same value every log line carries as
|
|
13569
|
-
* `tags.deviceId`. Never nullable and never absent: a contributor that
|
|
13570
|
-
* cannot name the camera must not emit the entry, because a fleet total
|
|
13571
|
-
* cannot answer the only question anybody asks of this surface.
|
|
13572
|
-
*/
|
|
13573
|
-
deviceId: number().int().positive(),
|
|
13574
|
-
/**
|
|
13575
|
-
* A second dimension inside the family: the model / step id for an inference
|
|
13576
|
-
* timeout, so "which camera AND which model" is one read. Absent when the
|
|
13577
|
-
* family has a single variant.
|
|
13578
|
-
*/
|
|
13579
|
-
variant: string().optional(),
|
|
13580
|
-
/**
|
|
13581
|
-
* Epoch ms this counter started — the INCARNATION MARKER. A consumer
|
|
13582
|
-
* differencing two reads must drop the interval when it changes, because the
|
|
13583
|
-
* counter restarted from zero in a respawned runner. Same discipline as
|
|
13584
|
-
* `LoadContribution.startedAtMs`.
|
|
13585
|
-
*/
|
|
13586
|
-
sinceMs: number(),
|
|
13587
|
-
/** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
|
|
13588
|
-
atMs: number(),
|
|
13589
|
-
/**
|
|
13590
|
-
* THE DENOMINATOR — every attempt on this path for this camera in the
|
|
13591
|
-
* window. A failure count published without it is the mistake this schema
|
|
13592
|
-
* exists to make impossible.
|
|
13593
|
-
*/
|
|
13594
|
-
attempts: number().int().nonnegative(),
|
|
13595
|
-
/** Attempts that landed. `attempts - succeeded` is the loss. */
|
|
13596
|
-
succeeded: number().int().nonnegative(),
|
|
13597
|
-
/** The loss, partitioned. Sums to `attempts - succeeded`. */
|
|
13598
|
-
reasons: array(FailureReasonCountSchema).readonly()
|
|
13599
|
-
});
|
|
13600
|
-
method(_void(), array(FailureContributionSchema).readonly());
|
|
13601
|
-
var LoadContributionSchema = object({
|
|
13602
|
-
role: _enum([
|
|
13603
|
-
"decode",
|
|
13604
|
-
"transcode",
|
|
13605
|
-
"recording",
|
|
13606
|
-
"streaming",
|
|
13607
|
-
"detection"
|
|
13608
|
-
]),
|
|
13609
|
-
/**
|
|
13610
|
-
* The NUMERIC device id — the same value every log line carries as
|
|
13611
|
-
* `tags.deviceId`. `null` means this cost genuinely belongs to no single
|
|
13612
|
-
* camera (a shared pool), NOT that the contributor forgot to look it up: a
|
|
13613
|
-
* contributor that cannot name its camera must not emit the entry at all,
|
|
13614
|
-
* because an unnamed per-camera entry is indistinguishable from a shared one
|
|
13615
|
-
* and would quietly turn one camera's cost into everybody's.
|
|
13616
|
-
*/
|
|
13617
|
-
deviceId: number().int().positive().nullable(),
|
|
13618
|
-
attribution: _enum([
|
|
13619
|
-
"measured",
|
|
13620
|
-
"accounted",
|
|
13621
|
-
"unattributable"
|
|
13622
|
-
]),
|
|
13623
|
-
/**
|
|
13624
|
-
* What ONE entry is, in the contributor's own words — `615/high`,
|
|
13625
|
-
* `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
|
|
13626
|
-
* family and inventing a common one would lose the only information that
|
|
13627
|
-
* makes two entries for the same camera distinguishable.
|
|
13628
|
-
*/
|
|
13629
|
-
unit: string(),
|
|
13630
|
-
/**
|
|
13631
|
-
* The OS process this cost lives in, when there is one. Present so a
|
|
13632
|
-
* consumer can (a) tell two generations of the same unit apart across a
|
|
13633
|
-
* restart, and (b) subtract claimed processes from the node's process
|
|
13634
|
-
* snapshot to see what NOBODY claimed. Absent for an entry that owns no
|
|
13635
|
-
* process of its own.
|
|
13636
|
-
*/
|
|
13637
|
-
pid: number().int().positive().optional(),
|
|
13638
|
-
/**
|
|
13639
|
-
* When this generation started. The pid's incarnation marker: a consumer
|
|
13640
|
-
* differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
|
|
13641
|
-
* window when this changes, because the counter restarted from zero in a new
|
|
13642
|
-
* process.
|
|
13643
|
-
*/
|
|
13644
|
-
startedAtMs: number().optional(),
|
|
13645
|
-
/**
|
|
13646
|
-
* CUMULATIVE CPU seconds this unit has consumed since it started — user +
|
|
13647
|
-
* system, read from the child's own `/proc/<pid>/stat` at the moment the
|
|
13648
|
-
* contribution is asked for.
|
|
13649
|
-
*
|
|
13650
|
-
* Cumulative and not a rate on purpose: a rate needs a window, a window
|
|
13651
|
-
* needs a sampler, and a new per-node sampler is the defect half of
|
|
13652
|
-
* `docs/architecture/load-ledger.md` documents. A counter can be differenced
|
|
13653
|
-
* by whoever already keeps a history; a rate cannot be un-averaged.
|
|
13654
|
-
*
|
|
13655
|
-
* Absent — never zero — on a node with no `/proc`, on a read failure, and on
|
|
13656
|
-
* an entry with no process.
|
|
13657
|
-
*/
|
|
13658
|
-
cpuSeconds: number().optional(),
|
|
13659
|
-
/** Resident bytes of this unit's process, same source and same rules. */
|
|
13660
|
-
rssBytes: number().optional()
|
|
13661
|
-
});
|
|
13662
|
-
method(_void(), array(LoadContributionSchema).readonly());
|
|
13663
|
-
/**
|
|
13664
13711
|
* `login-method` — collection cap through which auth addons contribute
|
|
13665
13712
|
* their pre-auth login surfaces to the login page. This is the SINGLE,
|
|
13666
13713
|
* generic mechanism that supersedes the dead `auth.listProviders` reader:
|
|
@@ -18320,12 +18367,53 @@ var MediaFileKindEnum = _enum([
|
|
|
18320
18367
|
"keyFrameSmall",
|
|
18321
18368
|
"thumbnailSmall"
|
|
18322
18369
|
]);
|
|
18370
|
+
/**
|
|
18371
|
+
* One media row ON THE WIRE: what it is, how big it is, and WHERE ITS BYTES
|
|
18372
|
+
* ARE — never the bytes themselves.
|
|
18373
|
+
*
|
|
18374
|
+
* ## Why `url` and not `base64`
|
|
18375
|
+
*
|
|
18376
|
+
* Measured on the live hub 2026-08-30: `getTrackMedia {trackId, deviceId}`
|
|
18377
|
+
* with no `kinds` returned 6 rows / **3 597 219 B**, of which `keyFrame` alone
|
|
18378
|
+
* was **2 824 077 B** — one full-resolution frame, base64, so +33 % on the
|
|
18379
|
+
* wire. Forty events is ~144 MB. Every byte of it was read off disk,
|
|
18380
|
+
* base64-encoded, held whole in a unary tRPC envelope, and materialised in
|
|
18381
|
+
* hub-main's heap on the way past — for an `<img>` that would have cached it.
|
|
18382
|
+
*
|
|
18383
|
+
* `url` points at the `event-media` data plane
|
|
18384
|
+
* (`/addon/<addonId>/event-media/<storedKey>`), which serves the same blob
|
|
18385
|
+
* with an ETag and `Cache-Control: immutable`, honours conditional GETs, can
|
|
18386
|
+
* render a `?variant=thumb`, and streams. The hub gate in front of it requires
|
|
18387
|
+
* a bearer or the session cookie (`access: 'authenticated'`), so the bytes are
|
|
18388
|
+
* no less protected than they were inside a `view`-level cap response — see
|
|
18389
|
+
* `data-plane-access.ts` for the rule and the one gap it does not close
|
|
18390
|
+
* (per-device scoping).
|
|
18391
|
+
*
|
|
18392
|
+
* The URL is built from the row's **stored** key, which is not always its
|
|
18393
|
+
* published `kind`: a track's face/plate crop is stored as `crop` under
|
|
18394
|
+
* `('face'|'plate', '<prefix>-<trackId>')` and published as
|
|
18395
|
+
* `faceCrop`/`plateCrop`. `MediaStore.getByKey` knows only the stored key.
|
|
18396
|
+
*
|
|
18397
|
+
* ## `base64` is TRANSITIONAL and is going away
|
|
18398
|
+
*
|
|
18399
|
+
* It is still populated for one reason: the deployed viewer's track-detail
|
|
18400
|
+
* HERO tile reads it (`use-track-media-entry.ts` → `parseMediaFiles`, which
|
|
18401
|
+
* REQUIRES the field), and a row without it parses as a FAILED read — the red
|
|
18402
|
+
* triangle — not as absence. Removing the field before that viewer ships is an
|
|
18403
|
+
* outage, not a cleanup. Once the viewer takes its hero bytes from `url`,
|
|
18404
|
+
* delete this line and the `withBytes` pass-through in
|
|
18405
|
+
* `analytics-query-facade.ts`; nothing else reads it.
|
|
18406
|
+
*/
|
|
18323
18407
|
var MediaFileSchema = object({
|
|
18324
18408
|
key: string(),
|
|
18325
18409
|
kind: MediaFileKindEnum,
|
|
18326
|
-
base64: string(),
|
|
18327
18410
|
sizeBytes: number(),
|
|
18328
18411
|
timestamp: number()
|
|
18412
|
+
}).extend({
|
|
18413
|
+
/** `/addon/<addonId>/event-media/<encoded stored key>`. Always present. */
|
|
18414
|
+
url: string(),
|
|
18415
|
+
/** @deprecated Transitional — see the schema docblock. Use {@link url}. */
|
|
18416
|
+
base64: string()
|
|
18329
18417
|
});
|
|
18330
18418
|
/**
|
|
18331
18419
|
* One media row WITHOUT its bytes.
|
|
@@ -18337,7 +18425,9 @@ var MediaFileSchema = object({
|
|
|
18337
18425
|
* blocks the whole view.
|
|
18338
18426
|
*
|
|
18339
18427
|
* `sizeBytes` is carried because it is what lets a client decide between the
|
|
18340
|
-
* stored blob and a `?variant=thumb` rendering without fetching either
|
|
18428
|
+
* stored blob and a `?variant=thumb` rendering without fetching either, and
|
|
18429
|
+
* `url` because a client that had to build the plane path itself is a second
|
|
18430
|
+
* copy of a route — the embed, the viewer and the admin UI each grew one.
|
|
18341
18431
|
*/
|
|
18342
18432
|
var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
|
|
18343
18433
|
/**
|
|
@@ -19026,6 +19116,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
19026
19116
|
}), array(MediaFileSchema).readonly()), method(object({
|
|
19027
19117
|
trackId: string(),
|
|
19028
19118
|
deviceId: number()
|
|
19119
|
+
}), array(MediaFileInfoSchema).readonly()), method(object({
|
|
19120
|
+
eventId: string(),
|
|
19121
|
+
deviceId: number()
|
|
19029
19122
|
}), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
|
|
19030
19123
|
kind: "mutation",
|
|
19031
19124
|
auth: "admin"
|
|
@@ -23299,10 +23392,24 @@ var FaceClusterSchema = object({
|
|
|
23299
23392
|
size: number().int(),
|
|
23300
23393
|
cohesion: number()
|
|
23301
23394
|
});
|
|
23395
|
+
/**
|
|
23396
|
+
* One gallery media row: what the crop is, how big it is, and WHERE its bytes
|
|
23397
|
+
* are — never the bytes.
|
|
23398
|
+
*
|
|
23399
|
+
* `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
|
|
23400
|
+
* track/event contract) is still populated because a deployed viewer requires
|
|
23401
|
+
* the field to parse a row at all; this method has no such reader. Its ONE
|
|
23402
|
+
* caller is the admin UI's detail modal, which was building
|
|
23403
|
+
* `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
|
|
23404
|
+
* dialog already rendering its key FRAME from the `event-media` plane.
|
|
23405
|
+
*
|
|
23406
|
+
* `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
|
|
23407
|
+
* media key directly, so this needed no new plane and no new access decision.
|
|
23408
|
+
*/
|
|
23302
23409
|
var MediaFileLiteSchema$1 = object({
|
|
23303
23410
|
key: string(),
|
|
23304
23411
|
kind: string(),
|
|
23305
|
-
|
|
23412
|
+
url: string(),
|
|
23306
23413
|
sizeBytes: number(),
|
|
23307
23414
|
timestamp: number()
|
|
23308
23415
|
});
|
|
@@ -25554,10 +25661,24 @@ var PlateInfoSchema = object({
|
|
|
25554
25661
|
*/
|
|
25555
25662
|
cropUrl: string().optional()
|
|
25556
25663
|
});
|
|
25664
|
+
/**
|
|
25665
|
+
* One gallery media row: what the crop is, how big it is, and WHERE its bytes
|
|
25666
|
+
* are — never the bytes.
|
|
25667
|
+
*
|
|
25668
|
+
* `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
|
|
25669
|
+
* track/event contract) is still populated because a deployed viewer requires
|
|
25670
|
+
* the field to parse a row at all; this method has no such reader. Its ONE
|
|
25671
|
+
* caller is the admin UI's detail modal, which was building
|
|
25672
|
+
* `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
|
|
25673
|
+
* dialog already rendering its key FRAME from the `event-media` plane.
|
|
25674
|
+
*
|
|
25675
|
+
* `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
|
|
25676
|
+
* media key directly, so this needed no new plane and no new access decision.
|
|
25677
|
+
*/
|
|
25557
25678
|
var MediaFileLiteSchema = object({
|
|
25558
25679
|
key: string(),
|
|
25559
25680
|
kind: string(),
|
|
25560
|
-
|
|
25681
|
+
url: string(),
|
|
25561
25682
|
sizeBytes: number(),
|
|
25562
25683
|
timestamp: number()
|
|
25563
25684
|
});
|
|
@@ -31570,6 +31691,12 @@ Object.freeze({
|
|
|
31570
31691
|
addonId: null,
|
|
31571
31692
|
access: "view"
|
|
31572
31693
|
},
|
|
31694
|
+
"pipelineAnalytics.listEventMedia": {
|
|
31695
|
+
capName: "pipeline-analytics",
|
|
31696
|
+
capScope: "device",
|
|
31697
|
+
addonId: null,
|
|
31698
|
+
access: "view"
|
|
31699
|
+
},
|
|
31573
31700
|
"pipelineAnalytics.listGroups": {
|
|
31574
31701
|
capName: "pipeline-analytics",
|
|
31575
31702
|
capScope: "device",
|
|
@@ -35193,6 +35320,11 @@ Object.freeze({
|
|
|
35193
35320
|
form: "array",
|
|
35194
35321
|
optional: false
|
|
35195
35322
|
}],
|
|
35323
|
+
"pipelineAnalytics.listEventMedia": [{
|
|
35324
|
+
name: "deviceId",
|
|
35325
|
+
form: "single",
|
|
35326
|
+
optional: false
|
|
35327
|
+
}],
|
|
35196
35328
|
"pipelineAnalytics.listGroups": [{
|
|
35197
35329
|
name: "deviceIds",
|
|
35198
35330
|
form: "array",
|
package/dist/smtp.addon.mjs
CHANGED
|
@@ -8022,6 +8022,21 @@ var RelocateJobSchema = object({
|
|
|
8022
8022
|
bytesMoved: number().int(),
|
|
8023
8023
|
/** Total files discovered up front; null while (or when) unknown. */
|
|
8024
8024
|
filesTotal: number().int().nullable(),
|
|
8025
|
+
/**
|
|
8026
|
+
* Rows this run CORRECTED while moving them — a durable mutation the move
|
|
8027
|
+
* made that nobody asked for, so it is reported where the operator reads the
|
|
8028
|
+
* job rather than only in a log line.
|
|
8029
|
+
*
|
|
8030
|
+
* A footage segment records its byte count in its own NAME, and the durable
|
|
8031
|
+
* hour row derives its aggregates from those names. A file that does not
|
|
8032
|
+
* match its name therefore makes the ledger's sums — and with them quota and
|
|
8033
|
+
* pressure eviction — wrong by the difference, and only a rename can fix it.
|
|
8034
|
+
* On 2026-08-30 one such row also stalled a 110 749-file drain permanently.
|
|
8035
|
+
*
|
|
8036
|
+
* Absent on lanes where the question has no meaning: a media blob's size is
|
|
8037
|
+
* in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
|
|
8038
|
+
*/
|
|
8039
|
+
rowsReconciled: number().int().nonnegative().optional(),
|
|
8025
8040
|
startedAt: number(),
|
|
8026
8041
|
finishedAt: number().nullable(),
|
|
8027
8042
|
error: string().nullable()
|
|
@@ -8090,14 +8105,42 @@ var RelocateMediaInputSchema = object({
|
|
|
8090
8105
|
/** Omitted = `move`, the pre-existing behaviour. */
|
|
8091
8106
|
mode: MediaRelocateModeSchema.optional()
|
|
8092
8107
|
});
|
|
8093
|
-
/**
|
|
8094
|
-
*
|
|
8095
|
-
*
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8108
|
+
/**
|
|
8109
|
+
* The unstamped population of ONE collection — split, because the gate and the
|
|
8110
|
+
* operator ask two different questions and only one of them has to be cheap.
|
|
8111
|
+
*
|
|
8112
|
+
* `present` is the GATE: "is there at least one row that would be orphaned by a
|
|
8113
|
+
* repoint". It is a single indexed seek to the first matching row, so it stays
|
|
8114
|
+
* answerable on a saturated disk and answers in O(log n) precisely in the state
|
|
8115
|
+
* that matters — after a seal, when the population is empty.
|
|
8116
|
+
*
|
|
8117
|
+
* `rows` is the NUMBER, for the refusal message and the operator's sense of
|
|
8118
|
+
* scale. It is a second, indexed `COUNT(*)`, and `null` means **not
|
|
8119
|
+
* measurable** — never zero. `{ present: true, rows: null }` is a legitimate
|
|
8120
|
+
* and useful answer: "there are some, and this read could not say how many"
|
|
8121
|
+
* still refuses the cutover, which is the whole job.
|
|
8122
|
+
*/
|
|
8123
|
+
var UnstampedRowsSchema = object({
|
|
8124
|
+
present: boolean(),
|
|
8125
|
+
rows: number().int().nonnegative().nullable()
|
|
8100
8126
|
});
|
|
8127
|
+
/**
|
|
8128
|
+
* How many rows still carry NO `locationId` — the population a repoint would
|
|
8129
|
+
* silently re-aim at a disk that does not hold their bytes.
|
|
8130
|
+
*
|
|
8131
|
+
* **`null` = the count could not be taken**, and it is NOT permission to cut
|
|
8132
|
+
* over. The gate opens on a measured absence and on nothing else; an unread
|
|
8133
|
+
* collection and an empty one are different facts, and this repo has already
|
|
8134
|
+
* paid for conflating them (`RelocateResidueSchema`, D295).
|
|
8135
|
+
*/
|
|
8136
|
+
var UnstampedEventMediaCountSchema = object({
|
|
8137
|
+
media: UnstampedRowsSchema,
|
|
8138
|
+
retrainFrames: UnstampedRowsSchema,
|
|
8139
|
+
/** True when EITHER collection holds one. The refusal reads this. */
|
|
8140
|
+
anyPresent: boolean(),
|
|
8141
|
+
/** Sum across both, or `null` when either lane could not be counted. */
|
|
8142
|
+
total: number().int().nonnegative().nullable()
|
|
8143
|
+
}).nullable();
|
|
8101
8144
|
var StorageMigrationMediaMoveInputSchema = RelocateMediaInputSchema.extend({ leaseId: string().min(1) });
|
|
8102
8145
|
/** The independently selectable logical storage classes — every class
|
|
8103
8146
|
* `storage.listLocationDeclarations` reports, so an operator never meets a
|
|
@@ -8208,6 +8251,10 @@ var StorageMigrationMoveProgressSchema = object({
|
|
|
8208
8251
|
/** The archive census — the **M** of "N of M" (D295). `null` = unknowable. */
|
|
8209
8252
|
filesTotal: number().int().nonnegative().nullable(),
|
|
8210
8253
|
bytesMoved: number().int().nonnegative(),
|
|
8254
|
+
/** Rows the mover corrected while moving them — see `RelocateJob`. Absent on
|
|
8255
|
+
* a lane that cannot reconcile. A migration that silently rewrote durable
|
|
8256
|
+
* rows would be the same failure as one that silently skipped them. */
|
|
8257
|
+
rowsReconciled: number().int().nonnegative().optional(),
|
|
8211
8258
|
/** The MOVER's start, not the migration's: a drain restarted after an addon
|
|
8212
8259
|
* crash gets a new mover, and a rate computed from the migration's start
|
|
8213
8260
|
* would silently average in the time nothing was running. */
|
|
@@ -12892,6 +12939,114 @@ method(object({
|
|
|
12892
12939
|
height: number()
|
|
12893
12940
|
}), EmbeddingResultSchema, { auth: "admin" }), method(object({ text: string() }), EmbeddingResultSchema, { auth: "admin" }), method(_void(), EmbeddingInfoSchema, { auth: "admin" });
|
|
12894
12941
|
/**
|
|
12942
|
+
* `failure-contribution` — the capability an addon reports its OWN losses
|
|
12943
|
+
* through, per camera, with the denominator attached. It stores nothing.
|
|
12944
|
+
*
|
|
12945
|
+
* ## The twin of `load-contribution`, and why it is a twin and not a field
|
|
12946
|
+
*
|
|
12947
|
+
* `load-contribution` answers *what did this camera COST*. This answers *what
|
|
12948
|
+
* did this camera LOSE*. The reporting discipline is identical and deliberately
|
|
12949
|
+
* copied: the contributor reports what it already knows, hub-main adds only
|
|
12950
|
+
* `addonId`, nothing needs global knowledge, and there is no central list for
|
|
12951
|
+
* somebody to forget to edit.
|
|
12952
|
+
*
|
|
12953
|
+
* They are not merged, because their invariants are opposites:
|
|
12954
|
+
*
|
|
12955
|
+
* - a `load-contribution` measurement is **absent, never zero** — a zero would
|
|
12956
|
+
* claim a camera cost nothing, which is a measurement nobody made;
|
|
12957
|
+
* - a `failure-contribution` zero is the **most valuable value on the
|
|
12958
|
+
* surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
|
|
12959
|
+
* and it is exactly what an absent entry cannot say.
|
|
12960
|
+
*
|
|
12961
|
+
* Putting a loss counter on a cost entry would also break the reconciliation
|
|
12962
|
+
* that gives `load-contribution` its point: contributions are subtracted from
|
|
12963
|
+
* `metrics.node-processes-snapshot` to find processes nobody claims. A failure
|
|
12964
|
+
* has no process.
|
|
12965
|
+
*
|
|
12966
|
+
* ## Why not a log line, since the counters already exist
|
|
12967
|
+
*
|
|
12968
|
+
* Several of these paths already counted themselves — `CaptureScheduler`'s
|
|
12969
|
+
* per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
|
|
12970
|
+
* ends in a log line, and a log line is the thing the operator asked to stop
|
|
12971
|
+
* needing: *"possiamo armare questi errori intanto? Così al prossimo giro
|
|
12972
|
+
* ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
|
|
12973
|
+
* hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
|
|
12974
|
+
* media blackout were both diagnosed. The counters stay; this is where they can
|
|
12975
|
+
* be READ.
|
|
12976
|
+
*
|
|
12977
|
+
* ## The rate is served with its denominator or not at all
|
|
12978
|
+
*
|
|
12979
|
+
* Every entry carries `attempts` and `succeeded`. A miss count alone is
|
|
12980
|
+
* unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
|
|
12981
|
+
* than yesterday" and was **flat across twelve hours** once divided by the
|
|
12982
|
+
* successes on the same path. A surface that publishes only the numerator
|
|
12983
|
+
* reproduces that mistake on every read.
|
|
12984
|
+
*
|
|
12985
|
+
* ## Shape
|
|
12986
|
+
*
|
|
12987
|
+
* Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
|
|
12988
|
+
* `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
|
|
12989
|
+
* generated hooks, while `addons.listCapabilityProviders` still enumerates it
|
|
12990
|
+
* and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
|
|
12991
|
+
* a forked runner's entries reach hub-main over transport that already exists.
|
|
12992
|
+
* No new UDS message, no second registry (D3). The operator reads the assembled
|
|
12993
|
+
* result through `system.getFailureContributions`.
|
|
12994
|
+
*/
|
|
12995
|
+
var FailureReasonCountSchema = object({
|
|
12996
|
+
/**
|
|
12997
|
+
* Why the attempt did not land, in the contributor's own vocabulary —
|
|
12998
|
+
* `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
|
|
12999
|
+
* strings that already appear in this repo's logs and, where one exists, the
|
|
13000
|
+
* same string the per-track `previewMissReason` records (D276): a second
|
|
13001
|
+
* vocabulary for the same loss would make the row and the counter
|
|
13002
|
+
* un-joinable.
|
|
13003
|
+
*/
|
|
13004
|
+
reason: string(),
|
|
13005
|
+
count: number().int().nonnegative()
|
|
13006
|
+
});
|
|
13007
|
+
var FailureContributionSchema = object({
|
|
13008
|
+
/**
|
|
13009
|
+
* The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
|
|
13010
|
+
* `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
|
|
13011
|
+
* `unit` free: the families are owned by different addons and a shared enum
|
|
13012
|
+
* is a central list that rots invisibly.
|
|
13013
|
+
*/
|
|
13014
|
+
family: string(),
|
|
13015
|
+
/**
|
|
13016
|
+
* The NUMERIC device id — the same value every log line carries as
|
|
13017
|
+
* `tags.deviceId`. Never nullable and never absent: a contributor that
|
|
13018
|
+
* cannot name the camera must not emit the entry, because a fleet total
|
|
13019
|
+
* cannot answer the only question anybody asks of this surface.
|
|
13020
|
+
*/
|
|
13021
|
+
deviceId: number().int().positive(),
|
|
13022
|
+
/**
|
|
13023
|
+
* A second dimension inside the family: the model / step id for an inference
|
|
13024
|
+
* timeout, so "which camera AND which model" is one read. Absent when the
|
|
13025
|
+
* family has a single variant.
|
|
13026
|
+
*/
|
|
13027
|
+
variant: string().optional(),
|
|
13028
|
+
/**
|
|
13029
|
+
* Epoch ms this counter started — the INCARNATION MARKER. A consumer
|
|
13030
|
+
* differencing two reads must drop the interval when it changes, because the
|
|
13031
|
+
* counter restarted from zero in a respawned runner. Same discipline as
|
|
13032
|
+
* `LoadContribution.startedAtMs`.
|
|
13033
|
+
*/
|
|
13034
|
+
sinceMs: number(),
|
|
13035
|
+
/** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
|
|
13036
|
+
atMs: number(),
|
|
13037
|
+
/**
|
|
13038
|
+
* THE DENOMINATOR — every attempt on this path for this camera in the
|
|
13039
|
+
* window. A failure count published without it is the mistake this schema
|
|
13040
|
+
* exists to make impossible.
|
|
13041
|
+
*/
|
|
13042
|
+
attempts: number().int().nonnegative(),
|
|
13043
|
+
/** Attempts that landed. `attempts - succeeded` is the loss. */
|
|
13044
|
+
succeeded: number().int().nonnegative(),
|
|
13045
|
+
/** The loss, partitioned. Sums to `attempts - succeeded`. */
|
|
13046
|
+
reasons: array(FailureReasonCountSchema).readonly()
|
|
13047
|
+
});
|
|
13048
|
+
method(_void(), array(FailureContributionSchema).readonly());
|
|
13049
|
+
/**
|
|
12895
13050
|
* filesystem-browse — per-node capability for browsing the node's local
|
|
12896
13051
|
* filesystem. Reads are unconfined (whole filesystem, from `/` down); WRITES
|
|
12897
13052
|
* are sandboxed to operator-configured allowed roots (D115). Used by the
|
|
@@ -13413,6 +13568,68 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
|
|
|
13413
13568
|
kind: "mutation",
|
|
13414
13569
|
auth: "admin"
|
|
13415
13570
|
});
|
|
13571
|
+
var LoadContributionSchema = object({
|
|
13572
|
+
role: _enum([
|
|
13573
|
+
"decode",
|
|
13574
|
+
"transcode",
|
|
13575
|
+
"recording",
|
|
13576
|
+
"streaming",
|
|
13577
|
+
"detection"
|
|
13578
|
+
]),
|
|
13579
|
+
/**
|
|
13580
|
+
* The NUMERIC device id — the same value every log line carries as
|
|
13581
|
+
* `tags.deviceId`. `null` means this cost genuinely belongs to no single
|
|
13582
|
+
* camera (a shared pool), NOT that the contributor forgot to look it up: a
|
|
13583
|
+
* contributor that cannot name its camera must not emit the entry at all,
|
|
13584
|
+
* because an unnamed per-camera entry is indistinguishable from a shared one
|
|
13585
|
+
* and would quietly turn one camera's cost into everybody's.
|
|
13586
|
+
*/
|
|
13587
|
+
deviceId: number().int().positive().nullable(),
|
|
13588
|
+
attribution: _enum([
|
|
13589
|
+
"measured",
|
|
13590
|
+
"accounted",
|
|
13591
|
+
"unattributable"
|
|
13592
|
+
]),
|
|
13593
|
+
/**
|
|
13594
|
+
* What ONE entry is, in the contributor's own words — `615/high`,
|
|
13595
|
+
* `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
|
|
13596
|
+
* family and inventing a common one would lose the only information that
|
|
13597
|
+
* makes two entries for the same camera distinguishable.
|
|
13598
|
+
*/
|
|
13599
|
+
unit: string(),
|
|
13600
|
+
/**
|
|
13601
|
+
* The OS process this cost lives in, when there is one. Present so a
|
|
13602
|
+
* consumer can (a) tell two generations of the same unit apart across a
|
|
13603
|
+
* restart, and (b) subtract claimed processes from the node's process
|
|
13604
|
+
* snapshot to see what NOBODY claimed. Absent for an entry that owns no
|
|
13605
|
+
* process of its own.
|
|
13606
|
+
*/
|
|
13607
|
+
pid: number().int().positive().optional(),
|
|
13608
|
+
/**
|
|
13609
|
+
* When this generation started. The pid's incarnation marker: a consumer
|
|
13610
|
+
* differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
|
|
13611
|
+
* window when this changes, because the counter restarted from zero in a new
|
|
13612
|
+
* process.
|
|
13613
|
+
*/
|
|
13614
|
+
startedAtMs: number().optional(),
|
|
13615
|
+
/**
|
|
13616
|
+
* CUMULATIVE CPU seconds this unit has consumed since it started — user +
|
|
13617
|
+
* system, read from the child's own `/proc/<pid>/stat` at the moment the
|
|
13618
|
+
* contribution is asked for.
|
|
13619
|
+
*
|
|
13620
|
+
* Cumulative and not a rate on purpose: a rate needs a window, a window
|
|
13621
|
+
* needs a sampler, and a new per-node sampler is the defect half of
|
|
13622
|
+
* `docs/architecture/load-ledger.md` documents. A counter can be differenced
|
|
13623
|
+
* by whoever already keeps a history; a rate cannot be un-averaged.
|
|
13624
|
+
*
|
|
13625
|
+
* Absent — never zero — on a node with no `/proc`, on a read failure, and on
|
|
13626
|
+
* an entry with no process.
|
|
13627
|
+
*/
|
|
13628
|
+
cpuSeconds: number().optional(),
|
|
13629
|
+
/** Resident bytes of this unit's process, same source and same rules. */
|
|
13630
|
+
rssBytes: number().optional()
|
|
13631
|
+
});
|
|
13632
|
+
method(_void(), array(LoadContributionSchema).readonly());
|
|
13416
13633
|
/**
|
|
13417
13634
|
* `log-channels` — the capability an addon DECLARES its diagnostic channels
|
|
13418
13635
|
* through. It stores nothing.
|
|
@@ -13489,176 +13706,6 @@ method(LogEntrySchema, _void(), { kind: "mutation" }), method(object({
|
|
|
13489
13706
|
tags: record(string(), string()).optional()
|
|
13490
13707
|
}), array(LogEntrySchema).readonly());
|
|
13491
13708
|
/**
|
|
13492
|
-
* `failure-contribution` — the capability an addon reports its OWN losses
|
|
13493
|
-
* through, per camera, with the denominator attached. It stores nothing.
|
|
13494
|
-
*
|
|
13495
|
-
* ## The twin of `load-contribution`, and why it is a twin and not a field
|
|
13496
|
-
*
|
|
13497
|
-
* `load-contribution` answers *what did this camera COST*. This answers *what
|
|
13498
|
-
* did this camera LOSE*. The reporting discipline is identical and deliberately
|
|
13499
|
-
* copied: the contributor reports what it already knows, hub-main adds only
|
|
13500
|
-
* `addonId`, nothing needs global knowledge, and there is no central list for
|
|
13501
|
-
* somebody to forget to edit.
|
|
13502
|
-
*
|
|
13503
|
-
* They are not merged, because their invariants are opposites:
|
|
13504
|
-
*
|
|
13505
|
-
* - a `load-contribution` measurement is **absent, never zero** — a zero would
|
|
13506
|
-
* claim a camera cost nothing, which is a measurement nobody made;
|
|
13507
|
-
* - a `failure-contribution` zero is the **most valuable value on the
|
|
13508
|
-
* surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
|
|
13509
|
-
* and it is exactly what an absent entry cannot say.
|
|
13510
|
-
*
|
|
13511
|
-
* Putting a loss counter on a cost entry would also break the reconciliation
|
|
13512
|
-
* that gives `load-contribution` its point: contributions are subtracted from
|
|
13513
|
-
* `metrics.node-processes-snapshot` to find processes nobody claims. A failure
|
|
13514
|
-
* has no process.
|
|
13515
|
-
*
|
|
13516
|
-
* ## Why not a log line, since the counters already exist
|
|
13517
|
-
*
|
|
13518
|
-
* Several of these paths already counted themselves — `CaptureScheduler`'s
|
|
13519
|
-
* per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
|
|
13520
|
-
* ends in a log line, and a log line is the thing the operator asked to stop
|
|
13521
|
-
* needing: *"possiamo armare questi errori intanto? Così al prossimo giro
|
|
13522
|
-
* ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
|
|
13523
|
-
* hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
|
|
13524
|
-
* media blackout were both diagnosed. The counters stay; this is where they can
|
|
13525
|
-
* be READ.
|
|
13526
|
-
*
|
|
13527
|
-
* ## The rate is served with its denominator or not at all
|
|
13528
|
-
*
|
|
13529
|
-
* Every entry carries `attempts` and `succeeded`. A miss count alone is
|
|
13530
|
-
* unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
|
|
13531
|
-
* than yesterday" and was **flat across twelve hours** once divided by the
|
|
13532
|
-
* successes on the same path. A surface that publishes only the numerator
|
|
13533
|
-
* reproduces that mistake on every read.
|
|
13534
|
-
*
|
|
13535
|
-
* ## Shape
|
|
13536
|
-
*
|
|
13537
|
-
* Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
|
|
13538
|
-
* `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
|
|
13539
|
-
* generated hooks, while `addons.listCapabilityProviders` still enumerates it
|
|
13540
|
-
* and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
|
|
13541
|
-
* a forked runner's entries reach hub-main over transport that already exists.
|
|
13542
|
-
* No new UDS message, no second registry (D3). The operator reads the assembled
|
|
13543
|
-
* result through `system.getFailureContributions`.
|
|
13544
|
-
*/
|
|
13545
|
-
var FailureReasonCountSchema = object({
|
|
13546
|
-
/**
|
|
13547
|
-
* Why the attempt did not land, in the contributor's own vocabulary —
|
|
13548
|
-
* `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
|
|
13549
|
-
* strings that already appear in this repo's logs and, where one exists, the
|
|
13550
|
-
* same string the per-track `previewMissReason` records (D276): a second
|
|
13551
|
-
* vocabulary for the same loss would make the row and the counter
|
|
13552
|
-
* un-joinable.
|
|
13553
|
-
*/
|
|
13554
|
-
reason: string(),
|
|
13555
|
-
count: number().int().nonnegative()
|
|
13556
|
-
});
|
|
13557
|
-
var FailureContributionSchema = object({
|
|
13558
|
-
/**
|
|
13559
|
-
* The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
|
|
13560
|
-
* `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
|
|
13561
|
-
* `unit` free: the families are owned by different addons and a shared enum
|
|
13562
|
-
* is a central list that rots invisibly.
|
|
13563
|
-
*/
|
|
13564
|
-
family: string(),
|
|
13565
|
-
/**
|
|
13566
|
-
* The NUMERIC device id — the same value every log line carries as
|
|
13567
|
-
* `tags.deviceId`. Never nullable and never absent: a contributor that
|
|
13568
|
-
* cannot name the camera must not emit the entry, because a fleet total
|
|
13569
|
-
* cannot answer the only question anybody asks of this surface.
|
|
13570
|
-
*/
|
|
13571
|
-
deviceId: number().int().positive(),
|
|
13572
|
-
/**
|
|
13573
|
-
* A second dimension inside the family: the model / step id for an inference
|
|
13574
|
-
* timeout, so "which camera AND which model" is one read. Absent when the
|
|
13575
|
-
* family has a single variant.
|
|
13576
|
-
*/
|
|
13577
|
-
variant: string().optional(),
|
|
13578
|
-
/**
|
|
13579
|
-
* Epoch ms this counter started — the INCARNATION MARKER. A consumer
|
|
13580
|
-
* differencing two reads must drop the interval when it changes, because the
|
|
13581
|
-
* counter restarted from zero in a respawned runner. Same discipline as
|
|
13582
|
-
* `LoadContribution.startedAtMs`.
|
|
13583
|
-
*/
|
|
13584
|
-
sinceMs: number(),
|
|
13585
|
-
/** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
|
|
13586
|
-
atMs: number(),
|
|
13587
|
-
/**
|
|
13588
|
-
* THE DENOMINATOR — every attempt on this path for this camera in the
|
|
13589
|
-
* window. A failure count published without it is the mistake this schema
|
|
13590
|
-
* exists to make impossible.
|
|
13591
|
-
*/
|
|
13592
|
-
attempts: number().int().nonnegative(),
|
|
13593
|
-
/** Attempts that landed. `attempts - succeeded` is the loss. */
|
|
13594
|
-
succeeded: number().int().nonnegative(),
|
|
13595
|
-
/** The loss, partitioned. Sums to `attempts - succeeded`. */
|
|
13596
|
-
reasons: array(FailureReasonCountSchema).readonly()
|
|
13597
|
-
});
|
|
13598
|
-
method(_void(), array(FailureContributionSchema).readonly());
|
|
13599
|
-
var LoadContributionSchema = object({
|
|
13600
|
-
role: _enum([
|
|
13601
|
-
"decode",
|
|
13602
|
-
"transcode",
|
|
13603
|
-
"recording",
|
|
13604
|
-
"streaming",
|
|
13605
|
-
"detection"
|
|
13606
|
-
]),
|
|
13607
|
-
/**
|
|
13608
|
-
* The NUMERIC device id — the same value every log line carries as
|
|
13609
|
-
* `tags.deviceId`. `null` means this cost genuinely belongs to no single
|
|
13610
|
-
* camera (a shared pool), NOT that the contributor forgot to look it up: a
|
|
13611
|
-
* contributor that cannot name its camera must not emit the entry at all,
|
|
13612
|
-
* because an unnamed per-camera entry is indistinguishable from a shared one
|
|
13613
|
-
* and would quietly turn one camera's cost into everybody's.
|
|
13614
|
-
*/
|
|
13615
|
-
deviceId: number().int().positive().nullable(),
|
|
13616
|
-
attribution: _enum([
|
|
13617
|
-
"measured",
|
|
13618
|
-
"accounted",
|
|
13619
|
-
"unattributable"
|
|
13620
|
-
]),
|
|
13621
|
-
/**
|
|
13622
|
-
* What ONE entry is, in the contributor's own words — `615/high`,
|
|
13623
|
-
* `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
|
|
13624
|
-
* family and inventing a common one would lose the only information that
|
|
13625
|
-
* makes two entries for the same camera distinguishable.
|
|
13626
|
-
*/
|
|
13627
|
-
unit: string(),
|
|
13628
|
-
/**
|
|
13629
|
-
* The OS process this cost lives in, when there is one. Present so a
|
|
13630
|
-
* consumer can (a) tell two generations of the same unit apart across a
|
|
13631
|
-
* restart, and (b) subtract claimed processes from the node's process
|
|
13632
|
-
* snapshot to see what NOBODY claimed. Absent for an entry that owns no
|
|
13633
|
-
* process of its own.
|
|
13634
|
-
*/
|
|
13635
|
-
pid: number().int().positive().optional(),
|
|
13636
|
-
/**
|
|
13637
|
-
* When this generation started. The pid's incarnation marker: a consumer
|
|
13638
|
-
* differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
|
|
13639
|
-
* window when this changes, because the counter restarted from zero in a new
|
|
13640
|
-
* process.
|
|
13641
|
-
*/
|
|
13642
|
-
startedAtMs: number().optional(),
|
|
13643
|
-
/**
|
|
13644
|
-
* CUMULATIVE CPU seconds this unit has consumed since it started — user +
|
|
13645
|
-
* system, read from the child's own `/proc/<pid>/stat` at the moment the
|
|
13646
|
-
* contribution is asked for.
|
|
13647
|
-
*
|
|
13648
|
-
* Cumulative and not a rate on purpose: a rate needs a window, a window
|
|
13649
|
-
* needs a sampler, and a new per-node sampler is the defect half of
|
|
13650
|
-
* `docs/architecture/load-ledger.md` documents. A counter can be differenced
|
|
13651
|
-
* by whoever already keeps a history; a rate cannot be un-averaged.
|
|
13652
|
-
*
|
|
13653
|
-
* Absent — never zero — on a node with no `/proc`, on a read failure, and on
|
|
13654
|
-
* an entry with no process.
|
|
13655
|
-
*/
|
|
13656
|
-
cpuSeconds: number().optional(),
|
|
13657
|
-
/** Resident bytes of this unit's process, same source and same rules. */
|
|
13658
|
-
rssBytes: number().optional()
|
|
13659
|
-
});
|
|
13660
|
-
method(_void(), array(LoadContributionSchema).readonly());
|
|
13661
|
-
/**
|
|
13662
13709
|
* `login-method` — collection cap through which auth addons contribute
|
|
13663
13710
|
* their pre-auth login surfaces to the login page. This is the SINGLE,
|
|
13664
13711
|
* generic mechanism that supersedes the dead `auth.listProviders` reader:
|
|
@@ -18318,12 +18365,53 @@ var MediaFileKindEnum = _enum([
|
|
|
18318
18365
|
"keyFrameSmall",
|
|
18319
18366
|
"thumbnailSmall"
|
|
18320
18367
|
]);
|
|
18368
|
+
/**
|
|
18369
|
+
* One media row ON THE WIRE: what it is, how big it is, and WHERE ITS BYTES
|
|
18370
|
+
* ARE — never the bytes themselves.
|
|
18371
|
+
*
|
|
18372
|
+
* ## Why `url` and not `base64`
|
|
18373
|
+
*
|
|
18374
|
+
* Measured on the live hub 2026-08-30: `getTrackMedia {trackId, deviceId}`
|
|
18375
|
+
* with no `kinds` returned 6 rows / **3 597 219 B**, of which `keyFrame` alone
|
|
18376
|
+
* was **2 824 077 B** — one full-resolution frame, base64, so +33 % on the
|
|
18377
|
+
* wire. Forty events is ~144 MB. Every byte of it was read off disk,
|
|
18378
|
+
* base64-encoded, held whole in a unary tRPC envelope, and materialised in
|
|
18379
|
+
* hub-main's heap on the way past — for an `<img>` that would have cached it.
|
|
18380
|
+
*
|
|
18381
|
+
* `url` points at the `event-media` data plane
|
|
18382
|
+
* (`/addon/<addonId>/event-media/<storedKey>`), which serves the same blob
|
|
18383
|
+
* with an ETag and `Cache-Control: immutable`, honours conditional GETs, can
|
|
18384
|
+
* render a `?variant=thumb`, and streams. The hub gate in front of it requires
|
|
18385
|
+
* a bearer or the session cookie (`access: 'authenticated'`), so the bytes are
|
|
18386
|
+
* no less protected than they were inside a `view`-level cap response — see
|
|
18387
|
+
* `data-plane-access.ts` for the rule and the one gap it does not close
|
|
18388
|
+
* (per-device scoping).
|
|
18389
|
+
*
|
|
18390
|
+
* The URL is built from the row's **stored** key, which is not always its
|
|
18391
|
+
* published `kind`: a track's face/plate crop is stored as `crop` under
|
|
18392
|
+
* `('face'|'plate', '<prefix>-<trackId>')` and published as
|
|
18393
|
+
* `faceCrop`/`plateCrop`. `MediaStore.getByKey` knows only the stored key.
|
|
18394
|
+
*
|
|
18395
|
+
* ## `base64` is TRANSITIONAL and is going away
|
|
18396
|
+
*
|
|
18397
|
+
* It is still populated for one reason: the deployed viewer's track-detail
|
|
18398
|
+
* HERO tile reads it (`use-track-media-entry.ts` → `parseMediaFiles`, which
|
|
18399
|
+
* REQUIRES the field), and a row without it parses as a FAILED read — the red
|
|
18400
|
+
* triangle — not as absence. Removing the field before that viewer ships is an
|
|
18401
|
+
* outage, not a cleanup. Once the viewer takes its hero bytes from `url`,
|
|
18402
|
+
* delete this line and the `withBytes` pass-through in
|
|
18403
|
+
* `analytics-query-facade.ts`; nothing else reads it.
|
|
18404
|
+
*/
|
|
18321
18405
|
var MediaFileSchema = object({
|
|
18322
18406
|
key: string(),
|
|
18323
18407
|
kind: MediaFileKindEnum,
|
|
18324
|
-
base64: string(),
|
|
18325
18408
|
sizeBytes: number(),
|
|
18326
18409
|
timestamp: number()
|
|
18410
|
+
}).extend({
|
|
18411
|
+
/** `/addon/<addonId>/event-media/<encoded stored key>`. Always present. */
|
|
18412
|
+
url: string(),
|
|
18413
|
+
/** @deprecated Transitional — see the schema docblock. Use {@link url}. */
|
|
18414
|
+
base64: string()
|
|
18327
18415
|
});
|
|
18328
18416
|
/**
|
|
18329
18417
|
* One media row WITHOUT its bytes.
|
|
@@ -18335,7 +18423,9 @@ var MediaFileSchema = object({
|
|
|
18335
18423
|
* blocks the whole view.
|
|
18336
18424
|
*
|
|
18337
18425
|
* `sizeBytes` is carried because it is what lets a client decide between the
|
|
18338
|
-
* stored blob and a `?variant=thumb` rendering without fetching either
|
|
18426
|
+
* stored blob and a `?variant=thumb` rendering without fetching either, and
|
|
18427
|
+
* `url` because a client that had to build the plane path itself is a second
|
|
18428
|
+
* copy of a route — the embed, the viewer and the admin UI each grew one.
|
|
18339
18429
|
*/
|
|
18340
18430
|
var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
|
|
18341
18431
|
/**
|
|
@@ -19024,6 +19114,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
19024
19114
|
}), array(MediaFileSchema).readonly()), method(object({
|
|
19025
19115
|
trackId: string(),
|
|
19026
19116
|
deviceId: number()
|
|
19117
|
+
}), array(MediaFileInfoSchema).readonly()), method(object({
|
|
19118
|
+
eventId: string(),
|
|
19119
|
+
deviceId: number()
|
|
19027
19120
|
}), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
|
|
19028
19121
|
kind: "mutation",
|
|
19029
19122
|
auth: "admin"
|
|
@@ -23297,10 +23390,24 @@ var FaceClusterSchema = object({
|
|
|
23297
23390
|
size: number().int(),
|
|
23298
23391
|
cohesion: number()
|
|
23299
23392
|
});
|
|
23393
|
+
/**
|
|
23394
|
+
* One gallery media row: what the crop is, how big it is, and WHERE its bytes
|
|
23395
|
+
* are — never the bytes.
|
|
23396
|
+
*
|
|
23397
|
+
* `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
|
|
23398
|
+
* track/event contract) is still populated because a deployed viewer requires
|
|
23399
|
+
* the field to parse a row at all; this method has no such reader. Its ONE
|
|
23400
|
+
* caller is the admin UI's detail modal, which was building
|
|
23401
|
+
* `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
|
|
23402
|
+
* dialog already rendering its key FRAME from the `event-media` plane.
|
|
23403
|
+
*
|
|
23404
|
+
* `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
|
|
23405
|
+
* media key directly, so this needed no new plane and no new access decision.
|
|
23406
|
+
*/
|
|
23300
23407
|
var MediaFileLiteSchema$1 = object({
|
|
23301
23408
|
key: string(),
|
|
23302
23409
|
kind: string(),
|
|
23303
|
-
|
|
23410
|
+
url: string(),
|
|
23304
23411
|
sizeBytes: number(),
|
|
23305
23412
|
timestamp: number()
|
|
23306
23413
|
});
|
|
@@ -25552,10 +25659,24 @@ var PlateInfoSchema = object({
|
|
|
25552
25659
|
*/
|
|
25553
25660
|
cropUrl: string().optional()
|
|
25554
25661
|
});
|
|
25662
|
+
/**
|
|
25663
|
+
* One gallery media row: what the crop is, how big it is, and WHERE its bytes
|
|
25664
|
+
* are — never the bytes.
|
|
25665
|
+
*
|
|
25666
|
+
* `base64` was deleted here rather than deprecated. `MediaFile.base64` (the
|
|
25667
|
+
* track/event contract) is still populated because a deployed viewer requires
|
|
25668
|
+
* the field to parse a row at all; this method has no such reader. Its ONE
|
|
25669
|
+
* caller is the admin UI's detail modal, which was building
|
|
25670
|
+
* `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
|
|
25671
|
+
* dialog already rendering its key FRAME from the `event-media` plane.
|
|
25672
|
+
*
|
|
25673
|
+
* `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
|
|
25674
|
+
* media key directly, so this needed no new plane and no new access decision.
|
|
25675
|
+
*/
|
|
25555
25676
|
var MediaFileLiteSchema = object({
|
|
25556
25677
|
key: string(),
|
|
25557
25678
|
kind: string(),
|
|
25558
|
-
|
|
25679
|
+
url: string(),
|
|
25559
25680
|
sizeBytes: number(),
|
|
25560
25681
|
timestamp: number()
|
|
25561
25682
|
});
|
|
@@ -31568,6 +31689,12 @@ Object.freeze({
|
|
|
31568
31689
|
addonId: null,
|
|
31569
31690
|
access: "view"
|
|
31570
31691
|
},
|
|
31692
|
+
"pipelineAnalytics.listEventMedia": {
|
|
31693
|
+
capName: "pipeline-analytics",
|
|
31694
|
+
capScope: "device",
|
|
31695
|
+
addonId: null,
|
|
31696
|
+
access: "view"
|
|
31697
|
+
},
|
|
31571
31698
|
"pipelineAnalytics.listGroups": {
|
|
31572
31699
|
capName: "pipeline-analytics",
|
|
31573
31700
|
capScope: "device",
|
|
@@ -35191,6 +35318,11 @@ Object.freeze({
|
|
|
35191
35318
|
form: "array",
|
|
35192
35319
|
optional: false
|
|
35193
35320
|
}],
|
|
35321
|
+
"pipelineAnalytics.listEventMedia": [{
|
|
35322
|
+
name: "deviceId",
|
|
35323
|
+
form: "single",
|
|
35324
|
+
optional: false
|
|
35325
|
+
}],
|
|
35194
35326
|
"pipelineAnalytics.listGroups": [{
|
|
35195
35327
|
name: "deviceIds",
|
|
35196
35328
|
form: "array",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-smtp-nodemailer",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.46",
|
|
4
4
|
"description": "SMTP email provider addon for CamStack — wraps `nodemailer` and registers a `smtp-provider` cap collection entry. Used by magic-link login + notifier addons.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|