@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.
- package/dist/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +10 -7
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +50 -12
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +50 -12
- package/dist/builtins/backup-orchestrator/write-targets.d.ts +63 -0
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-bindings-store.d.ts +56 -10
- package/dist/builtins/device-manager/device-manager.addon.d.ts +11 -0
- package/dist/builtins/device-manager/device-manager.addon.js +216 -81
- package/dist/builtins/device-manager/device-manager.addon.mjs +216 -81
- package/dist/builtins/device-manager/device-meta-actions.d.ts +9 -3
- package/dist/builtins/device-manager/device-meta-store.d.ts +32 -5
- package/dist/builtins/device-manager/device-queries.d.ts +7 -3
- package/dist/builtins/device-manager/write-lock.d.ts +17 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/filter-compiler.d.ts +41 -2
- package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +15 -7
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/storage-orchestrator/storage-migration-coordinator.d.ts +3 -2
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +63 -8
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +63 -8
- package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +19 -0
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-DLh4_rn3.mjs → dist-D4xNBlJM.mjs} +383 -214
- package/dist/{dist-Ck2jkBZk.js → dist-Keu5TDO7.js} +383 -214
- package/dist/index.js +29 -7
- package/dist/index.mjs +29 -8
- package/dist/kernel/index.d.ts +2 -2
- package/dist/kernel/lifecycle/lifecycle-job-engine.d.ts +13 -0
- package/dist/kernel/moleculer/cap-usage-registry.d.ts +132 -7
- package/dist/kernel/transport/local-child-registry.d.ts +57 -0
- package/dist/{manifest-system-deps-tZZYG3RR.mjs → manifest-system-deps-CiCuev2R.mjs} +271 -77
- package/dist/{manifest-system-deps-8boi90D9.js → manifest-system-deps-DYv4ZPo2.js} +276 -76
- package/dist/{retired-settings-keys-Dp_CyuCW.js → retired-settings-keys-BBohF-eC.js} +1 -1
- package/dist/{retired-settings-keys-Bi0ZKBjb.mjs → retired-settings-keys-COQMNlOJ.mjs} +1 -1
- 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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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
|
|
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
|
-
* `
|
|
214
|
-
*
|
|
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
|
-
* `
|
|
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.
|
|
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
|
-
|
|
324
|
+
explainQueryPlan(sql: string, params: readonly unknown[]): readonly string[];
|
|
317
325
|
/**
|
|
318
326
|
* Run one synchronous statement under the profiler.
|
|
319
327
|
*
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
|
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-
|
|
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
|
|
335
|
-
if (mode === "nonBlocking") throw new Error(
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
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-
|
|
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
|
|
328
|
-
if (mode === "nonBlocking") throw new Error(
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|