@camstack/system 1.2.157 → 1.2.160

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 (77) hide show
  1. package/dist/addon-runner.js +17 -16
  2. package/dist/addon-runner.mjs +2 -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/sqlite-storage/vector-index-shared.d.ts +30 -5
  52. package/dist/builtins/sqlite-storage/vector-index-vec.d.ts +28 -6
  53. package/dist/builtins/storage-orchestrator/access-guards.d.ts +32 -0
  54. package/dist/builtins/storage-orchestrator/location-secrets.d.ts +33 -0
  55. package/dist/builtins/storage-orchestrator/storage-migration-coordinator.d.ts +74 -3
  56. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +26 -0
  57. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +552 -15
  58. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +552 -15
  59. package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +56 -1
  60. package/dist/builtins/system-config/system-config.addon.js +1 -1
  61. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  62. package/dist/builtins/winston-logging/index.js +1 -1
  63. package/dist/builtins/winston-logging/index.mjs +1 -1
  64. package/dist/{dist-CDgIzo82.js → dist-CcvXUhHK.js} +537 -17
  65. package/dist/{dist-LvKus-mT.mjs → dist-DKs---Vx.mjs} +520 -18
  66. package/dist/index.js +141 -139
  67. package/dist/index.mjs +3 -3
  68. package/dist/kernel/deps/manifest-system-deps.d.ts +15 -0
  69. package/dist/kernel/index.d.ts +4 -2
  70. package/dist/kernel/moleculer/cap-usage-registry.d.ts +132 -7
  71. package/dist/kernel/storage-location-registry.d.ts +16 -1
  72. package/dist/kernel/transport/local-child-registry.d.ts +57 -0
  73. package/dist/{manifest-python-deps-CD14Pv0-.mjs → manifest-system-deps-DnoPpLGp.mjs} +466 -83
  74. package/dist/{manifest-python-deps-DVODn-qc.js → manifest-system-deps-oJMkWSX-.js} +477 -82
  75. package/dist/{retired-settings-keys-CV4QVdXj.mjs → retired-settings-keys-C3eONTvV.mjs} +1 -1
  76. package/dist/{retired-settings-keys-BfAzWvPC.js → retired-settings-keys-DXZ2xe7C.js} +1 -1
  77. package/package.json +2 -1
@@ -1,4 +1,4 @@
1
- import { B as doorbellCapability, Ft as DeviceType, Ht as createEvent, O as bareAddonId, Ot as errMsg, Q as isSourceCap, W as evaluateSensorEdge, Z as isSameAddonId, g as SOURCE_DEVICE_TYPES, h as SOURCE_CAPS, kt as BaseAddon, tn as EventCategory } from "../../dist-LvKus-mT.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-DKs---Vx.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-CDgIzo82.js");
6
+ const require_dist = require("../../dist-CcvXUhHK.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 { kt as BaseAddon, rt as logDestinationCapability } from "../../dist-LvKus-mT.mjs";
1
+ import { Mt as BaseAddon, ot as logDestinationCapability } from "../../dist-DKs---Vx.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-CDgIzo82.js");
3
+ const require_dist = require("../../dist-CcvXUhHK.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 { Ht as createEvent, Ot as errMsg, kt as BaseAddon, tn as EventCategory } from "../../dist-LvKus-mT.mjs";
1
+ import { Gt as createEvent, Mt as BaseAddon, in as EventCategory, jt as errMsg } from "../../dist-DKs---Vx.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-CDgIzo82.js");
6
+ const require_dist = require("../../dist-CcvXUhHK.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 { E as authProviderCapability, Tt as userManagementCapability, kt as BaseAddon, r as ApiKeyRecordSchema, v as ScopedTokenSchema, x as UserRecordSchema } from "../../dist-LvKus-mT.mjs";
2
+ import { C as UserRecordSchema, Mt as BaseAddon, O as authProviderCapability, Ot as userManagementCapability, b as ScopedTokenSchema, r as ApiKeyRecordSchema } from "../../dist-DKs---Vx.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-CDgIzo82.js");
3
+ const require_dist = require("../../dist-CcvXUhHK.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 { kt as BaseAddon, nt as localNetworkCapability, tn as EventCategory } from "../../dist-LvKus-mT.mjs";
1
+ import { Mt as BaseAddon, at as localNetworkCapability, in as EventCategory } from "../../dist-DKs---Vx.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-CDgIzo82.js");
6
+ const require_dist = require("../../dist-CcvXUhHK.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 { kt as BaseAddon, rt as logDestinationCapability } from "../../dist-LvKus-mT.mjs";
1
+ import { Mt as BaseAddon, ot as logDestinationCapability } from "../../dist-DKs---Vx.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-CDgIzo82.js");
6
+ const require_dist = require("../../dist-CcvXUhHK.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 { Ht as createEvent, Ot as errMsg, ft as reducePoints, kt as BaseAddon, ot as metricsProviderCapability, pt as resolveBucketMs, q as foldSnapshotByFunction, tn as EventCategory } from "../../dist-LvKus-mT.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-DKs---Vx.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-CDgIzo82.js");
6
+ const require_dist = require("../../dist-CcvXUhHK.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 { Ot as errMsg, V as enumerateInferenceDevices, Wt as emitReadiness, kt as BaseAddon, tn as EventCategory, ut as platformProbeCapability, vt as scoreRuntimes } from "../../dist-LvKus-mT.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-DKs---Vx.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-CDgIzo82.js");
6
+ const require_dist = require("../../dist-CcvXUhHK.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 { kt as BaseAddon, tn as EventCategory } from "../../dist-LvKus-mT.mjs";
1
+ import { Mt as BaseAddon, in as EventCategory } from "../../dist-DKs---Vx.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-CDgIzo82.js");
6
+ const require_dist = require("../../dist-CcvXUhHK.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 { Ft as DeviceType, I as deriveBatteryPresence, Jt as nodePin, Nt as DeviceFeature, O as bareAddonId, Ot as errMsg, bt as snapshotCapability, i as BatteryStatusSchema, k as batteryCapability, kt as BaseAddon, tn as EventCategory, wt as streamQualityLabel } from "../../dist-LvKus-mT.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-DKs---Vx.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-CDgIzo82.js");
6
+ const require_dist = require("../../dist-CcvXUhHK.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 { Ct as storageProviderCapability, K as filesystemBrowseCapability, kt as BaseAddon } from "../../dist-LvKus-mT.mjs";
1
+ import { Et as storageProviderCapability, Mt as BaseAddon, Y as filesystemBrowseCapability } from "../../dist-DKs---Vx.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
  *
@@ -8,6 +8,33 @@ export interface VectorMatchRow {
8
8
  readonly score: number;
9
9
  readonly metadata: VectorMetadata;
10
10
  }
11
+ /**
12
+ * What one ranked search returned, INCLUDING the `k` it really ran with.
13
+ *
14
+ * `effectiveTopK` is part of the answer rather than a log line for the reason
15
+ * `vector-store.cap.ts` records: a ceiling the caller learns about only from
16
+ * the provider's own log is a number truncated in silence.
17
+ */
18
+ export interface VectorQueryOutcome {
19
+ readonly matches: VectorMatchRow[];
20
+ readonly scanned: number;
21
+ readonly truncated: boolean;
22
+ readonly effectiveTopK: number;
23
+ }
24
+ /** One row read back WITH its vector, base64 Float32LE. */
25
+ export interface VectorFetchRow {
26
+ readonly id: string;
27
+ readonly vector: string;
28
+ readonly metadata: VectorMetadata;
29
+ }
30
+ /** One page of an unranked walk. `nextCursor: null` means end of index. */
31
+ export interface VectorScanPage {
32
+ readonly items: Array<{
33
+ id: string;
34
+ metadata: VectorMetadata;
35
+ }>;
36
+ readonly nextCursor: number | null;
37
+ }
11
38
  export interface VectorIndexStats {
12
39
  readonly backend: string;
13
40
  readonly count: number;
@@ -32,15 +59,13 @@ export interface VectorIndexBackend {
32
59
  topK: number;
33
60
  minScore?: number;
34
61
  filter?: VectorFilter;
35
- }): Promise<{
36
- matches: VectorMatchRow[];
37
- scanned: number;
38
- truncated: boolean;
39
- }>;
62
+ }): Promise<VectorQueryOutcome>;
40
63
  getByIds(index: string, ids: readonly string[]): Promise<Array<{
41
64
  id: string;
42
65
  metadata: VectorMetadata;
43
66
  }>>;
67
+ fetchByIds(index: string, ids: readonly string[]): Promise<VectorFetchRow[]>;
68
+ scan(index: string, cursor: number, limit: number): Promise<VectorScanPage>;
44
69
  deleteByIds(index: string, ids: readonly string[]): Promise<number>;
45
70
  deleteByFilter(index: string, filter: VectorFilter): Promise<number>;
46
71
  stats(index: string): Promise<VectorIndexStats>;
@@ -1,6 +1,6 @@
1
1
  import { IScopedLogger, VectorFilter, VectorMetadata, VectorMetric, encodeVectorBase64 } from '@camstack/types';
2
2
  import { SqliteOpProfiler } from './query-profiler.js';
3
- import { VectorIndexStats, VectorMatchRow } from './vector-index-shared.js';
3
+ import { VectorFetchRow, VectorIndexStats, VectorQueryOutcome, VectorScanPage } from './vector-index-shared.js';
4
4
  /** The raw statement surface this index needs from better-sqlite3. */
5
5
  export interface RawSqliteDatabase {
6
6
  exec(sql: string): unknown;
@@ -68,11 +68,33 @@ export declare class SqliteVecVectorIndex {
68
68
  topK: number;
69
69
  minScore?: number;
70
70
  filter?: VectorFilter;
71
- }): Promise<{
72
- matches: VectorMatchRow[];
73
- scanned: number;
74
- truncated: boolean;
75
- }>;
71
+ }): Promise<VectorQueryOutcome>;
72
+ /**
73
+ * Ids read back WITH their vectors.
74
+ *
75
+ * `vec0` returns a vector column as the packed Float32 blob it stores, which
76
+ * is byte-for-byte what `upsert` was given — so the round trip is a base64
77
+ * encode and nothing else. No re-normalisation, no float re-ordering: a
78
+ * gallery loaded through here ranks identically to one loaded from the JSON
79
+ * column it replaced.
80
+ */
81
+ fetchByIds(index: string, ids: readonly string[]): Promise<VectorFetchRow[]>;
82
+ /**
83
+ * One page of the whole index, unranked.
84
+ *
85
+ * `LIMIT ? OFFSET ?` over the `vec0` table with NO `MATCH` clause: a plain
86
+ * scan, so the extension's KNN `k` ceiling ({@link VEC_KNN_MAX_K}) does not
87
+ * apply and no distance is computed at all. Ordered by `id` so a cursor means
88
+ * the same thing across calls — `rowid` order is not stable under the
89
+ * DELETE-then-INSERT upsert this class performs.
90
+ *
91
+ * The cursor is an OFFSET, so rows deleted behind the walk shift the window.
92
+ * That is acceptable and deliberate for the one caller: a reconcile that
93
+ * misses a row this pass sees it next pass, and the alternative — a keyset
94
+ * cursor on a virtual table whose ordering the extension owns — buys nothing
95
+ * for a walk that is idempotent by construction.
96
+ */
97
+ scan(index: string, cursor: number, limit: number): Promise<VectorScanPage>;
76
98
  getByIds(index: string, ids: readonly string[]): Promise<Array<{
77
99
  id: string;
78
100
  metadata: VectorMetadata;
@@ -0,0 +1,32 @@
1
+ import { StorageLocation } from '@camstack/types';
2
+ /** What a provider answered about itself; `undefined` = not yet classified. */
3
+ export type ProviderLocality = boolean | undefined;
4
+ /**
5
+ * May `storage.resolve` return a path for this location?
6
+ *
7
+ * `resolve` is the only method on the cap whose result LEAVES the storage
8
+ * abstraction — every caller of it opens the string with `node:fs`. A remote
9
+ * provider answers with a path on the remote host, which on this node is
10
+ * either missing or an unrelated local directory of the same name. For the
11
+ * recorder, writing to the wrong place is not an error the operator sees; it
12
+ * is a black window in the footage.
13
+ *
14
+ * Only a POSITIVE `false` refuses. `undefined` is "not yet classified", not
15
+ * "remote" — and a conclusion drawn from a read that was never made is how a
16
+ * doorbell rang 0/3 (D49).
17
+ */
18
+ export declare function resolveRefusalFor(location: StorageLocation, locality: ProviderLocality): string | null;
19
+ /**
20
+ * Can this node's filesystem answer "does this location still hold anything?"
21
+ *
22
+ * Only for a provider it KNOWS to be node-local. Everything else is `unknown`,
23
+ * which refuses a delete.
24
+ *
25
+ * The direction that matters is the one this closes. A remote location carries
26
+ * a plain-looking `basePath` (`/backups/camstack`); `fs.readdir` of it on the
27
+ * hub answers `ENOENT`, and `ENOENT` is deliberately read as `empty` — because
28
+ * for a LOCAL location an absent root means there is nothing the delete can
29
+ * strand. Applied to a remote row, that same reasoning turns a full share into
30
+ * a vacant one and walks D293's delete guard straight past it.
31
+ */
32
+ export declare function canProbeOccupancyLocally(locality: ProviderLocality): boolean;
@@ -0,0 +1,33 @@
1
+ import { StorageLocation } from '@camstack/types';
2
+ /** Shared empty set for providers that declare no secrets (or are unknown). */
3
+ export declare const EMPTY_SECRET_KEYS: ReadonlySet<string>;
4
+ /** Minimal shape needed off a provider's `getProviderInfo()` result. */
5
+ export interface ProviderInfoLike {
6
+ readonly providerId: string;
7
+ readonly configSchema: unknown;
8
+ }
9
+ /** Secret config keys a provider declares, derived from its own form schema. */
10
+ export declare function secretKeysOfProviderInfo(info: ProviderInfoLike): ReadonlySet<string>;
11
+ /**
12
+ * A copy of `config` with every declared secret replaced by the sentinel.
13
+ * Returns the SAME object when there is nothing to redact, so the common case
14
+ * (a filesystem location) allocates nothing.
15
+ *
16
+ * A key the provider declares secret but that is absent from the stored config
17
+ * is NOT invented — an absent password must keep reading as absent, or the
18
+ * wizard would show a credential where none is set and the write-back would
19
+ * then "keep" a value that never existed.
20
+ */
21
+ export declare function redactLocationConfig(config: Readonly<Record<string, unknown>>, secretKeys: ReadonlySet<string>): Readonly<Record<string, unknown>>;
22
+ /** The same, applied to a whole location record. */
23
+ export declare function redactLocation(location: StorageLocation, secretKeys: ReadonlySet<string>): StorageLocation;
24
+ /**
25
+ * Undo the redaction on the way IN: any secret key whose incoming value is the
26
+ * sentinel is restored from what is already stored.
27
+ *
28
+ * A sentinel with NOTHING stored behind it is dropped rather than persisted —
29
+ * writing the literal `__camstack_redacted__` as a password is a
30
+ * lock-yourself-out bug that would only surface at the next connection
31
+ * attempt, hours later, as an authentication failure with no explanation.
32
+ */
33
+ export declare function restoreRedactedSecrets(incoming: Readonly<Record<string, unknown>>, stored: Readonly<Record<string, unknown>> | undefined, secretKeys: ReadonlySet<string>): Readonly<Record<string, unknown>>;
@@ -1,4 +1,4 @@
1
- import { RelocateFootageInput, RelocateJob, RelocateMediaInput, StorageMigrationFootageMoveInput, StorageMigrationInput, StorageMigrationJob, StorageMigrationMediaMoveInput, StorageMigrationPlan, UnstampedEventMediaCount } from '@camstack/types';
1
+ import { RelocatableMediaCount, RelocatableMediaCountInput, RelocateFootageInput, RelocateJob, RelocateMediaInput, RelocateResidue, RelocateResidueInput, StorageMigrationDrainInput, StorageMigrationFootageMoveInput, StorageMigrationInput, StorageMigrationJob, StorageMigrationMediaMoveInput, StorageMigrationMover, StorageMigrationPlan, StorageMigrationResidue, UnstampedEventMediaCount } from '@camstack/types';
2
2
  import { StorageOrchestratorService } from './storage-orchestrator.service.js';
3
3
  export interface MigrationState {
4
4
  get(): Promise<StorageMigrationJob | null>;
@@ -24,6 +24,10 @@ export interface FootageMigrationParticipant extends LeaseParticipant {
24
24
  getMove(jobId: string): Promise<RelocateJob | null>;
25
25
  cancelMove(jobId: string): Promise<boolean>;
26
26
  refresh(leaseId: string): Promise<void>;
27
+ /** Every mover this engine knows about — including ones no migration armed. */
28
+ listMovers(): Promise<readonly RelocateJob[]>;
29
+ /** What a source STILL holds, from the durable hour ledger (D295). */
30
+ residue(input: RelocateResidueInput): Promise<RelocateResidue>;
27
31
  }
28
32
  export interface MediaMigrationParticipant extends LeaseParticipant {
29
33
  startMove(input: StorageMigrationMediaMoveInput): Promise<{
@@ -41,6 +45,11 @@ export interface MediaMigrationParticipant extends LeaseParticipant {
41
45
  getMove(jobId: string): Promise<RelocateJob | null>;
42
46
  cancelMove(jobId: string): Promise<boolean>;
43
47
  refresh(leaseId: string): Promise<void>;
48
+ /** Every mover this engine knows about — including ones no migration armed. */
49
+ listMovers(): Promise<readonly RelocateJob[]>;
50
+ /** How many rows a pass would still act on — the media lane's residue, from
51
+ * the same derivation that produces its `filesTotal`. */
52
+ residue(input: RelocatableMediaCountInput): Promise<RelocatableMediaCount>;
44
53
  }
45
54
  export interface StorageMigrationParticipants {
46
55
  readonly pipeline: LeaseParticipant;
@@ -103,6 +112,67 @@ export declare class StorageMigrationCoordinator {
103
112
  start(input: StorageMigrationInput): Promise<string>;
104
113
  status(jobId?: string): Promise<StorageMigrationJob | null>;
105
114
  cancel(jobId: string): Promise<boolean>;
115
+ /**
116
+ * Every mover running right now, in both lanes — including the ones no
117
+ * migration armed.
118
+ *
119
+ * `status` already carries a migration's own progress (the coordinator folds
120
+ * it onto each move from the poll it is already doing). This exists for the
121
+ * other half: `recording.relocateFootage` and `pipelineAnalytics.relocateMedia`
122
+ * are operator-callable, and until {@link drain} existed that was the only way
123
+ * to run a drain at all. Such a mover has no job to fold into, so without this
124
+ * read a five-hour operation is invisible in the UI.
125
+ *
126
+ * `migrationJobId` is best-effort by construction: the coordinator keeps ONE
127
+ * durable job, so a mover armed by an older, since-overwritten migration
128
+ * reports `null`. That is the honest answer — nothing here can still claim it.
129
+ */
130
+ movers(): Promise<readonly StorageMigrationMover[]>;
131
+ /**
132
+ * What every class's source STILL holds — the census behind a "drain
133
+ * remaining" action.
134
+ *
135
+ * A class appears here only when something is (or might be) left on a
136
+ * location that is not its default. An empty result therefore means exactly
137
+ * "there is nothing to drain", which is what lets the UI offer the action
138
+ * only when it is true, and what lets {@link drain} refuse rather than start
139
+ * a job that would move nothing and report `done` — the failure mode D295
140
+ * exists to end.
141
+ *
142
+ * `items: null` is "the archive could not be asked" and is still listed. A
143
+ * residue nobody could measure is the case an operator most needs to see;
144
+ * dropping it because the read failed would be the quiet success again.
145
+ */
146
+ residue(): Promise<readonly StorageMigrationResidue[]>;
147
+ /**
148
+ * Run the DRAIN half alone, against classes whose default has already moved.
149
+ *
150
+ * ## Why this is a second verb rather than a looser `start`
151
+ *
152
+ * `start` refuses a destination that is already the class's default
153
+ * (`"recordingsLow:ssd" is already the "recordingsLow" default`). That refusal
154
+ * is correct and it is load-bearing: there is genuinely nothing left to
155
+ * repoint, and an operator must never be able to re-repoint a migrated class
156
+ * by accident. Making `start` idempotent — "an already-repointed class
157
+ * proceeds straight to draining" — would delete that protection AND make the
158
+ * verb mean two different things depending on state, so the confirmation an
159
+ * operator reads ("pauses the writers…") would be a lie half the time.
160
+ *
161
+ * `drain` instead cannot repoint AT ALL: it never touches
162
+ * `setDefaultLocations`, and its job starts in `draining` with `repointed`
163
+ * already true, so the `repointing` / `refreshing` / `resuming` blocks of
164
+ * {@link run} are behind it and unreachable. The two verbs are disjoint, and
165
+ * `start`'s refusal keeps meaning exactly what it meant.
166
+ *
167
+ * ## Why it re-derives the work instead of resuming the old job
168
+ *
169
+ * The finished job is the audit of what happened; re-opening it destroys
170
+ * that. And a drain is needed in cases where no migration job ever existed
171
+ * (a mover armed by hand, footage stranded on a location an operator added
172
+ * and then un-defaulted). One job = one operation, and the work list comes
173
+ * from {@link residue} — the archive — not from what a previous job believed.
174
+ */
175
+ drain(input: StorageMigrationDrainInput): Promise<string>;
106
176
  /** Boot recovery resumes a durable unfinished state. A missing in-memory
107
177
  * child mover is recreated from the same copy-if-absent input. */
108
178
  recover(): Promise<void>;
@@ -143,9 +213,10 @@ export declare class StorageMigrationCoordinator {
143
213
  *
144
214
  * The gate is the RE-COUNT, not the seal job's terminal state: a seal that
145
215
  * failed some rows still finishes, and "finished" is not "there are none
146
- * 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
147
217
  * and nothing has been repointed, which is the cheapest possible place to
148
- * 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.
149
220
  *
150
221
  * The seal's own mover job id is deliberately NOT durable. It is idempotent
151
222
  * and cheap, so a coordinator restart mid-seal simply re-runs the whole