camstack 1.2.66 → 1.2.68

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.
@@ -23633,9 +23633,9 @@ var require_zod = __commonJS({
23633
23633
  }
23634
23634
  });
23635
23635
 
23636
- // ../system/dist/dist-CcvXUhHK.js
23637
- var require_dist_CcvXUhHK = __commonJS({
23638
- "../system/dist/dist-CcvXUhHK.js"(exports) {
23636
+ // ../system/dist/dist-CCIBlacf.js
23637
+ var require_dist_CCIBlacf = __commonJS({
23638
+ "../system/dist/dist-CCIBlacf.js"(exports) {
23639
23639
  "use strict";
23640
23640
  var zod = require_zod();
23641
23641
  var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
@@ -31940,6 +31940,80 @@ var require_dist_CcvXUhHK = __commonJS({
31940
31940
  getInfo: method(zod.z.void(), EmbeddingInfoSchema, { auth: "admin" })
31941
31941
  }
31942
31942
  };
31943
+ var FailureReasonCountSchema = zod.z.object({
31944
+ /**
31945
+ * Why the attempt did not land, in the contributor's own vocabulary —
31946
+ * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
31947
+ * strings that already appear in this repo's logs and, where one exists, the
31948
+ * same string the per-track `previewMissReason` records (D276): a second
31949
+ * vocabulary for the same loss would make the row and the counter
31950
+ * un-joinable.
31951
+ */
31952
+ reason: zod.z.string(),
31953
+ count: zod.z.number().int().nonnegative()
31954
+ });
31955
+ var FailureContributionSchema = zod.z.object({
31956
+ /**
31957
+ * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
31958
+ * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
31959
+ * `unit` free: the families are owned by different addons and a shared enum
31960
+ * is a central list that rots invisibly.
31961
+ */
31962
+ family: zod.z.string(),
31963
+ /**
31964
+ * The NUMERIC device id — the same value every log line carries as
31965
+ * `tags.deviceId`. Never nullable and never absent: a contributor that
31966
+ * cannot name the camera must not emit the entry, because a fleet total
31967
+ * cannot answer the only question anybody asks of this surface.
31968
+ */
31969
+ deviceId: zod.z.number().int().positive(),
31970
+ /**
31971
+ * A second dimension inside the family: the model / step id for an inference
31972
+ * timeout, so "which camera AND which model" is one read. Absent when the
31973
+ * family has a single variant.
31974
+ */
31975
+ variant: zod.z.string().optional(),
31976
+ /**
31977
+ * Epoch ms this counter started — the INCARNATION MARKER. A consumer
31978
+ * differencing two reads must drop the interval when it changes, because the
31979
+ * counter restarted from zero in a respawned runner. Same discipline as
31980
+ * `LoadContribution.startedAtMs`.
31981
+ */
31982
+ sinceMs: zod.z.number(),
31983
+ /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
31984
+ atMs: zod.z.number(),
31985
+ /**
31986
+ * THE DENOMINATOR — every attempt on this path for this camera in the
31987
+ * window. A failure count published without it is the mistake this schema
31988
+ * exists to make impossible.
31989
+ */
31990
+ attempts: zod.z.number().int().nonnegative(),
31991
+ /** Attempts that landed. `attempts - succeeded` is the loss. */
31992
+ succeeded: zod.z.number().int().nonnegative(),
31993
+ /** The loss, partitioned. Sums to `attempts - succeeded`. */
31994
+ reasons: zod.z.array(FailureReasonCountSchema).readonly()
31995
+ });
31996
+ var failureContributionCapability = {
31997
+ name: "failure-contribution",
31998
+ scope: "system",
31999
+ mode: "collection",
32000
+ internal: true,
32001
+ methods: {
32002
+ /**
32003
+ * This addon's per-camera failure counters, read live from bounded in-RAM
32004
+ * state it already keeps. Inert: no persistence, no sampling, no timer.
32005
+ *
32006
+ * READING NEVER RESETS. The counters are CUMULATIVE since `sinceMs`, and a
32007
+ * consumer that wants a rate differences two reads. A draining read would
32008
+ * make two operators with the page open each destroy half of the other's
32009
+ * numbers, and `load-contribution` already settled the same question the
32010
+ * same way for `cpuSeconds`.
32011
+ */
32012
+ list: method(zod.z.void(), zod.z.array(FailureContributionSchema).readonly())
32013
+ },
32014
+ /** In-process only — enumerated through `addons.listCapabilityProviders`. */
32015
+ mount: { kind: "skip" }
32016
+ };
31943
32017
  var DirEntrySchema = zod.z.object({
31944
32018
  name: zod.z.string(),
31945
32019
  path: zod.z.string()
@@ -32477,145 +32551,6 @@ var require_dist_CcvXUhHK = __commonJS({
32477
32551
  })
32478
32552
  }
32479
32553
  };
32480
- var LogChannelApplyResultSchema = zod.z.object({
32481
- /** How many declared channels are armed in this process after the call. */
32482
- armed: zod.z.number().int().min(0),
32483
- /**
32484
- * Names the document armed that this process does not declare. Reported
32485
- * rather than swallowed: a name here is either a typo or an addon that has
32486
- * not booted, and both deserve a line instead of silence.
32487
- */
32488
- unknown: zod.z.array(zod.z.string()).readonly()
32489
- });
32490
- var logChannelsCapability = {
32491
- name: "log-channels",
32492
- scope: "system",
32493
- mode: "collection",
32494
- internal: true,
32495
- methods: {
32496
- /** The channels this addon declares. Inert: no value, no state. */
32497
- list: method(zod.z.void(), zod.z.array(LogChannelDescriptorSchema).readonly()),
32498
- /**
32499
- * Refresh this process's mirror from the document's FULL set of armed
32500
- * windows.
32501
- *
32502
- * Full and not incremental on purpose: the document is the authority, so a
32503
- * channel it does not name is disarmed here. An incremental apply would
32504
- * let a disarm get lost in transit and leave a channel running that
32505
- * nobody can see is running.
32506
- */
32507
- apply: method(zod.z.object({ windows: zod.z.array(LogChannelWindowSchema).readonly() }), LogChannelApplyResultSchema, { kind: "mutation" })
32508
- },
32509
- /** In-process only — enumerated through `addons.listCapabilityProviders`. */
32510
- mount: { kind: "skip" }
32511
- };
32512
- var LogLevelSchema = zod.z.enum([
32513
- "debug",
32514
- "info",
32515
- "warn",
32516
- "error"
32517
- ]);
32518
- var LogEntrySchema = zod.z.object({
32519
- timestamp: zod.z.date(),
32520
- level: LogLevelSchema,
32521
- scope: zod.z.array(zod.z.string()),
32522
- message: zod.z.string(),
32523
- meta: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
32524
- tags: zod.z.record(zod.z.string(), zod.z.string()).optional()
32525
- });
32526
- var logDestinationCapability = {
32527
- name: "log-destination",
32528
- scope: "system",
32529
- mode: "collection",
32530
- internal: true,
32531
- methods: {
32532
- write: method(LogEntrySchema, zod.z.void(), { kind: "mutation" }),
32533
- query: method(zod.z.object({
32534
- scope: zod.z.array(zod.z.string()).optional(),
32535
- level: LogLevelSchema.optional(),
32536
- since: zod.z.date().optional(),
32537
- until: zod.z.date().optional(),
32538
- limit: zod.z.number().optional(),
32539
- tags: zod.z.record(zod.z.string(), zod.z.string()).optional()
32540
- }), zod.z.array(LogEntrySchema).readonly())
32541
- },
32542
- /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
32543
- mount: { kind: "skip" }
32544
- };
32545
- var FailureReasonCountSchema = zod.z.object({
32546
- /**
32547
- * Why the attempt did not land, in the contributor's own vocabulary —
32548
- * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
32549
- * strings that already appear in this repo's logs and, where one exists, the
32550
- * same string the per-track `previewMissReason` records (D276): a second
32551
- * vocabulary for the same loss would make the row and the counter
32552
- * un-joinable.
32553
- */
32554
- reason: zod.z.string(),
32555
- count: zod.z.number().int().nonnegative()
32556
- });
32557
- var FailureContributionSchema = zod.z.object({
32558
- /**
32559
- * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
32560
- * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
32561
- * `unit` free: the families are owned by different addons and a shared enum
32562
- * is a central list that rots invisibly.
32563
- */
32564
- family: zod.z.string(),
32565
- /**
32566
- * The NUMERIC device id — the same value every log line carries as
32567
- * `tags.deviceId`. Never nullable and never absent: a contributor that
32568
- * cannot name the camera must not emit the entry, because a fleet total
32569
- * cannot answer the only question anybody asks of this surface.
32570
- */
32571
- deviceId: zod.z.number().int().positive(),
32572
- /**
32573
- * A second dimension inside the family: the model / step id for an inference
32574
- * timeout, so "which camera AND which model" is one read. Absent when the
32575
- * family has a single variant.
32576
- */
32577
- variant: zod.z.string().optional(),
32578
- /**
32579
- * Epoch ms this counter started — the INCARNATION MARKER. A consumer
32580
- * differencing two reads must drop the interval when it changes, because the
32581
- * counter restarted from zero in a respawned runner. Same discipline as
32582
- * `LoadContribution.startedAtMs`.
32583
- */
32584
- sinceMs: zod.z.number(),
32585
- /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
32586
- atMs: zod.z.number(),
32587
- /**
32588
- * THE DENOMINATOR — every attempt on this path for this camera in the
32589
- * window. A failure count published without it is the mistake this schema
32590
- * exists to make impossible.
32591
- */
32592
- attempts: zod.z.number().int().nonnegative(),
32593
- /** Attempts that landed. `attempts - succeeded` is the loss. */
32594
- succeeded: zod.z.number().int().nonnegative(),
32595
- /** The loss, partitioned. Sums to `attempts - succeeded`. */
32596
- reasons: zod.z.array(FailureReasonCountSchema).readonly()
32597
- });
32598
- var failureContributionCapability = {
32599
- name: "failure-contribution",
32600
- scope: "system",
32601
- mode: "collection",
32602
- internal: true,
32603
- methods: {
32604
- /**
32605
- * This addon's per-camera failure counters, read live from bounded in-RAM
32606
- * state it already keeps. Inert: no persistence, no sampling, no timer.
32607
- *
32608
- * READING NEVER RESETS. The counters are CUMULATIVE since `sinceMs`, and a
32609
- * consumer that wants a rate differences two reads. A draining read would
32610
- * make two operators with the page open each destroy half of the other's
32611
- * numbers, and `load-contribution` already settled the same question the
32612
- * same way for `cpuSeconds`.
32613
- */
32614
- list: method(zod.z.void(), zod.z.array(FailureContributionSchema).readonly())
32615
- },
32616
- /** In-process only — enumerated through `addons.listCapabilityProviders`. */
32617
- mount: { kind: "skip" }
32618
- };
32619
32554
  var LoadContributionSchema = zod.z.object({
32620
32555
  role: zod.z.enum([
32621
32556
  "decode",
@@ -32693,6 +32628,71 @@ var require_dist_CcvXUhHK = __commonJS({
32693
32628
  /** In-process only — enumerated through `addons.listCapabilityProviders`. */
32694
32629
  mount: { kind: "skip" }
32695
32630
  };
32631
+ var LogChannelApplyResultSchema = zod.z.object({
32632
+ /** How many declared channels are armed in this process after the call. */
32633
+ armed: zod.z.number().int().min(0),
32634
+ /**
32635
+ * Names the document armed that this process does not declare. Reported
32636
+ * rather than swallowed: a name here is either a typo or an addon that has
32637
+ * not booted, and both deserve a line instead of silence.
32638
+ */
32639
+ unknown: zod.z.array(zod.z.string()).readonly()
32640
+ });
32641
+ var logChannelsCapability = {
32642
+ name: "log-channels",
32643
+ scope: "system",
32644
+ mode: "collection",
32645
+ internal: true,
32646
+ methods: {
32647
+ /** The channels this addon declares. Inert: no value, no state. */
32648
+ list: method(zod.z.void(), zod.z.array(LogChannelDescriptorSchema).readonly()),
32649
+ /**
32650
+ * Refresh this process's mirror from the document's FULL set of armed
32651
+ * windows.
32652
+ *
32653
+ * Full and not incremental on purpose: the document is the authority, so a
32654
+ * channel it does not name is disarmed here. An incremental apply would
32655
+ * let a disarm get lost in transit and leave a channel running that
32656
+ * nobody can see is running.
32657
+ */
32658
+ apply: method(zod.z.object({ windows: zod.z.array(LogChannelWindowSchema).readonly() }), LogChannelApplyResultSchema, { kind: "mutation" })
32659
+ },
32660
+ /** In-process only — enumerated through `addons.listCapabilityProviders`. */
32661
+ mount: { kind: "skip" }
32662
+ };
32663
+ var LogLevelSchema = zod.z.enum([
32664
+ "debug",
32665
+ "info",
32666
+ "warn",
32667
+ "error"
32668
+ ]);
32669
+ var LogEntrySchema = zod.z.object({
32670
+ timestamp: zod.z.date(),
32671
+ level: LogLevelSchema,
32672
+ scope: zod.z.array(zod.z.string()),
32673
+ message: zod.z.string(),
32674
+ meta: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
32675
+ tags: zod.z.record(zod.z.string(), zod.z.string()).optional()
32676
+ });
32677
+ var logDestinationCapability = {
32678
+ name: "log-destination",
32679
+ scope: "system",
32680
+ mode: "collection",
32681
+ internal: true,
32682
+ methods: {
32683
+ write: method(LogEntrySchema, zod.z.void(), { kind: "mutation" }),
32684
+ query: method(zod.z.object({
32685
+ scope: zod.z.array(zod.z.string()).optional(),
32686
+ level: LogLevelSchema.optional(),
32687
+ since: zod.z.date().optional(),
32688
+ until: zod.z.date().optional(),
32689
+ limit: zod.z.number().optional(),
32690
+ tags: zod.z.record(zod.z.string(), zod.z.string()).optional()
32691
+ }), zod.z.array(LogEntrySchema).readonly())
32692
+ },
32693
+ /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
32694
+ mount: { kind: "skip" }
32695
+ };
32696
32696
  var LoginStageEnum = zod.z.enum(["primary", "second-factor"]);
32697
32697
  var RedirectLoginMethodSchema = zod.z.object({
32698
32698
  kind: zod.z.literal("redirect"),
@@ -36780,9 +36780,13 @@ var require_dist_CcvXUhHK = __commonJS({
36780
36780
  var MediaFileSchema = zod.z.object({
36781
36781
  key: zod.z.string(),
36782
36782
  kind: MediaFileKindEnum,
36783
- base64: zod.z.string(),
36784
36783
  sizeBytes: zod.z.number(),
36785
36784
  timestamp: zod.z.number()
36785
+ }).extend({
36786
+ /** `/addon/<addonId>/event-media/<encoded stored key>`. Always present. */
36787
+ url: zod.z.string(),
36788
+ /** @deprecated Transitional — see the schema docblock. Use {@link url}. */
36789
+ base64: zod.z.string()
36786
36790
  });
36787
36791
  var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
36788
36792
  var RetrainMacroClassSchema = zod.z.enum([
@@ -37090,6 +37094,25 @@ var require_dist_CcvXUhHK = __commonJS({
37090
37094
  totalBytes: zod.z.number().int(),
37091
37095
  devices: zod.z.array(EventStoreDeviceFootprintSchema).readonly()
37092
37096
  });
37097
+ var EventMediaKindFootprintSchema = zod.z.object({
37098
+ kind: MediaFileKindEnum,
37099
+ /** Media rows of this kind. */
37100
+ rows: zod.z.number().int(),
37101
+ /** Bytes on disk held by those rows. */
37102
+ bytes: zod.z.number().int()
37103
+ });
37104
+ var EventMediaKindBreakdownSchema = zod.z.object({
37105
+ /** Every media row in scope, from one unfiltered aggregate. */
37106
+ totalRows: zod.z.number().int(),
37107
+ /** Every media byte in scope, from that same aggregate. */
37108
+ totalBytes: zod.z.number().int(),
37109
+ /** Per-kind footprint, ordered by bytes descending. */
37110
+ kinds: zod.z.array(EventMediaKindFootprintSchema).readonly(),
37111
+ /** `totalRows` minus the summed `kinds` rows — see the schema note. */
37112
+ unaccountedRows: zod.z.number().int(),
37113
+ /** `totalBytes` minus the summed `kinds` bytes — see the schema note. */
37114
+ unaccountedBytes: zod.z.number().int()
37115
+ });
37093
37116
  var EventPruneCountsSchema = zod.z.object({
37094
37117
  motion: zod.z.number().int(),
37095
37118
  object: zod.z.number().int(),
@@ -37445,6 +37468,22 @@ var require_dist_CcvXUhHK = __commonJS({
37445
37468
  auth: "admin"
37446
37469
  }),
37447
37470
  /**
37471
+ * The same media footprint, broken down by {@link MediaFileKind} instead of
37472
+ * by camera — fleet-wide, or for one camera with `deviceId`.
37473
+ *
37474
+ * Separate from {@link getEventStoreFootprint} rather than a field on it:
37475
+ * the two answer different questions on different axes, the per-camera one
37476
+ * is what the management table renders on every open, and nothing should
37477
+ * pay for a per-kind pass to draw it. See
37478
+ * {@link EventMediaKindBreakdownSchema} for why `unaccounted*` exists —
37479
+ * `kinds` is enumerated, `totalBytes` is not, and the gap must be visible
37480
+ * to anyone sizing a deletion against it.
37481
+ */
37482
+ getEventMediaFootprintByKind: method(zod.z.object({ deviceId: zod.z.number().int().optional() }), EventMediaKindBreakdownSchema, {
37483
+ kind: "query",
37484
+ auth: "admin"
37485
+ }),
37486
+ /**
37448
37487
  * Cluster-wide prune of events older than `olderThanMs` (exclusive) across
37449
37488
  * every camera, deleting each event's media in lockstep. Logged to the
37450
37489
  * events ops-log with `reason` (default `'retention'`). Returns the summed
@@ -37849,6 +37888,26 @@ var require_dist_CcvXUhHK = __commonJS({
37849
37888
  deviceId: zod.z.number()
37850
37889
  }), zod.z.array(MediaFileInfoSchema).readonly()),
37851
37890
  /**
37891
+ * What media an EVENT has, without any of it — the twin `getEventMedia`
37892
+ * never had.
37893
+ *
37894
+ * `listTrackMedia` above got this treatment because a track's media is
37895
+ * 5-8 MB. An event's is worse per row, not better: an old-style event owns
37896
+ * a `crop` AND a native-resolution `fullFrameBoxed`, and the track DETAIL
37897
+ * modal — the one surface that legitimately shows the big kinds — unions
37898
+ * both listings to build its filmstrip. It then renders every tile from
37899
+ * the `event-media` plane by key and throws the bytes away. Measured on
37900
+ * the live hub: one event's `fullFrameBoxed` is 2 824 077 B, base64'd to
37901
+ * ~3.8 MB, allocated whole in hub-main's heap, for a list of keys.
37902
+ *
37903
+ * Same `deviceId` authorization subject as `getEventMedia`, and the same
37904
+ * rows — this is a projection of that method, never a different question.
37905
+ */
37906
+ listEventMedia: method(zod.z.object({
37907
+ eventId: zod.z.string(),
37908
+ deviceId: zod.z.number()
37909
+ }), zod.z.array(MediaFileInfoSchema).readonly()),
37910
+ /**
37852
37911
  * Search object events by text query using CLIP cosine similarity.
37853
37912
  * Encodes `text` via the `embedding-encoder` cap, queries the
37854
37913
  * `ObjectEmbeddingStore` with optional prefilters, ranks all matching
@@ -40023,6 +40082,20 @@ var require_dist_CcvXUhHK = __commonJS({
40023
40082
  listProviders: method(zod.z.void(), zod.z.array(ProviderListEntrySchema).readonly()),
40024
40083
  testConfig: method(zod.z.object({
40025
40084
  providerId: zod.z.string(),
40085
+ /**
40086
+ * The location this config is an UNSAVED edit of, when there is one.
40087
+ *
40088
+ * `listLocations` replaces every declared secret with the redaction
40089
+ * sentinel, so the edit modal's form state holds the sentinel for any
40090
+ * credential the operator did not retype — and posting that here
40091
+ * without a way to resolve it makes the provider try to authenticate
40092
+ * as `__camstack_redacted__` and report the operator's own working
40093
+ * password as wrong. Given this id, the orchestrator restores each
40094
+ * sentinel from the stored config (same rule as `upsertLocation`)
40095
+ * before dispatching. Omitted by the "Add location" wizard, where
40096
+ * every value was typed just now and nothing is stored yet.
40097
+ */
40098
+ locationId: zod.z.string().optional(),
40026
40099
  config: zod.z.record(zod.z.string(), zod.z.unknown())
40027
40100
  }), zod.z.object({
40028
40101
  ok: zod.z.boolean(),
@@ -43144,7 +43217,7 @@ var require_dist_CcvXUhHK = __commonJS({
43144
43217
  var MediaFileLiteSchema$1 = zod.z.object({
43145
43218
  key: zod.z.string(),
43146
43219
  kind: zod.z.string(),
43147
- base64: zod.z.string(),
43220
+ url: zod.z.string(),
43148
43221
  sizeBytes: zod.z.number(),
43149
43222
  timestamp: zod.z.number()
43150
43223
  });
@@ -46037,7 +46110,7 @@ var require_dist_CcvXUhHK = __commonJS({
46037
46110
  var MediaFileLiteSchema = zod.z.object({
46038
46111
  key: zod.z.string(),
46039
46112
  kind: zod.z.string(),
46040
- base64: zod.z.string(),
46113
+ url: zod.z.string(),
46041
46114
  sizeBytes: zod.z.number(),
46042
46115
  timestamp: zod.z.number()
46043
46116
  });
@@ -53249,6 +53322,12 @@ var require_dist_CcvXUhHK = __commonJS({
53249
53322
  addonId: null,
53250
53323
  access: "view"
53251
53324
  },
53325
+ "pipelineAnalytics.getEventMediaFootprintByKind": {
53326
+ capName: "pipeline-analytics",
53327
+ capScope: "device",
53328
+ addonId: null,
53329
+ access: "view"
53330
+ },
53252
53331
  "pipelineAnalytics.getEventStoreFootprint": {
53253
53332
  capName: "pipeline-analytics",
53254
53333
  capScope: "device",
@@ -53345,6 +53424,12 @@ var require_dist_CcvXUhHK = __commonJS({
53345
53424
  addonId: null,
53346
53425
  access: "view"
53347
53426
  },
53427
+ "pipelineAnalytics.listEventMedia": {
53428
+ capName: "pipeline-analytics",
53429
+ capScope: "device",
53430
+ addonId: null,
53431
+ access: "view"
53432
+ },
53348
53433
  "pipelineAnalytics.listGroups": {
53349
53434
  capName: "pipeline-analytics",
53350
53435
  capScope: "device",
@@ -56908,6 +56993,11 @@ var require_dist_CcvXUhHK = __commonJS({
56908
56993
  form: "single",
56909
56994
  optional: false
56910
56995
  }],
56996
+ "pipelineAnalytics.getEventMediaFootprintByKind": [{
56997
+ name: "deviceId",
56998
+ form: "single",
56999
+ optional: true
57000
+ }],
56911
57001
  "pipelineAnalytics.getGroup": [{
56912
57002
  name: "deviceId",
56913
57003
  form: "single",
@@ -56968,6 +57058,11 @@ var require_dist_CcvXUhHK = __commonJS({
56968
57058
  form: "array",
56969
57059
  optional: false
56970
57060
  }],
57061
+ "pipelineAnalytics.listEventMedia": [{
57062
+ name: "deviceId",
57063
+ form: "single",
57064
+ optional: false
57065
+ }],
56971
57066
  "pipelineAnalytics.listGroups": [{
56972
57067
  name: "deviceIds",
56973
57068
  form: "array",
@@ -59112,7 +59207,7 @@ var require_alerts_addon = __commonJS({
59112
59207
  [Symbol.toStringTag]: { value: "Module" }
59113
59208
  });
59114
59209
  require_chunk_Cek0wNdY();
59115
- var require_dist10 = require_dist_CcvXUhHK();
59210
+ var require_dist10 = require_dist_CCIBlacf();
59116
59211
  function selectExpired(alerts, cutoffMs) {
59117
59212
  return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
59118
59213
  }
@@ -59931,7 +60026,7 @@ var require_console_logging = __commonJS({
59931
60026
  [Symbol.toStringTag]: { value: "Module" }
59932
60027
  });
59933
60028
  require_chunk_Cek0wNdY();
59934
- var require_dist10 = require_dist_CcvXUhHK();
60029
+ var require_dist10 = require_dist_CCIBlacf();
59935
60030
  var require_formatter = require_formatter_DqAKDlvN();
59936
60031
  var LEVEL_RANK = {
59937
60032
  debug: 0,
@@ -60025,7 +60120,7 @@ var require_core_blocks_addon = __commonJS({
60025
60120
  "use strict";
60026
60121
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
60027
60122
  var require_chunk = require_chunk_Cek0wNdY();
60028
- var require_dist10 = require_dist_CcvXUhHK();
60123
+ var require_dist10 = require_dist_CCIBlacf();
60029
60124
  var node_crypto = __require("crypto");
60030
60125
  var node_fs_promises = __require("fs/promises");
60031
60126
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -60922,11 +61017,11 @@ var require_core_blocks = __commonJS({
60922
61017
  }
60923
61018
  });
60924
61019
 
60925
- // ../system/dist/retired-settings-keys-DXZ2xe7C.js
60926
- var require_retired_settings_keys_DXZ2xe7C = __commonJS({
60927
- "../system/dist/retired-settings-keys-DXZ2xe7C.js"(exports) {
61020
+ // ../system/dist/retired-settings-keys-BxV3e2Km.js
61021
+ var require_retired_settings_keys_BxV3e2Km = __commonJS({
61022
+ "../system/dist/retired-settings-keys-BxV3e2Km.js"(exports) {
60928
61023
  "use strict";
60929
- var require_dist10 = require_dist_CcvXUhHK();
61024
+ var require_dist10 = require_dist_CCIBlacf();
60930
61025
  function settingsStoreIsAuthoritativeHere(env) {
60931
61026
  const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
60932
61027
  return raw === "" || raw === "hub";
@@ -63140,8 +63235,8 @@ var require_device_manager_addon = __commonJS({
63140
63235
  [Symbol.toStringTag]: { value: "Module" }
63141
63236
  });
63142
63237
  require_chunk_Cek0wNdY();
63143
- var require_dist10 = require_dist_CcvXUhHK();
63144
- var require_retired_settings_keys = require_retired_settings_keys_DXZ2xe7C();
63238
+ var require_dist10 = require_dist_CCIBlacf();
63239
+ var require_retired_settings_keys = require_retired_settings_keys_BxV3e2Km();
63145
63240
  var node_crypto = __require("crypto");
63146
63241
  var _camstack_types_node = require_node();
63147
63242
  var JOB_HISTORY = 20;
@@ -67954,7 +68049,7 @@ var require_hub_forwarder = __commonJS({
67954
68049
  [Symbol.toStringTag]: { value: "Module" }
67955
68050
  });
67956
68051
  require_chunk_Cek0wNdY();
67957
- var require_dist10 = require_dist_CcvXUhHK();
68052
+ var require_dist10 = require_dist_CCIBlacf();
67958
68053
  var require_formatter = require_formatter_DqAKDlvN();
67959
68054
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
67960
68055
  var HubForwarderDestination = class {
@@ -68091,7 +68186,7 @@ var require_liveness_monitor_addon = __commonJS({
68091
68186
  "use strict";
68092
68187
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
68093
68188
  require_chunk_Cek0wNdY();
68094
- var require_dist10 = require_dist_CcvXUhHK();
68189
+ var require_dist10 = require_dist_CCIBlacf();
68095
68190
  var NO_DEVICES = "liveness:no-devices";
68096
68191
  var ALL_OFFLINE = "liveness:all-devices-offline";
68097
68192
  var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
@@ -68281,7 +68376,7 @@ var require_local_auth_addon = __commonJS({
68281
68376
  [Symbol.toStringTag]: { value: "Module" }
68282
68377
  });
68283
68378
  var require_chunk = require_chunk_Cek0wNdY();
68284
- var require_dist10 = require_dist_CcvXUhHK();
68379
+ var require_dist10 = require_dist_CCIBlacf();
68285
68380
  var node_crypto = __require("crypto");
68286
68381
  node_crypto = require_chunk.__toESM(node_crypto);
68287
68382
  var crypto$1 = __require("crypto");
@@ -76094,7 +76189,7 @@ var require_loki_logging = __commonJS({
76094
76189
  [Symbol.toStringTag]: { value: "Module" }
76095
76190
  });
76096
76191
  require_chunk_Cek0wNdY();
76097
- var require_dist10 = require_dist_CcvXUhHK();
76192
+ var require_dist10 = require_dist_CCIBlacf();
76098
76193
  function sanitizeLabelName(raw) {
76099
76194
  const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
76100
76195
  return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
@@ -76659,7 +76754,7 @@ var require_native_metrics_addon = __commonJS({
76659
76754
  [Symbol.toStringTag]: { value: "Module" }
76660
76755
  });
76661
76756
  var require_chunk = require_chunk_Cek0wNdY();
76662
- var require_dist10 = require_dist_CcvXUhHK();
76757
+ var require_dist10 = require_dist_CCIBlacf();
76663
76758
  var node_fs_promises = __require("fs/promises");
76664
76759
  var node_child_process = __require("child_process");
76665
76760
  var node_util = __require("util");
@@ -79281,7 +79376,7 @@ var require_filesystem_storage_addon = __commonJS({
79281
79376
  [Symbol.toStringTag]: { value: "Module" }
79282
79377
  });
79283
79378
  var require_chunk = require_chunk_Cek0wNdY();
79284
- var require_dist10 = require_dist_CcvXUhHK();
79379
+ var require_dist10 = require_dist_CCIBlacf();
79285
79380
  var node_crypto = __require("crypto");
79286
79381
  var node_fs_promises = __require("fs/promises");
79287
79382
  var node_path = __require("path");
@@ -80397,8 +80492,8 @@ var require_sqlite_settings_addon = __commonJS({
80397
80492
  [Symbol.toStringTag]: { value: "Module" }
80398
80493
  });
80399
80494
  var require_chunk = require_chunk_Cek0wNdY();
80400
- var require_dist10 = require_dist_CcvXUhHK();
80401
- var require_retired_settings_keys = require_retired_settings_keys_DXZ2xe7C();
80495
+ var require_dist10 = require_dist_CCIBlacf();
80496
+ var require_retired_settings_keys = require_retired_settings_keys_BxV3e2Km();
80402
80497
  var node_crypto = __require("crypto");
80403
80498
  var node_fs = __require("fs");
80404
80499
  var node_module = __require("module");
@@ -82777,7 +82872,7 @@ var require_storage_orchestrator_addon = __commonJS({
82777
82872
  [Symbol.toStringTag]: { value: "Module" }
82778
82873
  });
82779
82874
  var require_chunk = require_chunk_Cek0wNdY();
82780
- var require_dist10 = require_dist_CcvXUhHK();
82875
+ var require_dist10 = require_dist_CCIBlacf();
82781
82876
  var node_crypto = __require("crypto");
82782
82877
  var node_fs_promises = __require("fs/promises");
82783
82878
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -83760,10 +83855,21 @@ var require_storage_orchestrator_addon = __commonJS({
83760
83855
  declareCollection: async (input) => (await engine()).declareCollection(input)
83761
83856
  };
83762
83857
  }
83763
- var EMPTY_SECRET_KEYS = /* @__PURE__ */ new Set();
83858
+ var FRAMEWORK_CONFIG_KEYS = /* @__PURE__ */ new Set([
83859
+ "basePath",
83860
+ "readOnly",
83861
+ "maxUsedGb",
83862
+ "minFreePercent"
83863
+ ]);
83764
83864
  function secretKeysOfProviderInfo(info) {
83765
83865
  return require_dist10.collectSecretConfigKeys(info.configSchema);
83766
83866
  }
83867
+ function secretKeysForConfig(config, declared) {
83868
+ if (declared !== void 0) return declared;
83869
+ const out = /* @__PURE__ */ new Set();
83870
+ for (const key of Object.keys(config)) if (!FRAMEWORK_CONFIG_KEYS.has(key)) out.add(key);
83871
+ return out;
83872
+ }
83767
83873
  function redactLocationConfig(config, secretKeys) {
83768
83874
  if (secretKeys.size === 0) return config;
83769
83875
  let touched = false;
@@ -84749,7 +84855,7 @@ var require_storage_orchestrator_addon = __commonJS({
84749
84855
  listLocationDeclarations: async () => service.listDeclarations(),
84750
84856
  upsertLocation: async (input) => {
84751
84857
  const stored = service.getLocationById(input.id);
84752
- const config = restoreRedactedSecrets(input.config, stored?.config, this.secretKeysFor(input.providerId));
84858
+ const config = restoreRedactedSecrets(input.config, stored?.config, this.secretKeysFor(input.providerId, stored?.config ?? {}));
84753
84859
  const saved = service.upsertLocation(config === input.config ? input : {
84754
84860
  ...input,
84755
84861
  config
@@ -84782,11 +84888,14 @@ var require_storage_orchestrator_addon = __commonJS({
84782
84888
  } });
84783
84889
  });
84784
84890
  },
84785
- testConfig: async ({ providerId, config }) => {
84891
+ testConfig: async ({ providerId, locationId, config }) => {
84892
+ const stored = locationId === void 0 ? void 0 : service.getLocationById(locationId);
84893
+ const base = stored?.providerId === providerId ? stored.config : void 0;
84894
+ const resolved = restoreRedactedSecrets(config, base, this.secretKeysFor(providerId, base ?? {}));
84786
84895
  const providers = getProviders();
84787
84896
  for (const p of providers) try {
84788
84897
  if ((await p.getProviderInfo()).providerId !== providerId) continue;
84789
- return p.testLocation({ config });
84898
+ return p.testLocation({ config: resolved });
84790
84899
  } catch (err) {
84791
84900
  return {
84792
84901
  ok: false,
@@ -85106,13 +85215,21 @@ var require_storage_orchestrator_addon = __commonJS({
85106
85215
  }
85107
85216
  return out;
85108
85217
  }
85109
- /** Declared secret config keys for a provider; empty when unknown. */
85110
- secretKeysFor(providerId) {
85111
- return this.secretKeysByProvider.get(providerId) ?? EMPTY_SECRET_KEYS;
85218
+ /**
85219
+ * What the provider's own schema declared secret, or `undefined` when this
85220
+ * node has never heard from that provider. The distinction is the whole
85221
+ * point — see `secretKeysForConfig`, which fails CLOSED on `undefined`.
85222
+ */
85223
+ declaredSecretKeys(providerId) {
85224
+ return this.secretKeysByProvider.get(providerId);
85225
+ }
85226
+ /** Which keys of a given stored config must never leave this process. */
85227
+ secretKeysFor(providerId, config) {
85228
+ return secretKeysForConfig(config, this.declaredSecretKeys(providerId));
85112
85229
  }
85113
85230
  /** A location with its provider's declared secrets replaced by the sentinel. */
85114
85231
  redacted(location) {
85115
- return redactLocation(location, this.secretKeysFor(location.providerId));
85232
+ return redactLocation(location, this.secretKeysFor(location.providerId, location.config));
85116
85233
  }
85117
85234
  /**
85118
85235
  * Is this location backed by a provider that serves a genuine local
@@ -85352,7 +85469,7 @@ var require_system_config_addon = __commonJS({
85352
85469
  [Symbol.toStringTag]: { value: "Module" }
85353
85470
  });
85354
85471
  require_chunk_Cek0wNdY();
85355
- var require_dist10 = require_dist_CcvXUhHK();
85472
+ var require_dist10 = require_dist_CCIBlacf();
85356
85473
  var SECTION_TITLES = {
85357
85474
  server: "Server",
85358
85475
  auth: "Authentication"
@@ -103413,7 +103530,7 @@ var require_winston_logging = __commonJS({
103413
103530
  [Symbol.toStringTag]: { value: "Module" }
103414
103531
  });
103415
103532
  var require_chunk = require_chunk_Cek0wNdY();
103416
- var require_dist10 = require_dist_CcvXUhHK();
103533
+ var require_dist10 = require_dist_CCIBlacf();
103417
103534
  var require_formatter = require_formatter_DqAKDlvN();
103418
103535
  var node_path = __require("path");
103419
103536
  node_path = require_chunk.__toESM(node_path);
@@ -105356,9 +105473,9 @@ var require_event_category_BaEgqJNv = __commonJS({
105356
105473
  }
105357
105474
  });
105358
105475
 
105359
- // ../types/dist/sleep-CWWLTM6W.js
105360
- var require_sleep_CWWLTM6W = __commonJS({
105361
- "../types/dist/sleep-CWWLTM6W.js"(exports) {
105476
+ // ../types/dist/sleep-D5821NGq.js
105477
+ var require_sleep_D5821NGq = __commonJS({
105478
+ "../types/dist/sleep-D5821NGq.js"(exports) {
105362
105479
  "use strict";
105363
105480
  var require_event_category = require_event_category_BaEgqJNv();
105364
105481
  var zod = require_zod();
@@ -108049,6 +108166,7 @@ var require_sleep_CWWLTM6W = __commonJS({
108049
108166
  deleteTracks: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "deleteTracks", "mutation", input),
108050
108167
  setTrackFlags: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "setTrackFlags", "mutation", input),
108051
108168
  getEventStoreFootprint: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getEventStoreFootprint", "query", input),
108169
+ getEventMediaFootprintByKind: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getEventMediaFootprintByKind", "query", input),
108052
108170
  pruneEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "pruneEvents", "mutation", input),
108053
108171
  deleteDeviceEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "deleteDeviceEvents", "mutation", input),
108054
108172
  pauseForStorageMigration: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "pauseForStorageMigration", "mutation", input),
@@ -108080,6 +108198,7 @@ var require_sleep_CWWLTM6W = __commonJS({
108080
108198
  getEventMedia: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getEventMedia", "query", input),
108081
108199
  getTrackMedia: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getTrackMedia", "query", input),
108082
108200
  listTrackMedia: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listTrackMedia", "query", input),
108201
+ listEventMedia: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listEventMedia", "query", input),
108083
108202
  searchObjectEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "searchObjectEvents", "query", input),
108084
108203
  wipeObjectEmbeddings: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "wipeObjectEmbeddings", "mutation", input),
108085
108204
  rebuildObjectEmbeddings: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "rebuildObjectEmbeddings", "mutation", input),
@@ -109024,7 +109143,7 @@ var require_addon = __commonJS({
109024
109143
  "use strict";
109025
109144
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
109026
109145
  var require_event_category = require_event_category_BaEgqJNv();
109027
- var require_sleep = require_sleep_CWWLTM6W();
109146
+ var require_sleep = require_sleep_D5821NGq();
109028
109147
  var require_err_msg = require_err_msg_COpsHMw2();
109029
109148
  var CAP_INPUT_DEFAULTS = Object.freeze({
109030
109149
  "addons": { "getLogs": { "limit": 100 } },
@@ -115906,12 +116025,12 @@ var require_dist2 = __commonJS({
115906
116025
  }
115907
116026
  });
115908
116027
 
115909
- // ../system/dist/manifest-system-deps-oJMkWSX-.js
115910
- var require_manifest_system_deps_oJMkWSX = __commonJS({
115911
- "../system/dist/manifest-system-deps-oJMkWSX-.js"(exports) {
116028
+ // ../system/dist/manifest-system-deps-alrkBKVQ.js
116029
+ var require_manifest_system_deps_alrkBKVQ = __commonJS({
116030
+ "../system/dist/manifest-system-deps-alrkBKVQ.js"(exports) {
115912
116031
  "use strict";
115913
116032
  var require_chunk = require_chunk_Cek0wNdY();
115914
- require_dist_CcvXUhHK();
116033
+ require_dist_CCIBlacf();
115915
116034
  var node_crypto = __require("crypto");
115916
116035
  node_crypto = require_chunk.__toESM(node_crypto);
115917
116036
  var _camstack_types_node = require_node();
@@ -127940,7 +128059,7 @@ var require_dist3 = __commonJS({
127940
128059
  "use strict";
127941
128060
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
127942
128061
  var require_chunk = require_chunk_Cek0wNdY();
127943
- var require_dist10 = require_dist_CcvXUhHK();
128062
+ var require_dist10 = require_dist_CCIBlacf();
127944
128063
  var require_builtins_alerts_alerts_addon = require_alerts_addon();
127945
128064
  require_alerts();
127946
128065
  var require_formatter = require_formatter_DqAKDlvN();
@@ -127966,7 +128085,7 @@ var require_dist3 = __commonJS({
127966
128085
  var require_builtins_winston_logging_index = require_winston_logging();
127967
128086
  var require_file_data_plane = require_file_data_plane_DO8KbxCe();
127968
128087
  var require_tls$1 = require_tls_BxQlomxd();
127969
- var require_manifest_system_deps = require_manifest_system_deps_oJMkWSX();
128088
+ var require_manifest_system_deps = require_manifest_system_deps_alrkBKVQ();
127970
128089
  var require_resource_monitor = require_resource_monitor_CdnzxBLP();
127971
128090
  var require_custom_action_registry = require_custom_action_registry_jY0NOZK8();
127972
128091
  var zod = require_zod();
@@ -208178,6 +208297,19 @@ globstar while`, t, d, e, f, m), this.matchOne(t.slice(d), e.slice(f), s)) retur
208178
208297
  * per-task abort timeout. On success the resolved `stagedPath` is returned; on
208179
208298
  * failure the task is marked `failed` and `null` is returned (so the apply
208180
208299
  * phase skips it). Other tasks are unaffected.
208300
+ *
208301
+ * **`toVersion` is REWRITTEN here, to the version that was actually
208302
+ * fetched.** A task's `toVersion` starts as the REQUEST, and the request is
208303
+ * routinely a dist-tag: `addons.updatePackage` with no version resolves to
208304
+ * the literal `'latest'`. The staging area already resolves it — it reads
208305
+ * the version out of the unpacked tarball and returns `resolvedVersion` —
208306
+ * and dropping that answer sent the string `'latest'` onward as if it were a
208307
+ * version. It became the manifest's recorded version, the version the Addons
208308
+ * page displayed, and the `toVersion` on the `addon.updated` event, which an
208309
+ * operator read on his phone as "@camstack/addon-terminal 0.0.0 → latest"
208310
+ * (2026-08-30). Everything downstream of the apply reads a version; none of
208311
+ * it can resolve a tag, and none of it could tell that it had been handed
208312
+ * one.
208181
208313
  */
208182
208314
  async fetchAddonTask(job, task) {
208183
208315
  try {
@@ -208187,20 +208319,28 @@ globstar while`, t, d, e, f, m), this.matchOne(t.slice(d), e.slice(f), s)) retur
208187
208319
  });
208188
208320
  const ac = new AbortController();
208189
208321
  const timer = setTimeout(() => ac.abort(), this.deps.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS);
208190
- let stagedPath;
208322
+ let staged;
208191
208323
  try {
208192
- stagedPath = (await this.deps.staging.fetchAndStage({
208324
+ staged = await this.deps.staging.fetchAndStage({
208193
208325
  jobId: job.jobId,
208194
208326
  name: task.packageName,
208195
208327
  version: task.toVersion,
208196
208328
  signal: ac.signal
208197
- })).stagedPath;
208329
+ });
208198
208330
  } finally {
208199
208331
  clearTimeout(timer);
208200
208332
  }
208201
- this.advance(job.jobId, task, "staged", { stagedPath });
208333
+ const stagedPath = staged.stagedPath;
208334
+ const resolved = {
208335
+ ...task,
208336
+ toVersion: staged.resolvedVersion
208337
+ };
208338
+ this.advance(job.jobId, task, "staged", {
208339
+ stagedPath,
208340
+ toVersion: resolved.toVersion
208341
+ });
208202
208342
  return {
208203
- task,
208343
+ task: resolved,
208204
208344
  stagedPath
208205
208345
  };
208206
208346
  } catch (err) {
@@ -208769,7 +208909,7 @@ var require_dist4 = __commonJS({
208769
208909
  "use strict";
208770
208910
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
208771
208911
  var require_event_category = require_event_category_BaEgqJNv();
208772
- var require_sleep = require_sleep_CWWLTM6W();
208912
+ var require_sleep = require_sleep_D5821NGq();
208773
208913
  var require_canonical_hash = require_canonical_hash_DNV8S5ET();
208774
208914
  var require_enums2 = require_enums();
208775
208915
  var require_err_msg = require_err_msg_COpsHMw2();
@@ -216951,6 +217091,80 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
216951
217091
  getInfo: require_sleep.method(zod.z.void(), EmbeddingInfoSchema, { auth: "admin" })
216952
217092
  }
216953
217093
  };
217094
+ var FailureReasonCountSchema = zod.z.object({
217095
+ /**
217096
+ * Why the attempt did not land, in the contributor's own vocabulary —
217097
+ * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
217098
+ * strings that already appear in this repo's logs and, where one exists, the
217099
+ * same string the per-track `previewMissReason` records (D276): a second
217100
+ * vocabulary for the same loss would make the row and the counter
217101
+ * un-joinable.
217102
+ */
217103
+ reason: zod.z.string(),
217104
+ count: zod.z.number().int().nonnegative()
217105
+ });
217106
+ var FailureContributionSchema = zod.z.object({
217107
+ /**
217108
+ * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
217109
+ * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
217110
+ * `unit` free: the families are owned by different addons and a shared enum
217111
+ * is a central list that rots invisibly.
217112
+ */
217113
+ family: zod.z.string(),
217114
+ /**
217115
+ * The NUMERIC device id — the same value every log line carries as
217116
+ * `tags.deviceId`. Never nullable and never absent: a contributor that
217117
+ * cannot name the camera must not emit the entry, because a fleet total
217118
+ * cannot answer the only question anybody asks of this surface.
217119
+ */
217120
+ deviceId: zod.z.number().int().positive(),
217121
+ /**
217122
+ * A second dimension inside the family: the model / step id for an inference
217123
+ * timeout, so "which camera AND which model" is one read. Absent when the
217124
+ * family has a single variant.
217125
+ */
217126
+ variant: zod.z.string().optional(),
217127
+ /**
217128
+ * Epoch ms this counter started — the INCARNATION MARKER. A consumer
217129
+ * differencing two reads must drop the interval when it changes, because the
217130
+ * counter restarted from zero in a respawned runner. Same discipline as
217131
+ * `LoadContribution.startedAtMs`.
217132
+ */
217133
+ sinceMs: zod.z.number(),
217134
+ /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
217135
+ atMs: zod.z.number(),
217136
+ /**
217137
+ * THE DENOMINATOR — every attempt on this path for this camera in the
217138
+ * window. A failure count published without it is the mistake this schema
217139
+ * exists to make impossible.
217140
+ */
217141
+ attempts: zod.z.number().int().nonnegative(),
217142
+ /** Attempts that landed. `attempts - succeeded` is the loss. */
217143
+ succeeded: zod.z.number().int().nonnegative(),
217144
+ /** The loss, partitioned. Sums to `attempts - succeeded`. */
217145
+ reasons: zod.z.array(FailureReasonCountSchema).readonly()
217146
+ });
217147
+ var failureContributionCapability = {
217148
+ name: "failure-contribution",
217149
+ scope: "system",
217150
+ mode: "collection",
217151
+ internal: true,
217152
+ methods: {
217153
+ /**
217154
+ * This addon's per-camera failure counters, read live from bounded in-RAM
217155
+ * state it already keeps. Inert: no persistence, no sampling, no timer.
217156
+ *
217157
+ * READING NEVER RESETS. The counters are CUMULATIVE since `sinceMs`, and a
217158
+ * consumer that wants a rate differences two reads. A draining read would
217159
+ * make two operators with the page open each destroy half of the other's
217160
+ * numbers, and `load-contribution` already settled the same question the
217161
+ * same way for `cpuSeconds`.
217162
+ */
217163
+ list: require_sleep.method(zod.z.void(), zod.z.array(FailureContributionSchema).readonly())
217164
+ },
217165
+ /** In-process only — enumerated through `addons.listCapabilityProviders`. */
217166
+ mount: { kind: "skip" }
217167
+ };
216954
217168
  var DirEntrySchema = zod.z.object({
216955
217169
  name: zod.z.string(),
216956
217170
  path: zod.z.string()
@@ -217495,145 +217709,6 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
217495
217709
  })
217496
217710
  }
217497
217711
  };
217498
- var LogChannelApplyResultSchema = zod.z.object({
217499
- /** How many declared channels are armed in this process after the call. */
217500
- armed: zod.z.number().int().min(0),
217501
- /**
217502
- * Names the document armed that this process does not declare. Reported
217503
- * rather than swallowed: a name here is either a typo or an addon that has
217504
- * not booted, and both deserve a line instead of silence.
217505
- */
217506
- unknown: zod.z.array(zod.z.string()).readonly()
217507
- });
217508
- var logChannelsCapability = {
217509
- name: "log-channels",
217510
- scope: "system",
217511
- mode: "collection",
217512
- internal: true,
217513
- methods: {
217514
- /** The channels this addon declares. Inert: no value, no state. */
217515
- list: require_sleep.method(zod.z.void(), zod.z.array(LogChannelDescriptorSchema).readonly()),
217516
- /**
217517
- * Refresh this process's mirror from the document's FULL set of armed
217518
- * windows.
217519
- *
217520
- * Full and not incremental on purpose: the document is the authority, so a
217521
- * channel it does not name is disarmed here. An incremental apply would
217522
- * let a disarm get lost in transit and leave a channel running that
217523
- * nobody can see is running.
217524
- */
217525
- apply: require_sleep.method(zod.z.object({ windows: zod.z.array(LogChannelWindowSchema).readonly() }), LogChannelApplyResultSchema, { kind: "mutation" })
217526
- },
217527
- /** In-process only — enumerated through `addons.listCapabilityProviders`. */
217528
- mount: { kind: "skip" }
217529
- };
217530
- var LogLevelSchema = zod.z.enum([
217531
- "debug",
217532
- "info",
217533
- "warn",
217534
- "error"
217535
- ]);
217536
- var LogEntrySchema = zod.z.object({
217537
- timestamp: zod.z.date(),
217538
- level: LogLevelSchema,
217539
- scope: zod.z.array(zod.z.string()),
217540
- message: zod.z.string(),
217541
- meta: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
217542
- tags: zod.z.record(zod.z.string(), zod.z.string()).optional()
217543
- });
217544
- var logDestinationCapability = {
217545
- name: "log-destination",
217546
- scope: "system",
217547
- mode: "collection",
217548
- internal: true,
217549
- methods: {
217550
- write: require_sleep.method(LogEntrySchema, zod.z.void(), { kind: "mutation" }),
217551
- query: require_sleep.method(zod.z.object({
217552
- scope: zod.z.array(zod.z.string()).optional(),
217553
- level: LogLevelSchema.optional(),
217554
- since: zod.z.date().optional(),
217555
- until: zod.z.date().optional(),
217556
- limit: zod.z.number().optional(),
217557
- tags: zod.z.record(zod.z.string(), zod.z.string()).optional()
217558
- }), zod.z.array(LogEntrySchema).readonly())
217559
- },
217560
- /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
217561
- mount: { kind: "skip" }
217562
- };
217563
- var FailureReasonCountSchema = zod.z.object({
217564
- /**
217565
- * Why the attempt did not land, in the contributor's own vocabulary —
217566
- * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
217567
- * strings that already appear in this repo's logs and, where one exists, the
217568
- * same string the per-track `previewMissReason` records (D276): a second
217569
- * vocabulary for the same loss would make the row and the counter
217570
- * un-joinable.
217571
- */
217572
- reason: zod.z.string(),
217573
- count: zod.z.number().int().nonnegative()
217574
- });
217575
- var FailureContributionSchema = zod.z.object({
217576
- /**
217577
- * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
217578
- * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
217579
- * `unit` free: the families are owned by different addons and a shared enum
217580
- * is a central list that rots invisibly.
217581
- */
217582
- family: zod.z.string(),
217583
- /**
217584
- * The NUMERIC device id — the same value every log line carries as
217585
- * `tags.deviceId`. Never nullable and never absent: a contributor that
217586
- * cannot name the camera must not emit the entry, because a fleet total
217587
- * cannot answer the only question anybody asks of this surface.
217588
- */
217589
- deviceId: zod.z.number().int().positive(),
217590
- /**
217591
- * A second dimension inside the family: the model / step id for an inference
217592
- * timeout, so "which camera AND which model" is one read. Absent when the
217593
- * family has a single variant.
217594
- */
217595
- variant: zod.z.string().optional(),
217596
- /**
217597
- * Epoch ms this counter started — the INCARNATION MARKER. A consumer
217598
- * differencing two reads must drop the interval when it changes, because the
217599
- * counter restarted from zero in a respawned runner. Same discipline as
217600
- * `LoadContribution.startedAtMs`.
217601
- */
217602
- sinceMs: zod.z.number(),
217603
- /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
217604
- atMs: zod.z.number(),
217605
- /**
217606
- * THE DENOMINATOR — every attempt on this path for this camera in the
217607
- * window. A failure count published without it is the mistake this schema
217608
- * exists to make impossible.
217609
- */
217610
- attempts: zod.z.number().int().nonnegative(),
217611
- /** Attempts that landed. `attempts - succeeded` is the loss. */
217612
- succeeded: zod.z.number().int().nonnegative(),
217613
- /** The loss, partitioned. Sums to `attempts - succeeded`. */
217614
- reasons: zod.z.array(FailureReasonCountSchema).readonly()
217615
- });
217616
- var failureContributionCapability = {
217617
- name: "failure-contribution",
217618
- scope: "system",
217619
- mode: "collection",
217620
- internal: true,
217621
- methods: {
217622
- /**
217623
- * This addon's per-camera failure counters, read live from bounded in-RAM
217624
- * state it already keeps. Inert: no persistence, no sampling, no timer.
217625
- *
217626
- * READING NEVER RESETS. The counters are CUMULATIVE since `sinceMs`, and a
217627
- * consumer that wants a rate differences two reads. A draining read would
217628
- * make two operators with the page open each destroy half of the other's
217629
- * numbers, and `load-contribution` already settled the same question the
217630
- * same way for `cpuSeconds`.
217631
- */
217632
- list: require_sleep.method(zod.z.void(), zod.z.array(FailureContributionSchema).readonly())
217633
- },
217634
- /** In-process only — enumerated through `addons.listCapabilityProviders`. */
217635
- mount: { kind: "skip" }
217636
- };
217637
217712
  var LOAD_CONTRIBUTION_ROLES = [
217638
217713
  "decode",
217639
217714
  "transcode",
@@ -217713,6 +217788,71 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
217713
217788
  /** In-process only — enumerated through `addons.listCapabilityProviders`. */
217714
217789
  mount: { kind: "skip" }
217715
217790
  };
217791
+ var LogChannelApplyResultSchema = zod.z.object({
217792
+ /** How many declared channels are armed in this process after the call. */
217793
+ armed: zod.z.number().int().min(0),
217794
+ /**
217795
+ * Names the document armed that this process does not declare. Reported
217796
+ * rather than swallowed: a name here is either a typo or an addon that has
217797
+ * not booted, and both deserve a line instead of silence.
217798
+ */
217799
+ unknown: zod.z.array(zod.z.string()).readonly()
217800
+ });
217801
+ var logChannelsCapability = {
217802
+ name: "log-channels",
217803
+ scope: "system",
217804
+ mode: "collection",
217805
+ internal: true,
217806
+ methods: {
217807
+ /** The channels this addon declares. Inert: no value, no state. */
217808
+ list: require_sleep.method(zod.z.void(), zod.z.array(LogChannelDescriptorSchema).readonly()),
217809
+ /**
217810
+ * Refresh this process's mirror from the document's FULL set of armed
217811
+ * windows.
217812
+ *
217813
+ * Full and not incremental on purpose: the document is the authority, so a
217814
+ * channel it does not name is disarmed here. An incremental apply would
217815
+ * let a disarm get lost in transit and leave a channel running that
217816
+ * nobody can see is running.
217817
+ */
217818
+ apply: require_sleep.method(zod.z.object({ windows: zod.z.array(LogChannelWindowSchema).readonly() }), LogChannelApplyResultSchema, { kind: "mutation" })
217819
+ },
217820
+ /** In-process only — enumerated through `addons.listCapabilityProviders`. */
217821
+ mount: { kind: "skip" }
217822
+ };
217823
+ var LogLevelSchema = zod.z.enum([
217824
+ "debug",
217825
+ "info",
217826
+ "warn",
217827
+ "error"
217828
+ ]);
217829
+ var LogEntrySchema = zod.z.object({
217830
+ timestamp: zod.z.date(),
217831
+ level: LogLevelSchema,
217832
+ scope: zod.z.array(zod.z.string()),
217833
+ message: zod.z.string(),
217834
+ meta: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
217835
+ tags: zod.z.record(zod.z.string(), zod.z.string()).optional()
217836
+ });
217837
+ var logDestinationCapability = {
217838
+ name: "log-destination",
217839
+ scope: "system",
217840
+ mode: "collection",
217841
+ internal: true,
217842
+ methods: {
217843
+ write: require_sleep.method(LogEntrySchema, zod.z.void(), { kind: "mutation" }),
217844
+ query: require_sleep.method(zod.z.object({
217845
+ scope: zod.z.array(zod.z.string()).optional(),
217846
+ level: LogLevelSchema.optional(),
217847
+ since: zod.z.date().optional(),
217848
+ until: zod.z.date().optional(),
217849
+ limit: zod.z.number().optional(),
217850
+ tags: zod.z.record(zod.z.string(), zod.z.string()).optional()
217851
+ }), zod.z.array(LogEntrySchema).readonly())
217852
+ },
217853
+ /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
217854
+ mount: { kind: "skip" }
217855
+ };
217716
217856
  var LoginStageEnum = zod.z.enum(["primary", "second-factor"]);
217717
217857
  var RedirectLoginMethodSchema = zod.z.object({
217718
217858
  kind: zod.z.literal("redirect"),
@@ -222600,13 +222740,18 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
222600
222740
  "keyFrameSmall",
222601
222741
  "thumbnailSmall"
222602
222742
  ]);
222603
- var MediaFileSchema = zod.z.object({
222743
+ var MediaFileRefSchema = zod.z.object({
222604
222744
  key: zod.z.string(),
222605
222745
  kind: MediaFileKindEnum,
222606
- base64: zod.z.string(),
222607
222746
  sizeBytes: zod.z.number(),
222608
222747
  timestamp: zod.z.number()
222609
222748
  });
222749
+ var MediaFileSchema = MediaFileRefSchema.extend({
222750
+ /** `/addon/<addonId>/event-media/<encoded stored key>`. Always present. */
222751
+ url: zod.z.string(),
222752
+ /** @deprecated Transitional — see the schema docblock. Use {@link url}. */
222753
+ base64: zod.z.string()
222754
+ });
222610
222755
  var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
222611
222756
  var RetrainMacroClassSchema = zod.z.enum([
222612
222757
  "person",
@@ -222913,6 +223058,25 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
222913
223058
  totalBytes: zod.z.number().int(),
222914
223059
  devices: zod.z.array(EventStoreDeviceFootprintSchema).readonly()
222915
223060
  });
223061
+ var EventMediaKindFootprintSchema = zod.z.object({
223062
+ kind: MediaFileKindEnum,
223063
+ /** Media rows of this kind. */
223064
+ rows: zod.z.number().int(),
223065
+ /** Bytes on disk held by those rows. */
223066
+ bytes: zod.z.number().int()
223067
+ });
223068
+ var EventMediaKindBreakdownSchema = zod.z.object({
223069
+ /** Every media row in scope, from one unfiltered aggregate. */
223070
+ totalRows: zod.z.number().int(),
223071
+ /** Every media byte in scope, from that same aggregate. */
223072
+ totalBytes: zod.z.number().int(),
223073
+ /** Per-kind footprint, ordered by bytes descending. */
223074
+ kinds: zod.z.array(EventMediaKindFootprintSchema).readonly(),
223075
+ /** `totalRows` minus the summed `kinds` rows — see the schema note. */
223076
+ unaccountedRows: zod.z.number().int(),
223077
+ /** `totalBytes` minus the summed `kinds` bytes — see the schema note. */
223078
+ unaccountedBytes: zod.z.number().int()
223079
+ });
222916
223080
  var EventPruneCountsSchema = zod.z.object({
222917
223081
  motion: zod.z.number().int(),
222918
223082
  object: zod.z.number().int(),
@@ -223268,6 +223432,22 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
223268
223432
  auth: "admin"
223269
223433
  }),
223270
223434
  /**
223435
+ * The same media footprint, broken down by {@link MediaFileKind} instead of
223436
+ * by camera — fleet-wide, or for one camera with `deviceId`.
223437
+ *
223438
+ * Separate from {@link getEventStoreFootprint} rather than a field on it:
223439
+ * the two answer different questions on different axes, the per-camera one
223440
+ * is what the management table renders on every open, and nothing should
223441
+ * pay for a per-kind pass to draw it. See
223442
+ * {@link EventMediaKindBreakdownSchema} for why `unaccounted*` exists —
223443
+ * `kinds` is enumerated, `totalBytes` is not, and the gap must be visible
223444
+ * to anyone sizing a deletion against it.
223445
+ */
223446
+ getEventMediaFootprintByKind: require_sleep.method(zod.z.object({ deviceId: zod.z.number().int().optional() }), EventMediaKindBreakdownSchema, {
223447
+ kind: "query",
223448
+ auth: "admin"
223449
+ }),
223450
+ /**
223271
223451
  * Cluster-wide prune of events older than `olderThanMs` (exclusive) across
223272
223452
  * every camera, deleting each event's media in lockstep. Logged to the
223273
223453
  * events ops-log with `reason` (default `'retention'`). Returns the summed
@@ -223672,6 +223852,26 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
223672
223852
  deviceId: zod.z.number()
223673
223853
  }), zod.z.array(MediaFileInfoSchema).readonly()),
223674
223854
  /**
223855
+ * What media an EVENT has, without any of it — the twin `getEventMedia`
223856
+ * never had.
223857
+ *
223858
+ * `listTrackMedia` above got this treatment because a track's media is
223859
+ * 5-8 MB. An event's is worse per row, not better: an old-style event owns
223860
+ * a `crop` AND a native-resolution `fullFrameBoxed`, and the track DETAIL
223861
+ * modal — the one surface that legitimately shows the big kinds — unions
223862
+ * both listings to build its filmstrip. It then renders every tile from
223863
+ * the `event-media` plane by key and throws the bytes away. Measured on
223864
+ * the live hub: one event's `fullFrameBoxed` is 2 824 077 B, base64'd to
223865
+ * ~3.8 MB, allocated whole in hub-main's heap, for a list of keys.
223866
+ *
223867
+ * Same `deviceId` authorization subject as `getEventMedia`, and the same
223868
+ * rows — this is a projection of that method, never a different question.
223869
+ */
223870
+ listEventMedia: require_sleep.method(zod.z.object({
223871
+ eventId: zod.z.string(),
223872
+ deviceId: zod.z.number()
223873
+ }), zod.z.array(MediaFileInfoSchema).readonly()),
223874
+ /**
223675
223875
  * Search object events by text query using CLIP cosine similarity.
223676
223876
  * Encodes `text` via the `embedding-encoder` cap, queries the
223677
223877
  * `ObjectEmbeddingStore` with optional prefilters, ranks all matching
@@ -226010,6 +226210,20 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
226010
226210
  listProviders: require_sleep.method(zod.z.void(), zod.z.array(ProviderListEntrySchema).readonly()),
226011
226211
  testConfig: require_sleep.method(zod.z.object({
226012
226212
  providerId: zod.z.string(),
226213
+ /**
226214
+ * The location this config is an UNSAVED edit of, when there is one.
226215
+ *
226216
+ * `listLocations` replaces every declared secret with the redaction
226217
+ * sentinel, so the edit modal's form state holds the sentinel for any
226218
+ * credential the operator did not retype — and posting that here
226219
+ * without a way to resolve it makes the provider try to authenticate
226220
+ * as `__camstack_redacted__` and report the operator's own working
226221
+ * password as wrong. Given this id, the orchestrator restores each
226222
+ * sentinel from the stored config (same rule as `upsertLocation`)
226223
+ * before dispatching. Omitted by the "Add location" wizard, where
226224
+ * every value was typed just now and nothing is stored yet.
226225
+ */
226226
+ locationId: zod.z.string().optional(),
226013
226227
  config: zod.z.record(zod.z.string(), zod.z.unknown())
226014
226228
  }), zod.z.object({
226015
226229
  ok: zod.z.boolean(),
@@ -229134,7 +229348,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
229134
229348
  var MediaFileLiteSchema$1 = zod.z.object({
229135
229349
  key: zod.z.string(),
229136
229350
  kind: zod.z.string(),
229137
- base64: zod.z.string(),
229351
+ url: zod.z.string(),
229138
229352
  sizeBytes: zod.z.number(),
229139
229353
  timestamp: zod.z.number()
229140
229354
  });
@@ -232029,7 +232243,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
232029
232243
  var MediaFileLiteSchema = zod.z.object({
232030
232244
  key: zod.z.string(),
232031
232245
  kind: zod.z.string(),
232032
- base64: zod.z.string(),
232246
+ url: zod.z.string(),
232033
232247
  sizeBytes: zod.z.number(),
232034
232248
  timestamp: zod.z.number()
232035
232249
  });
@@ -242181,6 +242395,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
242181
242395
  addonId: null,
242182
242396
  access: "view"
242183
242397
  },
242398
+ "pipelineAnalytics.getEventMediaFootprintByKind": {
242399
+ capName: "pipeline-analytics",
242400
+ capScope: "device",
242401
+ addonId: null,
242402
+ access: "view"
242403
+ },
242184
242404
  "pipelineAnalytics.getEventStoreFootprint": {
242185
242405
  capName: "pipeline-analytics",
242186
242406
  capScope: "device",
@@ -242277,6 +242497,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
242277
242497
  addonId: null,
242278
242498
  access: "view"
242279
242499
  },
242500
+ "pipelineAnalytics.listEventMedia": {
242501
+ capName: "pipeline-analytics",
242502
+ capScope: "device",
242503
+ addonId: null,
242504
+ access: "view"
242505
+ },
242280
242506
  "pipelineAnalytics.listGroups": {
242281
242507
  capName: "pipeline-analytics",
242282
242508
  capScope: "device",
@@ -246098,6 +246324,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
246098
246324
  form: "single",
246099
246325
  optional: false
246100
246326
  }],
246327
+ "pipelineAnalytics.getEventMediaFootprintByKind": [{
246328
+ name: "deviceId",
246329
+ form: "single",
246330
+ optional: true
246331
+ }],
246101
246332
  "pipelineAnalytics.getGroup": [{
246102
246333
  name: "deviceId",
246103
246334
  form: "single",
@@ -246158,6 +246389,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
246158
246389
  form: "array",
246159
246390
  optional: false
246160
246391
  }],
246392
+ "pipelineAnalytics.listEventMedia": [{
246393
+ name: "deviceId",
246394
+ form: "single",
246395
+ optional: false
246396
+ }],
246161
246397
  "pipelineAnalytics.listGroups": [{
246162
246398
  name: "deviceIds",
246163
246399
  form: "array",
@@ -251198,6 +251434,8 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251198
251434
  exports.MaskShapeKindSchema = MaskShapeKindSchema;
251199
251435
  exports.MaskShapeSchema = MaskShapeSchema;
251200
251436
  exports.MediaFileInfoSchema = MediaFileInfoSchema;
251437
+ exports.MediaFileKindEnum = MediaFileKindEnum;
251438
+ exports.MediaFileRefSchema = MediaFileRefSchema;
251201
251439
  exports.MediaFileSchema = MediaFileSchema;
251202
251440
  exports.MediaPlayerRepeatSchema = MediaPlayerRepeatSchema;
251203
251441
  exports.MediaPlayerStateSchema = MediaPlayerStateSchema;
@@ -402904,7 +403142,7 @@ var require_addon_package_service = __commonJS({
402904
403142
  this.requireInstaller();
402905
403143
  const addonInstaller = this.installer;
402906
403144
  const previousVersion = this.getInstalledPackageVersion(name);
402907
- const isFirstInstall = !previousVersion;
403145
+ const isFirstInstall = previousVersion === null;
402908
403146
  let result;
402909
403147
  if (isFirstInstall) {
402910
403148
  const r = await addonInstaller.installFromNpm(name, version);
@@ -402930,12 +403168,13 @@ var require_addon_package_service = __commonJS({
402930
403168
  const targetDir = path.join(addonsDir, dirName);
402931
403169
  const packageSpec = version ? `${name}@${version}` : name;
402932
403170
  await installPackageFromNpm(packageSpec, targetDir);
402933
- updatedVersion = this.getInstalledPackageVersion(name);
403171
+ const installedVersion = this.getInstalledPackageVersion(name);
403172
+ updatedVersion = installedVersion ?? "";
402934
403173
  this.cachedUpdates = null;
402935
403174
  this.logger.info("Core package updated -- restart required", {
402936
- meta: { name, updatedVersion }
403175
+ meta: { name, updatedVersion: installedVersion }
402937
403176
  });
402938
- this.sendUpdateNotification(name, updatedVersion);
403177
+ this.sendUpdateNotification(name, installedVersion);
402939
403178
  return { success: true, version: updatedVersion, requiresRestart: true };
402940
403179
  } catch (error) {
402941
403180
  const msg = (0, types_1.errMsg)(error);
@@ -403622,36 +403861,39 @@ var require_addon_package_service = __commonJS({
403622
403861
  // Private: general helpers
403623
403862
  // =========================================================================
403624
403863
  /**
403625
- * Read the currently installed version of a package.
403626
- * Checks data/addons/ first, then falls back to Node module resolution.
403627
- * Returns '0.0.0' if not found.
403864
+ * The version of `packageName` INSTALLED RIGHT NOW, or `null` when the hub
403865
+ * has no readable copy.
403866
+ *
403867
+ * Delegates to the installer, which is the one place that knows the on-disk
403868
+ * layout (`<addonsDir>/@camstack/<pkg>/`, with the pre-scope flat form as a
403869
+ * fallback). This method used to restate that path and got it wrong —
403870
+ * `packageName.replace(/^@camstack\//, '')` looked ONLY in the flat form, so
403871
+ * every scoped install missed. It then fell back to
403872
+ * `require.resolve('<pkg>/package.json')`, which cannot succeed either: an
403873
+ * addon is installed under the data directory, not in the hub's own
403874
+ * node_modules. Both misses landed on the literal `'0.0.0'`, and that string
403875
+ * was published on the `addon.updated` event as the version the operator had
403876
+ * been running ("@camstack/addon-terminal 0.0.0 → latest", 2026-08-30).
403877
+ *
403878
+ * `null`, not `'0.0.0'`: a version nobody installed is indistinguishable
403879
+ * from one that was, and the caller must be forced to decide what to say
403880
+ * about "unknown" rather than inherit a number that looks like an answer.
403628
403881
  */
403629
403882
  getInstalledPackageVersion(packageName) {
403630
403883
  try {
403631
- const addonsDir = this.resolveAddonsDir();
403632
- const dirName = packageName.replace(/^@camstack\//, "");
403633
- const pkgJsonPath = path.join(addonsDir, dirName, "package.json");
403634
- if (fs.existsSync(pkgJsonPath)) {
403635
- const pkgJson = readJsonObject(pkgJsonPath);
403636
- const v = asString(pkgJson?.["version"]);
403637
- if (v)
403638
- return v;
403639
- }
403640
- } catch (err) {
403641
- this.logger.debug("Version lookup via fs failed, trying require.resolve", {
403642
- meta: { packageName, error: (0, types_1.errMsg)(err) }
403643
- });
403644
- }
403645
- try {
403646
- const pkgJsonPath = __require.resolve(`${packageName}/package.json`);
403647
- const pkgJson = readJsonObject(pkgJsonPath);
403648
- return asString(pkgJson?.["version"], "0.0.0");
403884
+ const installed = this.installer?.getInstalledPackage(packageName) ?? null;
403885
+ const version = installed === null ? "" : asString(installed.version);
403886
+ if (version)
403887
+ return version;
403649
403888
  } catch (err) {
403650
- this.logger.debug("Could not resolve version, returning 0.0.0", {
403889
+ this.logger.debug("Installed-version lookup failed", {
403651
403890
  meta: { packageName, error: (0, types_1.errMsg)(err) }
403652
403891
  });
403653
- return "0.0.0";
403654
403892
  }
403893
+ this.logger.debug("No readable installed copy \u2014 previous version is unknown", {
403894
+ meta: { packageName }
403895
+ });
403896
+ return null;
403655
403897
  }
403656
403898
  /** Only allow @camstack/* scoped packages */
403657
403899
  isAllowedPackage(name) {
@@ -403703,11 +403945,18 @@ var require_addon_package_service = __commonJS({
403703
403945
  throw new Error("AddonInstaller is not available -- @camstack/system may not be installed");
403704
403946
  }
403705
403947
  }
403706
- /** Send notification and toast for a successful package update */
403948
+ /**
403949
+ * Send notification and toast for a successful package update.
403950
+ *
403951
+ * `version === null` means the hub could not read back what it just
403952
+ * installed. The sentence then says the package was updated and stops —
403953
+ * never "updated to v0.0.0", which reads as a real version.
403954
+ */
403707
403955
  sendUpdateNotification(name, version) {
403956
+ const sentence = version === null ? `${name} updated` : `${name} updated to v${version}`;
403708
403957
  this.notificationService.notify({
403709
403958
  title: "Package Updated",
403710
- body: `${name} updated to v${version}`,
403959
+ body: sentence,
403711
403960
  format: "text",
403712
403961
  priority: 3,
403713
403962
  category: "system",
@@ -403718,7 +403967,7 @@ var require_addon_package_service = __commonJS({
403718
403967
  });
403719
403968
  this.toastService.broadcast({
403720
403969
  title: "Package Updated",
403721
- message: `${name} updated to v${version}`,
403970
+ message: sentence,
403722
403971
  severity: "info",
403723
403972
  duration: 5e3
403724
403973
  });
@@ -417257,6 +417506,7 @@ var require_addon_registry_service = __commonJS({
417257
417506
  Object.defineProperty(exports, "__esModule", { value: true });
417258
417507
  exports.AddonRegistryService = void 0;
417259
417508
  exports.shouldEvictMissingOnDisk = shouldEvictMissingOnDisk;
417509
+ exports.addonUpdatePayload = addonUpdatePayload;
417260
417510
  exports.shouldEmitProviderRegisteredReady = shouldEmitProviderRegisteredReady;
417261
417511
  var node_crypto_1 = __require("crypto");
417262
417512
  var fs = __importStar(__require("fs"));
@@ -417279,6 +417529,12 @@ var require_addon_registry_service = __commonJS({
417279
417529
  function shouldEvictMissingOnDisk(entry, id, onDiskIds) {
417280
417530
  return entry.source === "installed" && entry.packageName !== "@camstack/system" && !onDiskIds.has(id);
417281
417531
  }
417532
+ function addonUpdatePayload(fromVersion, toVersion) {
417533
+ return {
417534
+ ...fromVersion !== null ? { fromVersion } : {},
417535
+ toVersion
417536
+ };
417537
+ }
417282
417538
  function isSettingsStore(backend) {
417283
417539
  const required = [
417284
417540
  "getSystem",
@@ -418354,14 +418610,17 @@ var require_addon_registry_service = __commonJS({
418354
418610
  this.healthMonitor.forget(packageName);
418355
418611
  this.addonLoader.clearLoadFailures(packageName);
418356
418612
  }
418357
- /** Emit addon.updated lifecycle event for all addons belonging to a package */
418613
+ /**
418614
+ * Emit `addon.updated` for every addon belonging to a package.
418615
+ *
418616
+ * The payload shape is {@link addonUpdatePayload} — an unknown predecessor
418617
+ * is an ABSENT key, never a placeholder.
418618
+ */
418358
418619
  emitUpdateEvent(packageName, fromVersion, toVersion) {
418620
+ const payload = addonUpdatePayload(fromVersion, toVersion);
418359
418621
  for (const [id, entry] of this.addonEntries) {
418360
418622
  if (entry.packageName === packageName) {
418361
- this.emitAddonLifecycleEvent("addon.updated", id, {
418362
- fromVersion,
418363
- toVersion
418364
- });
418623
+ this.emitAddonLifecycleEvent("addon.updated", id, payload);
418365
418624
  }
418366
418625
  }
418367
418626
  }