@camstack/system 1.2.137 → 1.2.139

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  2. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  3. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/alerts/alerts.addon.js +1 -1
  6. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  7. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  8. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  9. package/dist/builtins/console-logging/index.js +1 -1
  10. package/dist/builtins/console-logging/index.mjs +1 -1
  11. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  12. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  13. package/dist/builtins/device-manager/device-manager.addon.js +119 -3
  14. package/dist/builtins/device-manager/device-manager.addon.mjs +119 -3
  15. package/dist/builtins/device-manager/device-row-store.d.ts +5 -1
  16. package/dist/builtins/device-manager/fleet-read-census.d.ts +47 -0
  17. package/dist/builtins/doorbell/virtual-doorbell.addon.d.ts +7 -0
  18. package/dist/builtins/doorbell/virtual-doorbell.addon.js +15 -4
  19. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +15 -4
  20. package/dist/builtins/hub-forwarder/index.js +1 -1
  21. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  22. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  23. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  24. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  25. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  26. package/dist/builtins/local-network/local-network.addon.js +1 -1
  27. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  28. package/dist/builtins/loki-logging/index.js +1 -1
  29. package/dist/builtins/loki-logging/index.mjs +1 -1
  30. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  31. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  32. package/dist/builtins/platform-probe/index.js +1 -1
  33. package/dist/builtins/platform-probe/index.mjs +1 -1
  34. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  35. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  36. package/dist/builtins/snapshot/index.js +1 -1
  37. package/dist/builtins/snapshot/index.mjs +1 -1
  38. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  39. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  40. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  41. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  42. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  43. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  44. package/dist/builtins/system-config/system-config.addon.js +1 -1
  45. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  46. package/dist/builtins/winston-logging/index.js +1 -1
  47. package/dist/builtins/winston-logging/index.mjs +1 -1
  48. package/dist/{dist-DMOTtKH8.mjs → dist-D595vzqY.mjs} +40 -2
  49. package/dist/{dist-mQ3BkrJd.js → dist-MqjCyx1E.js} +45 -1
  50. package/dist/index.js +2 -2
  51. package/dist/index.mjs +2 -2
  52. package/dist/{retired-settings-keys-CPRvyt-E.mjs → retired-settings-keys-BvxgnDT1.mjs} +1 -1
  53. package/dist/{retired-settings-keys-Dvkcu_zf.js → retired-settings-keys-IVq6xR9i.js} +1 -1
  54. package/package.json +1 -1
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-mQ3BkrJd.js");
6
+ const require_dist = require("../../dist-MqjCyx1E.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { Gt as EventCategory, v as addonPagesCapability, vt as errMsg, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
1
+ import { Kt as EventCategory, bt as BaseAddon, v as addonPagesCapability, yt as errMsg } from "../../dist-D595vzqY.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-mQ3BkrJd.js");
6
+ const require_dist = require("../../dist-MqjCyx1E.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { Gt as EventCategory, b as addonWidgetsCapability, vt as errMsg, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
1
+ import { Kt as EventCategory, b as addonWidgetsCapability, bt as BaseAddon, yt as errMsg } from "../../dist-D595vzqY.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-mQ3BkrJd.js");
6
+ const require_dist = require("../../dist-MqjCyx1E.js");
7
7
  //#region src/builtins/alerts/prune-policy.ts
8
8
  /**
9
9
  * Ids older than `cutoffMs`, oldest first.
@@ -1,4 +1,4 @@
1
- import { Gt as EventCategory, Mt as createEvent, n as AlertSchema, vt as errMsg, x as alertsCapability, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
1
+ import { Kt as EventCategory, Nt as createEvent, bt as BaseAddon, n as AlertSchema, x as alertsCapability, yt as errMsg } from "../../dist-D595vzqY.mjs";
2
2
  //#region src/builtins/alerts/prune-policy.ts
3
3
  /**
4
4
  * Ids older than `cutoffMs`, oldest first.
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-mQ3BkrJd.js");
6
+ const require_dist = require("../../dist-MqjCyx1E.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let buffer = require("buffer");
9
9
  let node_fs_promises = require("node:fs/promises");
@@ -1,4 +1,4 @@
1
- import { C as backupCapability, Gt as EventCategory, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
1
+ import { C as backupCapability, Kt as EventCategory, bt as BaseAddon } from "../../dist-D595vzqY.mjs";
2
2
  import { randomBytes, randomUUID } from "node:crypto";
3
3
  import { Buffer as Buffer$1 } from "buffer";
4
4
  import * as fsp from "node:fs/promises";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-mQ3BkrJd.js");
6
+ const require_dist = require("../../dist-MqjCyx1E.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/console-logging/console-destination.ts
9
9
  var LEVEL_RANK = {
@@ -1,4 +1,4 @@
1
- import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
1
+ import { X as logDestinationCapability, bt as BaseAddon } from "../../dist-D595vzqY.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/console-logging/console-destination.ts
4
4
  var LEVEL_RANK = {
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-mQ3BkrJd.js");
3
+ const require_dist = require("../../dist-MqjCyx1E.js");
4
4
  let node_crypto = require("node:crypto");
5
5
  let node_fs_promises = require("node:fs/promises");
6
6
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -1,4 +1,4 @@
1
- import { D as coreBlockAddonId, O as coreBlockIdFromAddonId, c as CoreBlockSchema, k as coreBlocksCapability, l as DeclaredDevices, o as CORE_BLOCKS_ADDON_ID, s as CORE_BLOCK_ADDON_PREFIX, vt as errMsg, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
1
+ import { D as coreBlockAddonId, O as coreBlockIdFromAddonId, bt as BaseAddon, c as CoreBlockSchema, k as coreBlocksCapability, l as DeclaredDevices, o as CORE_BLOCKS_ADDON_ID, s as CORE_BLOCK_ADDON_PREFIX, yt as errMsg } from "../../dist-D595vzqY.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import * as fs from "node:fs/promises";
4
4
  import * as path$1 from "node:path";
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-mQ3BkrJd.js");
7
- const require_retired_settings_keys = require("../../retired-settings-keys-Dvkcu_zf.js");
6
+ const require_dist = require("../../dist-MqjCyx1E.js");
7
+ const require_retired_settings_keys = require("../../retired-settings-keys-IVq6xR9i.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  let _camstack_types_node = require("@camstack/types/node");
10
10
  //#region src/builtins/device-manager/adoption-job-engine.ts
@@ -3949,6 +3949,112 @@ var DeviceMetaStore = class {
3949
3949
  };
3950
3950
  };
3951
3951
  //#endregion
3952
+ //#region src/builtins/device-manager/fleet-read-census.ts
3953
+ /** How many stack frames identify a call site. One frame is a method; three are
3954
+ * a cause — `listFleet < listAll < onDeviceRegistered` is the answer, `listAll`
3955
+ * alone is not. Same choice, for the same reason, as `read-census.ts`. */
3956
+ var CENSUS_STACK_FRAMES = 3;
3957
+ function siteFromStack(stack) {
3958
+ if (stack === void 0) return "unknown";
3959
+ const frames = stack.split("\n").slice(1).map((line) => line.trim().replace(/^at\s+/, "")).filter((line) => line.length > 0).slice(0, CENSUS_STACK_FRAMES).map((line) => {
3960
+ const open = line.indexOf(" (");
3961
+ return (open > 0 ? line.slice(0, open) : line).replace(/^async\s+/, "");
3962
+ });
3963
+ return frames.length === 0 ? "unknown" : frames.join(" < ");
3964
+ }
3965
+ var FleetReadCensus = class FleetReadCensus {
3966
+ /**
3967
+ * Longest window the census may be armed for.
3968
+ *
3969
+ * Ten minutes covers a boot several times over and bounds what an operator
3970
+ * pays if they set the variable and forget it. A diagnostic that can be left
3971
+ * on becomes a tax nobody remembers enabling.
3972
+ */
3973
+ static MAX_WINDOW_MS = 10 * 6e4;
3974
+ windowMs;
3975
+ closesAt;
3976
+ counts = /* @__PURE__ */ new Map();
3977
+ reported = false;
3978
+ constructor(rawEnv, nowMs) {
3979
+ const parsed = rawEnv === void 0 ? NaN : Number(rawEnv);
3980
+ const valid = Number.isFinite(parsed) && parsed > 0;
3981
+ this.windowMs = valid ? Math.min(parsed, FleetReadCensus.MAX_WINDOW_MS) : 0;
3982
+ this.closesAt = valid ? nowMs + this.windowMs : 0;
3983
+ }
3984
+ get armed() {
3985
+ return this.windowMs > 0;
3986
+ }
3987
+ /** Test seam: pretend the window closed. Production reads the clock. */
3988
+ closeAt(nowMs) {
3989
+ if (nowMs >= this.closesAt) this.forceClosed = true;
3990
+ }
3991
+ forceClosed = false;
3992
+ isOpen(nowMs) {
3993
+ if (!this.armed || this.forceClosed) return false;
3994
+ return nowMs < this.closesAt;
3995
+ }
3996
+ /**
3997
+ * Capture the caller, or `null` when the window is shut.
3998
+ *
3999
+ * Called at the START of a read. A read that began inside the window belongs
4000
+ * to it even if it lands after the close — otherwise the slow reads at the
4001
+ * edge would be attributed to nobody, and the slow ones are the interesting
4002
+ * ones.
4003
+ */
4004
+ siteOf(nowMs = Date.now()) {
4005
+ if (!this.isOpen(nowMs)) return null;
4006
+ const holder = {};
4007
+ Error.captureStackTrace(holder, this.siteOf);
4008
+ return siteFromStack(holder.stack);
4009
+ }
4010
+ /** Count one read at an already-captured site. A `null` site is not counted. */
4011
+ record(site, rowCount) {
4012
+ if (site === null) return;
4013
+ const existing = this.counts.get(site);
4014
+ if (existing === void 0) {
4015
+ this.counts.set(site, {
4016
+ site,
4017
+ calls: 1,
4018
+ rows: rowCount
4019
+ });
4020
+ return;
4021
+ }
4022
+ existing.calls += 1;
4023
+ existing.rows += rowCount;
4024
+ }
4025
+ /** Attributed sites, busiest first. */
4026
+ rows() {
4027
+ return [...this.counts.values()].map((c) => ({
4028
+ site: c.site,
4029
+ calls: c.calls,
4030
+ rows: c.rows
4031
+ })).sort((a, b) => b.calls - a.calls);
4032
+ }
4033
+ /**
4034
+ * Emit the report ONCE.
4035
+ *
4036
+ * `perMin` is included because the raw count is unreadable without the window
4037
+ * it was taken over — "4 149" means nothing until you know it was 66 seconds.
4038
+ */
4039
+ report(logger, elapsedMs) {
4040
+ if (this.reported || !this.armed) return;
4041
+ this.reported = true;
4042
+ const rows = this.rows();
4043
+ const totalCalls = rows.reduce((acc, r) => acc + r.calls, 0);
4044
+ logger.info("fleet read census", { meta: {
4045
+ elapsedMs,
4046
+ sites: rows.length,
4047
+ totalCalls,
4048
+ rows: rows.map((r) => ({
4049
+ site: r.site,
4050
+ calls: r.calls,
4051
+ rows: r.rows,
4052
+ perMin: elapsedMs === 0 ? 0 : Math.round(r.calls * 6e4 / elapsedMs)
4053
+ }))
4054
+ } });
4055
+ }
4056
+ };
4057
+ //#endregion
3952
4058
  //#region src/builtins/device-manager/device-row-store.ts
3953
4059
  /**
3954
4060
  * @durable class=ledger owner=device-manager
@@ -4278,9 +4384,17 @@ var DeviceRowStore = class {
4278
4384
  backend;
4279
4385
  logger;
4280
4386
  declared = null;
4281
- constructor(backend, logger) {
4387
+ /** Call-site attribution for fleet reads. Armed only by
4388
+ * `CAMSTACK_FLEET_READ_CENSUS_MS`; see `fleet-read-census.ts`. */
4389
+ census;
4390
+ constructor(backend, logger, census) {
4282
4391
  this.backend = backend;
4283
4392
  this.logger = logger;
4393
+ this.census = census ?? new FleetReadCensus(process.env["CAMSTACK_FLEET_READ_CENSUS_MS"], Date.now());
4394
+ if (this.census.armed) {
4395
+ const startedAt = Date.now();
4396
+ setTimeout(() => this.census.report(this.logger, Date.now() - startedAt), this.census.windowMs).unref?.();
4397
+ }
4284
4398
  }
4285
4399
  /**
4286
4400
  * Lazy, idempotent `declareCollection`, memoised on the PROMISE so N
@@ -4449,11 +4563,13 @@ var DeviceRowStore = class {
4449
4563
  }
4450
4564
  async list(filter, columns) {
4451
4565
  await this.declare();
4566
+ const site = this.census.siteOf();
4452
4567
  const records = await this.backend.query({
4453
4568
  collection: DEVICE_ROWS_COLLECTION,
4454
4569
  filter,
4455
4570
  ...columns === void 0 ? {} : { columns }
4456
4571
  });
4572
+ this.census.record(site, records.length);
4457
4573
  const out = [];
4458
4574
  for (const record of records) {
4459
4575
  const decoded = decodeDeviceRow(record.data);
@@ -1,5 +1,5 @@
1
- import { Ct as DeviceFeature, E as buildStreamParamsConfigSchema, F as deviceStatusCapability, Gt as EventCategory, Lt as isDeviceConfigCap, N as deviceManagerCapability, Ot as WELL_KNOWN_TAB_MAP, P as deviceStateCapability, R as enumerateItemArrayFields, Tt as DeviceType, Wt as sleep, a as CAP_NAMES_WITH_STATUS, at as runtimeStatePolicyFor, et as normalizeUnit, nt as parseStreamParamsFormPatch, p as STREAM_PROFILE_META, t as ALL_CAPABILITY_DEFINITIONS, u as DeviceStatusSchema, vt as errMsg, wt as DeviceRole, yt as BaseAddon, z as enumerateSchemaFields } from "../../dist-DMOTtKH8.mjs";
2
- import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-CPRvyt-E.mjs";
1
+ import { E as buildStreamParamsConfigSchema, Et as DeviceType, F as deviceStatusCapability, Gt as sleep, Kt as EventCategory, N as deviceManagerCapability, P as deviceStateCapability, R as enumerateItemArrayFields, Rt as isDeviceConfigCap, Tt as DeviceRole, a as CAP_NAMES_WITH_STATUS, bt as BaseAddon, et as normalizeUnit, kt as WELL_KNOWN_TAB_MAP, nt as parseStreamParamsFormPatch, ot as runtimeStatePolicyFor, p as STREAM_PROFILE_META, t as ALL_CAPABILITY_DEFINITIONS, u as DeviceStatusSchema, wt as DeviceFeature, yt as errMsg, z as enumerateSchemaFields } from "../../dist-D595vzqY.mjs";
2
+ import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-BvxgnDT1.mjs";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { canonicalDeviceFingerprint } from "@camstack/types/node";
5
5
  //#region src/builtins/device-manager/adoption-job-engine.ts
@@ -3944,6 +3944,112 @@ var DeviceMetaStore = class {
3944
3944
  };
3945
3945
  };
3946
3946
  //#endregion
3947
+ //#region src/builtins/device-manager/fleet-read-census.ts
3948
+ /** How many stack frames identify a call site. One frame is a method; three are
3949
+ * a cause — `listFleet < listAll < onDeviceRegistered` is the answer, `listAll`
3950
+ * alone is not. Same choice, for the same reason, as `read-census.ts`. */
3951
+ var CENSUS_STACK_FRAMES = 3;
3952
+ function siteFromStack(stack) {
3953
+ if (stack === void 0) return "unknown";
3954
+ const frames = stack.split("\n").slice(1).map((line) => line.trim().replace(/^at\s+/, "")).filter((line) => line.length > 0).slice(0, CENSUS_STACK_FRAMES).map((line) => {
3955
+ const open = line.indexOf(" (");
3956
+ return (open > 0 ? line.slice(0, open) : line).replace(/^async\s+/, "");
3957
+ });
3958
+ return frames.length === 0 ? "unknown" : frames.join(" < ");
3959
+ }
3960
+ var FleetReadCensus = class FleetReadCensus {
3961
+ /**
3962
+ * Longest window the census may be armed for.
3963
+ *
3964
+ * Ten minutes covers a boot several times over and bounds what an operator
3965
+ * pays if they set the variable and forget it. A diagnostic that can be left
3966
+ * on becomes a tax nobody remembers enabling.
3967
+ */
3968
+ static MAX_WINDOW_MS = 10 * 6e4;
3969
+ windowMs;
3970
+ closesAt;
3971
+ counts = /* @__PURE__ */ new Map();
3972
+ reported = false;
3973
+ constructor(rawEnv, nowMs) {
3974
+ const parsed = rawEnv === void 0 ? NaN : Number(rawEnv);
3975
+ const valid = Number.isFinite(parsed) && parsed > 0;
3976
+ this.windowMs = valid ? Math.min(parsed, FleetReadCensus.MAX_WINDOW_MS) : 0;
3977
+ this.closesAt = valid ? nowMs + this.windowMs : 0;
3978
+ }
3979
+ get armed() {
3980
+ return this.windowMs > 0;
3981
+ }
3982
+ /** Test seam: pretend the window closed. Production reads the clock. */
3983
+ closeAt(nowMs) {
3984
+ if (nowMs >= this.closesAt) this.forceClosed = true;
3985
+ }
3986
+ forceClosed = false;
3987
+ isOpen(nowMs) {
3988
+ if (!this.armed || this.forceClosed) return false;
3989
+ return nowMs < this.closesAt;
3990
+ }
3991
+ /**
3992
+ * Capture the caller, or `null` when the window is shut.
3993
+ *
3994
+ * Called at the START of a read. A read that began inside the window belongs
3995
+ * to it even if it lands after the close — otherwise the slow reads at the
3996
+ * edge would be attributed to nobody, and the slow ones are the interesting
3997
+ * ones.
3998
+ */
3999
+ siteOf(nowMs = Date.now()) {
4000
+ if (!this.isOpen(nowMs)) return null;
4001
+ const holder = {};
4002
+ Error.captureStackTrace(holder, this.siteOf);
4003
+ return siteFromStack(holder.stack);
4004
+ }
4005
+ /** Count one read at an already-captured site. A `null` site is not counted. */
4006
+ record(site, rowCount) {
4007
+ if (site === null) return;
4008
+ const existing = this.counts.get(site);
4009
+ if (existing === void 0) {
4010
+ this.counts.set(site, {
4011
+ site,
4012
+ calls: 1,
4013
+ rows: rowCount
4014
+ });
4015
+ return;
4016
+ }
4017
+ existing.calls += 1;
4018
+ existing.rows += rowCount;
4019
+ }
4020
+ /** Attributed sites, busiest first. */
4021
+ rows() {
4022
+ return [...this.counts.values()].map((c) => ({
4023
+ site: c.site,
4024
+ calls: c.calls,
4025
+ rows: c.rows
4026
+ })).sort((a, b) => b.calls - a.calls);
4027
+ }
4028
+ /**
4029
+ * Emit the report ONCE.
4030
+ *
4031
+ * `perMin` is included because the raw count is unreadable without the window
4032
+ * it was taken over — "4 149" means nothing until you know it was 66 seconds.
4033
+ */
4034
+ report(logger, elapsedMs) {
4035
+ if (this.reported || !this.armed) return;
4036
+ this.reported = true;
4037
+ const rows = this.rows();
4038
+ const totalCalls = rows.reduce((acc, r) => acc + r.calls, 0);
4039
+ logger.info("fleet read census", { meta: {
4040
+ elapsedMs,
4041
+ sites: rows.length,
4042
+ totalCalls,
4043
+ rows: rows.map((r) => ({
4044
+ site: r.site,
4045
+ calls: r.calls,
4046
+ rows: r.rows,
4047
+ perMin: elapsedMs === 0 ? 0 : Math.round(r.calls * 6e4 / elapsedMs)
4048
+ }))
4049
+ } });
4050
+ }
4051
+ };
4052
+ //#endregion
3947
4053
  //#region src/builtins/device-manager/device-row-store.ts
3948
4054
  /**
3949
4055
  * @durable class=ledger owner=device-manager
@@ -4273,9 +4379,17 @@ var DeviceRowStore = class {
4273
4379
  backend;
4274
4380
  logger;
4275
4381
  declared = null;
4276
- constructor(backend, logger) {
4382
+ /** Call-site attribution for fleet reads. Armed only by
4383
+ * `CAMSTACK_FLEET_READ_CENSUS_MS`; see `fleet-read-census.ts`. */
4384
+ census;
4385
+ constructor(backend, logger, census) {
4277
4386
  this.backend = backend;
4278
4387
  this.logger = logger;
4388
+ this.census = census ?? new FleetReadCensus(process.env["CAMSTACK_FLEET_READ_CENSUS_MS"], Date.now());
4389
+ if (this.census.armed) {
4390
+ const startedAt = Date.now();
4391
+ setTimeout(() => this.census.report(this.logger, Date.now() - startedAt), this.census.windowMs).unref?.();
4392
+ }
4279
4393
  }
4280
4394
  /**
4281
4395
  * Lazy, idempotent `declareCollection`, memoised on the PROMISE so N
@@ -4444,11 +4558,13 @@ var DeviceRowStore = class {
4444
4558
  }
4445
4559
  async list(filter, columns) {
4446
4560
  await this.declare();
4561
+ const site = this.census.siteOf();
4447
4562
  const records = await this.backend.query({
4448
4563
  collection: DEVICE_ROWS_COLLECTION,
4449
4564
  filter,
4450
4565
  ...columns === void 0 ? {} : { columns }
4451
4566
  });
4567
+ this.census.record(site, records.length);
4452
4568
  const out = [];
4453
4569
  for (const record of records) {
4454
4570
  const decoded = decodeDeviceRow(record.data);
@@ -1,6 +1,7 @@
1
1
  import { ChildLayout, CollectionColumn, CollectionIndex, DeviceDisplayOverride, IScopedLogger, MutationFilter, QueryFilter, SettingsRecord, SettingsStoreClient } from '@camstack/types';
2
2
  import { RetiredRowStore } from '../sqlite-storage/retired-settings-keys.js';
3
3
  import { PersistedDeviceMeta } from './device-meta-types.js';
4
+ import { FleetReadCensus } from './fleet-read-census.js';
4
5
  /**
5
6
  * @durable class=ledger owner=device-manager
6
7
  * write="one row per device, written by `allocateDeviceId` (identity placeholder),
@@ -211,7 +212,10 @@ export declare class DeviceRowStore {
211
212
  private readonly backend;
212
213
  private readonly logger;
213
214
  private declared;
214
- constructor(backend: DeviceRowBackend, logger: IScopedLogger);
215
+ /** Call-site attribution for fleet reads. Armed only by
216
+ * `CAMSTACK_FLEET_READ_CENSUS_MS`; see `fleet-read-census.ts`. */
217
+ private readonly census;
218
+ constructor(backend: DeviceRowBackend, logger: IScopedLogger, census?: FleetReadCensus);
215
219
  /**
216
220
  * Lazy, idempotent `declareCollection`, memoised on the PROMISE so N
217
221
  * concurrent first-callers issue one declaration rather than N. A rejection
@@ -0,0 +1,47 @@
1
+ import { IScopedLogger } from '@camstack/types';
2
+ /** One attributed call site. */
3
+ export interface FleetCensusRow {
4
+ readonly site: string;
5
+ readonly calls: number;
6
+ readonly rows: number;
7
+ }
8
+ export declare class FleetReadCensus {
9
+ /**
10
+ * Longest window the census may be armed for.
11
+ *
12
+ * Ten minutes covers a boot several times over and bounds what an operator
13
+ * pays if they set the variable and forget it. A diagnostic that can be left
14
+ * on becomes a tax nobody remembers enabling.
15
+ */
16
+ static readonly MAX_WINDOW_MS: number;
17
+ readonly windowMs: number;
18
+ private readonly closesAt;
19
+ private readonly counts;
20
+ private reported;
21
+ constructor(rawEnv: string | undefined, nowMs: number);
22
+ get armed(): boolean;
23
+ /** Test seam: pretend the window closed. Production reads the clock. */
24
+ closeAt(nowMs: number): void;
25
+ private forceClosed;
26
+ private isOpen;
27
+ /**
28
+ * Capture the caller, or `null` when the window is shut.
29
+ *
30
+ * Called at the START of a read. A read that began inside the window belongs
31
+ * to it even if it lands after the close — otherwise the slow reads at the
32
+ * edge would be attributed to nobody, and the slow ones are the interesting
33
+ * ones.
34
+ */
35
+ siteOf(nowMs?: number): string | null;
36
+ /** Count one read at an already-captured site. A `null` site is not counted. */
37
+ record(site: string | null, rowCount: number): void;
38
+ /** Attributed sites, busiest first. */
39
+ rows(): readonly FleetCensusRow[];
40
+ /**
41
+ * Emit the report ONCE.
42
+ *
43
+ * `perMin` is included because the raw count is unreadable without the window
44
+ * it was taken over — "4 149" means nothing until you know it was 66 seconds.
45
+ */
46
+ report(logger: IScopedLogger, elapsedMs: number): void;
47
+ }
@@ -71,6 +71,13 @@ export declare class VirtualDoorbellAddon extends BaseAddon<VirtualDoorbellAddon
71
71
  * an open mirror performs NO I/O and cannot fail. The mirror can only
72
72
  * DESTROY a press through the one branch below, and that branch is not
73
73
  * allowed to be the last word — see `ringsDespiteUnboundMirror`.
74
+ *
75
+ * `sourceDeviceId`/`capName` are the source that produced this rise —
76
+ * carried through purely so the terminal `pressed` line can name it. Every
77
+ * suppression branch beside it already reports its source
78
+ * (`logSuppressions`); the successful path was the one place that did not,
79
+ * which meant answering "what rang it" required hand-correlating a
80
+ * timestamp against provider logs (proven live 2026-08-25, camera 615).
74
81
  */
75
82
  private fireDoorbell;
76
83
  /**
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-mQ3BkrJd.js");
6
+ const require_dist = require("../../dist-MqjCyx1E.js");
7
7
  //#region src/builtins/doorbell/trigger-engine.ts
8
8
  /**
9
9
  * Pure trigger logic for the virtual-doorbell builtin — edge detection over
@@ -544,7 +544,7 @@ var VirtualDoorbellAddon = class extends require_dist.BaseAddon {
544
544
  await this.ensureAssignments();
545
545
  const result = this.engine.onStateSlice(data.deviceId, data.capName, data.slice);
546
546
  this.logSuppressions(data, result);
547
- for (const cameraId of result.fired) await this.fireDoorbell(cameraId, Date.now());
547
+ for (const cameraId of result.fired) await this.fireDoorbell(cameraId, Date.now(), data.deviceId, data.capName);
548
548
  }
549
549
  /**
550
550
  * Say out loud every case where a rise did NOT become a ring. Silence
@@ -585,8 +585,15 @@ var VirtualDoorbellAddon = class extends require_dist.BaseAddon {
585
585
  * an open mirror performs NO I/O and cannot fail. The mirror can only
586
586
  * DESTROY a press through the one branch below, and that branch is not
587
587
  * allowed to be the last word — see `ringsDespiteUnboundMirror`.
588
+ *
589
+ * `sourceDeviceId`/`capName` are the source that produced this rise —
590
+ * carried through purely so the terminal `pressed` line can name it. Every
591
+ * suppression branch beside it already reports its source
592
+ * (`logSuppressions`); the successful path was the one place that did not,
593
+ * which meant answering "what rang it" required hand-correlating a
594
+ * timestamp against provider logs (proven live 2026-08-25, camera 615).
588
595
  */
589
- async fireDoorbell(cameraId, timestamp) {
596
+ async fireDoorbell(cameraId, timestamp, sourceDeviceId, capName) {
590
597
  const binding = this.bindingMirror.get(cameraId);
591
598
  if (!allowsRing(binding) && !await this.ringsDespiteUnboundMirror(cameraId, binding)) return;
592
599
  if (!hasConfirmedBinding(binding)) this.ctx.logger.warn("virtual-doorbell: ringing on an unconfirmed binding — the bindings store has not corroborated an unbind, and a one-shot press is never dropped on an unproven read", {
@@ -623,7 +630,11 @@ var VirtualDoorbellAddon = class extends require_dist.BaseAddon {
623
630
  }));
624
631
  this.ctx.logger.info("virtual-doorbell: pressed", {
625
632
  tags: { deviceId: cameraId },
626
- meta: { timestamp }
633
+ meta: {
634
+ timestamp,
635
+ sourceDeviceId,
636
+ capName
637
+ }
627
638
  });
628
639
  }
629
640
  /**
@@ -1,4 +1,4 @@
1
- import { G as isSameAddonId, Gt as EventCategory, I as doorbellCapability, Mt as createEvent, Tt as DeviceType, vt as errMsg, w as bareAddonId, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
1
+ import { Et as DeviceType, G as isSameAddonId, I as doorbellCapability, Kt as EventCategory, Nt as createEvent, bt as BaseAddon, w as bareAddonId, yt as errMsg } from "../../dist-D595vzqY.mjs";
2
2
  //#region src/builtins/doorbell/trigger-engine.ts
3
3
  /**
4
4
  * Pure trigger logic for the virtual-doorbell builtin — edge detection over
@@ -539,7 +539,7 @@ var VirtualDoorbellAddon = class extends BaseAddon {
539
539
  await this.ensureAssignments();
540
540
  const result = this.engine.onStateSlice(data.deviceId, data.capName, data.slice);
541
541
  this.logSuppressions(data, result);
542
- for (const cameraId of result.fired) await this.fireDoorbell(cameraId, Date.now());
542
+ for (const cameraId of result.fired) await this.fireDoorbell(cameraId, Date.now(), data.deviceId, data.capName);
543
543
  }
544
544
  /**
545
545
  * Say out loud every case where a rise did NOT become a ring. Silence
@@ -580,8 +580,15 @@ var VirtualDoorbellAddon = class extends BaseAddon {
580
580
  * an open mirror performs NO I/O and cannot fail. The mirror can only
581
581
  * DESTROY a press through the one branch below, and that branch is not
582
582
  * allowed to be the last word — see `ringsDespiteUnboundMirror`.
583
+ *
584
+ * `sourceDeviceId`/`capName` are the source that produced this rise —
585
+ * carried through purely so the terminal `pressed` line can name it. Every
586
+ * suppression branch beside it already reports its source
587
+ * (`logSuppressions`); the successful path was the one place that did not,
588
+ * which meant answering "what rang it" required hand-correlating a
589
+ * timestamp against provider logs (proven live 2026-08-25, camera 615).
583
590
  */
584
- async fireDoorbell(cameraId, timestamp) {
591
+ async fireDoorbell(cameraId, timestamp, sourceDeviceId, capName) {
585
592
  const binding = this.bindingMirror.get(cameraId);
586
593
  if (!allowsRing(binding) && !await this.ringsDespiteUnboundMirror(cameraId, binding)) return;
587
594
  if (!hasConfirmedBinding(binding)) this.ctx.logger.warn("virtual-doorbell: ringing on an unconfirmed binding — the bindings store has not corroborated an unbind, and a one-shot press is never dropped on an unproven read", {
@@ -618,7 +625,11 @@ var VirtualDoorbellAddon = class extends BaseAddon {
618
625
  }));
619
626
  this.ctx.logger.info("virtual-doorbell: pressed", {
620
627
  tags: { deviceId: cameraId },
621
- meta: { timestamp }
628
+ meta: {
629
+ timestamp,
630
+ sourceDeviceId,
631
+ capName
632
+ }
622
633
  });
623
634
  }
624
635
  /**
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-mQ3BkrJd.js");
6
+ const require_dist = require("../../dist-MqjCyx1E.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
9
9
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
@@ -1,4 +1,4 @@
1
- import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
1
+ import { X as logDestinationCapability, bt as BaseAddon } from "../../dist-D595vzqY.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
4
4
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-mQ3BkrJd.js");
3
+ const require_dist = require("../../dist-MqjCyx1E.js");
4
4
  //#region src/builtins/liveness-monitor/liveness-checks.ts
5
5
  var NO_DEVICES = "liveness:no-devices";
6
6
  var ALL_OFFLINE = "liveness:all-devices-offline";
@@ -1,4 +1,4 @@
1
- import { Gt as EventCategory, Mt as createEvent, vt as errMsg, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
1
+ import { Kt as EventCategory, Nt as createEvent, bt as BaseAddon, yt as errMsg } from "../../dist-D595vzqY.mjs";
2
2
  //#region src/builtins/liveness-monitor/liveness-checks.ts
3
3
  var NO_DEVICES = "liveness:no-devices";
4
4
  var ALL_OFFLINE = "liveness:all-devices-offline";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-mQ3BkrJd.js");
6
+ const require_dist = require("../../dist-MqjCyx1E.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  node_crypto = require_chunk.__toESM(node_crypto);
9
9
  let crypto$1 = require("crypto");
@@ -1,5 +1,5 @@
1
1
  import { i as __require, o as __toESM, t as __commonJSMin } from "../../chunk-CNf5ZN-e.mjs";
2
- import { S as authProviderCapability, _ as UserRecordSchema, ht as userManagementCapability, m as ScopedTokenSchema, r as ApiKeyRecordSchema, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
2
+ import { S as authProviderCapability, _ as UserRecordSchema, bt as BaseAddon, gt as userManagementCapability, m as ScopedTokenSchema, r as ApiKeyRecordSchema } from "../../dist-D595vzqY.mjs";
3
3
  import * as crypto$2 from "node:crypto";
4
4
  import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
5
5
  import nodeCrypto from "crypto";
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-mQ3BkrJd.js");
3
+ const require_dist = require("../../dist-MqjCyx1E.js");
4
4
  const require_tls = require("../../tls-u8QCJCFE.js");
5
5
  let node_fs_promises = require("node:fs/promises");
6
6
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { Gt as EventCategory, Y as localNetworkCapability, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
1
+ import { Kt as EventCategory, Y as localNetworkCapability, bt as BaseAddon } from "../../dist-D595vzqY.mjs";
2
2
  import { S as readLanHttpState, _ as DEFAULT_LAN_HTTP_PORT, a as writeTlsMode, i as writeExtraSans, l as reissueTlsLeaf, n as readTlsAccessStatus, o as validateUploadedTls, r as readTlsMode, s as ensureTlsCert, t as readExtraSans, y as applyLanHttp } from "../../tls-CQhPGSJm.mjs";
3
3
  import { mkdir, writeFile } from "node:fs/promises";
4
4
  import { join } from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-mQ3BkrJd.js");
6
+ const require_dist = require("../../dist-MqjCyx1E.js");
7
7
  //#region src/builtins/loki-logging/loki-payload.ts
8
8
  /**
9
9
  * Loki rejects a label name that is not a valid Prometheus label
@@ -1,4 +1,4 @@
1
- import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
1
+ import { X as logDestinationCapability, bt as BaseAddon } from "../../dist-D595vzqY.mjs";
2
2
  //#region src/builtins/loki-logging/loki-payload.ts
3
3
  /**
4
4
  * Loki rejects a label name that is not a valid Prometheus label
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-mQ3BkrJd.js");
6
+ const require_dist = require("../../dist-MqjCyx1E.js");
7
7
  let node_child_process = require("node:child_process");
8
8
  let node_util = require("node:util");
9
9
  let node_os = require("node:os");
@@ -1,4 +1,4 @@
1
- import { $ as metricsProviderCapability, Gt as EventCategory, Mt as createEvent, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
1
+ import { $ as metricsProviderCapability, Kt as EventCategory, Nt as createEvent, bt as BaseAddon } from "../../dist-D595vzqY.mjs";
2
2
  import { execFile, execFileSync } from "node:child_process";
3
3
  import { promisify } from "node:util";
4
4
  import * as os from "node:os";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-mQ3BkrJd.js");
6
+ const require_dist = require("../../dist-MqjCyx1E.js");
7
7
  let node_child_process = require("node:child_process");
8
8
  let node_util = require("node:util");
9
9
  let node_os = require("node:os");
@@ -1,5 +1,5 @@
1
1
  import { o as __toESM } from "../../chunk-CNf5ZN-e.mjs";
2
- import { Gt as EventCategory, L as enumerateInferenceDevices, Pt as emitReadiness, ct as scoreRuntimes, rt as platformProbeCapability, vt as errMsg, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
2
+ import { Ft as emitReadiness, Kt as EventCategory, L as enumerateInferenceDevices, bt as BaseAddon, lt as scoreRuntimes, rt as platformProbeCapability, yt as errMsg } from "../../dist-D595vzqY.mjs";
3
3
  import { execFile } from "node:child_process";
4
4
  import { promisify } from "node:util";
5
5
  import * as os from "node:os";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-mQ3BkrJd.js");
6
+ const require_dist = require("../../dist-MqjCyx1E.js");
7
7
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
8
8
  /**
9
9
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -1,4 +1,4 @@
1
- import { Gt as EventCategory, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
1
+ import { Kt as EventCategory, bt as BaseAddon } from "../../dist-D595vzqY.mjs";
2
2
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
3
3
  /**
4
4
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-mQ3BkrJd.js");
6
+ const require_dist = require("../../dist-MqjCyx1E.js");
7
7
  let zod = require("zod");
8
8
  let node_crypto = require("node:crypto");
9
9
  let _camstack_types_node = require("@camstack/types/node");
@@ -1,4 +1,4 @@
1
- import { Ct as DeviceFeature, Gt as EventCategory, M as deriveBatteryPresence, Rt as nodePin, T as batteryCapability, Tt as DeviceType, i as BatteryStatusSchema, mt as streamQualityLabel, ut as snapshotCapability, vt as errMsg, w as bareAddonId, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
1
+ import { Et as DeviceType, Kt as EventCategory, M as deriveBatteryPresence, T as batteryCapability, bt as BaseAddon, dt as snapshotCapability, ht as streamQualityLabel, i as BatteryStatusSchema, w as bareAddonId, wt as DeviceFeature, yt as errMsg, zt as nodePin } from "../../dist-D595vzqY.mjs";
2
2
  import { z } from "zod";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { signExpiringUrl, verifyExpiringUrl } from "@camstack/types/node";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-mQ3BkrJd.js");
6
+ const require_dist = require("../../dist-MqjCyx1E.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_fs_promises = require("node:fs/promises");
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { V as filesystemBrowseCapability, pt as storageProviderCapability, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
1
+ import { V as filesystemBrowseCapability, bt as BaseAddon, mt as storageProviderCapability } from "../../dist-D595vzqY.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { mkdir, readdir, realpath, statfs } from "node:fs/promises";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-mQ3BkrJd.js");
6
+ const require_dist = require("../../dist-MqjCyx1E.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_fs_promises = require("node:fs/promises");
9
9
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -1,4 +1,4 @@
1
- import { dt as storageCapability, ft as storageMigrationCapability, g as StorageMigrationJobSchema, h as StorageLocationTypeSchema, lt as settingsStoreCapability, yt as BaseAddon, zt as parseJsonObject } from "../../dist-DMOTtKH8.mjs";
1
+ import { Bt as parseJsonObject, bt as BaseAddon, ft as storageCapability, g as StorageMigrationJobSchema, h as StorageLocationTypeSchema, pt as storageMigrationCapability, ut as settingsStoreCapability } from "../../dist-D595vzqY.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import * as fs from "node:fs/promises";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-mQ3BkrJd.js");
6
+ const require_dist = require("../../dist-MqjCyx1E.js");
7
7
  //#region src/builtins/system-config/system-config.addon.ts
8
8
  /**
9
9
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -1,4 +1,4 @@
1
- import { It as hydrateSchema, vt as errMsg, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
1
+ import { Lt as hydrateSchema, bt as BaseAddon, yt as errMsg } from "../../dist-D595vzqY.mjs";
2
2
  //#region src/builtins/system-config/system-config.addon.ts
3
3
  /**
4
4
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-mQ3BkrJd.js");
6
+ const require_dist = require("../../dist-MqjCyx1E.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  let node_path = require("node:path");
9
9
  node_path = require_chunk.__toESM(node_path);
@@ -1,4 +1,4 @@
1
- import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
1
+ import { X as logDestinationCapability, bt as BaseAddon } from "../../dist-D595vzqY.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  import * as path$1 from "node:path";
4
4
  import path from "node:path";
@@ -2534,7 +2534,7 @@ function method(input, output, options) {
2534
2534
  input,
2535
2535
  output,
2536
2536
  kind: options?.kind ?? "query",
2537
- auth: options?.auth ?? "protected",
2537
+ ...options?.auth !== void 0 ? { auth: options.auth } : {},
2538
2538
  ...options?.access !== void 0 ? { access: options.access } : {},
2539
2539
  ...options?.caller !== void 0 ? { caller: options.caller } : {},
2540
2540
  timeoutMs: options?.timeoutMs
@@ -7742,6 +7742,44 @@ function objectInputDeclaresAddonId(schema) {
7742
7742
  function resolveDefShape(defShape) {
7743
7743
  return typeof defShape === "function" ? defShape() : defShape;
7744
7744
  }
7745
+ /**
7746
+ * Effective auth for a cap method — the `internal: true` floor (Task 7.5c).
7747
+ *
7748
+ * `internal: true` on a cap def is a DECLARATION that its methods are for
7749
+ * in-process (`ctx.api`) consumption only. Nothing enforced that:
7750
+ * `resolveCapMount` never reads `internal` (an internal cap still gets a
7751
+ * full tRPC router), and a method that omitted an explicit `auth` used to
7752
+ * default — permanently, at `method()`-call time — to `'protected'`,
7753
+ * reachable by any authenticated session regardless of `internal`. That
7754
+ * silent default is how 45 procedures across 13 internal caps ended up
7755
+ * exposed, including `ssoBridge.signBridgeToken`: it accepted
7756
+ * caller-supplied claims (including `isAdmin`), was reachable by any
7757
+ * authenticated session, and its token is redeemed at
7758
+ * `/api/auth/sso/finish` to mint a session — a full privilege escalation.
7759
+ * See `docs/decisions/adr-0240-an-internal-cap-floors-its-unannotated-methods-to-admin.md`
7760
+ * (D240) for the record.
7761
+ *
7762
+ * The floor, exactly: an `internal` cap's method with NO explicit `auth`
7763
+ * resolves to `'admin'` instead of `'protected'`. A method that DELIBERATELY
7764
+ * declares an explicit `auth` — public, protected, or admin — keeps it
7765
+ * unconditionally; the author already said what they meant, and an
7766
+ * annotation that happens to agree with the floor is documentation, not
7767
+ * redundancy. A non-`internal` cap's un-annotated method is unaffected
7768
+ * (still `'protected'`). This is a FLOOR, not a hard `mount: 'skip'` — that
7769
+ * would also override a deliberate `auth: 'public'`/`'protected'` choice,
7770
+ * which is not the defect being closed.
7771
+ *
7772
+ * `method()` (`capability-definition.ts`) deliberately leaves `auth`
7773
+ * UNSET when the caller omits it (rather than defaulting there) so the
7774
+ * "was this explicit" bit survives to this single resolution point. Single
7775
+ * source of truth shared by the runtime builder (`cap-router-builder.ts`)
7776
+ * and the codegen (`generate-cap-routers.ts`, `gen-api-reference.ts`) — the
7777
+ * two must never disagree, a documented failure mode in this repo.
7778
+ */
7779
+ function resolveMethodAuth(def, method) {
7780
+ if (method.auth !== void 0) return method.auth;
7781
+ return def.internal === true ? "admin" : "protected";
7782
+ }
7745
7783
  /** Auth level → base-procedure key. `superAdmin` collapses to `admin`. */
7746
7784
  function procedureAuthKey(auth) {
7747
7785
  if (auth === "public") return "public";
@@ -39238,4 +39276,4 @@ function enumerateInferenceDevices(hw) {
39238
39276
  return out;
39239
39277
  }
39240
39278
  //#endregion
39241
- export { metricsProviderCapability as $, dataStoreProviderCapability as A, asNumber as At, extractNestedAddonId as B, parseJsonUnknown as Bt, backupCapability as C, DeviceFeature as Ct, coreBlockAddonId as D, ReadinessTimeoutError as Dt, buildStreamParamsConfigSchema as E, ReadinessRegistry as Et, deviceStatusCapability as F, expandCapMethods as Ft, isSameAddonId as G, EventCategory as Gt, isArrayOutputSchema as H, resolveCapMount as Ht, doorbellCapability as I, hydrateSchema as It, lifecycleJobSchema as J, isVoidInput as K, enumerateInferenceDevices as L, isDeviceConfigCap as Lt, deriveBatteryPresence as M, createEvent as Mt, deviceManagerCapability as N, emitDownForOwnedCaps as Nt, coreBlockIdFromAddonId as O, WELL_KNOWN_TAB_MAP as Ot, deviceStateCapability as P, emitReadiness as Pt, looseSchema as Q, enumerateItemArrayFields as R, nodePin as Rt, authProviderCapability as S, DEVICE_STATUS_METHOD as St, batteryCapability as T, DeviceType as Tt, isCollectionArrayMethod as U, scopeKey as Ut, filesystemBrowseCapability as V, readinessKey as Vt, isObjectInput as W, sleep as Wt, logDestinationCapability as X, localNetworkCapability as Y, logLevelAtMost as Z, UserRecordSchema as _, vectorStoreCapability as _t, CAP_NAMES_WITH_STATUS as a, runtimeStatePolicyFor as at, addonWidgetsCapability as b, DATAPLANE_SECRET_HEADER as bt, CoreBlockSchema as c, scoreRuntimes as ct, METHOD_ACCESS_MAP as d, storageCapability as dt, normalizeUnit as et, RUNTIME_DEFAULTS as f, storageMigrationCapability as ft, StorageMigrationJobSchema as g, vectorDimFromBase64 as gt, StorageLocationTypeSchema as h, userManagementCapability as ht, BatteryStatusSchema as i, procedureAuthKey as it, decodeVectorBase64 as j, asString as jt, coreBlocksCapability as k, asJsonObject as kt, DeclaredDevices as l, settingsStoreCapability as lt, ScopedTokenSchema as m, streamQualityLabel as mt, AlertSchema as n, parseStreamParamsFormPatch as nt, CORE_BLOCKS_ADDON_ID as o, scopesAllowAddon as ot, STREAM_PROFILE_META as p, storageProviderCapability as pt, kebabToCamel as q, ApiKeyRecordSchema as r, platformProbeCapability as rt, CORE_BLOCK_ADDON_PREFIX as s, scopesAllowDeviceCap as st, ALL_CAPABILITY_DEFINITIONS as t, objectInputDeclaresAddonId as tt, DeviceStatusSchema as u, snapshotCapability as ut, addonPagesCapability as v, errMsg as vt, bareAddonId as w, DeviceRole as wt, alertsCapability as x, DEVICE_SETTINGS_CONTRIBUTION_METHODS as xt, addonSettingsCapability as y, BaseAddon as yt, enumerateSchemaFields as z, parseJsonObject as zt };
39279
+ export { metricsProviderCapability as $, dataStoreProviderCapability as A, asJsonObject as At, extractNestedAddonId as B, parseJsonObject as Bt, backupCapability as C, DEVICE_STATUS_METHOD as Ct, coreBlockAddonId as D, ReadinessRegistry as Dt, buildStreamParamsConfigSchema as E, DeviceType as Et, deviceStatusCapability as F, emitReadiness as Ft, isSameAddonId as G, sleep as Gt, isArrayOutputSchema as H, readinessKey as Ht, doorbellCapability as I, expandCapMethods as It, lifecycleJobSchema as J, isVoidInput as K, EventCategory as Kt, enumerateInferenceDevices as L, hydrateSchema as Lt, deriveBatteryPresence as M, asString as Mt, deviceManagerCapability as N, createEvent as Nt, coreBlockIdFromAddonId as O, ReadinessTimeoutError as Ot, deviceStateCapability as P, emitDownForOwnedCaps as Pt, looseSchema as Q, enumerateItemArrayFields as R, isDeviceConfigCap as Rt, authProviderCapability as S, DEVICE_SETTINGS_CONTRIBUTION_METHODS as St, batteryCapability as T, DeviceRole as Tt, isCollectionArrayMethod as U, resolveCapMount as Ut, filesystemBrowseCapability as V, parseJsonUnknown as Vt, isObjectInput as W, scopeKey as Wt, logDestinationCapability as X, localNetworkCapability as Y, logLevelAtMost as Z, UserRecordSchema as _, vectorDimFromBase64 as _t, CAP_NAMES_WITH_STATUS as a, resolveMethodAuth as at, addonWidgetsCapability as b, BaseAddon as bt, CoreBlockSchema as c, scopesAllowDeviceCap as ct, METHOD_ACCESS_MAP as d, snapshotCapability as dt, normalizeUnit as et, RUNTIME_DEFAULTS as f, storageCapability as ft, StorageMigrationJobSchema as g, userManagementCapability as gt, StorageLocationTypeSchema as h, streamQualityLabel as ht, BatteryStatusSchema as i, procedureAuthKey as it, decodeVectorBase64 as j, asNumber as jt, coreBlocksCapability as k, WELL_KNOWN_TAB_MAP as kt, DeclaredDevices as l, scoreRuntimes as lt, ScopedTokenSchema as m, storageProviderCapability as mt, AlertSchema as n, parseStreamParamsFormPatch as nt, CORE_BLOCKS_ADDON_ID as o, runtimeStatePolicyFor as ot, STREAM_PROFILE_META as p, storageMigrationCapability as pt, kebabToCamel as q, ApiKeyRecordSchema as r, platformProbeCapability as rt, CORE_BLOCK_ADDON_PREFIX as s, scopesAllowAddon as st, ALL_CAPABILITY_DEFINITIONS as t, objectInputDeclaresAddonId as tt, DeviceStatusSchema as u, settingsStoreCapability as ut, addonPagesCapability as v, vectorStoreCapability as vt, bareAddonId as w, DeviceFeature as wt, alertsCapability as x, DATAPLANE_SECRET_HEADER as xt, addonSettingsCapability as y, errMsg as yt, enumerateSchemaFields as z, nodePin as zt };
@@ -2534,7 +2534,7 @@ function method(input, output, options) {
2534
2534
  input,
2535
2535
  output,
2536
2536
  kind: options?.kind ?? "query",
2537
- auth: options?.auth ?? "protected",
2537
+ ...options?.auth !== void 0 ? { auth: options.auth } : {},
2538
2538
  ...options?.access !== void 0 ? { access: options.access } : {},
2539
2539
  ...options?.caller !== void 0 ? { caller: options.caller } : {},
2540
2540
  timeoutMs: options?.timeoutMs
@@ -7742,6 +7742,44 @@ function objectInputDeclaresAddonId(schema) {
7742
7742
  function resolveDefShape(defShape) {
7743
7743
  return typeof defShape === "function" ? defShape() : defShape;
7744
7744
  }
7745
+ /**
7746
+ * Effective auth for a cap method — the `internal: true` floor (Task 7.5c).
7747
+ *
7748
+ * `internal: true` on a cap def is a DECLARATION that its methods are for
7749
+ * in-process (`ctx.api`) consumption only. Nothing enforced that:
7750
+ * `resolveCapMount` never reads `internal` (an internal cap still gets a
7751
+ * full tRPC router), and a method that omitted an explicit `auth` used to
7752
+ * default — permanently, at `method()`-call time — to `'protected'`,
7753
+ * reachable by any authenticated session regardless of `internal`. That
7754
+ * silent default is how 45 procedures across 13 internal caps ended up
7755
+ * exposed, including `ssoBridge.signBridgeToken`: it accepted
7756
+ * caller-supplied claims (including `isAdmin`), was reachable by any
7757
+ * authenticated session, and its token is redeemed at
7758
+ * `/api/auth/sso/finish` to mint a session — a full privilege escalation.
7759
+ * See `docs/decisions/adr-0240-an-internal-cap-floors-its-unannotated-methods-to-admin.md`
7760
+ * (D240) for the record.
7761
+ *
7762
+ * The floor, exactly: an `internal` cap's method with NO explicit `auth`
7763
+ * resolves to `'admin'` instead of `'protected'`. A method that DELIBERATELY
7764
+ * declares an explicit `auth` — public, protected, or admin — keeps it
7765
+ * unconditionally; the author already said what they meant, and an
7766
+ * annotation that happens to agree with the floor is documentation, not
7767
+ * redundancy. A non-`internal` cap's un-annotated method is unaffected
7768
+ * (still `'protected'`). This is a FLOOR, not a hard `mount: 'skip'` — that
7769
+ * would also override a deliberate `auth: 'public'`/`'protected'` choice,
7770
+ * which is not the defect being closed.
7771
+ *
7772
+ * `method()` (`capability-definition.ts`) deliberately leaves `auth`
7773
+ * UNSET when the caller omits it (rather than defaulting there) so the
7774
+ * "was this explicit" bit survives to this single resolution point. Single
7775
+ * source of truth shared by the runtime builder (`cap-router-builder.ts`)
7776
+ * and the codegen (`generate-cap-routers.ts`, `gen-api-reference.ts`) — the
7777
+ * two must never disagree, a documented failure mode in this repo.
7778
+ */
7779
+ function resolveMethodAuth(def, method) {
7780
+ if (method.auth !== void 0) return method.auth;
7781
+ return def.internal === true ? "admin" : "protected";
7782
+ }
7745
7783
  /** Auth level → base-procedure key. `superAdmin` collapses to `admin`. */
7746
7784
  function procedureAuthKey(auth) {
7747
7785
  if (auth === "public") return "public";
@@ -39742,6 +39780,12 @@ Object.defineProperty(exports, "resolveCapMount", {
39742
39780
  return resolveCapMount;
39743
39781
  }
39744
39782
  });
39783
+ Object.defineProperty(exports, "resolveMethodAuth", {
39784
+ enumerable: true,
39785
+ get: function() {
39786
+ return resolveMethodAuth;
39787
+ }
39788
+ });
39745
39789
  Object.defineProperty(exports, "runtimeStatePolicyFor", {
39746
39790
  enumerable: true,
39747
39791
  get: function() {
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("./chunk-Cek0wNdY.js");
3
- const require_dist = require("./dist-mQ3BkrJd.js");
3
+ const require_dist = require("./dist-MqjCyx1E.js");
4
4
  const require_builtins_alerts_alerts_addon = require("./builtins/alerts/alerts.addon.js");
5
5
  require("./builtins/alerts/index.js");
6
6
  const require_formatter = require("./formatter-DqAKDlvN.js");
@@ -6406,7 +6406,7 @@ function methodsFor(def) {
6406
6406
  return {
6407
6407
  name,
6408
6408
  kind: m.kind ?? "query",
6409
- auth: m.auth ?? "protected",
6409
+ auth: require_dist.resolveMethodAuth(def, m),
6410
6410
  schema: m,
6411
6411
  isVoid: require_dist.isVoidInput(m.input),
6412
6412
  isObject: require_dist.isObjectInput(m.input),
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM$1, r as __exportAll, t as __commonJSMin$1 } from "./chunk-CNf5ZN-e.mjs";
2
- import { At as asNumber, B as extractNestedAddonId, Bt as parseJsonUnknown$1, Dt as ReadinessTimeoutError, Et as ReadinessRegistry, Ft as expandCapMethods, Gt as EventCategory$1, H as isArrayOutputSchema, Ht as resolveCapMount, J as lifecycleJobSchema, K as isVoidInput, Mt as createEvent, Nt as emitDownForOwnedCaps, Q as looseSchema, St as DEVICE_STATUS_METHOD, U as isCollectionArrayMethod, Ut as scopeKey, Vt as readinessKey, W as isObjectInput, Z as logLevelAtMost, bt as DATAPLANE_SECRET_HEADER$1, d as METHOD_ACCESS_MAP, f as RUNTIME_DEFAULTS, it as procedureAuthKey, jt as asString$1, kt as asJsonObject$1, ot as scopesAllowAddon, q as kebabToCamel, st as scopesAllowDeviceCap, t as ALL_CAPABILITY_DEFINITIONS, tt as objectInputDeclaresAddonId, vt as errMsg$1, xt as DEVICE_SETTINGS_CONTRIBUTION_METHODS, y as addonSettingsCapability, zt as parseJsonObject } from "./dist-DMOTtKH8.mjs";
2
+ import { At as asJsonObject$1, B as extractNestedAddonId, Bt as parseJsonObject, Ct as DEVICE_STATUS_METHOD, Dt as ReadinessRegistry, H as isArrayOutputSchema, Ht as readinessKey, It as expandCapMethods, J as lifecycleJobSchema, K as isVoidInput, Kt as EventCategory$1, Mt as asString$1, Nt as createEvent, Ot as ReadinessTimeoutError, Pt as emitDownForOwnedCaps, Q as looseSchema, St as DEVICE_SETTINGS_CONTRIBUTION_METHODS, U as isCollectionArrayMethod, Ut as resolveCapMount, Vt as parseJsonUnknown$1, W as isObjectInput, Wt as scopeKey, Z as logLevelAtMost, at as resolveMethodAuth, ct as scopesAllowDeviceCap, d as METHOD_ACCESS_MAP, f as RUNTIME_DEFAULTS, it as procedureAuthKey, jt as asNumber, q as kebabToCamel, st as scopesAllowAddon, t as ALL_CAPABILITY_DEFINITIONS, tt as objectInputDeclaresAddonId, xt as DATAPLANE_SECRET_HEADER$1, y as addonSettingsCapability, yt as errMsg$1 } from "./dist-D595vzqY.mjs";
3
3
  import { AlertCenterAddon } from "./builtins/alerts/alerts.addon.mjs";
4
4
  import "./builtins/alerts/index.mjs";
5
5
  import { t as formatLogLine } from "./formatter-B7qW8bPJ.mjs";
@@ -6399,7 +6399,7 @@ function methodsFor(def) {
6399
6399
  return {
6400
6400
  name,
6401
6401
  kind: m.kind ?? "query",
6402
- auth: m.auth ?? "protected",
6402
+ auth: resolveMethodAuth(def, m),
6403
6403
  schema: m,
6404
6404
  isVoid: isVoidInput(m.input),
6405
6405
  isObject: isObjectInput(m.input),
@@ -1,4 +1,4 @@
1
- import { kt as asJsonObject } from "./dist-DMOTtKH8.mjs";
1
+ import { At as asJsonObject } from "./dist-D595vzqY.mjs";
2
2
  //#region src/builtins/sqlite-storage/retired-settings-keys.ts
3
3
  /**
4
4
  * Is THIS node the one whose settings store is the cluster's authority?
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-mQ3BkrJd.js");
1
+ const require_dist = require("./dist-MqjCyx1E.js");
2
2
  //#region src/builtins/sqlite-storage/retired-settings-keys.ts
3
3
  /**
4
4
  * Is THIS node the one whose settings store is the cluster's authority?
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.2.137",
3
+ "version": "1.2.139",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",