camstack 1.2.65 → 1.2.67

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-Ck2jkBZk.js
23637
- var require_dist_Ck2jkBZk = __commonJS({
23638
- "../system/dist/dist-Ck2jkBZk.js"(exports) {
23636
+ // ../system/dist/dist-Keu5TDO7.js
23637
+ var require_dist_Keu5TDO7 = __commonJS({
23638
+ "../system/dist/dist-Keu5TDO7.js"(exports) {
23639
23639
  "use strict";
23640
23640
  var zod = require_zod();
23641
23641
  var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
@@ -26259,6 +26259,21 @@ var require_dist_Ck2jkBZk = __commonJS({
26259
26259
  bytesMoved: zod.z.number().int(),
26260
26260
  /** Total files discovered up front; null while (or when) unknown. */
26261
26261
  filesTotal: zod.z.number().int().nullable(),
26262
+ /**
26263
+ * Rows this run CORRECTED while moving them — a durable mutation the move
26264
+ * made that nobody asked for, so it is reported where the operator reads the
26265
+ * job rather than only in a log line.
26266
+ *
26267
+ * A footage segment records its byte count in its own NAME, and the durable
26268
+ * hour row derives its aggregates from those names. A file that does not
26269
+ * match its name therefore makes the ledger's sums — and with them quota and
26270
+ * pressure eviction — wrong by the difference, and only a rename can fix it.
26271
+ * On 2026-08-30 one such row also stalled a 110 749-file drain permanently.
26272
+ *
26273
+ * Absent on lanes where the question has no meaning: a media blob's size is
26274
+ * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
26275
+ */
26276
+ rowsReconciled: zod.z.number().int().nonnegative().optional(),
26262
26277
  startedAt: zod.z.number(),
26263
26278
  finishedAt: zod.z.number().nullable(),
26264
26279
  error: zod.z.string().nullable()
@@ -26301,11 +26316,18 @@ var require_dist_Ck2jkBZk = __commonJS({
26301
26316
  /** Omitted = `move`, the pre-existing behaviour. */
26302
26317
  mode: MediaRelocateModeSchema.optional()
26303
26318
  });
26304
- var UnstampedEventMediaCountSchema = zod.z.object({
26305
- media: zod.z.number().int().nonnegative(),
26306
- retrainFrames: zod.z.number().int().nonnegative(),
26307
- total: zod.z.number().int().nonnegative()
26319
+ var UnstampedRowsSchema = zod.z.object({
26320
+ present: zod.z.boolean(),
26321
+ rows: zod.z.number().int().nonnegative().nullable()
26308
26322
  });
26323
+ var UnstampedEventMediaCountSchema = zod.z.object({
26324
+ media: UnstampedRowsSchema,
26325
+ retrainFrames: UnstampedRowsSchema,
26326
+ /** True when EITHER collection holds one. The refusal reads this. */
26327
+ anyPresent: zod.z.boolean(),
26328
+ /** Sum across both, or `null` when either lane could not be counted. */
26329
+ total: zod.z.number().int().nonnegative().nullable()
26330
+ }).nullable();
26309
26331
  var StorageMigrationMediaMoveInputSchema = RelocateMediaInputSchema.extend({ leaseId: zod.z.string().min(1) });
26310
26332
  var StorageMigrationClassSchema = zod.z.enum([
26311
26333
  "recordings",
@@ -26352,6 +26374,10 @@ var require_dist_Ck2jkBZk = __commonJS({
26352
26374
  /** The archive census — the **M** of "N of M" (D295). `null` = unknowable. */
26353
26375
  filesTotal: zod.z.number().int().nonnegative().nullable(),
26354
26376
  bytesMoved: zod.z.number().int().nonnegative(),
26377
+ /** Rows the mover corrected while moving them — see `RelocateJob`. Absent on
26378
+ * a lane that cannot reconcile. A migration that silently rewrote durable
26379
+ * rows would be the same failure as one that silently skipped them. */
26380
+ rowsReconciled: zod.z.number().int().nonnegative().optional(),
26355
26381
  /** The MOVER's start, not the migration's: a drain restarted after an addon
26356
26382
  * crash gets a new mover, and a rate computed from the migration's start
26357
26383
  * would silently average in the time nothing was running. */
@@ -31507,6 +31533,15 @@ var require_dist_Ck2jkBZk = __commonJS({
31507
31533
  * calls are sync. Bindings change rarely (only on wrapper toggle or
31508
31534
  * device add/remove) — clients invalidate via the
31509
31535
  * `capability.binding-changed` event.
31536
+ *
31537
+ * "A single round-trip" describes the CLIENT's side and used not to
31538
+ * describe the server's: until 2026-08-30 the resolver read the persisted
31539
+ * wrapper activations once per device, so answering this cost one
31540
+ * settings-door RPC per device — 1 020 on the live 1 019-device hub, and
31541
+ * it did not return in 240 s against `SystemMirror.init`'s 15 s budget.
31542
+ * The server side is now two reads for the whole fleet. Anything PERIODIC
31543
+ * still belongs on `getBindings` / `getBindingsBatch` (D12); this remains
31544
+ * a warm seed.
31510
31545
  */
31511
31546
  getAllBindings: method(zod.z.object({}), zod.z.array(DeviceBindingsForDeviceSchema)),
31512
31547
  /**
@@ -31905,6 +31940,80 @@ var require_dist_Ck2jkBZk = __commonJS({
31905
31940
  getInfo: method(zod.z.void(), EmbeddingInfoSchema, { auth: "admin" })
31906
31941
  }
31907
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
+ };
31908
32017
  var DirEntrySchema = zod.z.object({
31909
32018
  name: zod.z.string(),
31910
32019
  path: zod.z.string()
@@ -32442,145 +32551,6 @@ var require_dist_Ck2jkBZk = __commonJS({
32442
32551
  })
32443
32552
  }
32444
32553
  };
32445
- var LogChannelApplyResultSchema = zod.z.object({
32446
- /** How many declared channels are armed in this process after the call. */
32447
- armed: zod.z.number().int().min(0),
32448
- /**
32449
- * Names the document armed that this process does not declare. Reported
32450
- * rather than swallowed: a name here is either a typo or an addon that has
32451
- * not booted, and both deserve a line instead of silence.
32452
- */
32453
- unknown: zod.z.array(zod.z.string()).readonly()
32454
- });
32455
- var logChannelsCapability = {
32456
- name: "log-channels",
32457
- scope: "system",
32458
- mode: "collection",
32459
- internal: true,
32460
- methods: {
32461
- /** The channels this addon declares. Inert: no value, no state. */
32462
- list: method(zod.z.void(), zod.z.array(LogChannelDescriptorSchema).readonly()),
32463
- /**
32464
- * Refresh this process's mirror from the document's FULL set of armed
32465
- * windows.
32466
- *
32467
- * Full and not incremental on purpose: the document is the authority, so a
32468
- * channel it does not name is disarmed here. An incremental apply would
32469
- * let a disarm get lost in transit and leave a channel running that
32470
- * nobody can see is running.
32471
- */
32472
- apply: method(zod.z.object({ windows: zod.z.array(LogChannelWindowSchema).readonly() }), LogChannelApplyResultSchema, { kind: "mutation" })
32473
- },
32474
- /** In-process only — enumerated through `addons.listCapabilityProviders`. */
32475
- mount: { kind: "skip" }
32476
- };
32477
- var LogLevelSchema = zod.z.enum([
32478
- "debug",
32479
- "info",
32480
- "warn",
32481
- "error"
32482
- ]);
32483
- var LogEntrySchema = zod.z.object({
32484
- timestamp: zod.z.date(),
32485
- level: LogLevelSchema,
32486
- scope: zod.z.array(zod.z.string()),
32487
- message: zod.z.string(),
32488
- meta: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
32489
- tags: zod.z.record(zod.z.string(), zod.z.string()).optional()
32490
- });
32491
- var logDestinationCapability = {
32492
- name: "log-destination",
32493
- scope: "system",
32494
- mode: "collection",
32495
- internal: true,
32496
- methods: {
32497
- write: method(LogEntrySchema, zod.z.void(), { kind: "mutation" }),
32498
- query: method(zod.z.object({
32499
- scope: zod.z.array(zod.z.string()).optional(),
32500
- level: LogLevelSchema.optional(),
32501
- since: zod.z.date().optional(),
32502
- until: zod.z.date().optional(),
32503
- limit: zod.z.number().optional(),
32504
- tags: zod.z.record(zod.z.string(), zod.z.string()).optional()
32505
- }), zod.z.array(LogEntrySchema).readonly())
32506
- },
32507
- /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
32508
- mount: { kind: "skip" }
32509
- };
32510
- var FailureReasonCountSchema = zod.z.object({
32511
- /**
32512
- * Why the attempt did not land, in the contributor's own vocabulary —
32513
- * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
32514
- * strings that already appear in this repo's logs and, where one exists, the
32515
- * same string the per-track `previewMissReason` records (D276): a second
32516
- * vocabulary for the same loss would make the row and the counter
32517
- * un-joinable.
32518
- */
32519
- reason: zod.z.string(),
32520
- count: zod.z.number().int().nonnegative()
32521
- });
32522
- var FailureContributionSchema = zod.z.object({
32523
- /**
32524
- * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
32525
- * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
32526
- * `unit` free: the families are owned by different addons and a shared enum
32527
- * is a central list that rots invisibly.
32528
- */
32529
- family: zod.z.string(),
32530
- /**
32531
- * The NUMERIC device id — the same value every log line carries as
32532
- * `tags.deviceId`. Never nullable and never absent: a contributor that
32533
- * cannot name the camera must not emit the entry, because a fleet total
32534
- * cannot answer the only question anybody asks of this surface.
32535
- */
32536
- deviceId: zod.z.number().int().positive(),
32537
- /**
32538
- * A second dimension inside the family: the model / step id for an inference
32539
- * timeout, so "which camera AND which model" is one read. Absent when the
32540
- * family has a single variant.
32541
- */
32542
- variant: zod.z.string().optional(),
32543
- /**
32544
- * Epoch ms this counter started — the INCARNATION MARKER. A consumer
32545
- * differencing two reads must drop the interval when it changes, because the
32546
- * counter restarted from zero in a respawned runner. Same discipline as
32547
- * `LoadContribution.startedAtMs`.
32548
- */
32549
- sinceMs: zod.z.number(),
32550
- /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
32551
- atMs: zod.z.number(),
32552
- /**
32553
- * THE DENOMINATOR — every attempt on this path for this camera in the
32554
- * window. A failure count published without it is the mistake this schema
32555
- * exists to make impossible.
32556
- */
32557
- attempts: zod.z.number().int().nonnegative(),
32558
- /** Attempts that landed. `attempts - succeeded` is the loss. */
32559
- succeeded: zod.z.number().int().nonnegative(),
32560
- /** The loss, partitioned. Sums to `attempts - succeeded`. */
32561
- reasons: zod.z.array(FailureReasonCountSchema).readonly()
32562
- });
32563
- var failureContributionCapability = {
32564
- name: "failure-contribution",
32565
- scope: "system",
32566
- mode: "collection",
32567
- internal: true,
32568
- methods: {
32569
- /**
32570
- * This addon's per-camera failure counters, read live from bounded in-RAM
32571
- * state it already keeps. Inert: no persistence, no sampling, no timer.
32572
- *
32573
- * READING NEVER RESETS. The counters are CUMULATIVE since `sinceMs`, and a
32574
- * consumer that wants a rate differences two reads. A draining read would
32575
- * make two operators with the page open each destroy half of the other's
32576
- * numbers, and `load-contribution` already settled the same question the
32577
- * same way for `cpuSeconds`.
32578
- */
32579
- list: method(zod.z.void(), zod.z.array(FailureContributionSchema).readonly())
32580
- },
32581
- /** In-process only — enumerated through `addons.listCapabilityProviders`. */
32582
- mount: { kind: "skip" }
32583
- };
32584
32554
  var LoadContributionSchema = zod.z.object({
32585
32555
  role: zod.z.enum([
32586
32556
  "decode",
@@ -32658,6 +32628,71 @@ var require_dist_Ck2jkBZk = __commonJS({
32658
32628
  /** In-process only — enumerated through `addons.listCapabilityProviders`. */
32659
32629
  mount: { kind: "skip" }
32660
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
+ };
32661
32696
  var LoginStageEnum = zod.z.enum(["primary", "second-factor"]);
32662
32697
  var RedirectLoginMethodSchema = zod.z.object({
32663
32698
  kind: zod.z.literal("redirect"),
@@ -36745,9 +36780,13 @@ var require_dist_Ck2jkBZk = __commonJS({
36745
36780
  var MediaFileSchema = zod.z.object({
36746
36781
  key: zod.z.string(),
36747
36782
  kind: MediaFileKindEnum,
36748
- base64: zod.z.string(),
36749
36783
  sizeBytes: zod.z.number(),
36750
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()
36751
36790
  });
36752
36791
  var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
36753
36792
  var RetrainMacroClassSchema = zod.z.enum([
@@ -37480,6 +37519,17 @@ var require_dist_Ck2jkBZk = __commonJS({
37480
37519
  * happens to stamp it. This count is what the migration planner's
37481
37520
  * non-blocking gate reads; `relocateMedia({ mode: 'seal' })` is what drives
37482
37521
  * it to zero.
37522
+ *
37523
+ * TWO indexed statements per collection, not a walk. It used to page the
37524
+ * whole collection at 200 rows per RPC ordered by an unindexed column, so
37525
+ * on the live hub — 1 254 576 rows — it hit the 60 s RPC deadline every
37526
+ * time it was called, and the migration it gates could never start. The
37527
+ * cheap question (`present`: is there at least one) is asked first and
37528
+ * separately from the expensive one (`rows`), because only the first has
37529
+ * to be answerable for the gate to do its job.
37530
+ *
37531
+ * **`null` is "not measurable", never zero** — at either level. An
37532
+ * unreadable collection must not read as a sealed one.
37483
37533
  */
37484
37534
  countUnstampedEventMedia: method(zod.z.object({}), UnstampedEventMediaCountSchema, { auth: "admin" }),
37485
37535
  /**
@@ -37803,6 +37853,26 @@ var require_dist_Ck2jkBZk = __commonJS({
37803
37853
  deviceId: zod.z.number()
37804
37854
  }), zod.z.array(MediaFileInfoSchema).readonly()),
37805
37855
  /**
37856
+ * What media an EVENT has, without any of it — the twin `getEventMedia`
37857
+ * never had.
37858
+ *
37859
+ * `listTrackMedia` above got this treatment because a track's media is
37860
+ * 5-8 MB. An event's is worse per row, not better: an old-style event owns
37861
+ * a `crop` AND a native-resolution `fullFrameBoxed`, and the track DETAIL
37862
+ * modal — the one surface that legitimately shows the big kinds — unions
37863
+ * both listings to build its filmstrip. It then renders every tile from
37864
+ * the `event-media` plane by key and throws the bytes away. Measured on
37865
+ * the live hub: one event's `fullFrameBoxed` is 2 824 077 B, base64'd to
37866
+ * ~3.8 MB, allocated whole in hub-main's heap, for a list of keys.
37867
+ *
37868
+ * Same `deviceId` authorization subject as `getEventMedia`, and the same
37869
+ * rows — this is a projection of that method, never a different question.
37870
+ */
37871
+ listEventMedia: method(zod.z.object({
37872
+ eventId: zod.z.string(),
37873
+ deviceId: zod.z.number()
37874
+ }), zod.z.array(MediaFileInfoSchema).readonly()),
37875
+ /**
37806
37876
  * Search object events by text query using CLIP cosine similarity.
37807
37877
  * Encodes `text` via the `embedding-encoder` cap, queries the
37808
37878
  * `ObjectEmbeddingStore` with optional prefilters, ranks all matching
@@ -43098,7 +43168,7 @@ var require_dist_Ck2jkBZk = __commonJS({
43098
43168
  var MediaFileLiteSchema$1 = zod.z.object({
43099
43169
  key: zod.z.string(),
43100
43170
  kind: zod.z.string(),
43101
- base64: zod.z.string(),
43171
+ url: zod.z.string(),
43102
43172
  sizeBytes: zod.z.number(),
43103
43173
  timestamp: zod.z.number()
43104
43174
  });
@@ -45991,7 +46061,7 @@ var require_dist_Ck2jkBZk = __commonJS({
45991
46061
  var MediaFileLiteSchema = zod.z.object({
45992
46062
  key: zod.z.string(),
45993
46063
  kind: zod.z.string(),
45994
- base64: zod.z.string(),
46064
+ url: zod.z.string(),
45995
46065
  sizeBytes: zod.z.number(),
45996
46066
  timestamp: zod.z.number()
45997
46067
  });
@@ -53299,6 +53369,12 @@ var require_dist_Ck2jkBZk = __commonJS({
53299
53369
  addonId: null,
53300
53370
  access: "view"
53301
53371
  },
53372
+ "pipelineAnalytics.listEventMedia": {
53373
+ capName: "pipeline-analytics",
53374
+ capScope: "device",
53375
+ addonId: null,
53376
+ access: "view"
53377
+ },
53302
53378
  "pipelineAnalytics.listGroups": {
53303
53379
  capName: "pipeline-analytics",
53304
53380
  capScope: "device",
@@ -56922,6 +56998,11 @@ var require_dist_Ck2jkBZk = __commonJS({
56922
56998
  form: "array",
56923
56999
  optional: false
56924
57000
  }],
57001
+ "pipelineAnalytics.listEventMedia": [{
57002
+ name: "deviceId",
57003
+ form: "single",
57004
+ optional: false
57005
+ }],
56925
57006
  "pipelineAnalytics.listGroups": [{
56926
57007
  name: "deviceIds",
56927
57008
  form: "array",
@@ -59066,7 +59147,7 @@ var require_alerts_addon = __commonJS({
59066
59147
  [Symbol.toStringTag]: { value: "Module" }
59067
59148
  });
59068
59149
  require_chunk_Cek0wNdY();
59069
- var require_dist10 = require_dist_Ck2jkBZk();
59150
+ var require_dist10 = require_dist_Keu5TDO7();
59070
59151
  function selectExpired(alerts, cutoffMs) {
59071
59152
  return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
59072
59153
  }
@@ -59885,7 +59966,7 @@ var require_console_logging = __commonJS({
59885
59966
  [Symbol.toStringTag]: { value: "Module" }
59886
59967
  });
59887
59968
  require_chunk_Cek0wNdY();
59888
- var require_dist10 = require_dist_Ck2jkBZk();
59969
+ var require_dist10 = require_dist_Keu5TDO7();
59889
59970
  var require_formatter = require_formatter_DqAKDlvN();
59890
59971
  var LEVEL_RANK = {
59891
59972
  debug: 0,
@@ -59979,7 +60060,7 @@ var require_core_blocks_addon = __commonJS({
59979
60060
  "use strict";
59980
60061
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
59981
60062
  var require_chunk = require_chunk_Cek0wNdY();
59982
- var require_dist10 = require_dist_Ck2jkBZk();
60063
+ var require_dist10 = require_dist_Keu5TDO7();
59983
60064
  var node_crypto = __require("crypto");
59984
60065
  var node_fs_promises = __require("fs/promises");
59985
60066
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -60876,11 +60957,11 @@ var require_core_blocks = __commonJS({
60876
60957
  }
60877
60958
  });
60878
60959
 
60879
- // ../system/dist/retired-settings-keys-Dp_CyuCW.js
60880
- var require_retired_settings_keys_Dp_CyuCW = __commonJS({
60881
- "../system/dist/retired-settings-keys-Dp_CyuCW.js"(exports) {
60960
+ // ../system/dist/retired-settings-keys-BBohF-eC.js
60961
+ var require_retired_settings_keys_BBohF_eC = __commonJS({
60962
+ "../system/dist/retired-settings-keys-BBohF-eC.js"(exports) {
60882
60963
  "use strict";
60883
- var require_dist10 = require_dist_Ck2jkBZk();
60964
+ var require_dist10 = require_dist_Keu5TDO7();
60884
60965
  function settingsStoreIsAuthoritativeHere(env) {
60885
60966
  const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
60886
60967
  return raw === "" || raw === "hub";
@@ -63094,8 +63175,8 @@ var require_device_manager_addon = __commonJS({
63094
63175
  [Symbol.toStringTag]: { value: "Module" }
63095
63176
  });
63096
63177
  require_chunk_Cek0wNdY();
63097
- var require_dist10 = require_dist_Ck2jkBZk();
63098
- var require_retired_settings_keys = require_retired_settings_keys_Dp_CyuCW();
63178
+ var require_dist10 = require_dist_Keu5TDO7();
63179
+ var require_retired_settings_keys = require_retired_settings_keys_BBohF_eC();
63099
63180
  var node_crypto = __require("crypto");
63100
63181
  var _camstack_types_node = require_node();
63101
63182
  var JOB_HISTORY = 20;
@@ -63558,10 +63639,17 @@ var require_device_manager_addon = __commonJS({
63558
63639
  const live = deps.ctx.kernel?.deviceRegistry?.getById(deviceId)?.type;
63559
63640
  return typeof live === "string" && live.length > 0 ? live : void 0;
63560
63641
  }
63561
- async function resolveDevicePresence(deps, row, deviceId) {
63642
+ async function resolveDevicePresence(deps, row, deviceId, ledgerNonEmpty) {
63562
63643
  if (deps.ctx.kernel?.deviceRegistry?.getById(deviceId)) return "present";
63563
63644
  if (row !== null) return "present";
63564
- return await deps.rows.count() > 0 ? "absent" : "unknown";
63645
+ return await ledgerNonEmpty() ? "absent" : "unknown";
63646
+ }
63647
+ function onceLedgerNonEmpty(deps) {
63648
+ let pending = null;
63649
+ return () => {
63650
+ if (pending === null) pending = deps.rows.count().then((n) => n > 0);
63651
+ return pending;
63652
+ };
63565
63653
  }
63566
63654
  function capAppliesToDeviceType(def, deviceType) {
63567
63655
  if (deviceType === void 0) return true;
@@ -63570,14 +63658,17 @@ var require_device_manager_addon = __commonJS({
63570
63658
  return declared.some((t) => t === deviceType);
63571
63659
  }
63572
63660
  async function getBindings(deps, input) {
63573
- const row = await deps.rows.get(input.deviceId);
63574
- return resolveBindingsForDevice(deps, input.deviceId, row);
63661
+ const [row, store] = await Promise.all([deps.rows.get(input.deviceId), readBindingsStore(deps)]);
63662
+ return resolveBindingsForDevice(deps, input.deviceId, row, {
63663
+ store,
63664
+ ledgerNonEmpty: onceLedgerNonEmpty(deps)
63665
+ });
63575
63666
  }
63576
- async function resolveBindingsForDevice(deps, deviceId, row) {
63667
+ async function resolveBindingsForDevice(deps, deviceId, row, pass) {
63577
63668
  const storeKey = String(deviceId);
63578
- const perDevice = (await readBindingsStore(deps)).deviceBindings[storeKey] ?? {};
63669
+ const perDevice = pass.store.deviceBindings[storeKey] ?? {};
63579
63670
  const deviceType = resolveDeviceType(deps, row, deviceId);
63580
- const presence = await resolveDevicePresence(deps, row, deviceId);
63671
+ const presence = await resolveDevicePresence(deps, row, deviceId, pass.ledgerNonEmpty);
63581
63672
  const entries = [];
63582
63673
  const seenCaps = /* @__PURE__ */ new Set();
63583
63674
  const resolveRemote = (capName) => deps.remoteNativeCaps.get(deviceId)?.get(capName) ?? resolveRemoteNativeCapFromRegistry(deps, capName, deviceId);
@@ -63677,13 +63768,17 @@ var require_device_manager_addon = __commonJS({
63677
63768
  }
63678
63769
  async function getBindingsBatch(deps, input) {
63679
63770
  const ids = [...new Set(input.deviceIds)];
63680
- const rows = await deps.rows.getMany(ids);
63771
+ const [rows, store] = await Promise.all([deps.rows.getMany(ids), readBindingsStore(deps)]);
63772
+ const pass = {
63773
+ store,
63774
+ ledgerNonEmpty: onceLedgerNonEmpty(deps)
63775
+ };
63681
63776
  const out = [];
63682
- for (const deviceId of ids) out.push(await resolveBindingsForDevice(deps, deviceId, rows.get(deviceId) ?? null));
63777
+ for (const deviceId of ids) out.push(await resolveBindingsForDevice(deps, deviceId, rows.get(deviceId) ?? null, pass));
63683
63778
  return out;
63684
63779
  }
63685
63780
  async function getAllBindings(deps) {
63686
- const fleet = await deps.rows.listAll();
63781
+ const [fleet, store] = await Promise.all([deps.rows.listAll(), readBindingsStore(deps)]);
63687
63782
  const rowById = /* @__PURE__ */ new Map();
63688
63783
  const ids = /* @__PURE__ */ new Set();
63689
63784
  for (const row of fleet) {
@@ -63696,8 +63791,12 @@ var require_device_manager_addon = __commonJS({
63696
63791
  deps.ctx.logger.warn("getAllBindings found no devices \u2014 warm boot will see nothing", { meta: { hasRegistry: deps.ctx.kernel?.deviceRegistry !== void 0 } });
63697
63792
  return [];
63698
63793
  }
63794
+ const pass = {
63795
+ store,
63796
+ ledgerNonEmpty: onceLedgerNonEmpty(deps)
63797
+ };
63699
63798
  const out = [];
63700
- for (const deviceId of [...ids].sort((a, b) => a - b)) out.push(await resolveBindingsForDevice(deps, deviceId, rowById.get(deviceId) ?? null));
63799
+ for (const deviceId of [...ids].sort((a, b) => a - b)) out.push(await resolveBindingsForDevice(deps, deviceId, rowById.get(deviceId) ?? null, pass));
63701
63800
  return out;
63702
63801
  }
63703
63802
  async function lookupPersistedStableId(deps, deviceId) {
@@ -64257,7 +64356,12 @@ var require_device_manager_addon = __commonJS({
64257
64356
  ...sourceInfoGetDevice !== void 0 ? { sourceInfo: sourceInfoGetDevice } : {}
64258
64357
  };
64259
64358
  }
64260
- async function projectChildren(pctx, parentDeviceId, ownerAddonId, childRows, liveChildren) {
64359
+ function persistedChildIds(childRows, liveChildren) {
64360
+ const live = /* @__PURE__ */ new Set();
64361
+ for (const device of liveChildren) live.add(device.id);
64362
+ return childRows.filter((row) => !live.has(row.meta.id)).map((row) => row.meta.id);
64363
+ }
64364
+ function projectChildren(pctx, parentDeviceId, ownerAddonId, childRows, liveChildren, configs) {
64261
64365
  const results = [];
64262
64366
  const seen = /* @__PURE__ */ new Set();
64263
64367
  const rowById = /* @__PURE__ */ new Map();
@@ -64273,9 +64377,9 @@ var require_device_manager_addon = __commonJS({
64273
64377
  const childStableId = m.stableId;
64274
64378
  const key = String(m.id);
64275
64379
  if (seen.has(key)) continue;
64276
- const persistedConfig = await pctx.settings.readDeviceStore(m.id);
64380
+ const persistedConfig = configs.get(m.id) ?? {};
64277
64381
  const metadata = row.metadata;
64278
- const sourceInfoChild = buildSourceInfoFromConfig(persistedConfig ?? {}, childStableId, ownerAddonId);
64382
+ const sourceInfoChild = buildSourceInfoFromConfig(persistedConfig, childStableId, ownerAddonId);
64279
64383
  results.push({
64280
64384
  id: m.id,
64281
64385
  stableId: childStableId,
@@ -64290,7 +64394,7 @@ var require_device_manager_addon = __commonJS({
64290
64394
  probed: pctx.host.resolveDeviceProbed(m.id),
64291
64395
  features: persistedFeatures(m.features),
64292
64396
  isCamera: false,
64293
- config: persistedConfig ?? {},
64397
+ config: persistedConfig,
64294
64398
  metadata,
64295
64399
  ...m.integrationId !== void 0 ? { integrationId: m.integrationId } : {},
64296
64400
  ...m.linkDeviceId !== void 0 ? { linkDeviceId: m.linkDeviceId } : {},
@@ -64317,7 +64421,8 @@ var require_device_manager_addon = __commonJS({
64317
64421
  }
64318
64422
  const childRows = await pctx.metaStore.rows.listByParent(parentDeviceId);
64319
64423
  const liveChildren = pctx.registry?.getChildren(parentDeviceId) ?? [];
64320
- return [...await projectChildren(pctx, parentDeviceId, ownerAddonId, childRows, liveChildren)];
64424
+ const configs = await readDeviceConfigs(pctx, persistedChildIds(childRows, liveChildren));
64425
+ return [...projectChildren(pctx, parentDeviceId, ownerAddonId, childRows, liveChildren, configs)];
64321
64426
  }
64322
64427
  async function getChildrenBatch(pctx, input) {
64323
64428
  const parentIds = [...new Set(input.parentDeviceIds)];
@@ -64345,11 +64450,14 @@ var require_device_manager_addon = __commonJS({
64345
64450
  if (bucket === void 0) liveByParent.set(parentId, [device]);
64346
64451
  else bucket.push(device);
64347
64452
  }
64453
+ const configIds = [];
64454
+ for (const parentId of owners.keys()) configIds.push(...persistedChildIds(rowsByParent.get(parentId) ?? [], liveByParent.get(parentId) ?? []));
64455
+ const configs = await readDeviceConfigs(pctx, configIds);
64348
64456
  for (const [parentId, ownerAddonId] of owners) {
64349
64457
  const childRows = rowsByParent.get(parentId) ?? [];
64350
64458
  const liveChildren = liveByParent.get(parentId) ?? [];
64351
64459
  if (childRows.length === 0 && liveChildren.length === 0) continue;
64352
- const projected = await projectChildren(pctx, parentId, ownerAddonId, childRows, liveChildren);
64460
+ const projected = projectChildren(pctx, parentId, ownerAddonId, childRows, liveChildren, configs);
64353
64461
  if (projected.length > 0) out[String(parentId)] = [...projected];
64354
64462
  }
64355
64463
  return out;
@@ -65391,18 +65499,20 @@ var require_device_manager_addon = __commonJS({
65391
65499
  }
65392
65500
  async function setWrapperActive(deps, input) {
65393
65501
  const storeKey = String(input.deviceId);
65394
- const store = await readBindingsStore(deps.bindingsDeps);
65395
- const perDevice = { ...store.deviceBindings[storeKey] };
65396
- if (input.active) perDevice[input.capName] = { wrapperAddonId: input.wrapperAddonId };
65397
- else perDevice[input.capName] = { wrapperAddonId: null };
65398
- const nextDeviceBindings = Object.keys(perDevice).length > 0 ? {
65399
- ...store.deviceBindings,
65400
- [storeKey]: perDevice
65401
- } : (() => {
65402
- const { [storeKey]: _drop, ...rest } = store.deviceBindings;
65403
- return rest;
65404
- })();
65405
- await writeBindingsStore(deps.bindingsDeps, { deviceBindings: nextDeviceBindings });
65502
+ await deps.bindingsDeps.withAddonStoreWriteLock(async () => {
65503
+ const store = await readBindingsStore(deps.bindingsDeps);
65504
+ const perDevice = { ...store.deviceBindings[storeKey] };
65505
+ if (input.active) perDevice[input.capName] = { wrapperAddonId: input.wrapperAddonId };
65506
+ else perDevice[input.capName] = { wrapperAddonId: null };
65507
+ const nextDeviceBindings = Object.keys(perDevice).length > 0 ? {
65508
+ ...store.deviceBindings,
65509
+ [storeKey]: perDevice
65510
+ } : (() => {
65511
+ const { [storeKey]: _drop, ...rest } = store.deviceBindings;
65512
+ return rest;
65513
+ })();
65514
+ await writeBindingsStore(deps.bindingsDeps, { deviceBindings: nextDeviceBindings });
65515
+ });
65406
65516
  deps.ctx.eventBus.emit({
65407
65517
  id: (0, node_crypto.randomUUID)(),
65408
65518
  timestamp: /* @__PURE__ */ new Date(),
@@ -65681,12 +65791,13 @@ var require_device_manager_addon = __commonJS({
65681
65791
  });
65682
65792
  await pctx.settings.clearDeviceStore(deviceId);
65683
65793
  await pctx.settings.clearDeviceRuntimeState(deviceId);
65684
- const bindingsStore = await readBindingsStore(pctx.bindingsDeps);
65685
65794
  const bindingKey = String(deviceId);
65686
- if (bindingsStore.deviceBindings[bindingKey]) {
65795
+ await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
65796
+ const bindingsStore = await readBindingsStore(pctx.bindingsDeps);
65797
+ if (!bindingsStore.deviceBindings[bindingKey]) return;
65687
65798
  const { [bindingKey]: _removedBindings, ...restBindings } = bindingsStore.deviceBindings;
65688
65799
  await writeBindingsStore(pctx.bindingsDeps, { deviceBindings: restBindings });
65689
- }
65800
+ });
65690
65801
  pctx.host.remoteNativeCaps.delete(deviceId);
65691
65802
  pctx.host.capabilityRegistry?.unregisterAllNativeForDevice(deviceId);
65692
65803
  pctx.metaStore.idToAddonId.delete(deviceId);
@@ -66028,7 +66139,9 @@ var require_device_manager_addon = __commonJS({
66028
66139
  ...def,
66029
66140
  unit: require_dist10.normalizeUnit(def.unit) ?? def.unit
66030
66141
  } : def]));
66031
- await pctx.settings.writeAddonStore({ roleDisplayDefaults: normalized });
66142
+ await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
66143
+ await pctx.settings.writeAddonStore({ roleDisplayDefaults: normalized });
66144
+ });
66032
66145
  }
66033
66146
  async function applyInitialMeta(pctx, input) {
66034
66147
  const { deviceId, name, location, type, integrationId, linkDeviceId, role } = input;
@@ -66171,16 +66284,21 @@ var require_device_manager_addon = __commonJS({
66171
66284
  async function addLocation(pctx, input) {
66172
66285
  const trimmed = input.name.trim();
66173
66286
  if (trimmed.length === 0) throw new Error("[device-manager] addLocation: name must be non-empty");
66174
- const current = (await pctx.metaStore.readStore()).locations ?? [];
66175
- if (current.some((l) => l.toLowerCase() === trimmed.toLowerCase())) return;
66176
- await pctx.settings.writeAddonStore({ locations: [...current, trimmed] });
66287
+ await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
66288
+ const current = (await pctx.metaStore.readStore()).locations ?? [];
66289
+ if (current.some((l) => l.toLowerCase() === trimmed.toLowerCase())) return;
66290
+ await pctx.settings.writeAddonStore({ locations: [...current, trimmed] });
66291
+ });
66177
66292
  }
66178
66293
  async function removeLocation(pctx, input) {
66179
66294
  const trimmed = input.name.trim();
66180
66295
  if (trimmed.length === 0) return;
66181
- const current = (await pctx.metaStore.readStore()).locations ?? [];
66182
- const remaining = current.filter((l) => l.toLowerCase() !== trimmed.toLowerCase());
66183
- if (remaining.length !== current.length) await pctx.settings.writeAddonStore({ locations: remaining });
66296
+ await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
66297
+ const current = (await pctx.metaStore.readStore()).locations ?? [];
66298
+ const remaining = current.filter((l) => l.toLowerCase() !== trimmed.toLowerCase());
66299
+ if (remaining.length === current.length) return;
66300
+ await pctx.settings.writeAddonStore({ locations: remaining });
66301
+ });
66184
66302
  if (input.cascade !== true) return;
66185
66303
  const cleared = await pctx.metaStore.withMetaWriteLock(async () => {
66186
66304
  const out = [];
@@ -66221,23 +66339,40 @@ var require_device_manager_addon = __commonJS({
66221
66339
  function isRoleDisplayDefaults(value) {
66222
66340
  return value !== null && typeof value === "object" && !Array.isArray(value);
66223
66341
  }
66342
+ function createWriteLock() {
66343
+ let chain = Promise.resolve();
66344
+ return async (fn) => {
66345
+ const previous = chain;
66346
+ let release = () => {
66347
+ };
66348
+ chain = new Promise((resolve) => {
66349
+ release = resolve;
66350
+ });
66351
+ try {
66352
+ await previous.catch(() => {
66353
+ });
66354
+ return await fn();
66355
+ } finally {
66356
+ release();
66357
+ }
66358
+ };
66359
+ }
66224
66360
  var DeviceMetaStore = class {
66225
66361
  settings;
66226
66362
  registry;
66227
66363
  rows;
66364
+ withAddonStoreWriteLock;
66228
66365
  /** Synchronous ownership cache, keyed by NUMERIC deviceId → owning addonId.
66229
66366
  * The persisted row store is authoritative but reads are async; hub-side
66230
66367
  * callers (e.g. `CapabilityRegistry.getNativeProvider` fallback) need
66231
66368
  * ownership without awaiting. Kept in sync with every register/remove and
66232
66369
  * warmed from persistence on boot. */
66233
66370
  idToAddonId = /* @__PURE__ */ new Map();
66234
- /** Serialises every read-modify-write of a device row through one promise
66235
- * chain (see `withMetaWriteLock`). Per-instance state. */
66236
- metaWriteChain = Promise.resolve();
66237
- constructor(settings, registry, rows) {
66371
+ constructor(settings, registry, rows, withAddonStoreWriteLock = createWriteLock()) {
66238
66372
  this.settings = settings;
66239
66373
  this.registry = registry;
66240
66374
  this.rows = rows;
66375
+ this.withAddonStoreWriteLock = withAddonStoreWriteLock;
66241
66376
  }
66242
66377
  /** The read currently in flight, or null. Never a settled value — see
66243
66378
  * {@link readStore}. */
@@ -66268,22 +66403,7 @@ var require_device_manager_addon = __commonJS({
66268
66403
  this.inFlightRead = read;
66269
66404
  return read;
66270
66405
  };
66271
- withMetaWriteLock = async (fn) => {
66272
- const previous = this.metaWriteChain;
66273
- let release = () => {
66274
- };
66275
- const next = new Promise((resolve) => {
66276
- release = resolve;
66277
- });
66278
- this.metaWriteChain = next;
66279
- try {
66280
- await previous.catch(() => {
66281
- });
66282
- return await fn();
66283
- } finally {
66284
- release();
66285
- }
66286
- };
66406
+ withMetaWriteLock = createWriteLock();
66287
66407
  /** The whole persisted row for one device, or `null`. */
66288
66408
  getRow = async (deviceId) => this.rows.get(deviceId);
66289
66409
  /**
@@ -66317,11 +66437,24 @@ var require_device_manager_addon = __commonJS({
66317
66437
  ids.delete(parentId);
66318
66438
  return [...ids];
66319
66439
  };
66320
- allocateNextDeviceId = async () => {
66440
+ /**
66441
+ * Mint the next numeric device id.
66442
+ *
66443
+ * A read-modify-write of `nextDeviceId`, and it must hold the addon-store
66444
+ * lock for the whole of it. Serialising the WRITES is not enough and never
66445
+ * was: two callers that both read `N` both write `N + 1` and both return
66446
+ * `N`, so two devices receive the same id. (`writeAddonStore` is itself
66447
+ * read-modify-write over the addon's whole key range, so an unlocked bump
66448
+ * can also be reverted wholesale by a concurrent `setWrapperActive`.)
66449
+ *
66450
+ * Callers hold {@link withMetaWriteLock} — this is the one nesting, and it
66451
+ * only ever goes meta ⊃ addon-store.
66452
+ */
66453
+ allocateNextDeviceId = async () => this.withAddonStoreWriteLock(async () => {
66321
66454
  const current = (await this.readStore()).nextDeviceId ?? 1;
66322
66455
  await this.settings.writeAddonStore({ nextDeviceId: current + 1 });
66323
66456
  return current;
66324
- };
66457
+ });
66325
66458
  };
66326
66459
  var CENSUS_STACK_FRAMES = 3;
66327
66460
  function siteFromStack(stack) {
@@ -67515,6 +67648,17 @@ var require_device_manager_addon = __commonJS({
67515
67648
  * it through {@link bindingsDeps} or the `ProviderContext`.
67516
67649
  */
67517
67650
  deviceRows = null;
67651
+ /**
67652
+ * The ONE lock ordering every read-modify-write of this addon's
67653
+ * `addon-settings` key range (`deviceBindings`, `locations`, `nextDeviceId`,
67654
+ * `roleDisplayDefaults`).
67655
+ *
67656
+ * It lives on the addon rather than on `DeviceMetaStore` because the writers
67657
+ * do not all go through that class — `setWrapperActive` and `removeDevice`'s
67658
+ * bindings purge reach the store through `BindingsDeps` — and two locks over
67659
+ * one key range is the same defect one level up. Handed to both.
67660
+ */
67661
+ addonStoreWriteLock = createWriteLock();
67518
67662
  /** Build the dependency context the extracted binding resolvers consume. */
67519
67663
  get bindingsDeps() {
67520
67664
  const rows = this.deviceRows;
@@ -67523,7 +67667,8 @@ var require_device_manager_addon = __commonJS({
67523
67667
  ctx: this.ctx,
67524
67668
  capabilityRegistry: this.capabilityRegistry,
67525
67669
  remoteNativeCaps: this.remoteNativeCaps,
67526
- rows
67670
+ rows,
67671
+ withAddonStoreWriteLock: this.addonStoreWriteLock
67527
67672
  };
67528
67673
  }
67529
67674
  async getBindings(input) {
@@ -67592,7 +67737,7 @@ var require_device_manager_addon = __commonJS({
67592
67737
  } catch (err) {
67593
67738
  this.ctx.logger.warn("retired-row purge failed", { meta: { error: require_dist10.errMsg(err) } });
67594
67739
  }
67595
- const metaStore = new DeviceMetaStore(settings, registry, deviceRows);
67740
+ const metaStore = new DeviceMetaStore(settings, registry, deviceRows, this.addonStoreWriteLock);
67596
67741
  this.stateMirrorImpl = new DeviceStateMirror(this.ctx);
67597
67742
  const stateMirror = this.stateMirrorImpl;
67598
67743
  const resolvePersistedById = metaStore.resolvePersistedById;
@@ -67844,7 +67989,7 @@ var require_hub_forwarder = __commonJS({
67844
67989
  [Symbol.toStringTag]: { value: "Module" }
67845
67990
  });
67846
67991
  require_chunk_Cek0wNdY();
67847
- var require_dist10 = require_dist_Ck2jkBZk();
67992
+ var require_dist10 = require_dist_Keu5TDO7();
67848
67993
  var require_formatter = require_formatter_DqAKDlvN();
67849
67994
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
67850
67995
  var HubForwarderDestination = class {
@@ -67981,7 +68126,7 @@ var require_liveness_monitor_addon = __commonJS({
67981
68126
  "use strict";
67982
68127
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
67983
68128
  require_chunk_Cek0wNdY();
67984
- var require_dist10 = require_dist_Ck2jkBZk();
68129
+ var require_dist10 = require_dist_Keu5TDO7();
67985
68130
  var NO_DEVICES = "liveness:no-devices";
67986
68131
  var ALL_OFFLINE = "liveness:all-devices-offline";
67987
68132
  var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
@@ -68171,7 +68316,7 @@ var require_local_auth_addon = __commonJS({
68171
68316
  [Symbol.toStringTag]: { value: "Module" }
68172
68317
  });
68173
68318
  var require_chunk = require_chunk_Cek0wNdY();
68174
- var require_dist10 = require_dist_Ck2jkBZk();
68319
+ var require_dist10 = require_dist_Keu5TDO7();
68175
68320
  var node_crypto = __require("crypto");
68176
68321
  node_crypto = require_chunk.__toESM(node_crypto);
68177
68322
  var crypto$1 = __require("crypto");
@@ -75984,7 +76129,7 @@ var require_loki_logging = __commonJS({
75984
76129
  [Symbol.toStringTag]: { value: "Module" }
75985
76130
  });
75986
76131
  require_chunk_Cek0wNdY();
75987
- var require_dist10 = require_dist_Ck2jkBZk();
76132
+ var require_dist10 = require_dist_Keu5TDO7();
75988
76133
  function sanitizeLabelName(raw) {
75989
76134
  const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
75990
76135
  return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
@@ -76549,7 +76694,7 @@ var require_native_metrics_addon = __commonJS({
76549
76694
  [Symbol.toStringTag]: { value: "Module" }
76550
76695
  });
76551
76696
  var require_chunk = require_chunk_Cek0wNdY();
76552
- var require_dist10 = require_dist_Ck2jkBZk();
76697
+ var require_dist10 = require_dist_Keu5TDO7();
76553
76698
  var node_fs_promises = __require("fs/promises");
76554
76699
  var node_child_process = __require("child_process");
76555
76700
  var node_util = __require("util");
@@ -79171,7 +79316,7 @@ var require_filesystem_storage_addon = __commonJS({
79171
79316
  [Symbol.toStringTag]: { value: "Module" }
79172
79317
  });
79173
79318
  var require_chunk = require_chunk_Cek0wNdY();
79174
- var require_dist10 = require_dist_Ck2jkBZk();
79319
+ var require_dist10 = require_dist_Keu5TDO7();
79175
79320
  var node_crypto = __require("crypto");
79176
79321
  var node_fs_promises = __require("fs/promises");
79177
79322
  var node_path = __require("path");
@@ -80287,8 +80432,8 @@ var require_sqlite_settings_addon = __commonJS({
80287
80432
  [Symbol.toStringTag]: { value: "Module" }
80288
80433
  });
80289
80434
  var require_chunk = require_chunk_Cek0wNdY();
80290
- var require_dist10 = require_dist_Ck2jkBZk();
80291
- var require_retired_settings_keys = require_retired_settings_keys_Dp_CyuCW();
80435
+ var require_dist10 = require_dist_Keu5TDO7();
80436
+ var require_retired_settings_keys = require_retired_settings_keys_BBohF_eC();
80292
80437
  var node_crypto = __require("crypto");
80293
80438
  var node_fs = __require("fs");
80294
80439
  var node_module = __require("module");
@@ -80619,11 +80764,16 @@ var require_sqlite_settings_addon = __commonJS({
80619
80764
  const resolve = (field) => {
80620
80765
  const expr = fieldExprFor(field, shape);
80621
80766
  if (expr === null && mode === "mutate") throw new UnsafeFilterError(`filter refers to "${field}", which this collection cannot express \u2014 refusing to run a bulk mutation with a dropped predicate`);
80767
+ if (expr === null && mode === "measure") throw new UnsafeFilterError(`filter refers to "${field}", which this collection cannot express \u2014 refusing to answer with a number counted over a different question`);
80622
80768
  return expr;
80623
80769
  };
80624
80770
  for (const [field, value] of Object.entries(filter?.where ?? {})) {
80625
80771
  const expr = resolve(field);
80626
80772
  if (expr === null) continue;
80773
+ if (value === null) {
80774
+ clauses.push(`${expr} IS NULL`);
80775
+ continue;
80776
+ }
80627
80777
  clauses.push(`${expr} = ?`);
80628
80778
  params.push(serialize2(value));
80629
80779
  }
@@ -80646,6 +80796,10 @@ var require_sqlite_settings_addon = __commonJS({
80646
80796
  for (const [field, value] of Object.entries(filter?.whereNot ?? {})) {
80647
80797
  const expr = resolve(field);
80648
80798
  if (expr === null) continue;
80799
+ if (value === null) {
80800
+ clauses.push(`${expr} IS NOT NULL`);
80801
+ continue;
80802
+ }
80649
80803
  clauses.push(`(${expr} IS NULL OR ${expr} != ?)`);
80650
80804
  params.push(serialize2(value));
80651
80805
  }
@@ -81158,13 +81312,17 @@ var require_sqlite_settings_addon = __commonJS({
81158
81312
  * (visibility, kind, device, time window, expiry) — so `total` counted rows
81159
81313
  * the page could never show, including other users'.
81160
81314
  *
81161
- * `select` mode, deliberately: `count` must be forgiving in exactly the way
81162
- * `query` is, or the two disagree again for a new reason.
81315
+ * `measure` mode. `count` compiles the same predicates `query` does — the
81316
+ * two must never disagree about WHICH rows they are talking about — but it
81317
+ * refuses a predicate it cannot express rather than skipping it. `query`
81318
+ * hands back rows and a caller can see the filter did not bite; a count
81319
+ * hands back a number, and the whole collection and the intended subset are
81320
+ * the same shape.
81163
81321
  */
81164
81322
  async count({ namespace, collection, filter }) {
81165
81323
  const scoped = this.scopedName(namespace, collection);
81166
81324
  const decl = this.requireDeclared(scoped);
81167
- const { whereSql, params } = compileFilter$1(filter, this.shapeOf(decl), "select", (v) => this.serializeColumnValue(v));
81325
+ const { whereSql, params } = compileFilter$1(filter, this.shapeOf(decl), "measure", (v) => this.serializeColumnValue(v));
81168
81326
  const sql = `SELECT COUNT(*) AS cnt FROM "${scoped}"${whereSql}`;
81169
81327
  return this.measured({
81170
81328
  op: "count",
@@ -81182,8 +81340,10 @@ var require_sqlite_settings_addon = __commonJS({
81182
81340
  * projection — an unresolvable field THROWS rather than being dropped, because
81183
81341
  * a missing aggregate comes back as a number that looks real.
81184
81342
  *
81185
- * `select` mode on the filter, so this agrees with `query` and `count` about
81186
- * which rows it is talking about.
81343
+ * `measure` mode on the filter, so this agrees with `query` and `count`
81344
+ * about which rows it is talking about — and, since 2026-08-30, refuses an
81345
+ * unresolvable PREDICATE for the same reason it already refused an
81346
+ * unresolvable FIELD. Both come back as a number that looks real.
81187
81347
  */
81188
81348
  async aggregate({ namespace, collection, fields, filter }) {
81189
81349
  const scoped = this.scopedName(namespace, collection);
@@ -81195,7 +81355,7 @@ var require_sqlite_settings_addon = __commonJS({
81195
81355
  if (col === null) throw new UnsafeFilterError(`aggregate cannot read "${f.field}" on "${scoped}" \u2014 it is not a column of this collection, and answering without it would return a number that looks real`);
81196
81356
  selects.push(`${f.op.toUpperCase()}(${col}) AS "a${i}"`);
81197
81357
  });
81198
- const { whereSql, params } = compileFilter$1(filter, shape, "select", (v) => this.serializeColumnValue(v));
81358
+ const { whereSql, params } = compileFilter$1(filter, shape, "measure", (v) => this.serializeColumnValue(v));
81199
81359
  const sql = `SELECT ${selects.join(", ")} FROM "${scoped}"${whereSql}`;
81200
81360
  const row = this.measured({
81201
81361
  op: "aggregate",
@@ -81219,7 +81379,7 @@ var require_sqlite_settings_addon = __commonJS({
81219
81379
  const shape = this.shapeOf(decl);
81220
81380
  const col = fieldExprFor(field, shape);
81221
81381
  if (col === null) return [];
81222
- const { whereSql: where, params } = compileFilter$1(filter, shape, "select", (v) => this.serializeColumnValue(v));
81382
+ const { whereSql: where, params } = compileFilter$1(filter, shape, "measure", (v) => this.serializeColumnValue(v));
81223
81383
  const sql = `SELECT ${`CAST((${col} - ?) / ? AS INTEGER)`} AS bucket, COUNT(*) AS count FROM "${scoped}"${where} GROUP BY bucket ORDER BY bucket`;
81224
81384
  return this.measured({
81225
81385
  op: "histogram",
@@ -81500,8 +81660,10 @@ var require_sqlite_settings_addon = __commonJS({
81500
81660
  * connection whose `sqlite_stat1` visibility and temp schema match.
81501
81661
  *
81502
81662
  * `EXPLAIN QUERY PLAN` prepares and plans; it does not execute the statement,
81503
- * so it costs no page reads of its own. It is still called only from the
81504
- * slow-call path, once per shape per window.
81663
+ * so it costs no page reads of its own. In production it is called only from
81664
+ * the slow-call path, once per shape per window; it is public so a guard can
81665
+ * assert that a declared index is the one the planner actually picks — "we
81666
+ * added an index" and "the query uses it" are different claims.
81505
81667
  *
81506
81668
  * Never throws. A plan that cannot be taken (a statement the engine will no
81507
81669
  * longer prepare, a closed handle mid-shutdown) is a missing ANSWER, not a
@@ -82650,7 +82812,7 @@ var require_storage_orchestrator_addon = __commonJS({
82650
82812
  [Symbol.toStringTag]: { value: "Module" }
82651
82813
  });
82652
82814
  var require_chunk = require_chunk_Cek0wNdY();
82653
- var require_dist10 = require_dist_Ck2jkBZk();
82815
+ var require_dist10 = require_dist_Keu5TDO7();
82654
82816
  var node_crypto = __require("crypto");
82655
82817
  var node_fs_promises = __require("fs/promises");
82656
82818
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -82856,6 +83018,10 @@ var require_storage_orchestrator_addon = __commonJS({
82856
83018
  "recorder",
82857
83019
  "pipeline"
82858
83020
  ];
83021
+ function describeUnstamped(lane) {
83022
+ if (!lane.present) return "0";
83023
+ return lane.rows === null ? "an unknown number of" : lane.rows.toString();
83024
+ }
82859
83025
  var StorageMigrationCoordinator = class {
82860
83026
  deps;
82861
83027
  active = null;
@@ -82899,12 +83065,21 @@ var require_storage_orchestrator_addon = __commonJS({
82899
83065
  if (moves.length === 0) throw new Error("select at least one storage class");
82900
83066
  if (moves.some((move) => move.storageClass === "eventMedia")) {
82901
83067
  const unstamped = await this.deps.participants.analytics.countUnstamped();
82902
- if (unstamped.total > 0) {
82903
- if (mode === "nonBlocking") throw new Error(`${unstamped.total.toString()} event-media row(s) still carry no locationId (${unstamped.media.toString()} media, ${unstamped.retrainFrames.toString()} retrain frames). A non-blocking cutover would silently orphan them. Run the seal first: pipelineAnalytics.relocateMedia({ mode: "seal", toLocationId: "${this.deps.locations.getDefaultLocation("eventMedia")?.id ?? "eventMedia"}" }).`);
83068
+ if (unstamped === null) {
83069
+ if (mode === "nonBlocking") throw new Error("could not determine whether any event-media row still carries no locationId. A non-blocking cutover repoints the default and would silently orphan every such row, so an unmeasured collection is refused exactly like a non-empty one. Retry, or run the blocking mode, which stamps rows as it moves them.");
83070
+ findings.push({
83071
+ code: "unstampedEventMediaRows",
83072
+ storageClass: "eventMedia",
83073
+ message: "the count of event-media rows carrying no locationId could not be taken \u2014 treat it as unknown, not as zero. This blocking migration stamps them as it moves them, so it is safe regardless; a non-blocking one would be refused."
83074
+ });
83075
+ } else if (unstamped.anyPresent) {
83076
+ const scale = unstamped.total === null ? "an unknown number of" : unstamped.total.toString();
83077
+ const perLane = `${describeUnstamped(unstamped.media)} media, ${describeUnstamped(unstamped.retrainFrames)} retrain frames`;
83078
+ if (mode === "nonBlocking") throw new Error(`${scale} event-media row(s) still carry no locationId (${perLane}). A non-blocking cutover would silently orphan them. Run the seal first: pipelineAnalytics.relocateMedia({ mode: "seal", toLocationId: "${this.deps.locations.getDefaultLocation("eventMedia")?.id ?? "eventMedia"}" }).`);
82904
83079
  findings.push({
82905
83080
  code: "unstampedEventMediaRows",
82906
83081
  storageClass: "eventMedia",
82907
- message: `${unstamped.total.toString()} event-media row(s) carry no locationId. This blocking migration stamps them as it moves them, but a non-blocking one would be refused until a seal pass drives the count to zero.`
83082
+ message: `${scale} event-media row(s) carry no locationId (${perLane}). This blocking migration stamps them as it moves them, but a non-blocking one would be refused until a seal pass drives the count to zero.`
82908
83083
  });
82909
83084
  }
82910
83085
  }
@@ -83153,6 +83328,7 @@ var require_storage_orchestrator_addon = __commonJS({
83153
83328
  const remaining = residue.filter((entry) => entry.storageClass === storageClass);
83154
83329
  if (remaining.length === 0) throw new Error(`"${storageClass}" has nothing left outside its default location \u2014 there is nothing to drain. To CHANGE where it writes, use storageMigration.start with a new destination.`);
83155
83330
  for (const entry of remaining) {
83331
+ if (this.deps.locations.getLocationById(entry.toLocationId)?.enabled === false) throw new Error(`The "${storageClass}" drain destination "${entry.toLocationId}" is DISABLED \u2014 a disabled location takes no writes, and a drain is the largest write there is. Enable it on the Storage screen, or make an enabled location the "${storageClass}" default first.`);
83156
83332
  moves.push({
83157
83333
  storageClass,
83158
83334
  fromLocationId: entry.fromLocationId,
@@ -83404,6 +83580,7 @@ var require_storage_orchestrator_addon = __commonJS({
83404
83580
  filesMoved: status.filesMoved,
83405
83581
  filesTotal: status.filesTotal,
83406
83582
  bytesMoved: status.bytesMoved,
83583
+ ...status.rowsReconciled === void 0 ? {} : { rowsReconciled: status.rowsReconciled },
83407
83584
  startedAt: status.startedAt,
83408
83585
  observedAt: this.deps.now()
83409
83586
  };
@@ -83426,9 +83603,10 @@ var require_storage_orchestrator_addon = __commonJS({
83426
83603
  *
83427
83604
  * The gate is the RE-COUNT, not the seal job's terminal state: a seal that
83428
83605
  * failed some rows still finishes, and "finished" is not "there are none
83429
- * left". A non-zero count here fails the job while nothing has been paused
83606
+ * left". A remaining row here fails the job while nothing has been paused
83430
83607
  * and nothing has been repointed, which is the cheapest possible place to
83431
- * discover it.
83608
+ * discover it. So does a re-count that could not be TAKEN: the gate opens on
83609
+ * a measured absence, and only on that.
83432
83610
  *
83433
83611
  * The seal's own mover job id is deliberately NOT durable. It is idempotent
83434
83612
  * and cheap, so a coordinator restart mid-seal simply re-runs the whole
@@ -83438,7 +83616,8 @@ var require_storage_orchestrator_addon = __commonJS({
83438
83616
  async sealEventMedia(job) {
83439
83617
  const move = job.moves.find((candidate) => candidate.storageClass === "eventMedia");
83440
83618
  if (move === void 0) return;
83441
- if ((await this.deps.participants.analytics.countUnstamped()).total > 0) {
83619
+ const before = await this.deps.participants.analytics.countUnstamped();
83620
+ if (before === null || before.anyPresent) {
83442
83621
  const started = await this.deps.participants.analytics.startDrain({
83443
83622
  toLocationId: move.fromLocationId,
83444
83623
  mode: "seal"
@@ -83447,7 +83626,8 @@ var require_storage_orchestrator_addon = __commonJS({
83447
83626
  if (job.cancelRequested) return;
83448
83627
  }
83449
83628
  const after = await this.deps.participants.analytics.countUnstamped();
83450
- if (after.total > 0) throw new Error(`event-media seal left ${after.total.toString()} row(s) without a locationId (${after.media.toString()} media, ${after.retrainFrames.toString()} retrain frames). A non-blocking cutover would silently orphan them; nothing has been paused or repointed.`);
83629
+ if (after === null) throw new Error("event-media seal ran, but whether any row still carries no locationId could not be measured afterwards. Unknown is not zero; nothing has been paused or repointed.");
83630
+ if (after.anyPresent) throw new Error(`event-media seal left ${after.total === null ? "an unknown number of" : after.total.toString()} row(s) without a locationId (${describeUnstamped(after.media)} media, ${describeUnstamped(after.retrainFrames)} retrain frames). A non-blocking cutover would silently orphan them; nothing has been paused or repointed.`);
83451
83631
  }
83452
83632
  async waitForSeal(job, moverJobId) {
83453
83633
  const sleep = this.deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
@@ -83796,6 +83976,34 @@ var require_storage_orchestrator_addon = __commonJS({
83796
83976
  backfilled: backfill.length,
83797
83977
  total: this.locations.size
83798
83978
  } });
83979
+ this.reportDisabledDefaults();
83980
+ }
83981
+ /**
83982
+ * Shout about a persisted row that is BOTH the type default and disabled.
83983
+ *
83984
+ * `upsertLocation` cannot produce this (`resolveEnabled` force-enables a
83985
+ * default) and neither can `setDefaultLocations` — but hydrate writes rows
83986
+ * straight into the map without either, so a hand-edited store, a partial
83987
+ * migration or a row written by another build can. It matters because every
83988
+ * bare-type ref resolves through {@link getDefaultLocation}: on such a row
83989
+ * `storage.write({ location: 'eventMedia' })` and the migration coordinator's
83990
+ * drain destination both point at a disk the operator turned off. That is the
83991
+ * one thing this model must never do silently.
83992
+ *
83993
+ * Reported, never repaired: flipping an operator's flag back on at boot would
83994
+ * be the system overruling the switch instead of obeying it. The coordinator
83995
+ * refuses the drain (`storage-migration-coordinator.drain`); the bare-ref read
83996
+ * path is deliberately left working, because narrowing a READ is a worse bug
83997
+ * than the one this line reports.
83998
+ */
83999
+ reportDisabledDefaults() {
84000
+ for (const loc of this.locations.values()) {
84001
+ if (!loc.isDefault || loc.enabled !== false) continue;
84002
+ this.logger.error("storage-orchestrator: the type DEFAULT is disabled \u2014 every bare-type ref for this type resolves to a location the operator turned off. Enable it, or make another location the default.", { meta: {
84003
+ id: loc.id,
84004
+ type: loc.type
84005
+ } });
84006
+ }
83799
84007
  }
83800
84008
  /**
83801
84009
  * Inject the declaration-driven cardinality source. Called once by the
@@ -83867,6 +84075,7 @@ var require_storage_orchestrator_addon = __commonJS({
83867
84075
  loaded: this.locations.size,
83868
84076
  isSystemUpgraded: upgraded
83869
84077
  } });
84078
+ this.reportDisabledDefaults();
83870
84079
  }
83871
84080
  /**
83872
84081
  * Boot backfill (SP1): stamp `nodeId` on every persisted node-local
@@ -85178,7 +85387,7 @@ var require_system_config_addon = __commonJS({
85178
85387
  [Symbol.toStringTag]: { value: "Module" }
85179
85388
  });
85180
85389
  require_chunk_Cek0wNdY();
85181
- var require_dist10 = require_dist_Ck2jkBZk();
85390
+ var require_dist10 = require_dist_Keu5TDO7();
85182
85391
  var SECTION_TITLES = {
85183
85392
  server: "Server",
85184
85393
  auth: "Authentication"
@@ -103239,7 +103448,7 @@ var require_winston_logging = __commonJS({
103239
103448
  [Symbol.toStringTag]: { value: "Module" }
103240
103449
  });
103241
103450
  var require_chunk = require_chunk_Cek0wNdY();
103242
- var require_dist10 = require_dist_Ck2jkBZk();
103451
+ var require_dist10 = require_dist_Keu5TDO7();
103243
103452
  var require_formatter = require_formatter_DqAKDlvN();
103244
103453
  var node_path = __require("path");
103245
103454
  node_path = require_chunk.__toESM(node_path);
@@ -105182,9 +105391,9 @@ var require_event_category_BaEgqJNv = __commonJS({
105182
105391
  }
105183
105392
  });
105184
105393
 
105185
- // ../types/dist/sleep-CWWLTM6W.js
105186
- var require_sleep_CWWLTM6W = __commonJS({
105187
- "../types/dist/sleep-CWWLTM6W.js"(exports) {
105394
+ // ../types/dist/sleep-C3AniWy-.js
105395
+ var require_sleep_C3AniWy = __commonJS({
105396
+ "../types/dist/sleep-C3AniWy-.js"(exports) {
105188
105397
  "use strict";
105189
105398
  var require_event_category = require_event_category_BaEgqJNv();
105190
105399
  var zod = require_zod();
@@ -107906,6 +108115,7 @@ var require_sleep_CWWLTM6W = __commonJS({
107906
108115
  getEventMedia: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getEventMedia", "query", input),
107907
108116
  getTrackMedia: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getTrackMedia", "query", input),
107908
108117
  listTrackMedia: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listTrackMedia", "query", input),
108118
+ listEventMedia: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listEventMedia", "query", input),
107909
108119
  searchObjectEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "searchObjectEvents", "query", input),
107910
108120
  wipeObjectEmbeddings: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "wipeObjectEmbeddings", "mutation", input),
107911
108121
  rebuildObjectEmbeddings: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "rebuildObjectEmbeddings", "mutation", input),
@@ -108850,7 +109060,7 @@ var require_addon = __commonJS({
108850
109060
  "use strict";
108851
109061
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
108852
109062
  var require_event_category = require_event_category_BaEgqJNv();
108853
- var require_sleep = require_sleep_CWWLTM6W();
109063
+ var require_sleep = require_sleep_C3AniWy();
108854
109064
  var require_err_msg = require_err_msg_COpsHMw2();
108855
109065
  var CAP_INPUT_DEFAULTS = Object.freeze({
108856
109066
  "addons": { "getLogs": { "limit": 100 } },
@@ -115732,12 +115942,12 @@ var require_dist2 = __commonJS({
115732
115942
  }
115733
115943
  });
115734
115944
 
115735
- // ../system/dist/manifest-system-deps-8boi90D9.js
115736
- var require_manifest_system_deps_8boi90D9 = __commonJS({
115737
- "../system/dist/manifest-system-deps-8boi90D9.js"(exports) {
115945
+ // ../system/dist/manifest-system-deps-DYv4ZPo2.js
115946
+ var require_manifest_system_deps_DYv4ZPo2 = __commonJS({
115947
+ "../system/dist/manifest-system-deps-DYv4ZPo2.js"(exports) {
115738
115948
  "use strict";
115739
115949
  var require_chunk = require_chunk_Cek0wNdY();
115740
- require_dist_Ck2jkBZk();
115950
+ require_dist_Keu5TDO7();
115741
115951
  var node_crypto = __require("crypto");
115742
115952
  node_crypto = require_chunk.__toESM(node_crypto);
115743
115953
  var _camstack_types_node = require_node();
@@ -119845,6 +120055,174 @@ var require_manifest_system_deps_8boi90D9 = __commonJS({
119845
120055
  createClient: (nodeId) => new UdsLocalTransportClient(localEndpointPath(nodeId))
119846
120056
  };
119847
120057
  }
120058
+ var DEFAULT_RETENTION_SECONDS = 300;
120059
+ var DEFAULT_MAX_TRIPLES = 4e3;
120060
+ var PARENT_ROUTED_PROVIDER_ADDON_ID = "(unresolved: parent-routed)";
120061
+ var CapUsageRegistry = class {
120062
+ retentionSeconds;
120063
+ maxTriples;
120064
+ map = /* @__PURE__ */ new Map();
120065
+ triples = 0;
120066
+ droppedTriples = 0;
120067
+ constructor(opts) {
120068
+ this.retentionSeconds = Math.max(1, opts?.retentionSeconds ?? DEFAULT_RETENTION_SECONDS);
120069
+ this.maxTriples = Math.max(1, opts?.maxTriples ?? DEFAULT_MAX_TRIPLES);
120070
+ }
120071
+ /**
120072
+ * Record one observed cap call.
120073
+ *
120074
+ * On the hot path: this runs once per cross-process cap call on hub-main's
120075
+ * event loop, which is the cluster's only queue (D181). Three Map lookups, an
120076
+ * integer increment, and — only when the wall clock has moved on — a bounded
120077
+ * clear. No allocation once a triple exists, and it NEVER throws into the
120078
+ * call it observes.
120079
+ */
120080
+ recordCall(rec) {
120081
+ try {
120082
+ if (rec.callerAddonId === "" || rec.providerAddonId === "" || rec.capName === "") return;
120083
+ if (!Number.isFinite(rec.atMs) || rec.atMs < 0) return;
120084
+ const window2 = this.windowFor(rec);
120085
+ if (window2 === null) return;
120086
+ const sec = Math.floor(rec.atMs / 1e3);
120087
+ if (window2.lastCallAtMs !== 0) {
120088
+ const lastSec = Math.floor(window2.lastCallAtMs / 1e3);
120089
+ const advanced = Math.min(sec - lastSec, this.retentionSeconds);
120090
+ for (let k = 1; k <= advanced; k++) window2.buckets[(lastSec + k) % this.retentionSeconds] = 0;
120091
+ }
120092
+ const idx = sec % this.retentionSeconds;
120093
+ window2.buckets[idx] = (window2.buckets[idx] ?? 0) + 1;
120094
+ if (rec.atMs > window2.lastCallAtMs) window2.lastCallAtMs = rec.atMs;
120095
+ } catch {
120096
+ }
120097
+ }
120098
+ getGraph(opts) {
120099
+ const windowSeconds = Math.max(1, Math.min(opts.windowSeconds, this.retentionSeconds));
120100
+ const minMs = opts.nowMs - windowSeconds * 1e3;
120101
+ const nowSec = Math.floor(opts.nowMs / 1e3);
120102
+ const out = [];
120103
+ for (const [caller, byProvider] of this.map) for (const [provider, byCap] of byProvider) for (const [capName, window2] of byCap) {
120104
+ if (window2.lastCallAtMs < minMs) continue;
120105
+ const count = this.sumWindow(window2, nowSec, windowSeconds);
120106
+ if (count === 0) continue;
120107
+ const callsPerMin = count / windowSeconds * 60;
120108
+ out.push({
120109
+ callerAddonId: caller,
120110
+ providerAddonId: provider,
120111
+ capName,
120112
+ callsPerMin,
120113
+ lastCallAtMs: window2.lastCallAtMs
120114
+ });
120115
+ }
120116
+ return out;
120117
+ }
120118
+ /** See {@link CapUsageStats}. Cheap — three counters, no scan. */
120119
+ getStats() {
120120
+ return {
120121
+ triples: this.triples,
120122
+ maxTriples: this.maxTriples,
120123
+ droppedTriples: this.droppedTriples
120124
+ };
120125
+ }
120126
+ /** Test / diagnostic helper — drops all recorded calls. */
120127
+ clear() {
120128
+ this.map.clear();
120129
+ this.triples = 0;
120130
+ this.droppedTriples = 0;
120131
+ }
120132
+ /**
120133
+ * Sum the buckets covering `(nowSec - windowSeconds, nowSec]`.
120134
+ *
120135
+ * The scan starts at `lastSec`, never at `nowSec`. `recordCall` clears the
120136
+ * buckets the ring has advanced OVER, which by definition stops at the last
120137
+ * write: the buckets for the seconds since then still hold whatever the
120138
+ * previous pass left in them, and reading `nowSec` down would count it. A
120139
+ * triple that went quiet for a couple of minutes and then made one call would
120140
+ * report the burst it made five minutes earlier.
120141
+ *
120142
+ * No lower bound is needed beyond the window itself: every bucket at or below
120143
+ * `lastSec - retention` was cleared by the advance that reached `lastSec`, so
120144
+ * it reads zero anyway. The loop is bounded by `windowSeconds ≤ retention`.
120145
+ */
120146
+ sumWindow(window2, nowSec, windowSeconds) {
120147
+ const lastSec = Math.floor(window2.lastCallAtMs / 1e3);
120148
+ const from = Math.min(nowSec, lastSec);
120149
+ const floorSec = nowSec - windowSeconds;
120150
+ let count = 0;
120151
+ for (let s = from; s > floorSec; s--) count += window2.buckets[s % this.retentionSeconds] ?? 0;
120152
+ return count;
120153
+ }
120154
+ /**
120155
+ * The ring for this triple, creating it if the ceiling allows. `null` means
120156
+ * the observation is dropped — counted, never silent (see
120157
+ * {@link CapUsageStats.droppedTriples}).
120158
+ */
120159
+ windowFor(rec) {
120160
+ let byProvider = this.map.get(rec.callerAddonId);
120161
+ if (!byProvider) {
120162
+ byProvider = /* @__PURE__ */ new Map();
120163
+ this.map.set(rec.callerAddonId, byProvider);
120164
+ }
120165
+ let byCap = byProvider.get(rec.providerAddonId);
120166
+ if (!byCap) {
120167
+ byCap = /* @__PURE__ */ new Map();
120168
+ byProvider.set(rec.providerAddonId, byCap);
120169
+ }
120170
+ const existing = byCap.get(rec.capName);
120171
+ if (existing) return existing;
120172
+ if (this.triples >= this.maxTriples) {
120173
+ this.reclaimExpired(rec.atMs);
120174
+ if (this.triples >= this.maxTriples) {
120175
+ this.droppedTriples++;
120176
+ return null;
120177
+ }
120178
+ byProvider = this.map.get(rec.callerAddonId);
120179
+ if (!byProvider) {
120180
+ byProvider = /* @__PURE__ */ new Map();
120181
+ this.map.set(rec.callerAddonId, byProvider);
120182
+ }
120183
+ byCap = byProvider.get(rec.providerAddonId);
120184
+ if (!byCap) {
120185
+ byCap = /* @__PURE__ */ new Map();
120186
+ byProvider.set(rec.providerAddonId, byCap);
120187
+ }
120188
+ }
120189
+ const created = {
120190
+ buckets: new Uint32Array(this.retentionSeconds),
120191
+ lastCallAtMs: 0
120192
+ };
120193
+ byCap.set(rec.capName, created);
120194
+ this.triples++;
120195
+ return created;
120196
+ }
120197
+ /**
120198
+ * Drop every triple whose whole ring has aged out — its last call is older
120199
+ * than the retention window, so it can contribute to no query. O(triples),
120200
+ * and only reachable when the ceiling is full, so it is amortised away by the
120201
+ * admissions it enables.
120202
+ */
120203
+ reclaimExpired(nowMs) {
120204
+ const cutoff = nowMs - this.retentionSeconds * 1e3;
120205
+ for (const [caller, byProvider] of this.map) {
120206
+ for (const [provider, byCap] of byProvider) {
120207
+ for (const [capName, window2] of byCap) {
120208
+ if (window2.lastCallAtMs >= cutoff) continue;
120209
+ byCap.delete(capName);
120210
+ this.triples--;
120211
+ }
120212
+ if (byCap.size === 0) byProvider.delete(provider);
120213
+ }
120214
+ if (byProvider.size === 0) this.map.delete(caller);
120215
+ }
120216
+ }
120217
+ };
120218
+ var singleton = null;
120219
+ function getCapUsageRegistry() {
120220
+ if (!singleton) singleton = new CapUsageRegistry();
120221
+ return singleton;
120222
+ }
120223
+ function __resetCapUsageRegistryForTests() {
120224
+ singleton = null;
120225
+ }
119848
120226
  function createSharedBusState(retainRecent = false) {
119849
120227
  return {
119850
120228
  handlers: /* @__PURE__ */ new Map(),
@@ -120391,6 +120769,8 @@ var require_manifest_system_deps_8boi90D9 = __commonJS({
120391
120769
  isAddonPinnedCall;
120392
120770
  /** See {@link LocalChildRegistryOptions.capTimeoutMs}. */
120393
120771
  capTimeoutMs;
120772
+ /** See {@link LocalChildRegistryOptions.capUsageObserver}. */
120773
+ capUsageObserver;
120394
120774
  /** Tracks capNames already logged as UDS-routed; one INFO line per capName per process. */
120395
120775
  egressRoutedCaps = /* @__PURE__ */ new Set();
120396
120776
  /** Active event fan-out mode, read once from `CAMSTACK_UDS_EVENT_FANOUT`. */
@@ -120422,6 +120802,7 @@ var require_manifest_system_deps_8boi90D9 = __commonJS({
120422
120802
  this.isAggregatedCollectionMethod = opts.isAggregatedCollectionMethod;
120423
120803
  this.isAddonPinnedCall = opts.isAddonPinnedCall;
120424
120804
  this.capTimeoutMs = opts.capTimeoutMs;
120805
+ this.capUsageObserver = opts.capUsageObserver;
120425
120806
  } else {
120426
120807
  this.server = serverOrOptions;
120427
120808
  this.onUnownedCall = onUnownedCallArg;
@@ -120588,6 +120969,30 @@ var require_manifest_system_deps_8boi90D9 = __commonJS({
120588
120969
  }
120589
120970
  return candidates[0];
120590
120971
  }
120972
+ /**
120973
+ * Publish one cap-usage observation, if a sink is wired.
120974
+ *
120975
+ * Cost discipline — this is on the hot path (D181: hub-main's event loop is
120976
+ * the cluster's only queue): no sink means one undefined check; with a sink
120977
+ * it is one object literal and one `Date.now()`, and the sink itself is O(1).
120978
+ * Wrapped so a broken observer can never fail the call it observes — the
120979
+ * registry swallows too, and BOTH matter: this catch also covers a sink that
120980
+ * is not the registry.
120981
+ */
120982
+ recordCapUsage(callerChildId, providerChildId, capName, methodName) {
120983
+ const sink = this.capUsageObserver;
120984
+ if (sink === void 0 || callerChildId === null) return;
120985
+ try {
120986
+ sink({
120987
+ callerAddonId: callerChildId,
120988
+ providerAddonId: providerChildId ?? "(unresolved: parent-routed)",
120989
+ capName,
120990
+ methodName,
120991
+ atMs: Date.now()
120992
+ });
120993
+ } catch {
120994
+ }
120995
+ }
120591
120996
  /** First child whose cap manifest contains a descriptor matching `predicate`. */
120592
120997
  findChildId(predicate) {
120593
120998
  for (const entry of this.children.values()) if (entry.caps.some(predicate)) return entry.childId;
@@ -120857,7 +121262,9 @@ var require_manifest_system_deps_8boi90D9 = __commonJS({
120857
121262
  const pinTargetsThisNode = pinnedNodeId !== void 0 && this.ownNodeId !== void 0 && pinnedNodeId === this.ownNodeId;
120858
121263
  const aggregated = pinnedNodeId === void 0 && this.isAggregatedCollectionMethod?.(out.capName, out.method) === true;
120859
121264
  const addonPinned = pinnedNodeId === void 0 && this.isAddonPinnedCall?.(out.capName, out.method, out.args) === true;
120860
- if ((!(out.native === true) && !aggregated && !addonPinned && (pinnedNodeId === void 0 || pinTargetsThisNode) ? this.resolveChildId(out.capName, out.deviceId) : null) !== null) {
121265
+ const target = !(out.native === true) && !aggregated && !addonPinned && (pinnedNodeId === void 0 || pinTargetsThisNode) ? this.resolveChildId(out.capName, out.deviceId) : null;
121266
+ this.recordCapUsage(childId, target, out.capName, out.method);
121267
+ if (target !== null) {
120861
121268
  if (!this.egressRoutedCaps.has(out.capName)) {
120862
121269
  this.egressRoutedCaps.add(out.capName);
120863
121270
  this.logger?.info("routed child egress over UDS", { capName: out.capName });
@@ -121714,77 +122121,6 @@ var require_manifest_system_deps_8boi90D9 = __commonJS({
121714
122121
  }
121715
122122
  return links;
121716
122123
  }
121717
- var CapUsageRegistry = class {
121718
- retentionSeconds;
121719
- map = /* @__PURE__ */ new Map();
121720
- constructor(opts) {
121721
- this.retentionSeconds = Math.max(1, opts?.retentionSeconds ?? 300);
121722
- }
121723
- recordCall(rec) {
121724
- try {
121725
- if (rec.callerAddonId === "" || rec.providerAddonId === "" || rec.capName === "") return;
121726
- if (!Number.isFinite(rec.atMs) || rec.atMs < 0) return;
121727
- let byProvider = this.map.get(rec.callerAddonId);
121728
- if (!byProvider) {
121729
- byProvider = /* @__PURE__ */ new Map();
121730
- this.map.set(rec.callerAddonId, byProvider);
121731
- }
121732
- let byCap = byProvider.get(rec.providerAddonId);
121733
- if (!byCap) {
121734
- byCap = /* @__PURE__ */ new Map();
121735
- byProvider.set(rec.providerAddonId, byCap);
121736
- }
121737
- let window2 = byCap.get(rec.capName);
121738
- if (!window2) {
121739
- window2 = {
121740
- buckets: Array.from({ length: this.retentionSeconds }, () => 0),
121741
- lastCallAtMs: 0
121742
- };
121743
- byCap.set(rec.capName, window2);
121744
- }
121745
- const bucketIdx = Math.floor(rec.atMs / 1e3) % this.retentionSeconds;
121746
- window2.buckets[bucketIdx] = (window2.buckets[bucketIdx] ?? 0) + 1;
121747
- if (rec.atMs > window2.lastCallAtMs) window2.lastCallAtMs = rec.atMs;
121748
- } catch {
121749
- }
121750
- }
121751
- getGraph(opts) {
121752
- const windowSeconds = Math.max(1, Math.min(opts.windowSeconds, this.retentionSeconds));
121753
- const minMs = opts.nowMs - windowSeconds * 1e3;
121754
- const out = [];
121755
- for (const [caller, byProvider] of this.map) for (const [provider, byCap] of byProvider) for (const [capName, window2] of byCap) {
121756
- if (window2.lastCallAtMs < minMs) continue;
121757
- const nowBucket = Math.floor(opts.nowMs / 1e3);
121758
- let count = 0;
121759
- for (let i = 0; i < windowSeconds; i++) {
121760
- const b = (nowBucket - i + this.retentionSeconds * 1e6) % this.retentionSeconds;
121761
- count += window2.buckets[b] ?? 0;
121762
- }
121763
- if (count === 0) continue;
121764
- const callsPerMin = count / windowSeconds * 60;
121765
- out.push({
121766
- callerAddonId: caller,
121767
- providerAddonId: provider,
121768
- capName,
121769
- callsPerMin,
121770
- lastCallAtMs: window2.lastCallAtMs
121771
- });
121772
- }
121773
- return out;
121774
- }
121775
- /** Test / diagnostic helper — drops all recorded calls. */
121776
- clear() {
121777
- this.map.clear();
121778
- }
121779
- };
121780
- var singleton = null;
121781
- function getCapUsageRegistry() {
121782
- if (!singleton) singleton = new CapUsageRegistry();
121783
- return singleton;
121784
- }
121785
- function __resetCapUsageRegistryForTests() {
121786
- singleton = null;
121787
- }
121788
122124
  function safeExistsSync(path) {
121789
122125
  try {
121790
122126
  return node_fs.existsSync(path);
@@ -123220,6 +123556,12 @@ var require_manifest_system_deps_8boi90D9 = __commonJS({
123220
123556
  return NATIVE_PROVIDER_SERVICE_INFIX;
123221
123557
  }
123222
123558
  });
123559
+ Object.defineProperty(exports, "PARENT_ROUTED_PROVIDER_ADDON_ID", {
123560
+ enumerable: true,
123561
+ get: function() {
123562
+ return PARENT_ROUTED_PROVIDER_ADDON_ID;
123563
+ }
123564
+ });
123223
123565
  Object.defineProperty(exports, "RSS_BUDGET_REANNOUNCE_MIN_MS", {
123224
123566
  enumerable: true,
123225
123567
  get: function() {
@@ -127634,7 +127976,7 @@ var require_dist3 = __commonJS({
127634
127976
  "use strict";
127635
127977
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
127636
127978
  var require_chunk = require_chunk_Cek0wNdY();
127637
- var require_dist10 = require_dist_Ck2jkBZk();
127979
+ var require_dist10 = require_dist_Keu5TDO7();
127638
127980
  var require_builtins_alerts_alerts_addon = require_alerts_addon();
127639
127981
  require_alerts();
127640
127982
  var require_formatter = require_formatter_DqAKDlvN();
@@ -127660,7 +128002,7 @@ var require_dist3 = __commonJS({
127660
128002
  var require_builtins_winston_logging_index = require_winston_logging();
127661
128003
  var require_file_data_plane = require_file_data_plane_DO8KbxCe();
127662
128004
  var require_tls$1 = require_tls_BxQlomxd();
127663
- var require_manifest_system_deps = require_manifest_system_deps_8boi90D9();
128005
+ var require_manifest_system_deps = require_manifest_system_deps_DYv4ZPo2();
127664
128006
  var require_resource_monitor = require_resource_monitor_CdnzxBLP();
127665
128007
  var require_custom_action_registry = require_custom_action_registry_jY0NOZK8();
127666
128008
  var zod = require_zod();
@@ -207872,6 +208214,19 @@ globstar while`, t, d, e, f, m), this.matchOne(t.slice(d), e.slice(f), s)) retur
207872
208214
  * per-task abort timeout. On success the resolved `stagedPath` is returned; on
207873
208215
  * failure the task is marked `failed` and `null` is returned (so the apply
207874
208216
  * phase skips it). Other tasks are unaffected.
208217
+ *
208218
+ * **`toVersion` is REWRITTEN here, to the version that was actually
208219
+ * fetched.** A task's `toVersion` starts as the REQUEST, and the request is
208220
+ * routinely a dist-tag: `addons.updatePackage` with no version resolves to
208221
+ * the literal `'latest'`. The staging area already resolves it — it reads
208222
+ * the version out of the unpacked tarball and returns `resolvedVersion` —
208223
+ * and dropping that answer sent the string `'latest'` onward as if it were a
208224
+ * version. It became the manifest's recorded version, the version the Addons
208225
+ * page displayed, and the `toVersion` on the `addon.updated` event, which an
208226
+ * operator read on his phone as "@camstack/addon-terminal 0.0.0 → latest"
208227
+ * (2026-08-30). Everything downstream of the apply reads a version; none of
208228
+ * it can resolve a tag, and none of it could tell that it had been handed
208229
+ * one.
207875
208230
  */
207876
208231
  async fetchAddonTask(job, task) {
207877
208232
  try {
@@ -207881,20 +208236,28 @@ globstar while`, t, d, e, f, m), this.matchOne(t.slice(d), e.slice(f), s)) retur
207881
208236
  });
207882
208237
  const ac = new AbortController();
207883
208238
  const timer = setTimeout(() => ac.abort(), this.deps.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS);
207884
- let stagedPath;
208239
+ let staged;
207885
208240
  try {
207886
- stagedPath = (await this.deps.staging.fetchAndStage({
208241
+ staged = await this.deps.staging.fetchAndStage({
207887
208242
  jobId: job.jobId,
207888
208243
  name: task.packageName,
207889
208244
  version: task.toVersion,
207890
208245
  signal: ac.signal
207891
- })).stagedPath;
208246
+ });
207892
208247
  } finally {
207893
208248
  clearTimeout(timer);
207894
208249
  }
207895
- this.advance(job.jobId, task, "staged", { stagedPath });
208250
+ const stagedPath = staged.stagedPath;
208251
+ const resolved = {
208252
+ ...task,
208253
+ toVersion: staged.resolvedVersion
208254
+ };
208255
+ this.advance(job.jobId, task, "staged", {
208256
+ stagedPath,
208257
+ toVersion: resolved.toVersion
208258
+ });
207896
208259
  return {
207897
- task,
208260
+ task: resolved,
207898
208261
  stagedPath
207899
208262
  };
207900
208263
  } catch (err) {
@@ -208146,6 +208509,7 @@ globstar while`, t, d, e, f, m), this.matchOne(t.slice(d), e.slice(f), s)) retur
208146
208509
  exports.NativeMetricsProvider = require_builtins_native_metrics_native_metrics_addon.NativeMetricsProvider;
208147
208510
  exports.NetworkQualityTracker = NetworkQualityTracker;
208148
208511
  exports.NotificationService = NotificationService;
208512
+ exports.PARENT_ROUTED_PROVIDER_ADDON_ID = require_manifest_system_deps.PARENT_ROUTED_PROVIDER_ADDON_ID;
208149
208513
  Object.defineProperty(exports, "PYTHON_VERSION", {
208150
208514
  enumerable: true,
208151
208515
  get: function() {
@@ -208462,7 +208826,7 @@ var require_dist4 = __commonJS({
208462
208826
  "use strict";
208463
208827
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
208464
208828
  var require_event_category = require_event_category_BaEgqJNv();
208465
- var require_sleep = require_sleep_CWWLTM6W();
208829
+ var require_sleep = require_sleep_C3AniWy();
208466
208830
  var require_canonical_hash = require_canonical_hash_DNV8S5ET();
208467
208831
  var require_enums2 = require_enums();
208468
208832
  var require_err_msg = require_err_msg_COpsHMw2();
@@ -209794,6 +210158,21 @@ var require_dist4 = __commonJS({
209794
210158
  bytesMoved: zod.z.number().int(),
209795
210159
  /** Total files discovered up front; null while (or when) unknown. */
209796
210160
  filesTotal: zod.z.number().int().nullable(),
210161
+ /**
210162
+ * Rows this run CORRECTED while moving them — a durable mutation the move
210163
+ * made that nobody asked for, so it is reported where the operator reads the
210164
+ * job rather than only in a log line.
210165
+ *
210166
+ * A footage segment records its byte count in its own NAME, and the durable
210167
+ * hour row derives its aggregates from those names. A file that does not
210168
+ * match its name therefore makes the ledger's sums — and with them quota and
210169
+ * pressure eviction — wrong by the difference, and only a rename can fix it.
210170
+ * On 2026-08-30 one such row also stalled a 110 749-file drain permanently.
210171
+ *
210172
+ * Absent on lanes where the question has no meaning: a media blob's size is
210173
+ * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
210174
+ */
210175
+ rowsReconciled: zod.z.number().int().nonnegative().optional(),
209797
210176
  startedAt: zod.z.number(),
209798
210177
  finishedAt: zod.z.number().nullable(),
209799
210178
  error: zod.z.string().nullable()
@@ -209836,11 +210215,18 @@ var require_dist4 = __commonJS({
209836
210215
  /** Omitted = `move`, the pre-existing behaviour. */
209837
210216
  mode: MediaRelocateModeSchema.optional()
209838
210217
  });
209839
- var UnstampedEventMediaCountSchema = zod.z.object({
209840
- media: zod.z.number().int().nonnegative(),
209841
- retrainFrames: zod.z.number().int().nonnegative(),
209842
- total: zod.z.number().int().nonnegative()
210218
+ var UnstampedRowsSchema = zod.z.object({
210219
+ present: zod.z.boolean(),
210220
+ rows: zod.z.number().int().nonnegative().nullable()
209843
210221
  });
210222
+ var UnstampedEventMediaCountSchema = zod.z.object({
210223
+ media: UnstampedRowsSchema,
210224
+ retrainFrames: UnstampedRowsSchema,
210225
+ /** True when EITHER collection holds one. The refusal reads this. */
210226
+ anyPresent: zod.z.boolean(),
210227
+ /** Sum across both, or `null` when either lane could not be counted. */
210228
+ total: zod.z.number().int().nonnegative().nullable()
210229
+ }).nullable();
209844
210230
  var StorageMigrationMediaMoveInputSchema = RelocateMediaInputSchema.extend({ leaseId: zod.z.string().min(1) });
209845
210231
  var StorageMigrationClassSchema = zod.z.enum([
209846
210232
  "recordings",
@@ -209887,6 +210273,10 @@ var require_dist4 = __commonJS({
209887
210273
  /** The archive census — the **M** of "N of M" (D295). `null` = unknowable. */
209888
210274
  filesTotal: zod.z.number().int().nonnegative().nullable(),
209889
210275
  bytesMoved: zod.z.number().int().nonnegative(),
210276
+ /** Rows the mover corrected while moving them — see `RelocateJob`. Absent on
210277
+ * a lane that cannot reconcile. A migration that silently rewrote durable
210278
+ * rows would be the same failure as one that silently skipped them. */
210279
+ rowsReconciled: zod.z.number().int().nonnegative().optional(),
209890
210280
  /** The MOVER's start, not the migration's: a drain restarted after an addon
209891
210281
  * crash gets a new mover, and a rate computed from the migration's start
209892
210282
  * would silently average in the time nothing was running. */
@@ -216211,6 +216601,15 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
216211
216601
  * calls are sync. Bindings change rarely (only on wrapper toggle or
216212
216602
  * device add/remove) — clients invalidate via the
216213
216603
  * `capability.binding-changed` event.
216604
+ *
216605
+ * "A single round-trip" describes the CLIENT's side and used not to
216606
+ * describe the server's: until 2026-08-30 the resolver read the persisted
216607
+ * wrapper activations once per device, so answering this cost one
216608
+ * settings-door RPC per device — 1 020 on the live 1 019-device hub, and
216609
+ * it did not return in 240 s against `SystemMirror.init`'s 15 s budget.
216610
+ * The server side is now two reads for the whole fleet. Anything PERIODIC
216611
+ * still belongs on `getBindings` / `getBindingsBatch` (D12); this remains
216612
+ * a warm seed.
216214
216613
  */
216215
216614
  getAllBindings: require_sleep.method(zod.z.object({}), zod.z.array(DeviceBindingsForDeviceSchema)),
216216
216615
  /**
@@ -216609,6 +217008,80 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
216609
217008
  getInfo: require_sleep.method(zod.z.void(), EmbeddingInfoSchema, { auth: "admin" })
216610
217009
  }
216611
217010
  };
217011
+ var FailureReasonCountSchema = zod.z.object({
217012
+ /**
217013
+ * Why the attempt did not land, in the contributor's own vocabulary —
217014
+ * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
217015
+ * strings that already appear in this repo's logs and, where one exists, the
217016
+ * same string the per-track `previewMissReason` records (D276): a second
217017
+ * vocabulary for the same loss would make the row and the counter
217018
+ * un-joinable.
217019
+ */
217020
+ reason: zod.z.string(),
217021
+ count: zod.z.number().int().nonnegative()
217022
+ });
217023
+ var FailureContributionSchema = zod.z.object({
217024
+ /**
217025
+ * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
217026
+ * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
217027
+ * `unit` free: the families are owned by different addons and a shared enum
217028
+ * is a central list that rots invisibly.
217029
+ */
217030
+ family: zod.z.string(),
217031
+ /**
217032
+ * The NUMERIC device id — the same value every log line carries as
217033
+ * `tags.deviceId`. Never nullable and never absent: a contributor that
217034
+ * cannot name the camera must not emit the entry, because a fleet total
217035
+ * cannot answer the only question anybody asks of this surface.
217036
+ */
217037
+ deviceId: zod.z.number().int().positive(),
217038
+ /**
217039
+ * A second dimension inside the family: the model / step id for an inference
217040
+ * timeout, so "which camera AND which model" is one read. Absent when the
217041
+ * family has a single variant.
217042
+ */
217043
+ variant: zod.z.string().optional(),
217044
+ /**
217045
+ * Epoch ms this counter started — the INCARNATION MARKER. A consumer
217046
+ * differencing two reads must drop the interval when it changes, because the
217047
+ * counter restarted from zero in a respawned runner. Same discipline as
217048
+ * `LoadContribution.startedAtMs`.
217049
+ */
217050
+ sinceMs: zod.z.number(),
217051
+ /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
217052
+ atMs: zod.z.number(),
217053
+ /**
217054
+ * THE DENOMINATOR — every attempt on this path for this camera in the
217055
+ * window. A failure count published without it is the mistake this schema
217056
+ * exists to make impossible.
217057
+ */
217058
+ attempts: zod.z.number().int().nonnegative(),
217059
+ /** Attempts that landed. `attempts - succeeded` is the loss. */
217060
+ succeeded: zod.z.number().int().nonnegative(),
217061
+ /** The loss, partitioned. Sums to `attempts - succeeded`. */
217062
+ reasons: zod.z.array(FailureReasonCountSchema).readonly()
217063
+ });
217064
+ var failureContributionCapability = {
217065
+ name: "failure-contribution",
217066
+ scope: "system",
217067
+ mode: "collection",
217068
+ internal: true,
217069
+ methods: {
217070
+ /**
217071
+ * This addon's per-camera failure counters, read live from bounded in-RAM
217072
+ * state it already keeps. Inert: no persistence, no sampling, no timer.
217073
+ *
217074
+ * READING NEVER RESETS. The counters are CUMULATIVE since `sinceMs`, and a
217075
+ * consumer that wants a rate differences two reads. A draining read would
217076
+ * make two operators with the page open each destroy half of the other's
217077
+ * numbers, and `load-contribution` already settled the same question the
217078
+ * same way for `cpuSeconds`.
217079
+ */
217080
+ list: require_sleep.method(zod.z.void(), zod.z.array(FailureContributionSchema).readonly())
217081
+ },
217082
+ /** In-process only — enumerated through `addons.listCapabilityProviders`. */
217083
+ mount: { kind: "skip" }
217084
+ };
216612
217085
  var DirEntrySchema = zod.z.object({
216613
217086
  name: zod.z.string(),
216614
217087
  path: zod.z.string()
@@ -217153,145 +217626,6 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
217153
217626
  })
217154
217627
  }
217155
217628
  };
217156
- var LogChannelApplyResultSchema = zod.z.object({
217157
- /** How many declared channels are armed in this process after the call. */
217158
- armed: zod.z.number().int().min(0),
217159
- /**
217160
- * Names the document armed that this process does not declare. Reported
217161
- * rather than swallowed: a name here is either a typo or an addon that has
217162
- * not booted, and both deserve a line instead of silence.
217163
- */
217164
- unknown: zod.z.array(zod.z.string()).readonly()
217165
- });
217166
- var logChannelsCapability = {
217167
- name: "log-channels",
217168
- scope: "system",
217169
- mode: "collection",
217170
- internal: true,
217171
- methods: {
217172
- /** The channels this addon declares. Inert: no value, no state. */
217173
- list: require_sleep.method(zod.z.void(), zod.z.array(LogChannelDescriptorSchema).readonly()),
217174
- /**
217175
- * Refresh this process's mirror from the document's FULL set of armed
217176
- * windows.
217177
- *
217178
- * Full and not incremental on purpose: the document is the authority, so a
217179
- * channel it does not name is disarmed here. An incremental apply would
217180
- * let a disarm get lost in transit and leave a channel running that
217181
- * nobody can see is running.
217182
- */
217183
- apply: require_sleep.method(zod.z.object({ windows: zod.z.array(LogChannelWindowSchema).readonly() }), LogChannelApplyResultSchema, { kind: "mutation" })
217184
- },
217185
- /** In-process only — enumerated through `addons.listCapabilityProviders`. */
217186
- mount: { kind: "skip" }
217187
- };
217188
- var LogLevelSchema = zod.z.enum([
217189
- "debug",
217190
- "info",
217191
- "warn",
217192
- "error"
217193
- ]);
217194
- var LogEntrySchema = zod.z.object({
217195
- timestamp: zod.z.date(),
217196
- level: LogLevelSchema,
217197
- scope: zod.z.array(zod.z.string()),
217198
- message: zod.z.string(),
217199
- meta: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
217200
- tags: zod.z.record(zod.z.string(), zod.z.string()).optional()
217201
- });
217202
- var logDestinationCapability = {
217203
- name: "log-destination",
217204
- scope: "system",
217205
- mode: "collection",
217206
- internal: true,
217207
- methods: {
217208
- write: require_sleep.method(LogEntrySchema, zod.z.void(), { kind: "mutation" }),
217209
- query: require_sleep.method(zod.z.object({
217210
- scope: zod.z.array(zod.z.string()).optional(),
217211
- level: LogLevelSchema.optional(),
217212
- since: zod.z.date().optional(),
217213
- until: zod.z.date().optional(),
217214
- limit: zod.z.number().optional(),
217215
- tags: zod.z.record(zod.z.string(), zod.z.string()).optional()
217216
- }), zod.z.array(LogEntrySchema).readonly())
217217
- },
217218
- /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
217219
- mount: { kind: "skip" }
217220
- };
217221
- var FailureReasonCountSchema = zod.z.object({
217222
- /**
217223
- * Why the attempt did not land, in the contributor's own vocabulary —
217224
- * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
217225
- * strings that already appear in this repo's logs and, where one exists, the
217226
- * same string the per-track `previewMissReason` records (D276): a second
217227
- * vocabulary for the same loss would make the row and the counter
217228
- * un-joinable.
217229
- */
217230
- reason: zod.z.string(),
217231
- count: zod.z.number().int().nonnegative()
217232
- });
217233
- var FailureContributionSchema = zod.z.object({
217234
- /**
217235
- * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
217236
- * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
217237
- * `unit` free: the families are owned by different addons and a shared enum
217238
- * is a central list that rots invisibly.
217239
- */
217240
- family: zod.z.string(),
217241
- /**
217242
- * The NUMERIC device id — the same value every log line carries as
217243
- * `tags.deviceId`. Never nullable and never absent: a contributor that
217244
- * cannot name the camera must not emit the entry, because a fleet total
217245
- * cannot answer the only question anybody asks of this surface.
217246
- */
217247
- deviceId: zod.z.number().int().positive(),
217248
- /**
217249
- * A second dimension inside the family: the model / step id for an inference
217250
- * timeout, so "which camera AND which model" is one read. Absent when the
217251
- * family has a single variant.
217252
- */
217253
- variant: zod.z.string().optional(),
217254
- /**
217255
- * Epoch ms this counter started — the INCARNATION MARKER. A consumer
217256
- * differencing two reads must drop the interval when it changes, because the
217257
- * counter restarted from zero in a respawned runner. Same discipline as
217258
- * `LoadContribution.startedAtMs`.
217259
- */
217260
- sinceMs: zod.z.number(),
217261
- /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
217262
- atMs: zod.z.number(),
217263
- /**
217264
- * THE DENOMINATOR — every attempt on this path for this camera in the
217265
- * window. A failure count published without it is the mistake this schema
217266
- * exists to make impossible.
217267
- */
217268
- attempts: zod.z.number().int().nonnegative(),
217269
- /** Attempts that landed. `attempts - succeeded` is the loss. */
217270
- succeeded: zod.z.number().int().nonnegative(),
217271
- /** The loss, partitioned. Sums to `attempts - succeeded`. */
217272
- reasons: zod.z.array(FailureReasonCountSchema).readonly()
217273
- });
217274
- var failureContributionCapability = {
217275
- name: "failure-contribution",
217276
- scope: "system",
217277
- mode: "collection",
217278
- internal: true,
217279
- methods: {
217280
- /**
217281
- * This addon's per-camera failure counters, read live from bounded in-RAM
217282
- * state it already keeps. Inert: no persistence, no sampling, no timer.
217283
- *
217284
- * READING NEVER RESETS. The counters are CUMULATIVE since `sinceMs`, and a
217285
- * consumer that wants a rate differences two reads. A draining read would
217286
- * make two operators with the page open each destroy half of the other's
217287
- * numbers, and `load-contribution` already settled the same question the
217288
- * same way for `cpuSeconds`.
217289
- */
217290
- list: require_sleep.method(zod.z.void(), zod.z.array(FailureContributionSchema).readonly())
217291
- },
217292
- /** In-process only — enumerated through `addons.listCapabilityProviders`. */
217293
- mount: { kind: "skip" }
217294
- };
217295
217629
  var LOAD_CONTRIBUTION_ROLES = [
217296
217630
  "decode",
217297
217631
  "transcode",
@@ -217371,6 +217705,71 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
217371
217705
  /** In-process only — enumerated through `addons.listCapabilityProviders`. */
217372
217706
  mount: { kind: "skip" }
217373
217707
  };
217708
+ var LogChannelApplyResultSchema = zod.z.object({
217709
+ /** How many declared channels are armed in this process after the call. */
217710
+ armed: zod.z.number().int().min(0),
217711
+ /**
217712
+ * Names the document armed that this process does not declare. Reported
217713
+ * rather than swallowed: a name here is either a typo or an addon that has
217714
+ * not booted, and both deserve a line instead of silence.
217715
+ */
217716
+ unknown: zod.z.array(zod.z.string()).readonly()
217717
+ });
217718
+ var logChannelsCapability = {
217719
+ name: "log-channels",
217720
+ scope: "system",
217721
+ mode: "collection",
217722
+ internal: true,
217723
+ methods: {
217724
+ /** The channels this addon declares. Inert: no value, no state. */
217725
+ list: require_sleep.method(zod.z.void(), zod.z.array(LogChannelDescriptorSchema).readonly()),
217726
+ /**
217727
+ * Refresh this process's mirror from the document's FULL set of armed
217728
+ * windows.
217729
+ *
217730
+ * Full and not incremental on purpose: the document is the authority, so a
217731
+ * channel it does not name is disarmed here. An incremental apply would
217732
+ * let a disarm get lost in transit and leave a channel running that
217733
+ * nobody can see is running.
217734
+ */
217735
+ apply: require_sleep.method(zod.z.object({ windows: zod.z.array(LogChannelWindowSchema).readonly() }), LogChannelApplyResultSchema, { kind: "mutation" })
217736
+ },
217737
+ /** In-process only — enumerated through `addons.listCapabilityProviders`. */
217738
+ mount: { kind: "skip" }
217739
+ };
217740
+ var LogLevelSchema = zod.z.enum([
217741
+ "debug",
217742
+ "info",
217743
+ "warn",
217744
+ "error"
217745
+ ]);
217746
+ var LogEntrySchema = zod.z.object({
217747
+ timestamp: zod.z.date(),
217748
+ level: LogLevelSchema,
217749
+ scope: zod.z.array(zod.z.string()),
217750
+ message: zod.z.string(),
217751
+ meta: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
217752
+ tags: zod.z.record(zod.z.string(), zod.z.string()).optional()
217753
+ });
217754
+ var logDestinationCapability = {
217755
+ name: "log-destination",
217756
+ scope: "system",
217757
+ mode: "collection",
217758
+ internal: true,
217759
+ methods: {
217760
+ write: require_sleep.method(LogEntrySchema, zod.z.void(), { kind: "mutation" }),
217761
+ query: require_sleep.method(zod.z.object({
217762
+ scope: zod.z.array(zod.z.string()).optional(),
217763
+ level: LogLevelSchema.optional(),
217764
+ since: zod.z.date().optional(),
217765
+ until: zod.z.date().optional(),
217766
+ limit: zod.z.number().optional(),
217767
+ tags: zod.z.record(zod.z.string(), zod.z.string()).optional()
217768
+ }), zod.z.array(LogEntrySchema).readonly())
217769
+ },
217770
+ /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
217771
+ mount: { kind: "skip" }
217772
+ };
217374
217773
  var LoginStageEnum = zod.z.enum(["primary", "second-factor"]);
217375
217774
  var RedirectLoginMethodSchema = zod.z.object({
217376
217775
  kind: zod.z.literal("redirect"),
@@ -222258,13 +222657,18 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
222258
222657
  "keyFrameSmall",
222259
222658
  "thumbnailSmall"
222260
222659
  ]);
222261
- var MediaFileSchema = zod.z.object({
222660
+ var MediaFileRefSchema = zod.z.object({
222262
222661
  key: zod.z.string(),
222263
222662
  kind: MediaFileKindEnum,
222264
- base64: zod.z.string(),
222265
222663
  sizeBytes: zod.z.number(),
222266
222664
  timestamp: zod.z.number()
222267
222665
  });
222666
+ var MediaFileSchema = MediaFileRefSchema.extend({
222667
+ /** `/addon/<addonId>/event-media/<encoded stored key>`. Always present. */
222668
+ url: zod.z.string(),
222669
+ /** @deprecated Transitional — see the schema docblock. Use {@link url}. */
222670
+ base64: zod.z.string()
222671
+ });
222268
222672
  var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
222269
222673
  var RetrainMacroClassSchema = zod.z.enum([
222270
222674
  "person",
@@ -222996,6 +223400,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
222996
223400
  * happens to stamp it. This count is what the migration planner's
222997
223401
  * non-blocking gate reads; `relocateMedia({ mode: 'seal' })` is what drives
222998
223402
  * it to zero.
223403
+ *
223404
+ * TWO indexed statements per collection, not a walk. It used to page the
223405
+ * whole collection at 200 rows per RPC ordered by an unindexed column, so
223406
+ * on the live hub — 1 254 576 rows — it hit the 60 s RPC deadline every
223407
+ * time it was called, and the migration it gates could never start. The
223408
+ * cheap question (`present`: is there at least one) is asked first and
223409
+ * separately from the expensive one (`rows`), because only the first has
223410
+ * to be answerable for the gate to do its job.
223411
+ *
223412
+ * **`null` is "not measurable", never zero** — at either level. An
223413
+ * unreadable collection must not read as a sealed one.
222999
223414
  */
223000
223415
  countUnstampedEventMedia: require_sleep.method(zod.z.object({}), UnstampedEventMediaCountSchema, { auth: "admin" }),
223001
223416
  /**
@@ -223319,6 +223734,26 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
223319
223734
  deviceId: zod.z.number()
223320
223735
  }), zod.z.array(MediaFileInfoSchema).readonly()),
223321
223736
  /**
223737
+ * What media an EVENT has, without any of it — the twin `getEventMedia`
223738
+ * never had.
223739
+ *
223740
+ * `listTrackMedia` above got this treatment because a track's media is
223741
+ * 5-8 MB. An event's is worse per row, not better: an old-style event owns
223742
+ * a `crop` AND a native-resolution `fullFrameBoxed`, and the track DETAIL
223743
+ * modal — the one surface that legitimately shows the big kinds — unions
223744
+ * both listings to build its filmstrip. It then renders every tile from
223745
+ * the `event-media` plane by key and throws the bytes away. Measured on
223746
+ * the live hub: one event's `fullFrameBoxed` is 2 824 077 B, base64'd to
223747
+ * ~3.8 MB, allocated whole in hub-main's heap, for a list of keys.
223748
+ *
223749
+ * Same `deviceId` authorization subject as `getEventMedia`, and the same
223750
+ * rows — this is a projection of that method, never a different question.
223751
+ */
223752
+ listEventMedia: require_sleep.method(zod.z.object({
223753
+ eventId: zod.z.string(),
223754
+ deviceId: zod.z.number()
223755
+ }), zod.z.array(MediaFileInfoSchema).readonly()),
223756
+ /**
223322
223757
  * Search object events by text query using CLIP cosine similarity.
223323
223758
  * Encodes `text` via the `embedding-encoder` cap, queries the
223324
223759
  * `ObjectEmbeddingStore` with optional prefilters, ranks all matching
@@ -228781,7 +229216,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
228781
229216
  var MediaFileLiteSchema$1 = zod.z.object({
228782
229217
  key: zod.z.string(),
228783
229218
  kind: zod.z.string(),
228784
- base64: zod.z.string(),
229219
+ url: zod.z.string(),
228785
229220
  sizeBytes: zod.z.number(),
228786
229221
  timestamp: zod.z.number()
228787
229222
  });
@@ -231676,7 +232111,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
231676
232111
  var MediaFileLiteSchema = zod.z.object({
231677
232112
  key: zod.z.string(),
231678
232113
  kind: zod.z.string(),
231679
- base64: zod.z.string(),
232114
+ url: zod.z.string(),
231680
232115
  sizeBytes: zod.z.number(),
231681
232116
  timestamp: zod.z.number()
231682
232117
  });
@@ -241924,6 +242359,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
241924
242359
  addonId: null,
241925
242360
  access: "view"
241926
242361
  },
242362
+ "pipelineAnalytics.listEventMedia": {
242363
+ capName: "pipeline-analytics",
242364
+ capScope: "device",
242365
+ addonId: null,
242366
+ access: "view"
242367
+ },
241927
242368
  "pipelineAnalytics.listGroups": {
241928
242369
  capName: "pipeline-analytics",
241929
242370
  capScope: "device",
@@ -245805,6 +246246,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
245805
246246
  form: "array",
245806
246247
  optional: false
245807
246248
  }],
246249
+ "pipelineAnalytics.listEventMedia": [{
246250
+ name: "deviceId",
246251
+ form: "single",
246252
+ optional: false
246253
+ }],
245808
246254
  "pipelineAnalytics.listGroups": [{
245809
246255
  name: "deviceIds",
245810
246256
  form: "array",
@@ -250845,6 +251291,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250845
251291
  exports.MaskShapeKindSchema = MaskShapeKindSchema;
250846
251292
  exports.MaskShapeSchema = MaskShapeSchema;
250847
251293
  exports.MediaFileInfoSchema = MediaFileInfoSchema;
251294
+ exports.MediaFileRefSchema = MediaFileRefSchema;
250848
251295
  exports.MediaFileSchema = MediaFileSchema;
250849
251296
  exports.MediaPlayerRepeatSchema = MediaPlayerRepeatSchema;
250850
251297
  exports.MediaPlayerStateSchema = MediaPlayerStateSchema;
@@ -251323,6 +251770,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251323
251770
  exports.UnifiedBrokerInfoSchema = BrokerInfoSchema$1;
251324
251771
  exports.UnitConversionError = UnitConversionError;
251325
251772
  exports.UnstampedEventMediaCountSchema = UnstampedEventMediaCountSchema;
251773
+ exports.UnstampedRowsSchema = UnstampedRowsSchema;
251326
251774
  exports.UpdateIntegrationInputSchema = UpdateIntegrationInputSchema;
251327
251775
  exports.UpdateStatusSchema = UpdateStatusSchema;
251328
251776
  exports.UpdateUserInputSchema = UpdateUserInputSchema;
@@ -385633,6 +386081,14 @@ var require_main2 = __commonJS({
385633
386081
  // cap). A pin to ANOTHER node still bypasses the sibling → onUnownedCall.
385634
386082
  ownNodeId: agentNodeId,
385635
386083
  onUnownedCall
386084
+ // NO `capUsageObserver` here, deliberately. `getCapUsageRegistry()` is a
386085
+ // per-process singleton and the reader (`nodes.getCapUsageGraph`) runs on
386086
+ // hub-main; recording in the agent would produce a map nobody ever reads,
386087
+ // which is exactly the shape that made this graph return `[]` for
386088
+ // months. An agent's addon traffic is a KNOWN blind spot of the graph —
386089
+ // see `LocalChildRegistryOptions.capUsageObserver`. Closing it needs the
386090
+ // caller identity to survive `$hub-cap-fwd`'s envelope, which today it
386091
+ // does not.
385636
386092
  });
385637
386093
  await agentUdsRegistry.start();
385638
386094
  agentUdsRegistry.onChildRegistered((child) => {
@@ -402542,7 +402998,7 @@ var require_addon_package_service = __commonJS({
402542
402998
  this.requireInstaller();
402543
402999
  const addonInstaller = this.installer;
402544
403000
  const previousVersion = this.getInstalledPackageVersion(name);
402545
- const isFirstInstall = !previousVersion;
403001
+ const isFirstInstall = previousVersion === null;
402546
403002
  let result;
402547
403003
  if (isFirstInstall) {
402548
403004
  const r = await addonInstaller.installFromNpm(name, version);
@@ -402568,12 +403024,13 @@ var require_addon_package_service = __commonJS({
402568
403024
  const targetDir = path.join(addonsDir, dirName);
402569
403025
  const packageSpec = version ? `${name}@${version}` : name;
402570
403026
  await installPackageFromNpm(packageSpec, targetDir);
402571
- updatedVersion = this.getInstalledPackageVersion(name);
403027
+ const installedVersion = this.getInstalledPackageVersion(name);
403028
+ updatedVersion = installedVersion ?? "";
402572
403029
  this.cachedUpdates = null;
402573
403030
  this.logger.info("Core package updated -- restart required", {
402574
- meta: { name, updatedVersion }
403031
+ meta: { name, updatedVersion: installedVersion }
402575
403032
  });
402576
- this.sendUpdateNotification(name, updatedVersion);
403033
+ this.sendUpdateNotification(name, installedVersion);
402577
403034
  return { success: true, version: updatedVersion, requiresRestart: true };
402578
403035
  } catch (error) {
402579
403036
  const msg = (0, types_1.errMsg)(error);
@@ -403260,36 +403717,39 @@ var require_addon_package_service = __commonJS({
403260
403717
  // Private: general helpers
403261
403718
  // =========================================================================
403262
403719
  /**
403263
- * Read the currently installed version of a package.
403264
- * Checks data/addons/ first, then falls back to Node module resolution.
403265
- * Returns '0.0.0' if not found.
403720
+ * The version of `packageName` INSTALLED RIGHT NOW, or `null` when the hub
403721
+ * has no readable copy.
403722
+ *
403723
+ * Delegates to the installer, which is the one place that knows the on-disk
403724
+ * layout (`<addonsDir>/@camstack/<pkg>/`, with the pre-scope flat form as a
403725
+ * fallback). This method used to restate that path and got it wrong —
403726
+ * `packageName.replace(/^@camstack\//, '')` looked ONLY in the flat form, so
403727
+ * every scoped install missed. It then fell back to
403728
+ * `require.resolve('<pkg>/package.json')`, which cannot succeed either: an
403729
+ * addon is installed under the data directory, not in the hub's own
403730
+ * node_modules. Both misses landed on the literal `'0.0.0'`, and that string
403731
+ * was published on the `addon.updated` event as the version the operator had
403732
+ * been running ("@camstack/addon-terminal 0.0.0 → latest", 2026-08-30).
403733
+ *
403734
+ * `null`, not `'0.0.0'`: a version nobody installed is indistinguishable
403735
+ * from one that was, and the caller must be forced to decide what to say
403736
+ * about "unknown" rather than inherit a number that looks like an answer.
403266
403737
  */
403267
403738
  getInstalledPackageVersion(packageName) {
403268
403739
  try {
403269
- const addonsDir = this.resolveAddonsDir();
403270
- const dirName = packageName.replace(/^@camstack\//, "");
403271
- const pkgJsonPath = path.join(addonsDir, dirName, "package.json");
403272
- if (fs.existsSync(pkgJsonPath)) {
403273
- const pkgJson = readJsonObject(pkgJsonPath);
403274
- const v = asString(pkgJson?.["version"]);
403275
- if (v)
403276
- return v;
403277
- }
403278
- } catch (err) {
403279
- this.logger.debug("Version lookup via fs failed, trying require.resolve", {
403280
- meta: { packageName, error: (0, types_1.errMsg)(err) }
403281
- });
403282
- }
403283
- try {
403284
- const pkgJsonPath = __require.resolve(`${packageName}/package.json`);
403285
- const pkgJson = readJsonObject(pkgJsonPath);
403286
- return asString(pkgJson?.["version"], "0.0.0");
403740
+ const installed = this.installer?.getInstalledPackage(packageName) ?? null;
403741
+ const version = installed === null ? "" : asString(installed.version);
403742
+ if (version)
403743
+ return version;
403287
403744
  } catch (err) {
403288
- this.logger.debug("Could not resolve version, returning 0.0.0", {
403745
+ this.logger.debug("Installed-version lookup failed", {
403289
403746
  meta: { packageName, error: (0, types_1.errMsg)(err) }
403290
403747
  });
403291
- return "0.0.0";
403292
403748
  }
403749
+ this.logger.debug("No readable installed copy \u2014 previous version is unknown", {
403750
+ meta: { packageName }
403751
+ });
403752
+ return null;
403293
403753
  }
403294
403754
  /** Only allow @camstack/* scoped packages */
403295
403755
  isAllowedPackage(name) {
@@ -403341,11 +403801,18 @@ var require_addon_package_service = __commonJS({
403341
403801
  throw new Error("AddonInstaller is not available -- @camstack/system may not be installed");
403342
403802
  }
403343
403803
  }
403344
- /** Send notification and toast for a successful package update */
403804
+ /**
403805
+ * Send notification and toast for a successful package update.
403806
+ *
403807
+ * `version === null` means the hub could not read back what it just
403808
+ * installed. The sentence then says the package was updated and stops —
403809
+ * never "updated to v0.0.0", which reads as a real version.
403810
+ */
403345
403811
  sendUpdateNotification(name, version) {
403812
+ const sentence = version === null ? `${name} updated` : `${name} updated to v${version}`;
403346
403813
  this.notificationService.notify({
403347
403814
  title: "Package Updated",
403348
- body: `${name} updated to v${version}`,
403815
+ body: sentence,
403349
403816
  format: "text",
403350
403817
  priority: 3,
403351
403818
  category: "system",
@@ -403356,7 +403823,7 @@ var require_addon_package_service = __commonJS({
403356
403823
  });
403357
403824
  this.toastService.broadcast({
403358
403825
  title: "Package Updated",
403359
- message: `${name} updated to v${version}`,
403826
+ message: sentence,
403360
403827
  severity: "info",
403361
403828
  duration: 5e3
403362
403829
  });
@@ -410443,8 +410910,30 @@ var require_cap_providers = __commonJS({
410443
410910
  }
410444
410911
  return result;
410445
410912
  },
410913
+ /**
410914
+ * Observed caller → provider → cap edges over the last `windowSeconds`.
410915
+ *
410916
+ * **Read the blind spots before drawing a conclusion.** This reads
410917
+ * hub-main's `CapUsageRegistry`, written by the hub's `LocalChildRegistry`
410918
+ * on every `cap-call-out` a hub-local forked addon sends. It therefore does
410919
+ * NOT contain: hub-main → hub-main in-process calls (one shared `ctx.api`
410920
+ * client, no per-caller identity), a runner's own co-located calls, or any
410921
+ * traffic originating on an agent. An edge missing here means "not seen on
410922
+ * this plane", never "did not happen" — the full list is on
410923
+ * `LocalChildRegistryOptions.capUsageObserver`.
410924
+ */
410446
410925
  getCapUsageGraph: async (input) => {
410447
410926
  const reg = (0, system_1.getCapUsageRegistry)();
410927
+ const stats = reg.getStats();
410928
+ if (stats.droppedTriples > 0) {
410929
+ logger?.warn("cap-usage graph is truncated \u2014 the triple ceiling was reached", {
410930
+ meta: {
410931
+ triples: stats.triples,
410932
+ maxTriples: stats.maxTriples,
410933
+ droppedTriples: stats.droppedTriples
410934
+ }
410935
+ });
410936
+ }
410448
410937
  return reg.getGraph({ windowSeconds: input.windowSeconds, nowMs: Date.now() });
410449
410938
  },
410450
410939
  setProcessLogLevel: async (input) => {
@@ -416873,6 +417362,7 @@ var require_addon_registry_service = __commonJS({
416873
417362
  Object.defineProperty(exports, "__esModule", { value: true });
416874
417363
  exports.AddonRegistryService = void 0;
416875
417364
  exports.shouldEvictMissingOnDisk = shouldEvictMissingOnDisk;
417365
+ exports.addonUpdatePayload = addonUpdatePayload;
416876
417366
  exports.shouldEmitProviderRegisteredReady = shouldEmitProviderRegisteredReady;
416877
417367
  var node_crypto_1 = __require("crypto");
416878
417368
  var fs = __importStar(__require("fs"));
@@ -416895,6 +417385,12 @@ var require_addon_registry_service = __commonJS({
416895
417385
  function shouldEvictMissingOnDisk(entry, id, onDiskIds) {
416896
417386
  return entry.source === "installed" && entry.packageName !== "@camstack/system" && !onDiskIds.has(id);
416897
417387
  }
417388
+ function addonUpdatePayload(fromVersion, toVersion) {
417389
+ return {
417390
+ ...fromVersion !== null ? { fromVersion } : {},
417391
+ toVersion
417392
+ };
417393
+ }
416898
417394
  function isSettingsStore(backend) {
416899
417395
  const required = [
416900
417396
  "getSystem",
@@ -417970,14 +418466,17 @@ var require_addon_registry_service = __commonJS({
417970
418466
  this.healthMonitor.forget(packageName);
417971
418467
  this.addonLoader.clearLoadFailures(packageName);
417972
418468
  }
417973
- /** Emit addon.updated lifecycle event for all addons belonging to a package */
418469
+ /**
418470
+ * Emit `addon.updated` for every addon belonging to a package.
418471
+ *
418472
+ * The payload shape is {@link addonUpdatePayload} — an unknown predecessor
418473
+ * is an ABSENT key, never a placeholder.
418474
+ */
417974
418475
  emitUpdateEvent(packageName, fromVersion, toVersion) {
418476
+ const payload = addonUpdatePayload(fromVersion, toVersion);
417975
418477
  for (const [id, entry] of this.addonEntries) {
417976
418478
  if (entry.packageName === packageName) {
417977
- this.emitAddonLifecycleEvent("addon.updated", id, {
417978
- fromVersion,
417979
- toVersion
417980
- });
418479
+ this.emitAddonLifecycleEvent("addon.updated", id, payload);
417981
418480
  }
417982
418481
  }
417983
418482
  }
@@ -421469,6 +421968,14 @@ var require_moleculer_service = __commonJS({
421469
421968
  // below, so a method that survives 16 minutes across nodes survives it
421470
421969
  // across a socket too — and one that hangs is cut off on both.
421471
421970
  capTimeoutMs: (capName, method) => this.capabilityService.getRegistry()?.getDefinition(capName)?.methods?.[method]?.timeoutMs,
421971
+ // Feed the cap-usage graph from the one place that sees a forked
421972
+ // addon's outbound `ctx.api` traffic AND runs in the process that reads
421973
+ // it back (`nodes.getCapUsageGraph` → `cap-providers.ts`). The
421974
+ // runner-side observer in `addon-context-factory.ts` writes to the
421975
+ // RUNNER's own module singleton, which hub-main never sees — which is
421976
+ // why the graph answered `[]` on a hub running 64 addons. Blind spots
421977
+ // that remain are listed on `LocalChildRegistryOptions.capUsageObserver`.
421978
+ capUsageObserver: (obs) => (0, system_1.getCapUsageRegistry)().recordCall(obs),
421472
421979
  logger: {
421473
421980
  info: (msg, meta) => logger.info(msg, meta !== null && meta !== void 0 ? { meta } : void 0)
421474
421981
  },