@camstack/system 1.2.159 → 1.2.161

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 (71) 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.d.ts +10 -7
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +50 -12
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +50 -12
  12. package/dist/builtins/backup-orchestrator/write-targets.d.ts +63 -0
  13. package/dist/builtins/console-logging/index.js +1 -1
  14. package/dist/builtins/console-logging/index.mjs +1 -1
  15. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  16. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  17. package/dist/builtins/device-manager/device-bindings-store.d.ts +56 -10
  18. package/dist/builtins/device-manager/device-manager.addon.d.ts +11 -0
  19. package/dist/builtins/device-manager/device-manager.addon.js +216 -81
  20. package/dist/builtins/device-manager/device-manager.addon.mjs +216 -81
  21. package/dist/builtins/device-manager/device-meta-actions.d.ts +9 -3
  22. package/dist/builtins/device-manager/device-meta-store.d.ts +32 -5
  23. package/dist/builtins/device-manager/device-queries.d.ts +7 -3
  24. package/dist/builtins/device-manager/write-lock.d.ts +17 -0
  25. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  26. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  27. package/dist/builtins/hub-forwarder/index.js +1 -1
  28. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  29. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  30. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  31. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  32. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  33. package/dist/builtins/local-network/local-network.addon.js +1 -1
  34. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  35. package/dist/builtins/loki-logging/index.js +1 -1
  36. package/dist/builtins/loki-logging/index.mjs +1 -1
  37. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  38. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  39. package/dist/builtins/platform-probe/index.js +1 -1
  40. package/dist/builtins/platform-probe/index.mjs +1 -1
  41. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  42. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  43. package/dist/builtins/snapshot/index.js +1 -1
  44. package/dist/builtins/snapshot/index.mjs +1 -1
  45. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  46. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  47. package/dist/builtins/sqlite-storage/filter-compiler.d.ts +41 -2
  48. package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +15 -7
  49. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  50. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  51. package/dist/builtins/storage-orchestrator/storage-migration-coordinator.d.ts +3 -2
  52. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +63 -8
  53. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +63 -8
  54. package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +19 -0
  55. package/dist/builtins/system-config/system-config.addon.js +1 -1
  56. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  57. package/dist/builtins/winston-logging/index.js +1 -1
  58. package/dist/builtins/winston-logging/index.mjs +1 -1
  59. package/dist/{dist-DLh4_rn3.mjs → dist-D4xNBlJM.mjs} +383 -214
  60. package/dist/{dist-Ck2jkBZk.js → dist-Keu5TDO7.js} +383 -214
  61. package/dist/index.js +29 -7
  62. package/dist/index.mjs +29 -8
  63. package/dist/kernel/index.d.ts +2 -2
  64. package/dist/kernel/lifecycle/lifecycle-job-engine.d.ts +13 -0
  65. package/dist/kernel/moleculer/cap-usage-registry.d.ts +132 -7
  66. package/dist/kernel/transport/local-child-registry.d.ts +57 -0
  67. package/dist/{manifest-system-deps-tZZYG3RR.mjs → manifest-system-deps-CiCuev2R.mjs} +271 -77
  68. package/dist/{manifest-system-deps-8boi90D9.js → manifest-system-deps-DYv4ZPo2.js} +276 -76
  69. package/dist/{retired-settings-keys-Dp_CyuCW.js → retired-settings-keys-BBohF-eC.js} +1 -1
  70. package/dist/{retired-settings-keys-Bi0ZKBjb.mjs → retired-settings-keys-COQMNlOJ.mjs} +1 -1
  71. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { A as bareAddonId, Gt as createEvent, Mt as BaseAddon, Rt as DeviceType, U as doorbellCapability, _ as SOURCE_DEVICE_TYPES, et as isSameAddonId, g as SOURCE_CAPS, in as EventCategory, jt as errMsg, q as evaluateSensorEdge, tt as isSourceCap } from "../../dist-DLh4_rn3.mjs";
1
+ import { A as bareAddonId, Gt as createEvent, Mt as BaseAddon, Rt as DeviceType, U as doorbellCapability, _ as SOURCE_DEVICE_TYPES, et as isSameAddonId, g as SOURCE_CAPS, in as EventCategory, jt as errMsg, q as evaluateSensorEdge, tt as isSourceCap } from "../../dist-D4xNBlJM.mjs";
2
2
  //#region src/builtins/doorbell/trigger-engine.ts
3
3
  /**
4
4
  * Pure trigger logic for the virtual-doorbell builtin — edge detection over
@@ -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-Ck2jkBZk.js");
6
+ const require_dist = require("../../dist-Keu5TDO7.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
9
9
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
@@ -1,4 +1,4 @@
1
- import { Mt as BaseAddon, ot as logDestinationCapability } from "../../dist-DLh4_rn3.mjs";
1
+ import { Mt as BaseAddon, ot as logDestinationCapability } from "../../dist-D4xNBlJM.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
4
4
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-Ck2jkBZk.js");
3
+ const require_dist = require("../../dist-Keu5TDO7.js");
4
4
  //#region src/builtins/liveness-monitor/liveness-checks.ts
5
5
  var NO_DEVICES = "liveness:no-devices";
6
6
  var ALL_OFFLINE = "liveness:all-devices-offline";
@@ -1,4 +1,4 @@
1
- import { Gt as createEvent, Mt as BaseAddon, in as EventCategory, jt as errMsg } from "../../dist-DLh4_rn3.mjs";
1
+ import { Gt as createEvent, Mt as BaseAddon, in as EventCategory, jt as errMsg } from "../../dist-D4xNBlJM.mjs";
2
2
  //#region src/builtins/liveness-monitor/liveness-checks.ts
3
3
  var NO_DEVICES = "liveness:no-devices";
4
4
  var ALL_OFFLINE = "liveness:all-devices-offline";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-Ck2jkBZk.js");
6
+ const require_dist = require("../../dist-Keu5TDO7.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  node_crypto = require_chunk.__toESM(node_crypto);
9
9
  let crypto$1 = require("crypto");
@@ -1,5 +1,5 @@
1
1
  import { i as __require, o as __toESM, t as __commonJSMin } from "../../chunk-CNf5ZN-e.mjs";
2
- import { C as UserRecordSchema, Mt as BaseAddon, O as authProviderCapability, Ot as userManagementCapability, b as ScopedTokenSchema, r as ApiKeyRecordSchema } from "../../dist-DLh4_rn3.mjs";
2
+ import { C as UserRecordSchema, Mt as BaseAddon, O as authProviderCapability, Ot as userManagementCapability, b as ScopedTokenSchema, r as ApiKeyRecordSchema } from "../../dist-D4xNBlJM.mjs";
3
3
  import * as crypto$2 from "node:crypto";
4
4
  import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
5
5
  import nodeCrypto from "crypto";
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-Ck2jkBZk.js");
3
+ const require_dist = require("../../dist-Keu5TDO7.js");
4
4
  const require_tls = require("../../tls-BxQlomxd.js");
5
5
  let node_fs_promises = require("node:fs/promises");
6
6
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { Mt as BaseAddon, at as localNetworkCapability, in as EventCategory } from "../../dist-DLh4_rn3.mjs";
1
+ import { Mt as BaseAddon, at as localNetworkCapability, in as EventCategory } from "../../dist-D4xNBlJM.mjs";
2
2
  import { S as readLanHttpState, _ as DEFAULT_LAN_HTTP_PORT, a as writeTlsMode, i as writeExtraSans, l as reissueTlsLeaf, n as readTlsAccessStatus, o as validateUploadedTls, r as readTlsMode, s as ensureTlsCert, t as readExtraSans, y as applyLanHttp } from "../../tls-DOTmtLCW.mjs";
3
3
  import { mkdir, writeFile } from "node:fs/promises";
4
4
  import { join } from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-Ck2jkBZk.js");
6
+ const require_dist = require("../../dist-Keu5TDO7.js");
7
7
  //#region src/builtins/loki-logging/loki-payload.ts
8
8
  /**
9
9
  * Loki rejects a label name that is not a valid Prometheus label
@@ -1,4 +1,4 @@
1
- import { Mt as BaseAddon, ot as logDestinationCapability } from "../../dist-DLh4_rn3.mjs";
1
+ import { Mt as BaseAddon, ot as logDestinationCapability } from "../../dist-D4xNBlJM.mjs";
2
2
  //#region src/builtins/loki-logging/loki-payload.ts
3
3
  /**
4
4
  * Loki rejects a label name that is not a valid Prometheus label
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-Ck2jkBZk.js");
6
+ const require_dist = require("../../dist-Keu5TDO7.js");
7
7
  let node_fs_promises = require("node:fs/promises");
8
8
  let node_child_process = require("node:child_process");
9
9
  let node_util = require("node:util");
@@ -1,4 +1,4 @@
1
- import { Gt as createEvent, Mt as BaseAddon, X as foldSnapshotByFunction, gt as resolveBucketMs, ht as reducePoints, in as EventCategory, jt as errMsg, lt as metricsProviderCapability } from "../../dist-DLh4_rn3.mjs";
1
+ import { Gt as createEvent, Mt as BaseAddon, X as foldSnapshotByFunction, gt as resolveBucketMs, ht as reducePoints, in as EventCategory, jt as errMsg, lt as metricsProviderCapability } from "../../dist-D4xNBlJM.mjs";
2
2
  import { readFile, readdir } from "node:fs/promises";
3
3
  import { execFile, execFileSync } from "node:child_process";
4
4
  import { promisify } from "node:util";
@@ -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-Ck2jkBZk.js");
6
+ const require_dist = require("../../dist-Keu5TDO7.js");
7
7
  let node_child_process = require("node:child_process");
8
8
  let node_util = require("node:util");
9
9
  let node_fs = require("node:fs");
@@ -1,5 +1,5 @@
1
1
  import { o as __toESM } from "../../chunk-CNf5ZN-e.mjs";
2
- import { Mt as BaseAddon, W as enumerateInferenceDevices, in as EventCategory, jt as errMsg, pt as platformProbeCapability, qt as emitReadiness, xt as scoreRuntimes } from "../../dist-DLh4_rn3.mjs";
2
+ import { Mt as BaseAddon, W as enumerateInferenceDevices, in as EventCategory, jt as errMsg, pt as platformProbeCapability, qt as emitReadiness, xt as scoreRuntimes } from "../../dist-D4xNBlJM.mjs";
3
3
  import { execFile } from "node:child_process";
4
4
  import { promisify } from "node:util";
5
5
  import * as fs from "node:fs";
@@ -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-Ck2jkBZk.js");
6
+ const require_dist = require("../../dist-Keu5TDO7.js");
7
7
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
8
8
  /**
9
9
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -1,4 +1,4 @@
1
- import { Mt as BaseAddon, in as EventCategory } from "../../dist-DLh4_rn3.mjs";
1
+ import { Mt as BaseAddon, in as EventCategory } from "../../dist-D4xNBlJM.mjs";
2
2
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
3
3
  /**
4
4
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-Ck2jkBZk.js");
6
+ const require_dist = require("../../dist-Keu5TDO7.js");
7
7
  let zod = require("zod");
8
8
  let node_crypto = require("node:crypto");
9
9
  let _camstack_types_node = require("@camstack/types/node");
@@ -1,4 +1,4 @@
1
- import { A as bareAddonId, Ct as snapshotCapability, Dt as streamQualityLabel, It as DeviceFeature, Mt as BaseAddon, Rt as DeviceType, Zt as nodePin, i as BatteryStatusSchema, in as EventCategory, j as batteryCapability, jt as errMsg, z as deriveBatteryPresence } from "../../dist-DLh4_rn3.mjs";
1
+ import { A as bareAddonId, Ct as snapshotCapability, Dt as streamQualityLabel, It as DeviceFeature, Mt as BaseAddon, Rt as DeviceType, Zt as nodePin, i as BatteryStatusSchema, in as EventCategory, j as batteryCapability, jt as errMsg, z as deriveBatteryPresence } from "../../dist-D4xNBlJM.mjs";
2
2
  import { z } from "zod";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { signExpiringUrl, verifyExpiringUrl } from "@camstack/types/node";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-Ck2jkBZk.js");
6
+ const require_dist = require("../../dist-Keu5TDO7.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_fs_promises = require("node:fs/promises");
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { Et as storageProviderCapability, Mt as BaseAddon, Y as filesystemBrowseCapability } from "../../dist-DLh4_rn3.mjs";
1
+ import { Et as storageProviderCapability, Mt as BaseAddon, Y as filesystemBrowseCapability } from "../../dist-D4xNBlJM.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { mkdir, readdir, realpath, statfs } from "node:fs/promises";
4
4
  import * as path$1 from "node:path";
@@ -17,6 +17,22 @@
17
17
  * was written against it. `mutate` mode refuses both, before any SQL exists.
18
18
  * Emptying a collection is a legitimate thing to want, and it should be a
19
19
  * differently-named operation rather than a filter that happens to be empty.
20
+ * `measure` mode sits between them — see {@link FilterMode}.
21
+ *
22
+ * ── NULL is a value the language can name ────────────────────────────────
23
+ *
24
+ * A `null` on the right-hand side of `where` / `whereNot` compiles to `IS NULL`
25
+ * / `IS NOT NULL`, and binds no parameter. It used to compile to `= ?` / `!= ?`
26
+ * with a NULL parameter, and SQL three-valued logic makes both of those NULL
27
+ * for every row: the filter matched NOTHING and the caller got a `0` that
28
+ * looked like an answer. `pipelineAnalytics.countUnstampedEventMedia` — the
29
+ * gate that decides whether 152 GB of event media may be repointed onto
30
+ * another disk — asks exactly "how many rows have no `locationId`", and `0` is
31
+ * the one value that opens it. `whereNot: { field: null }` was the same defect
32
+ * mirrored: `(expr IS NULL OR expr != NULL)` is TRUE for a NULL row and NULL
33
+ * for every other, so it selected precisely the rows it was asked to exclude.
34
+ *
35
+ * Nothing could ever have MEANT `= NULL`; there is no caller to preserve.
20
36
  */
21
37
  /** The part of a declared collection this compiler needs. */
22
38
  export interface CollectionShape {
@@ -29,10 +45,28 @@ export interface CollectionShape {
29
45
  */
30
46
  readonly kvBlobColumn: string | null;
31
47
  }
32
- export type FilterMode = 'select' | 'mutate';
48
+ /**
49
+ * How forgiving this compilation is allowed to be.
50
+ *
51
+ * - `select` — a predicate naming a field the collection cannot express is
52
+ * SKIPPED. The caller receives the rows and can see for itself that the
53
+ * filter did not bite; the cost is extra rows, not a wrong belief.
54
+ * - `measure` — the same predicate is REFUSED. A count, an aggregate or a
55
+ * histogram hands back a NUMBER: the whole collection and the intended
56
+ * subset are the same shape, and the caller has no way to tell which one it
57
+ * was given. An empty filter is still fine — `COUNT(*)` over everything is a
58
+ * legitimate question, unlike `DELETE` over everything. This closes the
59
+ * asymmetry `aggregate` already applied to its FIELDS ("a dropped aggregate
60
+ * returns a number that is wrong and looks exactly like a real one") but not
61
+ * to its predicate.
62
+ * - `mutate` — refuses that AND an empty filter, which on a bulk write is the
63
+ * entire collection.
64
+ */
65
+ export type FilterMode = 'select' | 'measure' | 'mutate';
33
66
  /** Marshals a JS value to what the driver should bind. */
34
67
  export type SerializeValue = (value: unknown) => unknown;
35
68
  export interface QueryFilterInput {
69
+ /** Equality. A `null` value means `IS NULL` — see the file docblock. */
36
70
  readonly where?: Readonly<Record<string, unknown>>;
37
71
  readonly whereIn?: Readonly<Record<string, readonly unknown[]>>;
38
72
  readonly whereBetween?: Readonly<Record<string, readonly [unknown, unknown]>>;
@@ -40,6 +74,10 @@ export interface QueryFilterInput {
40
74
  * NULL-safe exclusion, compiled as `(expr IS NULL OR expr != ?)`. SQL `!=`
41
75
  * alone drops NULL rows, and on a nullable column those are precisely the
42
76
  * rows a "not this kind" filter must keep (they predate the column).
77
+ *
78
+ * A `null` VALUE means `IS NOT NULL` instead: "everything except the rows
79
+ * that carry no value" is the only reading of `whereNot: { f: null }` that
80
+ * is not a contradiction.
43
81
  */
44
82
  readonly whereNot?: Readonly<Record<string, unknown>>;
45
83
  }
@@ -53,7 +91,8 @@ export interface CompiledFilter {
53
91
  readonly whereSql: string;
54
92
  readonly params: readonly unknown[];
55
93
  }
56
- /** Thrown by `mutate` mode. Distinct type so a caller can map it to a 400. */
94
+ /** Thrown by `measure` and `mutate` mode. Distinct type so a caller can map it
95
+ * to a 400. */
57
96
  export declare class UnsafeFilterError extends Error {
58
97
  constructor(message: string);
59
98
  }
@@ -210,8 +210,12 @@ export declare class SqliteSettingsBackend implements ISettingsBackend {
210
210
  * (visibility, kind, device, time window, expiry) — so `total` counted rows
211
211
  * the page could never show, including other users'.
212
212
  *
213
- * `select` mode, deliberately: `count` must be forgiving in exactly the way
214
- * `query` is, or the two disagree again for a new reason.
213
+ * `measure` mode. `count` compiles the same predicates `query` does — the
214
+ * two must never disagree about WHICH rows they are talking about — but it
215
+ * refuses a predicate it cannot express rather than skipping it. `query`
216
+ * hands back rows and a caller can see the filter did not bite; a count
217
+ * hands back a number, and the whole collection and the intended subset are
218
+ * the same shape.
215
219
  */
216
220
  count({ namespace, collection, filter }: SettingsCountInput): Promise<number>;
217
221
  /**
@@ -223,8 +227,10 @@ export declare class SqliteSettingsBackend implements ISettingsBackend {
223
227
  * projection — an unresolvable field THROWS rather than being dropped, because
224
228
  * a missing aggregate comes back as a number that looks real.
225
229
  *
226
- * `select` mode on the filter, so this agrees with `query` and `count` about
227
- * which rows it is talking about.
230
+ * `measure` mode on the filter, so this agrees with `query` and `count`
231
+ * about which rows it is talking about — and, since 2026-08-30, refuses an
232
+ * unresolvable PREDICATE for the same reason it already refused an
233
+ * unresolvable FIELD. Both come back as a number that looks real.
228
234
  */
229
235
  aggregate({ namespace, collection, fields, filter, }: SettingsAggregateInput): Promise<SettingsAggregateResult>;
230
236
  histogram({ namespace, collection, field, bucketSize, origin, filter, }: SettingsHistogramInput): Promise<readonly HistogramBucket[]>;
@@ -305,15 +311,17 @@ export declare class SqliteSettingsBackend implements ISettingsBackend {
305
311
  * connection whose `sqlite_stat1` visibility and temp schema match.
306
312
  *
307
313
  * `EXPLAIN QUERY PLAN` prepares and plans; it does not execute the statement,
308
- * so it costs no page reads of its own. It is still called only from the
309
- * slow-call path, once per shape per window.
314
+ * so it costs no page reads of its own. In production it is called only from
315
+ * the slow-call path, once per shape per window; it is public so a guard can
316
+ * assert that a declared index is the one the planner actually picks — "we
317
+ * added an index" and "the query uses it" are different claims.
310
318
  *
311
319
  * Never throws. A plan that cannot be taken (a statement the engine will no
312
320
  * longer prepare, a closed handle mid-shutdown) is a missing ANSWER, not a
313
321
  * failure worth turning a slow query into a crash — the profiler falls back
314
322
  * to the line it would have printed anyway.
315
323
  */
316
- private explainQueryPlan;
324
+ explainQueryPlan(sql: string, params: readonly unknown[]): readonly string[];
317
325
  /**
318
326
  * Run one synchronous statement under the profiler.
319
327
  *
@@ -213,9 +213,10 @@ export declare class StorageMigrationCoordinator {
213
213
  *
214
214
  * The gate is the RE-COUNT, not the seal job's terminal state: a seal that
215
215
  * failed some rows still finishes, and "finished" is not "there are none
216
- * left". A non-zero count here fails the job while nothing has been paused
216
+ * left". A remaining row here fails the job while nothing has been paused
217
217
  * and nothing has been repointed, which is the cheapest possible place to
218
- * discover it.
218
+ * discover it. So does a re-count that could not be TAKEN: the gate opens on
219
+ * a measured absence, and only on that.
219
220
  *
220
221
  * The seal's own mover job id is deliberately NOT durable. It is idempotent
221
222
  * and cheap, so a coordinator restart mid-seal simply re-runs the whole
@@ -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-Ck2jkBZk.js");
6
+ const require_dist = require("../../dist-Keu5TDO7.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_fs_promises = require("node:fs/promises");
9
9
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -284,6 +284,18 @@ var REVERSE_PARTICIPANTS = [
284
284
  "pipeline"
285
285
  ];
286
286
  /**
287
+ * One lane of the seal gate, as an operator reads it.
288
+ *
289
+ * "0" and "unknown" must not print the same, and neither may be silently
290
+ * turned into the other: `{ present: true, rows: null }` is "there are some
291
+ * and the count did not land", which is a refusal with a missing number, not a
292
+ * clean lane.
293
+ */
294
+ function describeUnstamped(lane) {
295
+ if (!lane.present) return "0";
296
+ return lane.rows === null ? "an unknown number of" : lane.rows.toString();
297
+ }
298
+ /**
287
299
  * Durable orchestration layer over the independent recorder and event-media
288
300
  * movers. It owns the only public migration state machine; participants expose
289
301
  * intentionally narrow lease/move primitives and never alter camera settings.
@@ -331,12 +343,21 @@ var StorageMigrationCoordinator = class {
331
343
  if (moves.length === 0) throw new Error("select at least one storage class");
332
344
  if (moves.some((move) => move.storageClass === "eventMedia")) {
333
345
  const unstamped = await this.deps.participants.analytics.countUnstamped();
334
- if (unstamped.total > 0) {
335
- if (mode === "nonBlocking") throw new Error(`${unstamped.total.toString()} event-media row(s) still carry no locationId (${unstamped.media.toString()} media, ${unstamped.retrainFrames.toString()} retrain frames). A non-blocking cutover would silently orphan them. Run the seal first: pipelineAnalytics.relocateMedia({ mode: "seal", toLocationId: "${this.deps.locations.getDefaultLocation("eventMedia")?.id ?? "eventMedia"}" }).`);
346
+ if (unstamped === null) {
347
+ if (mode === "nonBlocking") throw new Error("could not determine whether any event-media row still carries no locationId. A non-blocking cutover repoints the default and would silently orphan every such row, so an unmeasured collection is refused exactly like a non-empty one. Retry, or run the blocking mode, which stamps rows as it moves them.");
336
348
  findings.push({
337
349
  code: "unstampedEventMediaRows",
338
350
  storageClass: "eventMedia",
339
- message: `${unstamped.total.toString()} event-media row(s) carry no locationId. This blocking migration stamps them as it moves them, but a non-blocking one would be refused until a seal pass drives the count to zero.`
351
+ message: "the count of event-media rows carrying no locationId could not be taken — treat it as unknown, not as zero. This blocking migration stamps them as it moves them, so it is safe regardless; a non-blocking one would be refused."
352
+ });
353
+ } else if (unstamped.anyPresent) {
354
+ const scale = unstamped.total === null ? "an unknown number of" : unstamped.total.toString();
355
+ const perLane = `${describeUnstamped(unstamped.media)} media, ${describeUnstamped(unstamped.retrainFrames)} retrain frames`;
356
+ if (mode === "nonBlocking") throw new Error(`${scale} event-media row(s) still carry no locationId (${perLane}). A non-blocking cutover would silently orphan them. Run the seal first: pipelineAnalytics.relocateMedia({ mode: "seal", toLocationId: "${this.deps.locations.getDefaultLocation("eventMedia")?.id ?? "eventMedia"}" }).`);
357
+ findings.push({
358
+ code: "unstampedEventMediaRows",
359
+ storageClass: "eventMedia",
360
+ message: `${scale} event-media row(s) carry no locationId (${perLane}). This blocking migration stamps them as it moves them, but a non-blocking one would be refused until a seal pass drives the count to zero.`
340
361
  });
341
362
  }
342
363
  }
@@ -585,6 +606,7 @@ var StorageMigrationCoordinator = class {
585
606
  const remaining = residue.filter((entry) => entry.storageClass === storageClass);
586
607
  if (remaining.length === 0) throw new Error(`"${storageClass}" has nothing left outside its default location — there is nothing to drain. To CHANGE where it writes, use storageMigration.start with a new destination.`);
587
608
  for (const entry of remaining) {
609
+ if (this.deps.locations.getLocationById(entry.toLocationId)?.enabled === false) throw new Error(`The "${storageClass}" drain destination "${entry.toLocationId}" is DISABLED — a disabled location takes no writes, and a drain is the largest write there is. Enable it on the Storage screen, or make an enabled location the "${storageClass}" default first.`);
588
610
  moves.push({
589
611
  storageClass,
590
612
  fromLocationId: entry.fromLocationId,
@@ -835,6 +857,7 @@ var StorageMigrationCoordinator = class {
835
857
  filesMoved: status.filesMoved,
836
858
  filesTotal: status.filesTotal,
837
859
  bytesMoved: status.bytesMoved,
860
+ ...status.rowsReconciled === void 0 ? {} : { rowsReconciled: status.rowsReconciled },
838
861
  startedAt: status.startedAt,
839
862
  observedAt: this.deps.now()
840
863
  };
@@ -857,9 +880,10 @@ var StorageMigrationCoordinator = class {
857
880
  *
858
881
  * The gate is the RE-COUNT, not the seal job's terminal state: a seal that
859
882
  * failed some rows still finishes, and "finished" is not "there are none
860
- * left". A non-zero count here fails the job while nothing has been paused
883
+ * left". A remaining row here fails the job while nothing has been paused
861
884
  * and nothing has been repointed, which is the cheapest possible place to
862
- * discover it.
885
+ * discover it. So does a re-count that could not be TAKEN: the gate opens on
886
+ * a measured absence, and only on that.
863
887
  *
864
888
  * The seal's own mover job id is deliberately NOT durable. It is idempotent
865
889
  * and cheap, so a coordinator restart mid-seal simply re-runs the whole
@@ -869,7 +893,8 @@ var StorageMigrationCoordinator = class {
869
893
  async sealEventMedia(job) {
870
894
  const move = job.moves.find((candidate) => candidate.storageClass === "eventMedia");
871
895
  if (move === void 0) return;
872
- if ((await this.deps.participants.analytics.countUnstamped()).total > 0) {
896
+ const before = await this.deps.participants.analytics.countUnstamped();
897
+ if (before === null || before.anyPresent) {
873
898
  const started = await this.deps.participants.analytics.startDrain({
874
899
  toLocationId: move.fromLocationId,
875
900
  mode: "seal"
@@ -878,7 +903,8 @@ var StorageMigrationCoordinator = class {
878
903
  if (job.cancelRequested) return;
879
904
  }
880
905
  const after = await this.deps.participants.analytics.countUnstamped();
881
- if (after.total > 0) throw new Error(`event-media seal left ${after.total.toString()} row(s) without a locationId (${after.media.toString()} media, ${after.retrainFrames.toString()} retrain frames). A non-blocking cutover would silently orphan them; nothing has been paused or repointed.`);
906
+ if (after === null) throw new Error("event-media seal ran, but whether any row still carries no locationId could not be measured afterwards. Unknown is not zero; nothing has been paused or repointed.");
907
+ if (after.anyPresent) throw new Error(`event-media seal left ${after.total === null ? "an unknown number of" : after.total.toString()} row(s) without a locationId (${describeUnstamped(after.media)} media, ${describeUnstamped(after.retrainFrames)} retrain frames). A non-blocking cutover would silently orphan them; nothing has been paused or repointed.`);
882
908
  }
883
909
  async waitForSeal(job, moverJobId) {
884
910
  const sleep = this.deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
@@ -1426,6 +1452,34 @@ var StorageOrchestratorService = class {
1426
1452
  backfilled: backfill.length,
1427
1453
  total: this.locations.size
1428
1454
  } });
1455
+ this.reportDisabledDefaults();
1456
+ }
1457
+ /**
1458
+ * Shout about a persisted row that is BOTH the type default and disabled.
1459
+ *
1460
+ * `upsertLocation` cannot produce this (`resolveEnabled` force-enables a
1461
+ * default) and neither can `setDefaultLocations` — but hydrate writes rows
1462
+ * straight into the map without either, so a hand-edited store, a partial
1463
+ * migration or a row written by another build can. It matters because every
1464
+ * bare-type ref resolves through {@link getDefaultLocation}: on such a row
1465
+ * `storage.write({ location: 'eventMedia' })` and the migration coordinator's
1466
+ * drain destination both point at a disk the operator turned off. That is the
1467
+ * one thing this model must never do silently.
1468
+ *
1469
+ * Reported, never repaired: flipping an operator's flag back on at boot would
1470
+ * be the system overruling the switch instead of obeying it. The coordinator
1471
+ * refuses the drain (`storage-migration-coordinator.drain`); the bare-ref read
1472
+ * path is deliberately left working, because narrowing a READ is a worse bug
1473
+ * than the one this line reports.
1474
+ */
1475
+ reportDisabledDefaults() {
1476
+ for (const loc of this.locations.values()) {
1477
+ if (!loc.isDefault || loc.enabled !== false) continue;
1478
+ this.logger.error("storage-orchestrator: the type DEFAULT is disabled — every bare-type ref for this type resolves to a location the operator turned off. Enable it, or make another location the default.", { meta: {
1479
+ id: loc.id,
1480
+ type: loc.type
1481
+ } });
1482
+ }
1429
1483
  }
1430
1484
  /**
1431
1485
  * Inject the declaration-driven cardinality source. Called once by the
@@ -1497,6 +1551,7 @@ var StorageOrchestratorService = class {
1497
1551
  loaded: this.locations.size,
1498
1552
  isSystemUpgraded: upgraded
1499
1553
  } });
1554
+ this.reportDisabledDefaults();
1500
1555
  }
1501
1556
  /**
1502
1557
  * Boot backfill (SP1): stamp `nodeId` on every persisted node-local
@@ -1,4 +1,4 @@
1
- import { Mt as BaseAddon, N as collectSecretConfigKeys, Qt as parseJsonObject, S as StorageMigrationJobSchema, St as settingsStoreCapability, Tt as storageMigrationCapability, m as REDACTED_SECRET, wt as storageCapability, x as StorageLocationTypeSchema } from "../../dist-DLh4_rn3.mjs";
1
+ import { Mt as BaseAddon, N as collectSecretConfigKeys, Qt as parseJsonObject, S as StorageMigrationJobSchema, St as settingsStoreCapability, Tt as storageMigrationCapability, m as REDACTED_SECRET, wt as storageCapability, x as StorageLocationTypeSchema } from "../../dist-D4xNBlJM.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";
@@ -277,6 +277,18 @@ var REVERSE_PARTICIPANTS = [
277
277
  "pipeline"
278
278
  ];
279
279
  /**
280
+ * One lane of the seal gate, as an operator reads it.
281
+ *
282
+ * "0" and "unknown" must not print the same, and neither may be silently
283
+ * turned into the other: `{ present: true, rows: null }` is "there are some
284
+ * and the count did not land", which is a refusal with a missing number, not a
285
+ * clean lane.
286
+ */
287
+ function describeUnstamped(lane) {
288
+ if (!lane.present) return "0";
289
+ return lane.rows === null ? "an unknown number of" : lane.rows.toString();
290
+ }
291
+ /**
280
292
  * Durable orchestration layer over the independent recorder and event-media
281
293
  * movers. It owns the only public migration state machine; participants expose
282
294
  * intentionally narrow lease/move primitives and never alter camera settings.
@@ -324,12 +336,21 @@ var StorageMigrationCoordinator = class {
324
336
  if (moves.length === 0) throw new Error("select at least one storage class");
325
337
  if (moves.some((move) => move.storageClass === "eventMedia")) {
326
338
  const unstamped = await this.deps.participants.analytics.countUnstamped();
327
- if (unstamped.total > 0) {
328
- if (mode === "nonBlocking") throw new Error(`${unstamped.total.toString()} event-media row(s) still carry no locationId (${unstamped.media.toString()} media, ${unstamped.retrainFrames.toString()} retrain frames). A non-blocking cutover would silently orphan them. Run the seal first: pipelineAnalytics.relocateMedia({ mode: "seal", toLocationId: "${this.deps.locations.getDefaultLocation("eventMedia")?.id ?? "eventMedia"}" }).`);
339
+ if (unstamped === null) {
340
+ if (mode === "nonBlocking") throw new Error("could not determine whether any event-media row still carries no locationId. A non-blocking cutover repoints the default and would silently orphan every such row, so an unmeasured collection is refused exactly like a non-empty one. Retry, or run the blocking mode, which stamps rows as it moves them.");
329
341
  findings.push({
330
342
  code: "unstampedEventMediaRows",
331
343
  storageClass: "eventMedia",
332
- message: `${unstamped.total.toString()} event-media row(s) carry no locationId. This blocking migration stamps them as it moves them, but a non-blocking one would be refused until a seal pass drives the count to zero.`
344
+ message: "the count of event-media rows carrying no locationId could not be taken — treat it as unknown, not as zero. This blocking migration stamps them as it moves them, so it is safe regardless; a non-blocking one would be refused."
345
+ });
346
+ } else if (unstamped.anyPresent) {
347
+ const scale = unstamped.total === null ? "an unknown number of" : unstamped.total.toString();
348
+ const perLane = `${describeUnstamped(unstamped.media)} media, ${describeUnstamped(unstamped.retrainFrames)} retrain frames`;
349
+ if (mode === "nonBlocking") throw new Error(`${scale} event-media row(s) still carry no locationId (${perLane}). A non-blocking cutover would silently orphan them. Run the seal first: pipelineAnalytics.relocateMedia({ mode: "seal", toLocationId: "${this.deps.locations.getDefaultLocation("eventMedia")?.id ?? "eventMedia"}" }).`);
350
+ findings.push({
351
+ code: "unstampedEventMediaRows",
352
+ storageClass: "eventMedia",
353
+ message: `${scale} event-media row(s) carry no locationId (${perLane}). This blocking migration stamps them as it moves them, but a non-blocking one would be refused until a seal pass drives the count to zero.`
333
354
  });
334
355
  }
335
356
  }
@@ -578,6 +599,7 @@ var StorageMigrationCoordinator = class {
578
599
  const remaining = residue.filter((entry) => entry.storageClass === storageClass);
579
600
  if (remaining.length === 0) throw new Error(`"${storageClass}" has nothing left outside its default location — there is nothing to drain. To CHANGE where it writes, use storageMigration.start with a new destination.`);
580
601
  for (const entry of remaining) {
602
+ if (this.deps.locations.getLocationById(entry.toLocationId)?.enabled === false) throw new Error(`The "${storageClass}" drain destination "${entry.toLocationId}" is DISABLED — a disabled location takes no writes, and a drain is the largest write there is. Enable it on the Storage screen, or make an enabled location the "${storageClass}" default first.`);
581
603
  moves.push({
582
604
  storageClass,
583
605
  fromLocationId: entry.fromLocationId,
@@ -828,6 +850,7 @@ var StorageMigrationCoordinator = class {
828
850
  filesMoved: status.filesMoved,
829
851
  filesTotal: status.filesTotal,
830
852
  bytesMoved: status.bytesMoved,
853
+ ...status.rowsReconciled === void 0 ? {} : { rowsReconciled: status.rowsReconciled },
831
854
  startedAt: status.startedAt,
832
855
  observedAt: this.deps.now()
833
856
  };
@@ -850,9 +873,10 @@ var StorageMigrationCoordinator = class {
850
873
  *
851
874
  * The gate is the RE-COUNT, not the seal job's terminal state: a seal that
852
875
  * failed some rows still finishes, and "finished" is not "there are none
853
- * left". A non-zero count here fails the job while nothing has been paused
876
+ * left". A remaining row here fails the job while nothing has been paused
854
877
  * and nothing has been repointed, which is the cheapest possible place to
855
- * discover it.
878
+ * discover it. So does a re-count that could not be TAKEN: the gate opens on
879
+ * a measured absence, and only on that.
856
880
  *
857
881
  * The seal's own mover job id is deliberately NOT durable. It is idempotent
858
882
  * and cheap, so a coordinator restart mid-seal simply re-runs the whole
@@ -862,7 +886,8 @@ var StorageMigrationCoordinator = class {
862
886
  async sealEventMedia(job) {
863
887
  const move = job.moves.find((candidate) => candidate.storageClass === "eventMedia");
864
888
  if (move === void 0) return;
865
- if ((await this.deps.participants.analytics.countUnstamped()).total > 0) {
889
+ const before = await this.deps.participants.analytics.countUnstamped();
890
+ if (before === null || before.anyPresent) {
866
891
  const started = await this.deps.participants.analytics.startDrain({
867
892
  toLocationId: move.fromLocationId,
868
893
  mode: "seal"
@@ -871,7 +896,8 @@ var StorageMigrationCoordinator = class {
871
896
  if (job.cancelRequested) return;
872
897
  }
873
898
  const after = await this.deps.participants.analytics.countUnstamped();
874
- if (after.total > 0) throw new Error(`event-media seal left ${after.total.toString()} row(s) without a locationId (${after.media.toString()} media, ${after.retrainFrames.toString()} retrain frames). A non-blocking cutover would silently orphan them; nothing has been paused or repointed.`);
899
+ if (after === null) throw new Error("event-media seal ran, but whether any row still carries no locationId could not be measured afterwards. Unknown is not zero; nothing has been paused or repointed.");
900
+ if (after.anyPresent) throw new Error(`event-media seal left ${after.total === null ? "an unknown number of" : after.total.toString()} row(s) without a locationId (${describeUnstamped(after.media)} media, ${describeUnstamped(after.retrainFrames)} retrain frames). A non-blocking cutover would silently orphan them; nothing has been paused or repointed.`);
875
901
  }
876
902
  async waitForSeal(job, moverJobId) {
877
903
  const sleep = this.deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
@@ -1419,6 +1445,34 @@ var StorageOrchestratorService = class {
1419
1445
  backfilled: backfill.length,
1420
1446
  total: this.locations.size
1421
1447
  } });
1448
+ this.reportDisabledDefaults();
1449
+ }
1450
+ /**
1451
+ * Shout about a persisted row that is BOTH the type default and disabled.
1452
+ *
1453
+ * `upsertLocation` cannot produce this (`resolveEnabled` force-enables a
1454
+ * default) and neither can `setDefaultLocations` — but hydrate writes rows
1455
+ * straight into the map without either, so a hand-edited store, a partial
1456
+ * migration or a row written by another build can. It matters because every
1457
+ * bare-type ref resolves through {@link getDefaultLocation}: on such a row
1458
+ * `storage.write({ location: 'eventMedia' })` and the migration coordinator's
1459
+ * drain destination both point at a disk the operator turned off. That is the
1460
+ * one thing this model must never do silently.
1461
+ *
1462
+ * Reported, never repaired: flipping an operator's flag back on at boot would
1463
+ * be the system overruling the switch instead of obeying it. The coordinator
1464
+ * refuses the drain (`storage-migration-coordinator.drain`); the bare-ref read
1465
+ * path is deliberately left working, because narrowing a READ is a worse bug
1466
+ * than the one this line reports.
1467
+ */
1468
+ reportDisabledDefaults() {
1469
+ for (const loc of this.locations.values()) {
1470
+ if (!loc.isDefault || loc.enabled !== false) continue;
1471
+ this.logger.error("storage-orchestrator: the type DEFAULT is disabled — every bare-type ref for this type resolves to a location the operator turned off. Enable it, or make another location the default.", { meta: {
1472
+ id: loc.id,
1473
+ type: loc.type
1474
+ } });
1475
+ }
1422
1476
  }
1423
1477
  /**
1424
1478
  * Inject the declaration-driven cardinality source. Called once by the
@@ -1490,6 +1544,7 @@ var StorageOrchestratorService = class {
1490
1544
  loaded: this.locations.size,
1491
1545
  isSystemUpgraded: upgraded
1492
1546
  } });
1547
+ this.reportDisabledDefaults();
1493
1548
  }
1494
1549
  /**
1495
1550
  * Boot backfill (SP1): stamp `nodeId` on every persisted node-local