@camstack/system 1.2.140 → 1.2.141

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 (61) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  11. package/dist/builtins/console-logging/index.js +1 -1
  12. package/dist/builtins/console-logging/index.mjs +1 -1
  13. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  14. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  15. package/dist/builtins/device-manager/device-linked-devices.d.ts +4 -2
  16. package/dist/builtins/device-manager/device-manager.addon.js +210 -21
  17. package/dist/builtins/device-manager/device-manager.addon.mjs +210 -21
  18. package/dist/builtins/device-manager/device-meta-actions.d.ts +27 -0
  19. package/dist/builtins/device-manager/device-row-store.d.ts +21 -3
  20. package/dist/builtins/device-manager/fleet-read-census.d.ts +20 -1
  21. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  22. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  23. package/dist/builtins/hub-forwarder/index.js +1 -1
  24. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  25. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  26. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  27. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  28. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  29. package/dist/builtins/local-network/local-network.addon.js +1 -1
  30. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  31. package/dist/builtins/loki-logging/index.js +1 -1
  32. package/dist/builtins/loki-logging/index.mjs +1 -1
  33. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  34. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  35. package/dist/builtins/platform-probe/index.js +1 -1
  36. package/dist/builtins/platform-probe/index.mjs +1 -1
  37. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  38. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  39. package/dist/builtins/snapshot/index.js +1 -1
  40. package/dist/builtins/snapshot/index.mjs +1 -1
  41. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  42. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  43. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  44. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  45. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  46. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  47. package/dist/builtins/system-config/system-config.addon.js +1 -1
  48. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  49. package/dist/builtins/winston-logging/index.js +1 -1
  50. package/dist/builtins/winston-logging/index.mjs +1 -1
  51. package/dist/{dist-Clses4Bs.mjs → dist-6mn3LQvB.mjs} +2502 -2380
  52. package/dist/{dist-D5Jhh8Vk.js → dist-BNhvXrQk.js} +2502 -2380
  53. package/dist/index.js +2 -2
  54. package/dist/index.mjs +2 -2
  55. package/dist/kernel/transport/cap-route-resolver.d.ts +11 -1
  56. package/dist/kernel/transport/child-cap-protocol.d.ts +14 -0
  57. package/dist/{manifest-python-deps-Dxifi-o0.mjs → manifest-python-deps-D3zYlMEa.mjs} +35 -6
  58. package/dist/{manifest-python-deps-Bu8H1_rg.js → manifest-python-deps-DBQarwp9.js} +35 -6
  59. package/dist/{retired-settings-keys-Dflres-F.js → retired-settings-keys-DVgdWTd7.js} +1 -1
  60. package/dist/{retired-settings-keys-Cr8N6C6X.mjs → retired-settings-keys-SYwVv_Ym.mjs} +1 -1
  61. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
- const require_manifest_python_deps = require("./manifest-python-deps-Bu8H1_rg.js");
2
+ const require_manifest_python_deps = require("./manifest-python-deps-DBQarwp9.js");
3
3
  const require_custom_action_registry = require("./custom-action-registry-jY0NOZK8.js");
4
4
  let node_path = require("node:path");
5
5
  node_path = require_chunk.__toESM(node_path);
@@ -1,4 +1,4 @@
1
- import { F as createUdsLoggerWithControl, I as LocalChildClient, Nt as startRunnerHeapWatch, bt as resolveAddonClass, i as createUdsAddonContext, it as setWorkerNativeCapsChangeListener, nt as getWorkerNativeCapSnapshot, ot as validateProviderRegistrations, t as installManifestPythonDeps, tt as getWorkerNativeCapProvider, yt as installManifestNativeDeps } from "./manifest-python-deps-Dxifi-o0.mjs";
1
+ import { F as createUdsLoggerWithControl, I as LocalChildClient, Nt as startRunnerHeapWatch, bt as resolveAddonClass, i as createUdsAddonContext, it as setWorkerNativeCapsChangeListener, nt as getWorkerNativeCapSnapshot, ot as validateProviderRegistrations, t as installManifestPythonDeps, tt as getWorkerNativeCapProvider, yt as installManifestNativeDeps } from "./manifest-python-deps-D3zYlMEa.mjs";
2
2
  import { t as CustomActionRegistry } from "./custom-action-registry-F__gp_VX.mjs";
3
3
  import { register } from "node:module";
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-D5Jhh8Vk.js");
6
+ const require_dist = require("../../dist-BNhvXrQk.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 { Kt as EventCategory, bt as BaseAddon, v as addonPagesCapability, yt as errMsg } from "../../dist-Clses4Bs.mjs";
1
+ import { Kt as EventCategory, bt as BaseAddon, v as addonPagesCapability, yt as errMsg } from "../../dist-6mn3LQvB.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-D5Jhh8Vk.js");
6
+ const require_dist = require("../../dist-BNhvXrQk.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 { Kt as EventCategory, b as addonWidgetsCapability, bt as BaseAddon, yt as errMsg } from "../../dist-Clses4Bs.mjs";
1
+ import { Kt as EventCategory, b as addonWidgetsCapability, bt as BaseAddon, yt as errMsg } from "../../dist-6mn3LQvB.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-D5Jhh8Vk.js");
6
+ const require_dist = require("../../dist-BNhvXrQk.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 { Kt as EventCategory, Nt as createEvent, bt as BaseAddon, n as AlertSchema, x as alertsCapability, yt as errMsg } from "../../dist-Clses4Bs.mjs";
1
+ import { Kt as EventCategory, Nt as createEvent, bt as BaseAddon, n as AlertSchema, x as alertsCapability, yt as errMsg } from "../../dist-6mn3LQvB.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-D5Jhh8Vk.js");
6
+ const require_dist = require("../../dist-BNhvXrQk.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, Kt as EventCategory, bt as BaseAddon } from "../../dist-Clses4Bs.mjs";
1
+ import { C as backupCapability, Kt as EventCategory, bt as BaseAddon } from "../../dist-6mn3LQvB.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-D5Jhh8Vk.js");
6
+ const require_dist = require("../../dist-BNhvXrQk.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, bt as BaseAddon } from "../../dist-Clses4Bs.mjs";
1
+ import { X as logDestinationCapability, bt as BaseAddon } from "../../dist-6mn3LQvB.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-D5Jhh8Vk.js");
3
+ const require_dist = require("../../dist-BNhvXrQk.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, 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-Clses4Bs.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-6mn3LQvB.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";
@@ -1,4 +1,4 @@
1
- import { ConfigUISchemaWithValues, IDeviceManagerProvider, LinkedDevicesMode } from '@camstack/types';
1
+ import { ConfigUISchemaWithValues, IDeviceManagerProvider, LinkedDevicesMode, DeviceType } from '@camstack/types';
2
2
  import { ProviderContext } from './device-provider-context.js';
3
3
  /** Reserved device-config keys (cf. `_profileMap`). */
4
4
  export declare const LINKED_MODE_CONFIG_KEY = "_linkedDevicesMode";
@@ -21,10 +21,12 @@ export interface LinkCandidate {
21
21
  readonly id: number;
22
22
  readonly parentDeviceId: number | null;
23
23
  readonly location: string | null;
24
+ readonly type: DeviceType;
24
25
  }
25
26
  /**
26
27
  * Pure resolution core: children ∪ (auto → same-location | manual → list),
27
- * minus self, deduped. Order: children first, then mode additions.
28
+ * minus self, deduped, CONTAINERS expanded to their children (see file
29
+ * header). Order: children first, then mode additions.
28
30
  */
29
31
  export declare function resolveLinkedDeviceIds(params: {
30
32
  readonly selfId: number;
@@ -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-D5Jhh8Vk.js");
7
- const require_retired_settings_keys = require("../../retired-settings-keys-Dflres-F.js");
6
+ const require_dist = require("../../dist-BNhvXrQk.js");
7
+ const require_retired_settings_keys = require("../../retired-settings-keys-DVgdWTd7.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
@@ -1298,6 +1298,20 @@ function resolveDeviceById(registry, deviceId) {
1298
1298
  }
1299
1299
  //#endregion
1300
1300
  //#region src/builtins/device-manager/device-queries.ts
1301
+ /**
1302
+ * The ORIGINATING addon id off a cap-channel call's `args`, or `undefined`.
1303
+ *
1304
+ * Not part of `IDeviceManagerProvider['listAll']`'s public Zod schema —
1305
+ * `CapRouteResolver`'s hub-in-process dispatch merges it into `args` as an
1306
+ * out-of-band field (mirrors how `nodeId` already rides inline in args on
1307
+ * this same path), so it never crosses the tRPC/HTTP boundary and never
1308
+ * reaches a browser client. Only ever present on a call the parent itself
1309
+ * routed from a registered UDS child, never client-suppliable.
1310
+ */
1311
+ function extractCallerAddonId(input) {
1312
+ const raw = Reflect.get(input, "callerAddonId");
1313
+ return typeof raw === "string" && raw.length > 0 ? raw : void 0;
1314
+ }
1301
1315
  async function listPersistedByAddon(pctx, input) {
1302
1316
  const { addonId } = input;
1303
1317
  return (await pctx.metaStore.rows.listByAddon(addonId)).map(({ meta: m }) => ({
@@ -1339,6 +1353,7 @@ async function readDeviceConfigs(pctx, deviceIds) {
1339
1353
  async function listAll(pctx, input) {
1340
1354
  const ownerFilter = Reflect.get(input, "addonId");
1341
1355
  if (ownerFilter !== void 0 && (typeof ownerFilter !== "string" || ownerFilter.length === 0)) throw new Error(`deviceManager.listAll: addonId must be a non-empty string or be omitted — got ${JSON.stringify(ownerFilter)}. An owner filter that is present but empty is never widened to every device. On an addon context the id is \`ctx.id\`; there is no \`ctx.addonId\`.`);
1356
+ const callerAddonId = pctx.metaStore.rows.censusArmed ? extractCallerAddonId(input) : void 0;
1342
1357
  const { addonId } = input;
1343
1358
  const slim = input.projection === "slim";
1344
1359
  const camerasOnly = input.isCamera === true;
@@ -1348,7 +1363,7 @@ async function listAll(pctx, input) {
1348
1363
  const fleet = addonId ? await pctx.metaStore.rows.listByAddon(addonId) : await pctx.metaStore.rows.listFleet({
1349
1364
  ...input.deviceIds !== void 0 ? { deviceIds: input.deviceIds } : {},
1350
1365
  slim
1351
- });
1366
+ }, callerAddonId);
1352
1367
  const rowById = /* @__PURE__ */ new Map();
1353
1368
  for (const row of fleet) rowById.set(row.meta.id, row);
1354
1369
  if (pctx.registry) {
@@ -1869,21 +1884,58 @@ function parseLinkedDevicesConfig(blob) {
1869
1884
  };
1870
1885
  }
1871
1886
  /**
1887
+ * Resolve one candidate id to the ids it should actually contribute to a
1888
+ * linked set: itself, unless it is a CONTAINER, in which case its own
1889
+ * children (recursively expanded the same way — a container-of-containers
1890
+ * is vanishingly unlikely per `DeviceType.Container`'s contract, but this
1891
+ * costs nothing to handle). `visited` guards a pathological parent-cycle in
1892
+ * persisted data from recursing forever; a candidate already on the current
1893
+ * expansion path is dropped rather than re-expanded.
1894
+ *
1895
+ * This is the STRUCTURAL, location-independent way a container's child
1896
+ * becomes reachable: the device-tree "children of the camera" rule and a
1897
+ * persisted manual pick are parentDeviceId chains, never location-based —
1898
+ * a container named there is excluded by expanding into whatever it
1899
+ * actually contains, regardless of what its (or its children's) `location`
1900
+ * column holds.
1901
+ */
1902
+ function expandContainer(id, byId, byParent, visited, out) {
1903
+ if (visited.has(id)) return;
1904
+ const candidate = byId.get(id);
1905
+ if (!candidate) return;
1906
+ if (candidate.type !== require_dist.DeviceType.Container) {
1907
+ out.add(id);
1908
+ return;
1909
+ }
1910
+ const nextVisited = new Set(visited);
1911
+ nextVisited.add(id);
1912
+ const children = byParent.get(id) ?? [];
1913
+ for (const child of children) expandContainer(child.id, byId, byParent, nextVisited, out);
1914
+ }
1915
+ /**
1872
1916
  * Pure resolution core: children ∪ (auto → same-location | manual → list),
1873
- * minus self, deduped. Order: children first, then mode additions.
1917
+ * minus self, deduped, CONTAINERS expanded to their children (see file
1918
+ * header). Order: children first, then mode additions.
1874
1919
  */
1875
1920
  function resolveLinkedDeviceIds(params) {
1876
1921
  const { selfId, selfLocation, config, candidates } = params;
1922
+ const byId = new Map(candidates.map((c) => [c.id, c]));
1923
+ const byParent = /* @__PURE__ */ new Map();
1924
+ for (const c of candidates) {
1925
+ if (c.parentDeviceId === null) continue;
1926
+ const siblings = byParent.get(c.parentDeviceId);
1927
+ if (siblings) siblings.push(c);
1928
+ else byParent.set(c.parentDeviceId, [c]);
1929
+ }
1877
1930
  const ids = /* @__PURE__ */ new Set();
1878
- for (const c of candidates) if (c.parentDeviceId === selfId) ids.add(c.id);
1931
+ const expand = (id) => expandContainer(id, byId, byParent, /* @__PURE__ */ new Set(), ids);
1932
+ for (const c of candidates) if (c.parentDeviceId === selfId) expand(c.id);
1879
1933
  if (config.mode === "auto") {
1880
- if (selfLocation !== null && selfLocation.length > 0) {
1881
- for (const c of candidates) if (c.location === selfLocation) ids.add(c.id);
1934
+ if (selfLocation !== null && selfLocation.length > 0) for (const c of candidates) {
1935
+ if (c.type === require_dist.DeviceType.Container) continue;
1936
+ if (c.location === selfLocation) ids.add(c.id);
1882
1937
  }
1883
- } else {
1884
- const known = new Set(candidates.map((c) => c.id));
1885
- for (const id of config.manualIds) if (known.has(id)) ids.add(id);
1886
- }
1938
+ } else for (const id of config.manualIds) if (byId.has(id)) expand(id);
1887
1939
  ids.delete(selfId);
1888
1940
  return [...ids];
1889
1941
  }
@@ -2007,7 +2059,7 @@ async function buildLinkedDevicesContribution(pctx, deviceId) {
2007
2059
  if (!persisted || persisted.meta.type !== require_dist.DeviceType.Camera) return null;
2008
2060
  const [all, blob] = await Promise.all([listAll(pctx, LINK_CANDIDATE_PROJECTION), pctx.settings.readDeviceStore(deviceId)]);
2009
2061
  const config = parseLinkedDevicesConfig(blob);
2010
- const options = all.filter((d) => d.id !== deviceId).map((d) => ({
2062
+ const options = all.filter((d) => d.id !== deviceId && d.type !== require_dist.DeviceType.Container).map((d) => ({
2011
2063
  value: String(d.id),
2012
2064
  label: d.location !== null ? `${d.name} (${d.location})` : d.name
2013
2065
  }));
@@ -3343,16 +3395,83 @@ async function setName(pctx, input) {
3343
3395
  });
3344
3396
  }
3345
3397
  /**
3398
+ * Every descendant of `rootId`, walked via `parentDeviceId` chains
3399
+ * (BFS, `listByParent` per level — the same indexed lookup `setName`'s
3400
+ * rename cascade uses one level of). `visited` seeded with `rootId` guards a
3401
+ * pathological parent-cycle in persisted data from recursing forever; a row
3402
+ * already seen is never re-queued or re-emitted. Depth is unbounded on
3403
+ * purpose — a nested container's own children are still `rootId`'s
3404
+ * descendants and must align too.
3405
+ */
3406
+ async function collectDescendants(metaStore, rootId) {
3407
+ const out = [];
3408
+ const visited = new Set([rootId]);
3409
+ const queue = [rootId];
3410
+ while (queue.length > 0) {
3411
+ const parentId = queue.shift();
3412
+ if (parentId === void 0) break;
3413
+ for (const row of await metaStore.rows.listByParent(parentId)) {
3414
+ if (visited.has(row.meta.id)) continue;
3415
+ visited.add(row.meta.id);
3416
+ out.push(row.meta);
3417
+ queue.push(row.meta.id);
3418
+ }
3419
+ }
3420
+ return out;
3421
+ }
3422
+ /**
3346
3423
  * Update the operator-organisational location label. `null`
3347
3424
  * clears it. Mirrors the same persist-then-emit shape as
3348
3425
  * `setName`; consumers subscribe to `DeviceMetaChanged` and
3349
3426
  * filter on `field: 'location'`.
3427
+ *
3428
+ * CASCADE (operator decision, 2026-08-26 — supersedes the read-time
3429
+ * inheritance `device-linked-devices.ts` used to resolve): when `deviceId`
3430
+ * is a `DeviceType.Container`, the SAME location is written to every
3431
+ * descendant's own `location` column, recursively, in this exact call —
3432
+ * never resolved live by a reader again. This DELIBERATELY overwrites a
3433
+ * descendant's own, possibly different, location: a cascade is a write, and
3434
+ * a write that stops at an existing value is not a cascade — the operator
3435
+ * setting the container's location is understood as organising the whole
3436
+ * subtree by it. Only a CONTAINER cascades; a non-container device's
3437
+ * location write is a single row, same as before.
3438
+ *
3439
+ * RETROACTIVITY: this only runs when `setLocation` is called. A container
3440
+ * that already had a location before this shipped does NOT retroactively
3441
+ * align its descendants — they stay unaligned until the operator re-saves
3442
+ * the container's location.
3443
+ *
3444
+ * ATOMICITY: the whole cascade runs inside ONE `withMetaWriteLock` critical
3445
+ * section, so no concurrent writer can interleave a read or a write into the
3446
+ * middle of it — but it is still N separate row writes, not one transaction.
3447
+ * A row write that throws does NOT abort the cascade (a swallowed continue,
3448
+ * not a silent one): it is collected as a failure, every other descendant is
3449
+ * still attempted, and if any failed the outcome is logged at `error` with
3450
+ * every failing id — never indistinguishable from a clean cascade. A
3451
+ * `DeviceMetaChanged` event fires for the container itself and for every
3452
+ * descendant actually written (never for one that failed), so linked-device
3453
+ * consumers (auto-link) see exactly what landed.
3350
3454
  */
3351
3455
  async function setLocation(pctx, input) {
3352
3456
  const { deviceId, location } = input;
3457
+ const cascaded = [];
3458
+ const failed = [];
3353
3459
  await pctx.metaStore.withMetaWriteLock(async () => {
3354
- if (!await pctx.metaStore.resolvePersistedById(deviceId)) throw new Error(`[device-manager] setLocation: unknown device id=${deviceId}`);
3460
+ const persisted = await pctx.metaStore.resolvePersistedById(deviceId);
3461
+ if (!persisted) throw new Error(`[device-manager] setLocation: unknown device id=${deviceId}`);
3355
3462
  await pctx.metaStore.rows.patch(deviceId, { location });
3463
+ if (persisted.meta.type === require_dist.DeviceType.Container) {
3464
+ const descendants = await collectDescendants(pctx.metaStore, deviceId);
3465
+ for (const descendant of descendants) try {
3466
+ await pctx.metaStore.rows.patch(descendant.id, { location });
3467
+ cascaded.push(descendant.id);
3468
+ } catch (err) {
3469
+ failed.push({
3470
+ id: descendant.id,
3471
+ error: err
3472
+ });
3473
+ }
3474
+ }
3356
3475
  });
3357
3476
  pctx.host.ctx.eventBus.emit({
3358
3477
  id: (0, node_crypto.randomUUID)(),
@@ -3368,6 +3487,36 @@ async function setLocation(pctx, input) {
3368
3487
  value: location
3369
3488
  }
3370
3489
  });
3490
+ for (const childId of cascaded) pctx.host.ctx.eventBus.emit({
3491
+ id: (0, node_crypto.randomUUID)(),
3492
+ timestamp: /* @__PURE__ */ new Date(),
3493
+ source: {
3494
+ type: "device",
3495
+ id: childId
3496
+ },
3497
+ category: require_dist.EventCategory.DeviceMetaChanged,
3498
+ data: {
3499
+ deviceId: childId,
3500
+ field: "location",
3501
+ value: location
3502
+ }
3503
+ });
3504
+ if (cascaded.length > 0 || failed.length > 0) pctx.host.ctx.logger.info("setLocation: cascaded location to container descendants", {
3505
+ tags: { deviceId },
3506
+ meta: {
3507
+ location,
3508
+ updated: cascaded.length,
3509
+ failed: failed.length
3510
+ }
3511
+ });
3512
+ if (failed.length > 0) pctx.host.ctx.logger.error("setLocation: cascade partially failed — some descendants NOT aligned to the new location", {
3513
+ tags: { deviceId },
3514
+ meta: {
3515
+ location,
3516
+ failedIds: failed.map((f) => f.id),
3517
+ errors: failed.map((f) => f.error instanceof Error ? f.error.message : String(f.error))
3518
+ }
3519
+ });
3371
3520
  }
3372
3521
  /**
3373
3522
  * Update the device type. Writes the meta row, emits a
@@ -4000,9 +4149,29 @@ var FleetReadCensus = class FleetReadCensus {
4000
4149
  * to it even if it lands after the close — otherwise the slow reads at the
4001
4150
  * edge would be attributed to nobody, and the slow ones are the interesting
4002
4151
  * ones.
4152
+ *
4153
+ * `callerAddonId`, when given, is formatted as the site VERBATIM instead of
4154
+ * walking the local stack. A call that arrived over the cap channel — a
4155
+ * forked addon's `ctx.api.deviceManager.listAll.query()`, routed through
4156
+ * `LocalChildRegistry` → `onUnownedCall` → `CapRouteResolver` — carries its
4157
+ * origin as DATA (the registered `childId` the parent captured off the UDS
4158
+ * connection), and no amount of stack depth would ever recover it: every
4159
+ * frame between the socket read and this call is the SAME generic
4160
+ * dispatcher code (`onUnownedCall`, `dispatch`, `invoke`) regardless of
4161
+ * which addon called — the 2026-08-25 boot census measured this directly,
4162
+ * five thousand calls landing on `listAll` with nothing nameable above it.
4163
+ *
4164
+ * `isOpen` is checked FIRST, before the id is even looked at — a caller
4165
+ * threading a (cheap, already-known) `callerAddonId` through on every
4166
+ * request, armed or not, must not pay for the template string when
4167
+ * disarmed. This is also why formatting lives HERE rather than at the call
4168
+ * site: `DeviceRowStore` passes the raw id through unconditionally, and
4169
+ * only this method — which already knows whether armed — decides whether
4170
+ * to spend anything on it.
4003
4171
  */
4004
- siteOf(nowMs = Date.now()) {
4172
+ siteOf(callerAddonId, nowMs = Date.now()) {
4005
4173
  if (!this.isOpen(nowMs)) return null;
4174
+ if (callerAddonId !== void 0) return `addon:${callerAddonId}`;
4006
4175
  const holder = {};
4007
4176
  Error.captureStackTrace(holder, this.siteOf);
4008
4177
  return siteFromStack(holder.stack);
@@ -4387,6 +4556,16 @@ var DeviceRowStore = class {
4387
4556
  /** Call-site attribution for fleet reads. Armed only by
4388
4557
  * `CAMSTACK_FLEET_READ_CENSUS_MS`; see `fleet-read-census.ts`. */
4389
4558
  census;
4559
+ /**
4560
+ * Is the fleet-read census armed right now? Exposed so a caller ABOVE this
4561
+ * store (`device-queries.ts`) can skip extracting a caller identity off its
4562
+ * input entirely when nobody is measuring — "the boolean field read first"
4563
+ * pattern this repo already uses elsewhere to keep a diagnostic seam at
4564
+ * zero cost while disarmed.
4565
+ */
4566
+ get censusArmed() {
4567
+ return this.census.armed;
4568
+ }
4390
4569
  constructor(backend, logger, census) {
4391
4570
  this.backend = backend;
4392
4571
  this.logger = logger;
@@ -4435,15 +4614,23 @@ var DeviceRowStore = class {
4435
4614
  }
4436
4615
  return decoded;
4437
4616
  }
4438
- /** Every device, ordered by numeric id. */
4439
- async listAll() {
4617
+ /**
4618
+ * Every device, ordered by numeric id.
4619
+ *
4620
+ * `callerAddonId`, when known, is the addon that ORIGINATED this call —
4621
+ * captured off the cap-channel envelope (`CapCallInput.callerAddonId`, set
4622
+ * by `LocalChildRegistry` from the registered `childId`), never guessed.
4623
+ * Passed straight to the census; a stack walk can never name it (see
4624
+ * `fleet-read-census.ts#siteOf`).
4625
+ */
4626
+ async listAll(callerAddonId) {
4440
4627
  return this.list({
4441
4628
  orderBy: {
4442
4629
  field: "deviceId",
4443
4630
  direction: "asc"
4444
4631
  },
4445
4632
  limit: DEVICE_ROWS_FLEET_LIMIT
4446
- });
4633
+ }, void 0, callerAddonId);
4447
4634
  }
4448
4635
  /**
4449
4636
  * The fleet, read as narrowly as the question allows.
@@ -4460,8 +4647,10 @@ var DeviceRowStore = class {
4460
4647
  *
4461
4648
  * `isCamera` is deliberately NOT one of them — see that constant's note and
4462
4649
  * `device-row-store-filtered.spec.ts`.
4650
+ *
4651
+ * `callerAddonId` — see {@link listAll}'s doc; same meaning here.
4463
4652
  */
4464
- async listFleet(opts) {
4653
+ async listFleet(opts, callerAddonId) {
4465
4654
  if (opts.deviceIds !== void 0 && opts.deviceIds.length === 0) return [];
4466
4655
  return this.list({
4467
4656
  ...opts.deviceIds !== void 0 ? { whereIn: { deviceId: [...new Set(opts.deviceIds)] } } : {},
@@ -4470,7 +4659,7 @@ var DeviceRowStore = class {
4470
4659
  direction: "asc"
4471
4660
  },
4472
4661
  limit: DEVICE_ROWS_FLEET_LIMIT
4473
- }, opts.slim === true ? DEVICE_ROWS_SLIM_COLUMNS : void 0);
4662
+ }, opts.slim === true ? DEVICE_ROWS_SLIM_COLUMNS : void 0, callerAddonId);
4474
4663
  }
4475
4664
  /**
4476
4665
  * Many devices, by numeric id, in ONE query.
@@ -4561,8 +4750,8 @@ var DeviceRowStore = class {
4561
4750
  await this.declare();
4562
4751
  return this.backend.count({ collection: DEVICE_ROWS_COLLECTION });
4563
4752
  }
4564
- async list(filter, columns) {
4565
- const site = this.census.siteOf();
4753
+ async list(filter, columns, callerAddonId) {
4754
+ const site = this.census.siteOf(callerAddonId);
4566
4755
  await this.declare();
4567
4756
  const records = await this.backend.query({
4568
4757
  collection: DEVICE_ROWS_COLLECTION,