camstack 1.2.76 → 1.2.78

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.
@@ -15614,7 +15614,8 @@ var OpsLogReasonSchema = external_exports.enum([
15614
15614
  "quota",
15615
15615
  "manual",
15616
15616
  "operator",
15617
- "maintenance"
15617
+ "maintenance",
15618
+ "orphaned-device"
15618
15619
  ]);
15619
15620
  var OpsLogEntrySchema = external_exports.object({
15620
15621
  /** Unique row id. */
@@ -20544,6 +20545,27 @@ var DevicePersistConfigPayloadSchema = external_exports.object({
20544
20545
  deviceId: external_exports.number(),
20545
20546
  data: external_exports.record(external_exports.string(), external_exports.unknown())
20546
20547
  });
20548
+ var MigrateSwitchOutcomeSchema = external_exports.enum([
20549
+ "off",
20550
+ "on",
20551
+ "not-offered",
20552
+ "unreachable"
20553
+ ]);
20554
+ var MigrateSwitchReportSchema = external_exports.object({
20555
+ deviceId: external_exports.number(),
20556
+ switchId: CameraSwitchIdSchema,
20557
+ outcome: MigrateSwitchOutcomeSchema,
20558
+ detail: external_exports.string().optional()
20559
+ });
20560
+ var MigrateDeviceResultSchema = external_exports.object({
20561
+ sourceId: external_exports.number(),
20562
+ targetId: external_exports.number(),
20563
+ switches: external_exports.array(MigrateSwitchReportSchema).readonly(),
20564
+ /** Switches that could NOT be turned off on the source. Empty is the only
20565
+ * value meaning the replaced hardware is quiet. */
20566
+ sourceStillLive: external_exports.array(CameraSwitchIdSchema).readonly(),
20567
+ swapped: external_exports.boolean()
20568
+ });
20547
20569
  var deviceManagerCapability = {
20548
20570
  name: "device-manager",
20549
20571
  scope: "system",
@@ -20558,6 +20580,33 @@ var deviceManagerCapability = {
20558
20580
  addonId: external_exports.string(),
20559
20581
  stableId: external_exports.string()
20560
20582
  }), external_exports.object({ id: external_exports.number() }), { kind: "mutation" }),
20583
+ /**
20584
+ * Replace a camera: the new hardware inherits the number the system knows.
20585
+ *
20586
+ * Everything general about a camera is keyed on the numeric id and nothing
20587
+ * else — the recording path on disk, the hour ledger, the media store, zone
20588
+ * ownership, and all four ecosystem exports. So a replacement moves the
20589
+ * NUMBER rather than the work, and the operator keeps their footage, their
20590
+ * rules and their integrations.
20591
+ *
20592
+ * Both cameras are switched off first, then the identities are exchanged
20593
+ * (children follow their parent), then the target — now answering on the
20594
+ * inherited number — is switched back on.
20595
+ *
20596
+ * **A switch that could not be written is REPORTED, not swallowed.** The
20597
+ * source is characteristically broken, and two of the eight switches write
20598
+ * the camera itself, so on a dead one they cannot be written at all.
20599
+ * `sourceStillLive` names what stayed live; empty is the only value that
20600
+ * means the old hardware is quiet. The migration proceeds either way —
20601
+ * refusing would refuse the case this exists for.
20602
+ */
20603
+ migrateDevice: method(external_exports.object({
20604
+ sourceId: external_exports.number(),
20605
+ targetId: external_exports.number()
20606
+ }), MigrateDeviceResultSchema, {
20607
+ kind: "mutation",
20608
+ auth: "admin"
20609
+ }),
20561
20610
  /** Register a device in the DB + in-memory registry. Called by DeviceManagerApi.register(). */
20562
20611
  registerDevice: method(DeviceRegisterPayloadSchema, external_exports.void(), { kind: "mutation" }),
20563
20612
  /** Remove a device from the DB + in-memory registry. Called by DeviceManagerApi.remove(). */
@@ -40655,6 +40704,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
40655
40704
  addonId: null,
40656
40705
  access: "view"
40657
40706
  },
40707
+ "deviceManager.migrateDevice": {
40708
+ capName: "device-manager",
40709
+ capScope: "system",
40710
+ addonId: null,
40711
+ access: "create"
40712
+ },
40658
40713
  "deviceManager.persistConfig": {
40659
40714
  capName: "device-manager",
40660
40715
  capScope: "system",
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runDiscover
4
- } from "./chunk-U3GHKSOG.js";
4
+ } from "./chunk-EDAEBWHA.js";
5
5
  import "./chunk-LMMQX4CK.js";
6
6
 
7
7
  // src/cli.ts
@@ -38,7 +38,7 @@ async function runServe(args) {
38
38
  ...typeof values.data === "string" ? { data: values.data } : {}
39
39
  };
40
40
  Object.assign(process.env, buildServeEnv(opts));
41
- await import("./launcher-WOTGVQN3.js");
41
+ await import("./launcher-22Q4HRAB.js");
42
42
  }
43
43
 
44
44
  // src/commands/agent.ts
@@ -83,7 +83,7 @@ async function runAgent(args) {
83
83
  ...typeof values.port === "string" ? { port: values.port } : {}
84
84
  };
85
85
  Object.assign(process.env, buildAgentEnv(opts));
86
- await import("./launcher-WOTGVQN3.js");
86
+ await import("./launcher-22Q4HRAB.js");
87
87
  }
88
88
 
89
89
  // src/commands/setup.ts
@@ -1130,7 +1130,7 @@ function isUnknown(_value) {
1130
1130
  return true;
1131
1131
  }
1132
1132
  async function resolveServerInteractive(presetNamespace) {
1133
- const { discoverNodes, resolveHubFromDiscovered, filterHubNodes, DEFAULT_HUB_HTTPS_PORT } = await import("./discover-OD7ECRSD.js");
1133
+ const { discoverNodes, resolveHubFromDiscovered, filterHubNodes, DEFAULT_HUB_HTTPS_PORT } = await import("./discover-HIST37I5.js");
1134
1134
  if (presetNamespace) {
1135
1135
  const spinner4 = clack.spinner();
1136
1136
  spinner4.start(`Discovering hub on LAN (namespace "${presetNamespace}")`);
@@ -5,7 +5,7 @@ import {
5
5
  filterHubNodes,
6
6
  resolveHubFromDiscovered,
7
7
  runDiscover
8
- } from "./chunk-U3GHKSOG.js";
8
+ } from "./chunk-EDAEBWHA.js";
9
9
  import "./chunk-LMMQX4CK.js";
10
10
  export {
11
11
  DEFAULT_HUB_HTTPS_PORT,
@@ -23633,9 +23633,9 @@ var require_zod = __commonJS({
23633
23633
  }
23634
23634
  });
23635
23635
 
23636
- // ../system/dist/dist-DsoVfJSR.js
23637
- var require_dist_DsoVfJSR = __commonJS({
23638
- "../system/dist/dist-DsoVfJSR.js"(exports) {
23636
+ // ../system/dist/dist-BzPNEPHo.js
23637
+ var require_dist_BzPNEPHo = __commonJS({
23638
+ "../system/dist/dist-BzPNEPHo.js"(exports) {
23639
23639
  "use strict";
23640
23640
  var zod = require_zod();
23641
23641
  var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
@@ -25953,6 +25953,16 @@ var require_dist_DsoVfJSR = __commonJS({
25953
25953
  "recording",
25954
25954
  "notifications"
25955
25955
  ]);
25956
+ var CAMERA_SWITCH_ORDER = [
25957
+ "stream-broker",
25958
+ "object-detection",
25959
+ "privacy-mask",
25960
+ "device-audio",
25961
+ "broker-audio",
25962
+ "audio-analysis",
25963
+ "recording",
25964
+ "notifications"
25965
+ ];
25956
25966
  var CameraSwitchAuthoritySchema = zod.z.discriminatedUnion("kind", [
25957
25967
  zod.z.object({ kind: zod.z.literal("device-disabled") }),
25958
25968
  zod.z.object({
@@ -26102,7 +26112,8 @@ var require_dist_DsoVfJSR = __commonJS({
26102
26112
  "quota",
26103
26113
  "manual",
26104
26114
  "operator",
26105
- "maintenance"
26115
+ "maintenance",
26116
+ "orphaned-device"
26106
26117
  ]);
26107
26118
  var OpsLogEntrySchema = zod.z.object({
26108
26119
  /** Unique row id. */
@@ -31247,6 +31258,27 @@ var require_dist_DsoVfJSR = __commonJS({
31247
31258
  deviceId: zod.z.number(),
31248
31259
  data: zod.z.record(zod.z.string(), zod.z.unknown())
31249
31260
  });
31261
+ var MigrateSwitchOutcomeSchema = zod.z.enum([
31262
+ "off",
31263
+ "on",
31264
+ "not-offered",
31265
+ "unreachable"
31266
+ ]);
31267
+ var MigrateSwitchReportSchema = zod.z.object({
31268
+ deviceId: zod.z.number(),
31269
+ switchId: CameraSwitchIdSchema,
31270
+ outcome: MigrateSwitchOutcomeSchema,
31271
+ detail: zod.z.string().optional()
31272
+ });
31273
+ var MigrateDeviceResultSchema = zod.z.object({
31274
+ sourceId: zod.z.number(),
31275
+ targetId: zod.z.number(),
31276
+ switches: zod.z.array(MigrateSwitchReportSchema).readonly(),
31277
+ /** Switches that could NOT be turned off on the source. Empty is the only
31278
+ * value meaning the replaced hardware is quiet. */
31279
+ sourceStillLive: zod.z.array(CameraSwitchIdSchema).readonly(),
31280
+ swapped: zod.z.boolean()
31281
+ });
31250
31282
  var deviceManagerCapability = {
31251
31283
  name: "device-manager",
31252
31284
  scope: "system",
@@ -31261,6 +31293,33 @@ var require_dist_DsoVfJSR = __commonJS({
31261
31293
  addonId: zod.z.string(),
31262
31294
  stableId: zod.z.string()
31263
31295
  }), zod.z.object({ id: zod.z.number() }), { kind: "mutation" }),
31296
+ /**
31297
+ * Replace a camera: the new hardware inherits the number the system knows.
31298
+ *
31299
+ * Everything general about a camera is keyed on the numeric id and nothing
31300
+ * else — the recording path on disk, the hour ledger, the media store, zone
31301
+ * ownership, and all four ecosystem exports. So a replacement moves the
31302
+ * NUMBER rather than the work, and the operator keeps their footage, their
31303
+ * rules and their integrations.
31304
+ *
31305
+ * Both cameras are switched off first, then the identities are exchanged
31306
+ * (children follow their parent), then the target — now answering on the
31307
+ * inherited number — is switched back on.
31308
+ *
31309
+ * **A switch that could not be written is REPORTED, not swallowed.** The
31310
+ * source is characteristically broken, and two of the eight switches write
31311
+ * the camera itself, so on a dead one they cannot be written at all.
31312
+ * `sourceStillLive` names what stayed live; empty is the only value that
31313
+ * means the old hardware is quiet. The migration proceeds either way —
31314
+ * refusing would refuse the case this exists for.
31315
+ */
31316
+ migrateDevice: method(zod.z.object({
31317
+ sourceId: zod.z.number(),
31318
+ targetId: zod.z.number()
31319
+ }), MigrateDeviceResultSchema, {
31320
+ kind: "mutation",
31321
+ auth: "admin"
31322
+ }),
31264
31323
  /** Register a device in the DB + in-memory registry. Called by DeviceManagerApi.register(). */
31265
31324
  registerDevice: method(DeviceRegisterPayloadSchema, zod.z.void(), { kind: "mutation" }),
31266
31325
  /** Remove a device from the DB + in-memory registry. Called by DeviceManagerApi.remove(). */
@@ -51971,6 +52030,12 @@ var require_dist_DsoVfJSR = __commonJS({
51971
52030
  addonId: null,
51972
52031
  access: "view"
51973
52032
  },
52033
+ "deviceManager.migrateDevice": {
52034
+ capName: "device-manager",
52035
+ capScope: "system",
52036
+ addonId: null,
52037
+ access: "create"
52038
+ },
51974
52039
  "deviceManager.persistConfig": {
51975
52040
  capName: "device-manager",
51976
52041
  capScope: "system",
@@ -59055,6 +59120,12 @@ var require_dist_DsoVfJSR = __commonJS({
59055
59120
  return BatteryStatusSchema;
59056
59121
  }
59057
59122
  });
59123
+ Object.defineProperty(exports, "CAMERA_SWITCH_ORDER", {
59124
+ enumerable: true,
59125
+ get: function() {
59126
+ return CAMERA_SWITCH_ORDER;
59127
+ }
59128
+ });
59058
59129
  Object.defineProperty(exports, "CAP_NAMES_WITH_STATUS", {
59059
59130
  enumerable: true,
59060
59131
  get: function() {
@@ -59715,7 +59786,7 @@ var require_alerts_addon = __commonJS({
59715
59786
  [Symbol.toStringTag]: { value: "Module" }
59716
59787
  });
59717
59788
  require_chunk_Cek0wNdY();
59718
- var require_dist10 = require_dist_DsoVfJSR();
59789
+ var require_dist10 = require_dist_BzPNEPHo();
59719
59790
  function selectExpired(alerts, cutoffMs) {
59720
59791
  return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
59721
59792
  }
@@ -60534,7 +60605,7 @@ var require_console_logging = __commonJS({
60534
60605
  [Symbol.toStringTag]: { value: "Module" }
60535
60606
  });
60536
60607
  require_chunk_Cek0wNdY();
60537
- var require_dist10 = require_dist_DsoVfJSR();
60608
+ var require_dist10 = require_dist_BzPNEPHo();
60538
60609
  var require_formatter = require_formatter_DqAKDlvN();
60539
60610
  var LEVEL_RANK = {
60540
60611
  debug: 0,
@@ -60628,7 +60699,7 @@ var require_core_blocks_addon = __commonJS({
60628
60699
  "use strict";
60629
60700
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
60630
60701
  var require_chunk = require_chunk_Cek0wNdY();
60631
- var require_dist10 = require_dist_DsoVfJSR();
60702
+ var require_dist10 = require_dist_BzPNEPHo();
60632
60703
  var node_crypto = __require("crypto");
60633
60704
  var node_fs_promises = __require("fs/promises");
60634
60705
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -61525,11 +61596,11 @@ var require_core_blocks = __commonJS({
61525
61596
  }
61526
61597
  });
61527
61598
 
61528
- // ../system/dist/retired-settings-keys-DTE5k4lU.js
61529
- var require_retired_settings_keys_DTE5k4lU = __commonJS({
61530
- "../system/dist/retired-settings-keys-DTE5k4lU.js"(exports) {
61599
+ // ../system/dist/retired-settings-keys-DZmeJ8ja.js
61600
+ var require_retired_settings_keys_DZmeJ8ja = __commonJS({
61601
+ "../system/dist/retired-settings-keys-DZmeJ8ja.js"(exports) {
61531
61602
  "use strict";
61532
- var require_dist10 = require_dist_DsoVfJSR();
61603
+ var require_dist10 = require_dist_BzPNEPHo();
61533
61604
  function settingsStoreIsAuthoritativeHere(env) {
61534
61605
  const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
61535
61606
  return raw === "" || raw === "hub";
@@ -63743,8 +63814,8 @@ var require_device_manager_addon = __commonJS({
63743
63814
  [Symbol.toStringTag]: { value: "Module" }
63744
63815
  });
63745
63816
  require_chunk_Cek0wNdY();
63746
- var require_dist10 = require_dist_DsoVfJSR();
63747
- var require_retired_settings_keys = require_retired_settings_keys_DTE5k4lU();
63817
+ var require_dist10 = require_dist_BzPNEPHo();
63818
+ var require_retired_settings_keys = require_retired_settings_keys_DZmeJ8ja();
63748
63819
  var node_crypto = __require("crypto");
63749
63820
  var _camstack_types_node = require_node();
63750
63821
  var JOB_HISTORY = 20;
@@ -66226,6 +66297,62 @@ var require_device_manager_addon = __commonJS({
66226
66297
  return chain;
66227
66298
  }
66228
66299
  };
66300
+ function classify(err) {
66301
+ const detail = err instanceof Error ? err.message : String(err);
66302
+ return {
66303
+ outcome: /does not offer|not available|unsupported/i.test(detail) ? "not-offered" : "unreachable",
66304
+ detail
66305
+ };
66306
+ }
66307
+ async function applyAll(deps, deviceId, enabled) {
66308
+ const out = [];
66309
+ for (const switchId of require_dist10.CAMERA_SWITCH_ORDER) try {
66310
+ await deps.setSwitch(deviceId, switchId, enabled);
66311
+ out.push({
66312
+ deviceId,
66313
+ switchId,
66314
+ outcome: enabled ? "on" : "off"
66315
+ });
66316
+ } catch (err) {
66317
+ const { outcome, detail } = classify(err);
66318
+ out.push({
66319
+ deviceId,
66320
+ switchId,
66321
+ outcome,
66322
+ detail
66323
+ });
66324
+ deps.log("camera switch refused", {
66325
+ deviceId,
66326
+ switchId,
66327
+ enabled,
66328
+ outcome,
66329
+ detail
66330
+ });
66331
+ }
66332
+ return out;
66333
+ }
66334
+ async function migrateDevice$1(deps, input) {
66335
+ const { sourceId, targetId } = input;
66336
+ if (sourceId === targetId) throw new Error(`[device-manager] migrateDevice: ${sourceId} is both source and target`);
66337
+ const down = [...await applyAll(deps, sourceId, false), ...await applyAll(deps, targetId, false)];
66338
+ const sourceStillLive = down.filter((r) => r.deviceId === sourceId && r.outcome === "unreachable").map((r) => r.switchId);
66339
+ await deps.swapIds(sourceId, targetId);
66340
+ const up = await applyAll(deps, sourceId, true);
66341
+ await deps.respawnRunners([sourceId, targetId]);
66342
+ deps.log("camera migrated", {
66343
+ sourceId,
66344
+ targetId,
66345
+ sourceStillLive,
66346
+ refusals: [...down, ...up].filter((r) => r.outcome === "unreachable").length
66347
+ });
66348
+ return {
66349
+ sourceId,
66350
+ targetId,
66351
+ switches: [...down, ...up],
66352
+ sourceStillLive,
66353
+ swapped: true
66354
+ };
66355
+ }
66229
66356
  async function stampIntegrationId(metaStore, ctx, deviceId, integrationId) {
66230
66357
  await metaStore.withMetaWriteLock(async () => {
66231
66358
  if (!await metaStore.resolvePersistedById(deviceId)) throw new Error(`[device-manager] stampIntegrationId: unknown device id=${deviceId}`);
@@ -66268,6 +66395,42 @@ var require_device_manager_addon = __commonJS({
66268
66395
  return { id };
66269
66396
  });
66270
66397
  }
66398
+ async function migrateDevice(pctx, input) {
66399
+ const { sourceId, targetId } = input;
66400
+ const ctx = pctx.host.ctx;
66401
+ return await pctx.metaStore.withMetaWriteLock(async () => {
66402
+ const tempId = await pctx.metaStore.allocateNextDeviceId();
66403
+ return await migrateDevice$1({
66404
+ setSwitch: async (deviceId, switchId, enabled) => {
66405
+ await ctx.api.pipelineOrchestrator.setCameraSwitch.mutate({
66406
+ deviceId,
66407
+ switchId,
66408
+ enabled
66409
+ });
66410
+ },
66411
+ swapIds: async (a, b) => {
66412
+ await pctx.metaStore.rows.swapIds(a, b, tempId);
66413
+ },
66414
+ respawnRunners: async (deviceIds) => {
66415
+ const owners = /* @__PURE__ */ new Set();
66416
+ for (const deviceId of deviceIds) {
66417
+ const addonId = pctx.metaStore.idToAddonId.get(deviceId);
66418
+ if (addonId !== void 0) owners.add(addonId);
66419
+ }
66420
+ for (const addonId of owners) await ctx.api.addons.restartAddon.mutate({ addonId });
66421
+ },
66422
+ log: (message, meta) => {
66423
+ ctx.logger.info(message, {
66424
+ tags: { deviceId: sourceId },
66425
+ meta
66426
+ });
66427
+ }
66428
+ }, {
66429
+ sourceId,
66430
+ targetId
66431
+ });
66432
+ });
66433
+ }
66271
66434
  async function registerDevice(pctx, input) {
66272
66435
  const { addonId, stableId, id, type, name, parentDeviceId, features, config } = input;
66273
66436
  const featuresArr = Array.isArray(features) ? [...features] : [];
@@ -67743,6 +67906,86 @@ var require_device_manager_addon = __commonJS({
67743
67906
  });
67744
67907
  }
67745
67908
  /** Drop the device's row. Idempotent. */
67909
+ /**
67910
+ * Exchange the ids of two devices, carrying their children with them.
67911
+ *
67912
+ * ## Why this exists as a primitive
67913
+ *
67914
+ * Replacing a camera means the new hardware has to become the device the rest
67915
+ * of the system already knows. Everything general about a camera is keyed on
67916
+ * the NUMERIC id and on nothing else — the recording path on disk is
67917
+ * `<deviceId>/profile/…`, the hour ledger, the media store and zone ownership
67918
+ * carry `deviceId` and not one `stableId` between them, and all four
67919
+ * ecosystem exports (Home Assistant, Alexa, HomeKit, Google) key on it too.
67920
+ * Moving that work is the expensive, lossy operation. Moving the NUMBER is
67921
+ * two rows.
67922
+ *
67923
+ * ## Children are re-pointed, not renumbered
67924
+ *
67925
+ * A child keeps its own id and follows its parent: every row whose
67926
+ * `parentDeviceId` was `a` now names `b`, and the reverse. Renumbering the
67927
+ * children as well would move THEIR general work for no reason — the
67928
+ * accessory's own recordings and rules are keyed on the child id, which is
67929
+ * not the id being inherited.
67930
+ *
67931
+ * ## There are no transactions here, so the ORDER is the safety
67932
+ *
67933
+ * The backing store is a collection API (`set` / `delete` / `updateWhere`);
67934
+ * there is no rollback to lean on. So the sequence is chosen to make every
67935
+ * crash point LOUD rather than plausible: `a` first moves to a freshly
67936
+ * allocated id, which the monotonic counter guarantees was never used and
67937
+ * will never be reused. A crash before the end therefore leaves a device
67938
+ * missing from its number and a stray at an unfamiliar one — visible in the
67939
+ * device list at a glance. What it can never leave is two rows on one number,
67940
+ * or a row that looks correct while its children point elsewhere.
67941
+ *
67942
+ * The caller holds {@link DeviceMetaStore.withMetaWriteLock}. This method does
67943
+ * not take it: taking it here would deadlock the migration that has to disable
67944
+ * functions on both devices in the same critical section.
67945
+ */
67946
+ async swapIds(a, b, tempId) {
67947
+ if (a === b) throw new Error(`[device-manager] swapIds: ${a} and ${b} are the same device`);
67948
+ await this.declare();
67949
+ const rowA = await this.get(a);
67950
+ const rowB = await this.get(b);
67951
+ if (rowA === null) throw new Error(`[device-manager] swapIds: device ${a} does not exist`);
67952
+ if (rowB === null) throw new Error(`[device-manager] swapIds: device ${b} does not exist`);
67953
+ if (await this.get(tempId) !== null) throw new Error(`[device-manager] swapIds: temp id ${tempId} is taken`);
67954
+ const childrenOfA = (await this.listByParent(a)).map((r) => r.meta.id);
67955
+ const childrenOfB = (await this.listByParent(b)).map((r) => r.meta.id);
67956
+ await this.moveRow(rowA, tempId);
67957
+ await this.moveRow(rowB, a);
67958
+ await this.moveRow({
67959
+ ...rowA,
67960
+ meta: {
67961
+ ...rowA.meta,
67962
+ id: tempId
67963
+ }
67964
+ }, b);
67965
+ for (const childId of childrenOfA) await this.patch(childId, { parentDeviceId: b });
67966
+ for (const childId of childrenOfB) await this.patch(childId, { parentDeviceId: a });
67967
+ this.logger.info("device ids swapped", {
67968
+ tags: { deviceId: a },
67969
+ meta: {
67970
+ a,
67971
+ b,
67972
+ childrenOfA,
67973
+ childrenOfB
67974
+ }
67975
+ });
67976
+ }
67977
+ /** Rewrite a row under a new id and drop the old key. Not exported: the only
67978
+ * legitimate reason to move a row is {@link swapIds}. */
67979
+ async moveRow(row, toId) {
67980
+ await this.upsert({
67981
+ ...row.meta,
67982
+ id: toId
67983
+ }, {
67984
+ registered: row.registered,
67985
+ metadata: row.metadata
67986
+ });
67987
+ await this.remove(row.meta.id);
67988
+ }
67746
67989
  async remove(deviceId) {
67747
67990
  await this.declare();
67748
67991
  await this.backend.delete({
@@ -68376,6 +68619,7 @@ var require_device_manager_addon = __commonJS({
68376
68619
  resolveDeviceOwnerSync: (deviceId) => resolveDeviceOwnerSync(pctx, deviceId),
68377
68620
  resolveNativeCapOwnerSync: (capName, deviceId) => resolveNativeCapOwnerSync(pctx, capName, deviceId),
68378
68621
  allocateDeviceId: (input) => allocateDeviceId(pctx, input),
68622
+ migrateDevice: (input) => migrateDevice(pctx, input),
68379
68623
  registerDevice: (input) => registerDevice(pctx, input),
68380
68624
  removeDevice: (input) => removeDevice(pctx, input),
68381
68625
  persistConfig: (input) => persistConfig(pctx, input),
@@ -68606,7 +68850,7 @@ var require_hub_forwarder = __commonJS({
68606
68850
  [Symbol.toStringTag]: { value: "Module" }
68607
68851
  });
68608
68852
  require_chunk_Cek0wNdY();
68609
- var require_dist10 = require_dist_DsoVfJSR();
68853
+ var require_dist10 = require_dist_BzPNEPHo();
68610
68854
  var require_formatter = require_formatter_DqAKDlvN();
68611
68855
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
68612
68856
  var HubForwarderDestination = class {
@@ -68743,7 +68987,7 @@ var require_liveness_monitor_addon = __commonJS({
68743
68987
  "use strict";
68744
68988
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
68745
68989
  require_chunk_Cek0wNdY();
68746
- var require_dist10 = require_dist_DsoVfJSR();
68990
+ var require_dist10 = require_dist_BzPNEPHo();
68747
68991
  var NO_DEVICES = "liveness:no-devices";
68748
68992
  var ALL_OFFLINE = "liveness:all-devices-offline";
68749
68993
  var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
@@ -68933,7 +69177,7 @@ var require_local_auth_addon = __commonJS({
68933
69177
  [Symbol.toStringTag]: { value: "Module" }
68934
69178
  });
68935
69179
  var require_chunk = require_chunk_Cek0wNdY();
68936
- var require_dist10 = require_dist_DsoVfJSR();
69180
+ var require_dist10 = require_dist_BzPNEPHo();
68937
69181
  var node_crypto = __require("crypto");
68938
69182
  node_crypto = require_chunk.__toESM(node_crypto);
68939
69183
  var crypto$1 = __require("crypto");
@@ -76746,7 +76990,7 @@ var require_loki_logging = __commonJS({
76746
76990
  [Symbol.toStringTag]: { value: "Module" }
76747
76991
  });
76748
76992
  require_chunk_Cek0wNdY();
76749
- var require_dist10 = require_dist_DsoVfJSR();
76993
+ var require_dist10 = require_dist_BzPNEPHo();
76750
76994
  function sanitizeLabelName(raw) {
76751
76995
  const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
76752
76996
  return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
@@ -77311,7 +77555,7 @@ var require_native_metrics_addon = __commonJS({
77311
77555
  [Symbol.toStringTag]: { value: "Module" }
77312
77556
  });
77313
77557
  var require_chunk = require_chunk_Cek0wNdY();
77314
- var require_dist10 = require_dist_DsoVfJSR();
77558
+ var require_dist10 = require_dist_BzPNEPHo();
77315
77559
  var node_fs_promises = __require("fs/promises");
77316
77560
  var node_child_process = __require("child_process");
77317
77561
  var node_util = __require("util");
@@ -79933,7 +80177,7 @@ var require_filesystem_storage_addon = __commonJS({
79933
80177
  [Symbol.toStringTag]: { value: "Module" }
79934
80178
  });
79935
80179
  var require_chunk = require_chunk_Cek0wNdY();
79936
- var require_dist10 = require_dist_DsoVfJSR();
80180
+ var require_dist10 = require_dist_BzPNEPHo();
79937
80181
  var node_crypto = __require("crypto");
79938
80182
  var node_fs_promises = __require("fs/promises");
79939
80183
  var node_path = __require("path");
@@ -81049,8 +81293,8 @@ var require_sqlite_settings_addon = __commonJS({
81049
81293
  [Symbol.toStringTag]: { value: "Module" }
81050
81294
  });
81051
81295
  var require_chunk = require_chunk_Cek0wNdY();
81052
- var require_dist10 = require_dist_DsoVfJSR();
81053
- var require_retired_settings_keys = require_retired_settings_keys_DTE5k4lU();
81296
+ var require_dist10 = require_dist_BzPNEPHo();
81297
+ var require_retired_settings_keys = require_retired_settings_keys_DZmeJ8ja();
81054
81298
  var node_crypto = __require("crypto");
81055
81299
  var node_fs = __require("fs");
81056
81300
  var node_module = __require("module");
@@ -83532,7 +83776,7 @@ var require_storage_orchestrator_addon = __commonJS({
83532
83776
  [Symbol.toStringTag]: { value: "Module" }
83533
83777
  });
83534
83778
  var require_chunk = require_chunk_Cek0wNdY();
83535
- var require_dist10 = require_dist_DsoVfJSR();
83779
+ var require_dist10 = require_dist_BzPNEPHo();
83536
83780
  var require_hub_cap_forward = require_hub_cap_forward_DmHNjbB0();
83537
83781
  var zod = require_zod();
83538
83782
  var node_crypto = __require("crypto");
@@ -86483,7 +86727,7 @@ var require_system_config_addon = __commonJS({
86483
86727
  [Symbol.toStringTag]: { value: "Module" }
86484
86728
  });
86485
86729
  require_chunk_Cek0wNdY();
86486
- var require_dist10 = require_dist_DsoVfJSR();
86730
+ var require_dist10 = require_dist_BzPNEPHo();
86487
86731
  var SECTION_TITLES = {
86488
86732
  server: "Server",
86489
86733
  auth: "Authentication"
@@ -104544,7 +104788,7 @@ var require_winston_logging = __commonJS({
104544
104788
  [Symbol.toStringTag]: { value: "Module" }
104545
104789
  });
104546
104790
  var require_chunk = require_chunk_Cek0wNdY();
104547
- var require_dist10 = require_dist_DsoVfJSR();
104791
+ var require_dist10 = require_dist_BzPNEPHo();
104548
104792
  var require_formatter = require_formatter_DqAKDlvN();
104549
104793
  var node_path = __require("path");
104550
104794
  node_path = require_chunk.__toESM(node_path);
@@ -117044,12 +117288,12 @@ var require_dist2 = __commonJS({
117044
117288
  }
117045
117289
  });
117046
117290
 
117047
- // ../system/dist/manifest-system-deps-ir_XTNpe.js
117048
- var require_manifest_system_deps_ir_XTNpe = __commonJS({
117049
- "../system/dist/manifest-system-deps-ir_XTNpe.js"(exports) {
117291
+ // ../system/dist/manifest-system-deps-B0mzJPmb.js
117292
+ var require_manifest_system_deps_B0mzJPmb = __commonJS({
117293
+ "../system/dist/manifest-system-deps-B0mzJPmb.js"(exports) {
117050
117294
  "use strict";
117051
117295
  var require_chunk = require_chunk_Cek0wNdY();
117052
- require_dist_DsoVfJSR();
117296
+ require_dist_BzPNEPHo();
117053
117297
  require_hub_cap_forward_DmHNjbB0();
117054
117298
  var node_crypto = __require("crypto");
117055
117299
  node_crypto = require_chunk.__toESM(node_crypto);
@@ -129145,7 +129389,7 @@ var require_dist3 = __commonJS({
129145
129389
  "use strict";
129146
129390
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
129147
129391
  var require_chunk = require_chunk_Cek0wNdY();
129148
- var require_dist10 = require_dist_DsoVfJSR();
129392
+ var require_dist10 = require_dist_BzPNEPHo();
129149
129393
  var require_builtins_alerts_alerts_addon = require_alerts_addon();
129150
129394
  require_alerts();
129151
129395
  var require_formatter = require_formatter_DqAKDlvN();
@@ -129172,7 +129416,7 @@ var require_dist3 = __commonJS({
129172
129416
  var require_builtins_winston_logging_index = require_winston_logging();
129173
129417
  var require_file_data_plane = require_file_data_plane_DO8KbxCe();
129174
129418
  var require_tls$1 = require_tls_BxQlomxd();
129175
- var require_manifest_system_deps = require_manifest_system_deps_ir_XTNpe();
129419
+ var require_manifest_system_deps = require_manifest_system_deps_B0mzJPmb();
129176
129420
  var require_resource_monitor = require_resource_monitor_CdnzxBLP();
129177
129421
  var require_custom_action_registry = require_custom_action_registry_jY0NOZK8();
129178
129422
  var zod = require_zod();
@@ -210982,7 +211226,8 @@ var require_dist4 = __commonJS({
210982
211226
  "quota",
210983
211227
  "manual",
210984
211228
  "operator",
210985
- "maintenance"
211229
+ "maintenance",
211230
+ "orphaned-device"
210986
211231
  ]);
210987
211232
  var OpsLogEntrySchema = zod.z.object({
210988
211233
  /** Unique row id. */
@@ -217321,6 +217566,27 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
217321
217566
  deviceId: zod.z.number(),
217322
217567
  data: zod.z.record(zod.z.string(), zod.z.unknown())
217323
217568
  });
217569
+ var MigrateSwitchOutcomeSchema = zod.z.enum([
217570
+ "off",
217571
+ "on",
217572
+ "not-offered",
217573
+ "unreachable"
217574
+ ]);
217575
+ var MigrateSwitchReportSchema = zod.z.object({
217576
+ deviceId: zod.z.number(),
217577
+ switchId: CameraSwitchIdSchema,
217578
+ outcome: MigrateSwitchOutcomeSchema,
217579
+ detail: zod.z.string().optional()
217580
+ });
217581
+ var MigrateDeviceResultSchema = zod.z.object({
217582
+ sourceId: zod.z.number(),
217583
+ targetId: zod.z.number(),
217584
+ switches: zod.z.array(MigrateSwitchReportSchema).readonly(),
217585
+ /** Switches that could NOT be turned off on the source. Empty is the only
217586
+ * value meaning the replaced hardware is quiet. */
217587
+ sourceStillLive: zod.z.array(CameraSwitchIdSchema).readonly(),
217588
+ swapped: zod.z.boolean()
217589
+ });
217324
217590
  var deviceManagerCapability = {
217325
217591
  name: "device-manager",
217326
217592
  scope: "system",
@@ -217335,6 +217601,33 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
217335
217601
  addonId: zod.z.string(),
217336
217602
  stableId: zod.z.string()
217337
217603
  }), zod.z.object({ id: zod.z.number() }), { kind: "mutation" }),
217604
+ /**
217605
+ * Replace a camera: the new hardware inherits the number the system knows.
217606
+ *
217607
+ * Everything general about a camera is keyed on the numeric id and nothing
217608
+ * else — the recording path on disk, the hour ledger, the media store, zone
217609
+ * ownership, and all four ecosystem exports. So a replacement moves the
217610
+ * NUMBER rather than the work, and the operator keeps their footage, their
217611
+ * rules and their integrations.
217612
+ *
217613
+ * Both cameras are switched off first, then the identities are exchanged
217614
+ * (children follow their parent), then the target — now answering on the
217615
+ * inherited number — is switched back on.
217616
+ *
217617
+ * **A switch that could not be written is REPORTED, not swallowed.** The
217618
+ * source is characteristically broken, and two of the eight switches write
217619
+ * the camera itself, so on a dead one they cannot be written at all.
217620
+ * `sourceStillLive` names what stayed live; empty is the only value that
217621
+ * means the old hardware is quiet. The migration proceeds either way —
217622
+ * refusing would refuse the case this exists for.
217623
+ */
217624
+ migrateDevice: require_sleep.method(zod.z.object({
217625
+ sourceId: zod.z.number(),
217626
+ targetId: zod.z.number()
217627
+ }), MigrateDeviceResultSchema, {
217628
+ kind: "mutation",
217629
+ auth: "admin"
217630
+ }),
217338
217631
  /** Register a device in the DB + in-memory registry. Called by DeviceManagerApi.register(). */
217339
217632
  registerDevice: require_sleep.method(DeviceRegisterPayloadSchema, zod.z.void(), { kind: "mutation" }),
217340
217633
  /** Remove a device from the DB + in-memory registry. Called by DeviceManagerApi.remove(). */
@@ -241969,6 +242262,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
241969
242262
  addonId: null,
241970
242263
  access: "view"
241971
242264
  },
242265
+ "deviceManager.migrateDevice": {
242266
+ capName: "device-manager",
242267
+ capScope: "system",
242268
+ addonId: null,
242269
+ access: "create"
242270
+ },
241972
242271
  "deviceManager.persistConfig": {
241973
242272
  capName: "device-manager",
241974
242273
  capScope: "system",
@@ -249275,6 +249574,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249275
249574
  },
249276
249575
  deviceManager: {
249277
249576
  allocateDeviceId: (input) => dispatch("deviceManager", "allocateDeviceId", "mutation", input),
249577
+ migrateDevice: (input) => dispatch("deviceManager", "migrateDevice", "mutation", input),
249278
249578
  registerDevice: (input) => dispatch("deviceManager", "registerDevice", "mutation", input),
249279
249579
  removeDevice: (input) => dispatch("deviceManager", "removeDevice", "mutation", input),
249280
249580
  persistConfig: (input) => dispatch("deviceManager", "persistConfig", "mutation", input),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "camstack",
3
- "version": "1.2.76",
3
+ "version": "1.2.78",
4
4
  "description": "CLI tool for managing and running CamStack server",
5
5
  "keywords": [
6
6
  "camstack",