@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.
- package/dist/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-linked-devices.d.ts +4 -2
- package/dist/builtins/device-manager/device-manager.addon.js +210 -21
- package/dist/builtins/device-manager/device-manager.addon.mjs +210 -21
- package/dist/builtins/device-manager/device-meta-actions.d.ts +27 -0
- package/dist/builtins/device-manager/device-row-store.d.ts +21 -3
- package/dist/builtins/device-manager/fleet-read-census.d.ts +20 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-Clses4Bs.mjs → dist-6mn3LQvB.mjs} +2502 -2380
- package/dist/{dist-D5Jhh8Vk.js → dist-BNhvXrQk.js} +2502 -2380
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/dist/kernel/transport/cap-route-resolver.d.ts +11 -1
- package/dist/kernel/transport/child-cap-protocol.d.ts +14 -0
- package/dist/{manifest-python-deps-Dxifi-o0.mjs → manifest-python-deps-D3zYlMEa.mjs} +35 -6
- package/dist/{manifest-python-deps-Bu8H1_rg.js → manifest-python-deps-DBQarwp9.js} +35 -6
- package/dist/{retired-settings-keys-Dflres-F.js → retired-settings-keys-DVgdWTd7.js} +1 -1
- package/dist/{retired-settings-keys-Cr8N6C6X.mjs → retired-settings-keys-SYwVv_Ym.mjs} +1 -1
- package/package.json +1 -1
package/dist/addon-runner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
2
|
-
const require_manifest_python_deps = require("./manifest-python-deps-
|
|
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);
|
package/dist/addon-runner.mjs
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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-
|
|
7
|
-
const require_retired_settings_keys = require("../../retired-settings-keys-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
4439
|
-
|
|
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,
|