camstack 1.2.65 → 1.2.66
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.
|
@@ -23633,9 +23633,9 @@ var require_zod = __commonJS({
|
|
|
23633
23633
|
}
|
|
23634
23634
|
});
|
|
23635
23635
|
|
|
23636
|
-
// ../system/dist/dist-
|
|
23637
|
-
var
|
|
23638
|
-
"../system/dist/dist-
|
|
23636
|
+
// ../system/dist/dist-CcvXUhHK.js
|
|
23637
|
+
var require_dist_CcvXUhHK = __commonJS({
|
|
23638
|
+
"../system/dist/dist-CcvXUhHK.js"(exports) {
|
|
23639
23639
|
"use strict";
|
|
23640
23640
|
var zod = require_zod();
|
|
23641
23641
|
var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
|
|
@@ -26259,6 +26259,21 @@ var require_dist_Ck2jkBZk = __commonJS({
|
|
|
26259
26259
|
bytesMoved: zod.z.number().int(),
|
|
26260
26260
|
/** Total files discovered up front; null while (or when) unknown. */
|
|
26261
26261
|
filesTotal: zod.z.number().int().nullable(),
|
|
26262
|
+
/**
|
|
26263
|
+
* Rows this run CORRECTED while moving them — a durable mutation the move
|
|
26264
|
+
* made that nobody asked for, so it is reported where the operator reads the
|
|
26265
|
+
* job rather than only in a log line.
|
|
26266
|
+
*
|
|
26267
|
+
* A footage segment records its byte count in its own NAME, and the durable
|
|
26268
|
+
* hour row derives its aggregates from those names. A file that does not
|
|
26269
|
+
* match its name therefore makes the ledger's sums — and with them quota and
|
|
26270
|
+
* pressure eviction — wrong by the difference, and only a rename can fix it.
|
|
26271
|
+
* On 2026-08-30 one such row also stalled a 110 749-file drain permanently.
|
|
26272
|
+
*
|
|
26273
|
+
* Absent on lanes where the question has no meaning: a media blob's size is
|
|
26274
|
+
* in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
|
|
26275
|
+
*/
|
|
26276
|
+
rowsReconciled: zod.z.number().int().nonnegative().optional(),
|
|
26262
26277
|
startedAt: zod.z.number(),
|
|
26263
26278
|
finishedAt: zod.z.number().nullable(),
|
|
26264
26279
|
error: zod.z.string().nullable()
|
|
@@ -26301,11 +26316,18 @@ var require_dist_Ck2jkBZk = __commonJS({
|
|
|
26301
26316
|
/** Omitted = `move`, the pre-existing behaviour. */
|
|
26302
26317
|
mode: MediaRelocateModeSchema.optional()
|
|
26303
26318
|
});
|
|
26304
|
-
var
|
|
26305
|
-
|
|
26306
|
-
|
|
26307
|
-
total: zod.z.number().int().nonnegative()
|
|
26319
|
+
var UnstampedRowsSchema = zod.z.object({
|
|
26320
|
+
present: zod.z.boolean(),
|
|
26321
|
+
rows: zod.z.number().int().nonnegative().nullable()
|
|
26308
26322
|
});
|
|
26323
|
+
var UnstampedEventMediaCountSchema = zod.z.object({
|
|
26324
|
+
media: UnstampedRowsSchema,
|
|
26325
|
+
retrainFrames: UnstampedRowsSchema,
|
|
26326
|
+
/** True when EITHER collection holds one. The refusal reads this. */
|
|
26327
|
+
anyPresent: zod.z.boolean(),
|
|
26328
|
+
/** Sum across both, or `null` when either lane could not be counted. */
|
|
26329
|
+
total: zod.z.number().int().nonnegative().nullable()
|
|
26330
|
+
}).nullable();
|
|
26309
26331
|
var StorageMigrationMediaMoveInputSchema = RelocateMediaInputSchema.extend({ leaseId: zod.z.string().min(1) });
|
|
26310
26332
|
var StorageMigrationClassSchema = zod.z.enum([
|
|
26311
26333
|
"recordings",
|
|
@@ -26352,6 +26374,10 @@ var require_dist_Ck2jkBZk = __commonJS({
|
|
|
26352
26374
|
/** The archive census — the **M** of "N of M" (D295). `null` = unknowable. */
|
|
26353
26375
|
filesTotal: zod.z.number().int().nonnegative().nullable(),
|
|
26354
26376
|
bytesMoved: zod.z.number().int().nonnegative(),
|
|
26377
|
+
/** Rows the mover corrected while moving them — see `RelocateJob`. Absent on
|
|
26378
|
+
* a lane that cannot reconcile. A migration that silently rewrote durable
|
|
26379
|
+
* rows would be the same failure as one that silently skipped them. */
|
|
26380
|
+
rowsReconciled: zod.z.number().int().nonnegative().optional(),
|
|
26355
26381
|
/** The MOVER's start, not the migration's: a drain restarted after an addon
|
|
26356
26382
|
* crash gets a new mover, and a rate computed from the migration's start
|
|
26357
26383
|
* would silently average in the time nothing was running. */
|
|
@@ -31507,6 +31533,15 @@ var require_dist_Ck2jkBZk = __commonJS({
|
|
|
31507
31533
|
* calls are sync. Bindings change rarely (only on wrapper toggle or
|
|
31508
31534
|
* device add/remove) — clients invalidate via the
|
|
31509
31535
|
* `capability.binding-changed` event.
|
|
31536
|
+
*
|
|
31537
|
+
* "A single round-trip" describes the CLIENT's side and used not to
|
|
31538
|
+
* describe the server's: until 2026-08-30 the resolver read the persisted
|
|
31539
|
+
* wrapper activations once per device, so answering this cost one
|
|
31540
|
+
* settings-door RPC per device — 1 020 on the live 1 019-device hub, and
|
|
31541
|
+
* it did not return in 240 s against `SystemMirror.init`'s 15 s budget.
|
|
31542
|
+
* The server side is now two reads for the whole fleet. Anything PERIODIC
|
|
31543
|
+
* still belongs on `getBindings` / `getBindingsBatch` (D12); this remains
|
|
31544
|
+
* a warm seed.
|
|
31510
31545
|
*/
|
|
31511
31546
|
getAllBindings: method(zod.z.object({}), zod.z.array(DeviceBindingsForDeviceSchema)),
|
|
31512
31547
|
/**
|
|
@@ -37480,6 +37515,17 @@ var require_dist_Ck2jkBZk = __commonJS({
|
|
|
37480
37515
|
* happens to stamp it. This count is what the migration planner's
|
|
37481
37516
|
* non-blocking gate reads; `relocateMedia({ mode: 'seal' })` is what drives
|
|
37482
37517
|
* it to zero.
|
|
37518
|
+
*
|
|
37519
|
+
* TWO indexed statements per collection, not a walk. It used to page the
|
|
37520
|
+
* whole collection at 200 rows per RPC ordered by an unindexed column, so
|
|
37521
|
+
* on the live hub — 1 254 576 rows — it hit the 60 s RPC deadline every
|
|
37522
|
+
* time it was called, and the migration it gates could never start. The
|
|
37523
|
+
* cheap question (`present`: is there at least one) is asked first and
|
|
37524
|
+
* separately from the expensive one (`rows`), because only the first has
|
|
37525
|
+
* to be answerable for the gate to do its job.
|
|
37526
|
+
*
|
|
37527
|
+
* **`null` is "not measurable", never zero** — at either level. An
|
|
37528
|
+
* unreadable collection must not read as a sealed one.
|
|
37483
37529
|
*/
|
|
37484
37530
|
countUnstampedEventMedia: method(zod.z.object({}), UnstampedEventMediaCountSchema, { auth: "admin" }),
|
|
37485
37531
|
/**
|
|
@@ -59066,7 +59112,7 @@ var require_alerts_addon = __commonJS({
|
|
|
59066
59112
|
[Symbol.toStringTag]: { value: "Module" }
|
|
59067
59113
|
});
|
|
59068
59114
|
require_chunk_Cek0wNdY();
|
|
59069
|
-
var require_dist10 =
|
|
59115
|
+
var require_dist10 = require_dist_CcvXUhHK();
|
|
59070
59116
|
function selectExpired(alerts, cutoffMs) {
|
|
59071
59117
|
return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
|
|
59072
59118
|
}
|
|
@@ -59885,7 +59931,7 @@ var require_console_logging = __commonJS({
|
|
|
59885
59931
|
[Symbol.toStringTag]: { value: "Module" }
|
|
59886
59932
|
});
|
|
59887
59933
|
require_chunk_Cek0wNdY();
|
|
59888
|
-
var require_dist10 =
|
|
59934
|
+
var require_dist10 = require_dist_CcvXUhHK();
|
|
59889
59935
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
59890
59936
|
var LEVEL_RANK = {
|
|
59891
59937
|
debug: 0,
|
|
@@ -59979,7 +60025,7 @@ var require_core_blocks_addon = __commonJS({
|
|
|
59979
60025
|
"use strict";
|
|
59980
60026
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
59981
60027
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
59982
|
-
var require_dist10 =
|
|
60028
|
+
var require_dist10 = require_dist_CcvXUhHK();
|
|
59983
60029
|
var node_crypto = __require("crypto");
|
|
59984
60030
|
var node_fs_promises = __require("fs/promises");
|
|
59985
60031
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
@@ -60876,11 +60922,11 @@ var require_core_blocks = __commonJS({
|
|
|
60876
60922
|
}
|
|
60877
60923
|
});
|
|
60878
60924
|
|
|
60879
|
-
// ../system/dist/retired-settings-keys-
|
|
60880
|
-
var
|
|
60881
|
-
"../system/dist/retired-settings-keys-
|
|
60925
|
+
// ../system/dist/retired-settings-keys-DXZ2xe7C.js
|
|
60926
|
+
var require_retired_settings_keys_DXZ2xe7C = __commonJS({
|
|
60927
|
+
"../system/dist/retired-settings-keys-DXZ2xe7C.js"(exports) {
|
|
60882
60928
|
"use strict";
|
|
60883
|
-
var require_dist10 =
|
|
60929
|
+
var require_dist10 = require_dist_CcvXUhHK();
|
|
60884
60930
|
function settingsStoreIsAuthoritativeHere(env) {
|
|
60885
60931
|
const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
|
|
60886
60932
|
return raw === "" || raw === "hub";
|
|
@@ -63094,8 +63140,8 @@ var require_device_manager_addon = __commonJS({
|
|
|
63094
63140
|
[Symbol.toStringTag]: { value: "Module" }
|
|
63095
63141
|
});
|
|
63096
63142
|
require_chunk_Cek0wNdY();
|
|
63097
|
-
var require_dist10 =
|
|
63098
|
-
var require_retired_settings_keys =
|
|
63143
|
+
var require_dist10 = require_dist_CcvXUhHK();
|
|
63144
|
+
var require_retired_settings_keys = require_retired_settings_keys_DXZ2xe7C();
|
|
63099
63145
|
var node_crypto = __require("crypto");
|
|
63100
63146
|
var _camstack_types_node = require_node();
|
|
63101
63147
|
var JOB_HISTORY = 20;
|
|
@@ -63558,10 +63604,17 @@ var require_device_manager_addon = __commonJS({
|
|
|
63558
63604
|
const live = deps.ctx.kernel?.deviceRegistry?.getById(deviceId)?.type;
|
|
63559
63605
|
return typeof live === "string" && live.length > 0 ? live : void 0;
|
|
63560
63606
|
}
|
|
63561
|
-
async function resolveDevicePresence(deps, row, deviceId) {
|
|
63607
|
+
async function resolveDevicePresence(deps, row, deviceId, ledgerNonEmpty) {
|
|
63562
63608
|
if (deps.ctx.kernel?.deviceRegistry?.getById(deviceId)) return "present";
|
|
63563
63609
|
if (row !== null) return "present";
|
|
63564
|
-
return await
|
|
63610
|
+
return await ledgerNonEmpty() ? "absent" : "unknown";
|
|
63611
|
+
}
|
|
63612
|
+
function onceLedgerNonEmpty(deps) {
|
|
63613
|
+
let pending = null;
|
|
63614
|
+
return () => {
|
|
63615
|
+
if (pending === null) pending = deps.rows.count().then((n) => n > 0);
|
|
63616
|
+
return pending;
|
|
63617
|
+
};
|
|
63565
63618
|
}
|
|
63566
63619
|
function capAppliesToDeviceType(def, deviceType) {
|
|
63567
63620
|
if (deviceType === void 0) return true;
|
|
@@ -63570,14 +63623,17 @@ var require_device_manager_addon = __commonJS({
|
|
|
63570
63623
|
return declared.some((t) => t === deviceType);
|
|
63571
63624
|
}
|
|
63572
63625
|
async function getBindings(deps, input) {
|
|
63573
|
-
const row = await deps.rows.get(input.deviceId);
|
|
63574
|
-
return resolveBindingsForDevice(deps, input.deviceId, row
|
|
63626
|
+
const [row, store] = await Promise.all([deps.rows.get(input.deviceId), readBindingsStore(deps)]);
|
|
63627
|
+
return resolveBindingsForDevice(deps, input.deviceId, row, {
|
|
63628
|
+
store,
|
|
63629
|
+
ledgerNonEmpty: onceLedgerNonEmpty(deps)
|
|
63630
|
+
});
|
|
63575
63631
|
}
|
|
63576
|
-
async function resolveBindingsForDevice(deps, deviceId, row) {
|
|
63632
|
+
async function resolveBindingsForDevice(deps, deviceId, row, pass) {
|
|
63577
63633
|
const storeKey = String(deviceId);
|
|
63578
|
-
const perDevice =
|
|
63634
|
+
const perDevice = pass.store.deviceBindings[storeKey] ?? {};
|
|
63579
63635
|
const deviceType = resolveDeviceType(deps, row, deviceId);
|
|
63580
|
-
const presence = await resolveDevicePresence(deps, row, deviceId);
|
|
63636
|
+
const presence = await resolveDevicePresence(deps, row, deviceId, pass.ledgerNonEmpty);
|
|
63581
63637
|
const entries = [];
|
|
63582
63638
|
const seenCaps = /* @__PURE__ */ new Set();
|
|
63583
63639
|
const resolveRemote = (capName) => deps.remoteNativeCaps.get(deviceId)?.get(capName) ?? resolveRemoteNativeCapFromRegistry(deps, capName, deviceId);
|
|
@@ -63677,13 +63733,17 @@ var require_device_manager_addon = __commonJS({
|
|
|
63677
63733
|
}
|
|
63678
63734
|
async function getBindingsBatch(deps, input) {
|
|
63679
63735
|
const ids = [...new Set(input.deviceIds)];
|
|
63680
|
-
const rows = await deps.rows.getMany(ids);
|
|
63736
|
+
const [rows, store] = await Promise.all([deps.rows.getMany(ids), readBindingsStore(deps)]);
|
|
63737
|
+
const pass = {
|
|
63738
|
+
store,
|
|
63739
|
+
ledgerNonEmpty: onceLedgerNonEmpty(deps)
|
|
63740
|
+
};
|
|
63681
63741
|
const out = [];
|
|
63682
|
-
for (const deviceId of ids) out.push(await resolveBindingsForDevice(deps, deviceId, rows.get(deviceId) ?? null));
|
|
63742
|
+
for (const deviceId of ids) out.push(await resolveBindingsForDevice(deps, deviceId, rows.get(deviceId) ?? null, pass));
|
|
63683
63743
|
return out;
|
|
63684
63744
|
}
|
|
63685
63745
|
async function getAllBindings(deps) {
|
|
63686
|
-
const fleet = await deps.rows.listAll();
|
|
63746
|
+
const [fleet, store] = await Promise.all([deps.rows.listAll(), readBindingsStore(deps)]);
|
|
63687
63747
|
const rowById = /* @__PURE__ */ new Map();
|
|
63688
63748
|
const ids = /* @__PURE__ */ new Set();
|
|
63689
63749
|
for (const row of fleet) {
|
|
@@ -63696,8 +63756,12 @@ var require_device_manager_addon = __commonJS({
|
|
|
63696
63756
|
deps.ctx.logger.warn("getAllBindings found no devices \u2014 warm boot will see nothing", { meta: { hasRegistry: deps.ctx.kernel?.deviceRegistry !== void 0 } });
|
|
63697
63757
|
return [];
|
|
63698
63758
|
}
|
|
63759
|
+
const pass = {
|
|
63760
|
+
store,
|
|
63761
|
+
ledgerNonEmpty: onceLedgerNonEmpty(deps)
|
|
63762
|
+
};
|
|
63699
63763
|
const out = [];
|
|
63700
|
-
for (const deviceId of [...ids].sort((a, b) => a - b)) out.push(await resolveBindingsForDevice(deps, deviceId, rowById.get(deviceId) ?? null));
|
|
63764
|
+
for (const deviceId of [...ids].sort((a, b) => a - b)) out.push(await resolveBindingsForDevice(deps, deviceId, rowById.get(deviceId) ?? null, pass));
|
|
63701
63765
|
return out;
|
|
63702
63766
|
}
|
|
63703
63767
|
async function lookupPersistedStableId(deps, deviceId) {
|
|
@@ -64257,7 +64321,12 @@ var require_device_manager_addon = __commonJS({
|
|
|
64257
64321
|
...sourceInfoGetDevice !== void 0 ? { sourceInfo: sourceInfoGetDevice } : {}
|
|
64258
64322
|
};
|
|
64259
64323
|
}
|
|
64260
|
-
|
|
64324
|
+
function persistedChildIds(childRows, liveChildren) {
|
|
64325
|
+
const live = /* @__PURE__ */ new Set();
|
|
64326
|
+
for (const device of liveChildren) live.add(device.id);
|
|
64327
|
+
return childRows.filter((row) => !live.has(row.meta.id)).map((row) => row.meta.id);
|
|
64328
|
+
}
|
|
64329
|
+
function projectChildren(pctx, parentDeviceId, ownerAddonId, childRows, liveChildren, configs) {
|
|
64261
64330
|
const results = [];
|
|
64262
64331
|
const seen = /* @__PURE__ */ new Set();
|
|
64263
64332
|
const rowById = /* @__PURE__ */ new Map();
|
|
@@ -64273,9 +64342,9 @@ var require_device_manager_addon = __commonJS({
|
|
|
64273
64342
|
const childStableId = m.stableId;
|
|
64274
64343
|
const key = String(m.id);
|
|
64275
64344
|
if (seen.has(key)) continue;
|
|
64276
|
-
const persistedConfig =
|
|
64345
|
+
const persistedConfig = configs.get(m.id) ?? {};
|
|
64277
64346
|
const metadata = row.metadata;
|
|
64278
|
-
const sourceInfoChild = buildSourceInfoFromConfig(persistedConfig
|
|
64347
|
+
const sourceInfoChild = buildSourceInfoFromConfig(persistedConfig, childStableId, ownerAddonId);
|
|
64279
64348
|
results.push({
|
|
64280
64349
|
id: m.id,
|
|
64281
64350
|
stableId: childStableId,
|
|
@@ -64290,7 +64359,7 @@ var require_device_manager_addon = __commonJS({
|
|
|
64290
64359
|
probed: pctx.host.resolveDeviceProbed(m.id),
|
|
64291
64360
|
features: persistedFeatures(m.features),
|
|
64292
64361
|
isCamera: false,
|
|
64293
|
-
config: persistedConfig
|
|
64362
|
+
config: persistedConfig,
|
|
64294
64363
|
metadata,
|
|
64295
64364
|
...m.integrationId !== void 0 ? { integrationId: m.integrationId } : {},
|
|
64296
64365
|
...m.linkDeviceId !== void 0 ? { linkDeviceId: m.linkDeviceId } : {},
|
|
@@ -64317,7 +64386,8 @@ var require_device_manager_addon = __commonJS({
|
|
|
64317
64386
|
}
|
|
64318
64387
|
const childRows = await pctx.metaStore.rows.listByParent(parentDeviceId);
|
|
64319
64388
|
const liveChildren = pctx.registry?.getChildren(parentDeviceId) ?? [];
|
|
64320
|
-
|
|
64389
|
+
const configs = await readDeviceConfigs(pctx, persistedChildIds(childRows, liveChildren));
|
|
64390
|
+
return [...projectChildren(pctx, parentDeviceId, ownerAddonId, childRows, liveChildren, configs)];
|
|
64321
64391
|
}
|
|
64322
64392
|
async function getChildrenBatch(pctx, input) {
|
|
64323
64393
|
const parentIds = [...new Set(input.parentDeviceIds)];
|
|
@@ -64345,11 +64415,14 @@ var require_device_manager_addon = __commonJS({
|
|
|
64345
64415
|
if (bucket === void 0) liveByParent.set(parentId, [device]);
|
|
64346
64416
|
else bucket.push(device);
|
|
64347
64417
|
}
|
|
64418
|
+
const configIds = [];
|
|
64419
|
+
for (const parentId of owners.keys()) configIds.push(...persistedChildIds(rowsByParent.get(parentId) ?? [], liveByParent.get(parentId) ?? []));
|
|
64420
|
+
const configs = await readDeviceConfigs(pctx, configIds);
|
|
64348
64421
|
for (const [parentId, ownerAddonId] of owners) {
|
|
64349
64422
|
const childRows = rowsByParent.get(parentId) ?? [];
|
|
64350
64423
|
const liveChildren = liveByParent.get(parentId) ?? [];
|
|
64351
64424
|
if (childRows.length === 0 && liveChildren.length === 0) continue;
|
|
64352
|
-
const projected =
|
|
64425
|
+
const projected = projectChildren(pctx, parentId, ownerAddonId, childRows, liveChildren, configs);
|
|
64353
64426
|
if (projected.length > 0) out[String(parentId)] = [...projected];
|
|
64354
64427
|
}
|
|
64355
64428
|
return out;
|
|
@@ -65391,18 +65464,20 @@ var require_device_manager_addon = __commonJS({
|
|
|
65391
65464
|
}
|
|
65392
65465
|
async function setWrapperActive(deps, input) {
|
|
65393
65466
|
const storeKey = String(input.deviceId);
|
|
65394
|
-
|
|
65395
|
-
|
|
65396
|
-
|
|
65397
|
-
|
|
65398
|
-
|
|
65399
|
-
|
|
65400
|
-
|
|
65401
|
-
|
|
65402
|
-
|
|
65403
|
-
|
|
65404
|
-
|
|
65405
|
-
|
|
65467
|
+
await deps.bindingsDeps.withAddonStoreWriteLock(async () => {
|
|
65468
|
+
const store = await readBindingsStore(deps.bindingsDeps);
|
|
65469
|
+
const perDevice = { ...store.deviceBindings[storeKey] };
|
|
65470
|
+
if (input.active) perDevice[input.capName] = { wrapperAddonId: input.wrapperAddonId };
|
|
65471
|
+
else perDevice[input.capName] = { wrapperAddonId: null };
|
|
65472
|
+
const nextDeviceBindings = Object.keys(perDevice).length > 0 ? {
|
|
65473
|
+
...store.deviceBindings,
|
|
65474
|
+
[storeKey]: perDevice
|
|
65475
|
+
} : (() => {
|
|
65476
|
+
const { [storeKey]: _drop, ...rest } = store.deviceBindings;
|
|
65477
|
+
return rest;
|
|
65478
|
+
})();
|
|
65479
|
+
await writeBindingsStore(deps.bindingsDeps, { deviceBindings: nextDeviceBindings });
|
|
65480
|
+
});
|
|
65406
65481
|
deps.ctx.eventBus.emit({
|
|
65407
65482
|
id: (0, node_crypto.randomUUID)(),
|
|
65408
65483
|
timestamp: /* @__PURE__ */ new Date(),
|
|
@@ -65681,12 +65756,13 @@ var require_device_manager_addon = __commonJS({
|
|
|
65681
65756
|
});
|
|
65682
65757
|
await pctx.settings.clearDeviceStore(deviceId);
|
|
65683
65758
|
await pctx.settings.clearDeviceRuntimeState(deviceId);
|
|
65684
|
-
const bindingsStore = await readBindingsStore(pctx.bindingsDeps);
|
|
65685
65759
|
const bindingKey = String(deviceId);
|
|
65686
|
-
|
|
65760
|
+
await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
|
|
65761
|
+
const bindingsStore = await readBindingsStore(pctx.bindingsDeps);
|
|
65762
|
+
if (!bindingsStore.deviceBindings[bindingKey]) return;
|
|
65687
65763
|
const { [bindingKey]: _removedBindings, ...restBindings } = bindingsStore.deviceBindings;
|
|
65688
65764
|
await writeBindingsStore(pctx.bindingsDeps, { deviceBindings: restBindings });
|
|
65689
|
-
}
|
|
65765
|
+
});
|
|
65690
65766
|
pctx.host.remoteNativeCaps.delete(deviceId);
|
|
65691
65767
|
pctx.host.capabilityRegistry?.unregisterAllNativeForDevice(deviceId);
|
|
65692
65768
|
pctx.metaStore.idToAddonId.delete(deviceId);
|
|
@@ -66028,7 +66104,9 @@ var require_device_manager_addon = __commonJS({
|
|
|
66028
66104
|
...def,
|
|
66029
66105
|
unit: require_dist10.normalizeUnit(def.unit) ?? def.unit
|
|
66030
66106
|
} : def]));
|
|
66031
|
-
await pctx.
|
|
66107
|
+
await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
|
|
66108
|
+
await pctx.settings.writeAddonStore({ roleDisplayDefaults: normalized });
|
|
66109
|
+
});
|
|
66032
66110
|
}
|
|
66033
66111
|
async function applyInitialMeta(pctx, input) {
|
|
66034
66112
|
const { deviceId, name, location, type, integrationId, linkDeviceId, role } = input;
|
|
@@ -66171,16 +66249,21 @@ var require_device_manager_addon = __commonJS({
|
|
|
66171
66249
|
async function addLocation(pctx, input) {
|
|
66172
66250
|
const trimmed = input.name.trim();
|
|
66173
66251
|
if (trimmed.length === 0) throw new Error("[device-manager] addLocation: name must be non-empty");
|
|
66174
|
-
|
|
66175
|
-
|
|
66176
|
-
|
|
66252
|
+
await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
|
|
66253
|
+
const current = (await pctx.metaStore.readStore()).locations ?? [];
|
|
66254
|
+
if (current.some((l) => l.toLowerCase() === trimmed.toLowerCase())) return;
|
|
66255
|
+
await pctx.settings.writeAddonStore({ locations: [...current, trimmed] });
|
|
66256
|
+
});
|
|
66177
66257
|
}
|
|
66178
66258
|
async function removeLocation(pctx, input) {
|
|
66179
66259
|
const trimmed = input.name.trim();
|
|
66180
66260
|
if (trimmed.length === 0) return;
|
|
66181
|
-
|
|
66182
|
-
|
|
66183
|
-
|
|
66261
|
+
await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
|
|
66262
|
+
const current = (await pctx.metaStore.readStore()).locations ?? [];
|
|
66263
|
+
const remaining = current.filter((l) => l.toLowerCase() !== trimmed.toLowerCase());
|
|
66264
|
+
if (remaining.length === current.length) return;
|
|
66265
|
+
await pctx.settings.writeAddonStore({ locations: remaining });
|
|
66266
|
+
});
|
|
66184
66267
|
if (input.cascade !== true) return;
|
|
66185
66268
|
const cleared = await pctx.metaStore.withMetaWriteLock(async () => {
|
|
66186
66269
|
const out = [];
|
|
@@ -66221,23 +66304,40 @@ var require_device_manager_addon = __commonJS({
|
|
|
66221
66304
|
function isRoleDisplayDefaults(value) {
|
|
66222
66305
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
66223
66306
|
}
|
|
66307
|
+
function createWriteLock() {
|
|
66308
|
+
let chain = Promise.resolve();
|
|
66309
|
+
return async (fn) => {
|
|
66310
|
+
const previous = chain;
|
|
66311
|
+
let release = () => {
|
|
66312
|
+
};
|
|
66313
|
+
chain = new Promise((resolve) => {
|
|
66314
|
+
release = resolve;
|
|
66315
|
+
});
|
|
66316
|
+
try {
|
|
66317
|
+
await previous.catch(() => {
|
|
66318
|
+
});
|
|
66319
|
+
return await fn();
|
|
66320
|
+
} finally {
|
|
66321
|
+
release();
|
|
66322
|
+
}
|
|
66323
|
+
};
|
|
66324
|
+
}
|
|
66224
66325
|
var DeviceMetaStore = class {
|
|
66225
66326
|
settings;
|
|
66226
66327
|
registry;
|
|
66227
66328
|
rows;
|
|
66329
|
+
withAddonStoreWriteLock;
|
|
66228
66330
|
/** Synchronous ownership cache, keyed by NUMERIC deviceId → owning addonId.
|
|
66229
66331
|
* The persisted row store is authoritative but reads are async; hub-side
|
|
66230
66332
|
* callers (e.g. `CapabilityRegistry.getNativeProvider` fallback) need
|
|
66231
66333
|
* ownership without awaiting. Kept in sync with every register/remove and
|
|
66232
66334
|
* warmed from persistence on boot. */
|
|
66233
66335
|
idToAddonId = /* @__PURE__ */ new Map();
|
|
66234
|
-
|
|
66235
|
-
* chain (see `withMetaWriteLock`). Per-instance state. */
|
|
66236
|
-
metaWriteChain = Promise.resolve();
|
|
66237
|
-
constructor(settings, registry, rows) {
|
|
66336
|
+
constructor(settings, registry, rows, withAddonStoreWriteLock = createWriteLock()) {
|
|
66238
66337
|
this.settings = settings;
|
|
66239
66338
|
this.registry = registry;
|
|
66240
66339
|
this.rows = rows;
|
|
66340
|
+
this.withAddonStoreWriteLock = withAddonStoreWriteLock;
|
|
66241
66341
|
}
|
|
66242
66342
|
/** The read currently in flight, or null. Never a settled value — see
|
|
66243
66343
|
* {@link readStore}. */
|
|
@@ -66268,22 +66368,7 @@ var require_device_manager_addon = __commonJS({
|
|
|
66268
66368
|
this.inFlightRead = read;
|
|
66269
66369
|
return read;
|
|
66270
66370
|
};
|
|
66271
|
-
withMetaWriteLock =
|
|
66272
|
-
const previous = this.metaWriteChain;
|
|
66273
|
-
let release = () => {
|
|
66274
|
-
};
|
|
66275
|
-
const next = new Promise((resolve) => {
|
|
66276
|
-
release = resolve;
|
|
66277
|
-
});
|
|
66278
|
-
this.metaWriteChain = next;
|
|
66279
|
-
try {
|
|
66280
|
-
await previous.catch(() => {
|
|
66281
|
-
});
|
|
66282
|
-
return await fn();
|
|
66283
|
-
} finally {
|
|
66284
|
-
release();
|
|
66285
|
-
}
|
|
66286
|
-
};
|
|
66371
|
+
withMetaWriteLock = createWriteLock();
|
|
66287
66372
|
/** The whole persisted row for one device, or `null`. */
|
|
66288
66373
|
getRow = async (deviceId) => this.rows.get(deviceId);
|
|
66289
66374
|
/**
|
|
@@ -66317,11 +66402,24 @@ var require_device_manager_addon = __commonJS({
|
|
|
66317
66402
|
ids.delete(parentId);
|
|
66318
66403
|
return [...ids];
|
|
66319
66404
|
};
|
|
66320
|
-
|
|
66405
|
+
/**
|
|
66406
|
+
* Mint the next numeric device id.
|
|
66407
|
+
*
|
|
66408
|
+
* A read-modify-write of `nextDeviceId`, and it must hold the addon-store
|
|
66409
|
+
* lock for the whole of it. Serialising the WRITES is not enough and never
|
|
66410
|
+
* was: two callers that both read `N` both write `N + 1` and both return
|
|
66411
|
+
* `N`, so two devices receive the same id. (`writeAddonStore` is itself
|
|
66412
|
+
* read-modify-write over the addon's whole key range, so an unlocked bump
|
|
66413
|
+
* can also be reverted wholesale by a concurrent `setWrapperActive`.)
|
|
66414
|
+
*
|
|
66415
|
+
* Callers hold {@link withMetaWriteLock} — this is the one nesting, and it
|
|
66416
|
+
* only ever goes meta ⊃ addon-store.
|
|
66417
|
+
*/
|
|
66418
|
+
allocateNextDeviceId = async () => this.withAddonStoreWriteLock(async () => {
|
|
66321
66419
|
const current = (await this.readStore()).nextDeviceId ?? 1;
|
|
66322
66420
|
await this.settings.writeAddonStore({ nextDeviceId: current + 1 });
|
|
66323
66421
|
return current;
|
|
66324
|
-
};
|
|
66422
|
+
});
|
|
66325
66423
|
};
|
|
66326
66424
|
var CENSUS_STACK_FRAMES = 3;
|
|
66327
66425
|
function siteFromStack(stack) {
|
|
@@ -67515,6 +67613,17 @@ var require_device_manager_addon = __commonJS({
|
|
|
67515
67613
|
* it through {@link bindingsDeps} or the `ProviderContext`.
|
|
67516
67614
|
*/
|
|
67517
67615
|
deviceRows = null;
|
|
67616
|
+
/**
|
|
67617
|
+
* The ONE lock ordering every read-modify-write of this addon's
|
|
67618
|
+
* `addon-settings` key range (`deviceBindings`, `locations`, `nextDeviceId`,
|
|
67619
|
+
* `roleDisplayDefaults`).
|
|
67620
|
+
*
|
|
67621
|
+
* It lives on the addon rather than on `DeviceMetaStore` because the writers
|
|
67622
|
+
* do not all go through that class — `setWrapperActive` and `removeDevice`'s
|
|
67623
|
+
* bindings purge reach the store through `BindingsDeps` — and two locks over
|
|
67624
|
+
* one key range is the same defect one level up. Handed to both.
|
|
67625
|
+
*/
|
|
67626
|
+
addonStoreWriteLock = createWriteLock();
|
|
67518
67627
|
/** Build the dependency context the extracted binding resolvers consume. */
|
|
67519
67628
|
get bindingsDeps() {
|
|
67520
67629
|
const rows = this.deviceRows;
|
|
@@ -67523,7 +67632,8 @@ var require_device_manager_addon = __commonJS({
|
|
|
67523
67632
|
ctx: this.ctx,
|
|
67524
67633
|
capabilityRegistry: this.capabilityRegistry,
|
|
67525
67634
|
remoteNativeCaps: this.remoteNativeCaps,
|
|
67526
|
-
rows
|
|
67635
|
+
rows,
|
|
67636
|
+
withAddonStoreWriteLock: this.addonStoreWriteLock
|
|
67527
67637
|
};
|
|
67528
67638
|
}
|
|
67529
67639
|
async getBindings(input) {
|
|
@@ -67592,7 +67702,7 @@ var require_device_manager_addon = __commonJS({
|
|
|
67592
67702
|
} catch (err) {
|
|
67593
67703
|
this.ctx.logger.warn("retired-row purge failed", { meta: { error: require_dist10.errMsg(err) } });
|
|
67594
67704
|
}
|
|
67595
|
-
const metaStore = new DeviceMetaStore(settings, registry, deviceRows);
|
|
67705
|
+
const metaStore = new DeviceMetaStore(settings, registry, deviceRows, this.addonStoreWriteLock);
|
|
67596
67706
|
this.stateMirrorImpl = new DeviceStateMirror(this.ctx);
|
|
67597
67707
|
const stateMirror = this.stateMirrorImpl;
|
|
67598
67708
|
const resolvePersistedById = metaStore.resolvePersistedById;
|
|
@@ -67844,7 +67954,7 @@ var require_hub_forwarder = __commonJS({
|
|
|
67844
67954
|
[Symbol.toStringTag]: { value: "Module" }
|
|
67845
67955
|
});
|
|
67846
67956
|
require_chunk_Cek0wNdY();
|
|
67847
|
-
var require_dist10 =
|
|
67957
|
+
var require_dist10 = require_dist_CcvXUhHK();
|
|
67848
67958
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
67849
67959
|
var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
|
|
67850
67960
|
var HubForwarderDestination = class {
|
|
@@ -67981,7 +68091,7 @@ var require_liveness_monitor_addon = __commonJS({
|
|
|
67981
68091
|
"use strict";
|
|
67982
68092
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
67983
68093
|
require_chunk_Cek0wNdY();
|
|
67984
|
-
var require_dist10 =
|
|
68094
|
+
var require_dist10 = require_dist_CcvXUhHK();
|
|
67985
68095
|
var NO_DEVICES = "liveness:no-devices";
|
|
67986
68096
|
var ALL_OFFLINE = "liveness:all-devices-offline";
|
|
67987
68097
|
var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
|
|
@@ -68171,7 +68281,7 @@ var require_local_auth_addon = __commonJS({
|
|
|
68171
68281
|
[Symbol.toStringTag]: { value: "Module" }
|
|
68172
68282
|
});
|
|
68173
68283
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
68174
|
-
var require_dist10 =
|
|
68284
|
+
var require_dist10 = require_dist_CcvXUhHK();
|
|
68175
68285
|
var node_crypto = __require("crypto");
|
|
68176
68286
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
68177
68287
|
var crypto$1 = __require("crypto");
|
|
@@ -75984,7 +76094,7 @@ var require_loki_logging = __commonJS({
|
|
|
75984
76094
|
[Symbol.toStringTag]: { value: "Module" }
|
|
75985
76095
|
});
|
|
75986
76096
|
require_chunk_Cek0wNdY();
|
|
75987
|
-
var require_dist10 =
|
|
76097
|
+
var require_dist10 = require_dist_CcvXUhHK();
|
|
75988
76098
|
function sanitizeLabelName(raw) {
|
|
75989
76099
|
const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
|
|
75990
76100
|
return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
|
|
@@ -76549,7 +76659,7 @@ var require_native_metrics_addon = __commonJS({
|
|
|
76549
76659
|
[Symbol.toStringTag]: { value: "Module" }
|
|
76550
76660
|
});
|
|
76551
76661
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
76552
|
-
var require_dist10 =
|
|
76662
|
+
var require_dist10 = require_dist_CcvXUhHK();
|
|
76553
76663
|
var node_fs_promises = __require("fs/promises");
|
|
76554
76664
|
var node_child_process = __require("child_process");
|
|
76555
76665
|
var node_util = __require("util");
|
|
@@ -79171,7 +79281,7 @@ var require_filesystem_storage_addon = __commonJS({
|
|
|
79171
79281
|
[Symbol.toStringTag]: { value: "Module" }
|
|
79172
79282
|
});
|
|
79173
79283
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
79174
|
-
var require_dist10 =
|
|
79284
|
+
var require_dist10 = require_dist_CcvXUhHK();
|
|
79175
79285
|
var node_crypto = __require("crypto");
|
|
79176
79286
|
var node_fs_promises = __require("fs/promises");
|
|
79177
79287
|
var node_path = __require("path");
|
|
@@ -80287,8 +80397,8 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
80287
80397
|
[Symbol.toStringTag]: { value: "Module" }
|
|
80288
80398
|
});
|
|
80289
80399
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
80290
|
-
var require_dist10 =
|
|
80291
|
-
var require_retired_settings_keys =
|
|
80400
|
+
var require_dist10 = require_dist_CcvXUhHK();
|
|
80401
|
+
var require_retired_settings_keys = require_retired_settings_keys_DXZ2xe7C();
|
|
80292
80402
|
var node_crypto = __require("crypto");
|
|
80293
80403
|
var node_fs = __require("fs");
|
|
80294
80404
|
var node_module = __require("module");
|
|
@@ -80619,11 +80729,16 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
80619
80729
|
const resolve = (field) => {
|
|
80620
80730
|
const expr = fieldExprFor(field, shape);
|
|
80621
80731
|
if (expr === null && mode === "mutate") throw new UnsafeFilterError(`filter refers to "${field}", which this collection cannot express \u2014 refusing to run a bulk mutation with a dropped predicate`);
|
|
80732
|
+
if (expr === null && mode === "measure") throw new UnsafeFilterError(`filter refers to "${field}", which this collection cannot express \u2014 refusing to answer with a number counted over a different question`);
|
|
80622
80733
|
return expr;
|
|
80623
80734
|
};
|
|
80624
80735
|
for (const [field, value] of Object.entries(filter?.where ?? {})) {
|
|
80625
80736
|
const expr = resolve(field);
|
|
80626
80737
|
if (expr === null) continue;
|
|
80738
|
+
if (value === null) {
|
|
80739
|
+
clauses.push(`${expr} IS NULL`);
|
|
80740
|
+
continue;
|
|
80741
|
+
}
|
|
80627
80742
|
clauses.push(`${expr} = ?`);
|
|
80628
80743
|
params.push(serialize2(value));
|
|
80629
80744
|
}
|
|
@@ -80646,6 +80761,10 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
80646
80761
|
for (const [field, value] of Object.entries(filter?.whereNot ?? {})) {
|
|
80647
80762
|
const expr = resolve(field);
|
|
80648
80763
|
if (expr === null) continue;
|
|
80764
|
+
if (value === null) {
|
|
80765
|
+
clauses.push(`${expr} IS NOT NULL`);
|
|
80766
|
+
continue;
|
|
80767
|
+
}
|
|
80649
80768
|
clauses.push(`(${expr} IS NULL OR ${expr} != ?)`);
|
|
80650
80769
|
params.push(serialize2(value));
|
|
80651
80770
|
}
|
|
@@ -81158,13 +81277,17 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
81158
81277
|
* (visibility, kind, device, time window, expiry) — so `total` counted rows
|
|
81159
81278
|
* the page could never show, including other users'.
|
|
81160
81279
|
*
|
|
81161
|
-
* `
|
|
81162
|
-
*
|
|
81280
|
+
* `measure` mode. `count` compiles the same predicates `query` does — the
|
|
81281
|
+
* two must never disagree about WHICH rows they are talking about — but it
|
|
81282
|
+
* refuses a predicate it cannot express rather than skipping it. `query`
|
|
81283
|
+
* hands back rows and a caller can see the filter did not bite; a count
|
|
81284
|
+
* hands back a number, and the whole collection and the intended subset are
|
|
81285
|
+
* the same shape.
|
|
81163
81286
|
*/
|
|
81164
81287
|
async count({ namespace, collection, filter }) {
|
|
81165
81288
|
const scoped = this.scopedName(namespace, collection);
|
|
81166
81289
|
const decl = this.requireDeclared(scoped);
|
|
81167
|
-
const { whereSql, params } = compileFilter$1(filter, this.shapeOf(decl), "
|
|
81290
|
+
const { whereSql, params } = compileFilter$1(filter, this.shapeOf(decl), "measure", (v) => this.serializeColumnValue(v));
|
|
81168
81291
|
const sql = `SELECT COUNT(*) AS cnt FROM "${scoped}"${whereSql}`;
|
|
81169
81292
|
return this.measured({
|
|
81170
81293
|
op: "count",
|
|
@@ -81182,8 +81305,10 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
81182
81305
|
* projection — an unresolvable field THROWS rather than being dropped, because
|
|
81183
81306
|
* a missing aggregate comes back as a number that looks real.
|
|
81184
81307
|
*
|
|
81185
|
-
* `
|
|
81186
|
-
* which rows it is talking about
|
|
81308
|
+
* `measure` mode on the filter, so this agrees with `query` and `count`
|
|
81309
|
+
* about which rows it is talking about — and, since 2026-08-30, refuses an
|
|
81310
|
+
* unresolvable PREDICATE for the same reason it already refused an
|
|
81311
|
+
* unresolvable FIELD. Both come back as a number that looks real.
|
|
81187
81312
|
*/
|
|
81188
81313
|
async aggregate({ namespace, collection, fields, filter }) {
|
|
81189
81314
|
const scoped = this.scopedName(namespace, collection);
|
|
@@ -81195,7 +81320,7 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
81195
81320
|
if (col === null) throw new UnsafeFilterError(`aggregate cannot read "${f.field}" on "${scoped}" \u2014 it is not a column of this collection, and answering without it would return a number that looks real`);
|
|
81196
81321
|
selects.push(`${f.op.toUpperCase()}(${col}) AS "a${i}"`);
|
|
81197
81322
|
});
|
|
81198
|
-
const { whereSql, params } = compileFilter$1(filter, shape, "
|
|
81323
|
+
const { whereSql, params } = compileFilter$1(filter, shape, "measure", (v) => this.serializeColumnValue(v));
|
|
81199
81324
|
const sql = `SELECT ${selects.join(", ")} FROM "${scoped}"${whereSql}`;
|
|
81200
81325
|
const row = this.measured({
|
|
81201
81326
|
op: "aggregate",
|
|
@@ -81219,7 +81344,7 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
81219
81344
|
const shape = this.shapeOf(decl);
|
|
81220
81345
|
const col = fieldExprFor(field, shape);
|
|
81221
81346
|
if (col === null) return [];
|
|
81222
|
-
const { whereSql: where, params } = compileFilter$1(filter, shape, "
|
|
81347
|
+
const { whereSql: where, params } = compileFilter$1(filter, shape, "measure", (v) => this.serializeColumnValue(v));
|
|
81223
81348
|
const sql = `SELECT ${`CAST((${col} - ?) / ? AS INTEGER)`} AS bucket, COUNT(*) AS count FROM "${scoped}"${where} GROUP BY bucket ORDER BY bucket`;
|
|
81224
81349
|
return this.measured({
|
|
81225
81350
|
op: "histogram",
|
|
@@ -81500,8 +81625,10 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
81500
81625
|
* connection whose `sqlite_stat1` visibility and temp schema match.
|
|
81501
81626
|
*
|
|
81502
81627
|
* `EXPLAIN QUERY PLAN` prepares and plans; it does not execute the statement,
|
|
81503
|
-
* so it costs no page reads of its own.
|
|
81504
|
-
* slow-call path, once per shape per window
|
|
81628
|
+
* so it costs no page reads of its own. In production it is called only from
|
|
81629
|
+
* the slow-call path, once per shape per window; it is public so a guard can
|
|
81630
|
+
* assert that a declared index is the one the planner actually picks — "we
|
|
81631
|
+
* added an index" and "the query uses it" are different claims.
|
|
81505
81632
|
*
|
|
81506
81633
|
* Never throws. A plan that cannot be taken (a statement the engine will no
|
|
81507
81634
|
* longer prepare, a closed handle mid-shutdown) is a missing ANSWER, not a
|
|
@@ -82650,7 +82777,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
82650
82777
|
[Symbol.toStringTag]: { value: "Module" }
|
|
82651
82778
|
});
|
|
82652
82779
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
82653
|
-
var require_dist10 =
|
|
82780
|
+
var require_dist10 = require_dist_CcvXUhHK();
|
|
82654
82781
|
var node_crypto = __require("crypto");
|
|
82655
82782
|
var node_fs_promises = __require("fs/promises");
|
|
82656
82783
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
@@ -82856,6 +82983,10 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
82856
82983
|
"recorder",
|
|
82857
82984
|
"pipeline"
|
|
82858
82985
|
];
|
|
82986
|
+
function describeUnstamped(lane) {
|
|
82987
|
+
if (!lane.present) return "0";
|
|
82988
|
+
return lane.rows === null ? "an unknown number of" : lane.rows.toString();
|
|
82989
|
+
}
|
|
82859
82990
|
var StorageMigrationCoordinator = class {
|
|
82860
82991
|
deps;
|
|
82861
82992
|
active = null;
|
|
@@ -82899,12 +83030,21 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
82899
83030
|
if (moves.length === 0) throw new Error("select at least one storage class");
|
|
82900
83031
|
if (moves.some((move) => move.storageClass === "eventMedia")) {
|
|
82901
83032
|
const unstamped = await this.deps.participants.analytics.countUnstamped();
|
|
82902
|
-
if (unstamped
|
|
82903
|
-
if (mode === "nonBlocking") throw new Error(
|
|
83033
|
+
if (unstamped === null) {
|
|
83034
|
+
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.");
|
|
83035
|
+
findings.push({
|
|
83036
|
+
code: "unstampedEventMediaRows",
|
|
83037
|
+
storageClass: "eventMedia",
|
|
83038
|
+
message: "the count of event-media rows carrying no locationId could not be taken \u2014 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."
|
|
83039
|
+
});
|
|
83040
|
+
} else if (unstamped.anyPresent) {
|
|
83041
|
+
const scale = unstamped.total === null ? "an unknown number of" : unstamped.total.toString();
|
|
83042
|
+
const perLane = `${describeUnstamped(unstamped.media)} media, ${describeUnstamped(unstamped.retrainFrames)} retrain frames`;
|
|
83043
|
+
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"}" }).`);
|
|
82904
83044
|
findings.push({
|
|
82905
83045
|
code: "unstampedEventMediaRows",
|
|
82906
83046
|
storageClass: "eventMedia",
|
|
82907
|
-
message: `${
|
|
83047
|
+
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.`
|
|
82908
83048
|
});
|
|
82909
83049
|
}
|
|
82910
83050
|
}
|
|
@@ -83153,6 +83293,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
83153
83293
|
const remaining = residue.filter((entry) => entry.storageClass === storageClass);
|
|
83154
83294
|
if (remaining.length === 0) throw new Error(`"${storageClass}" has nothing left outside its default location \u2014 there is nothing to drain. To CHANGE where it writes, use storageMigration.start with a new destination.`);
|
|
83155
83295
|
for (const entry of remaining) {
|
|
83296
|
+
if (this.deps.locations.getLocationById(entry.toLocationId)?.enabled === false) throw new Error(`The "${storageClass}" drain destination "${entry.toLocationId}" is DISABLED \u2014 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.`);
|
|
83156
83297
|
moves.push({
|
|
83157
83298
|
storageClass,
|
|
83158
83299
|
fromLocationId: entry.fromLocationId,
|
|
@@ -83404,6 +83545,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
83404
83545
|
filesMoved: status.filesMoved,
|
|
83405
83546
|
filesTotal: status.filesTotal,
|
|
83406
83547
|
bytesMoved: status.bytesMoved,
|
|
83548
|
+
...status.rowsReconciled === void 0 ? {} : { rowsReconciled: status.rowsReconciled },
|
|
83407
83549
|
startedAt: status.startedAt,
|
|
83408
83550
|
observedAt: this.deps.now()
|
|
83409
83551
|
};
|
|
@@ -83426,9 +83568,10 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
83426
83568
|
*
|
|
83427
83569
|
* The gate is the RE-COUNT, not the seal job's terminal state: a seal that
|
|
83428
83570
|
* failed some rows still finishes, and "finished" is not "there are none
|
|
83429
|
-
* left". A
|
|
83571
|
+
* left". A remaining row here fails the job while nothing has been paused
|
|
83430
83572
|
* and nothing has been repointed, which is the cheapest possible place to
|
|
83431
|
-
* discover it.
|
|
83573
|
+
* discover it. So does a re-count that could not be TAKEN: the gate opens on
|
|
83574
|
+
* a measured absence, and only on that.
|
|
83432
83575
|
*
|
|
83433
83576
|
* The seal's own mover job id is deliberately NOT durable. It is idempotent
|
|
83434
83577
|
* and cheap, so a coordinator restart mid-seal simply re-runs the whole
|
|
@@ -83438,7 +83581,8 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
83438
83581
|
async sealEventMedia(job) {
|
|
83439
83582
|
const move = job.moves.find((candidate) => candidate.storageClass === "eventMedia");
|
|
83440
83583
|
if (move === void 0) return;
|
|
83441
|
-
|
|
83584
|
+
const before = await this.deps.participants.analytics.countUnstamped();
|
|
83585
|
+
if (before === null || before.anyPresent) {
|
|
83442
83586
|
const started = await this.deps.participants.analytics.startDrain({
|
|
83443
83587
|
toLocationId: move.fromLocationId,
|
|
83444
83588
|
mode: "seal"
|
|
@@ -83447,7 +83591,8 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
83447
83591
|
if (job.cancelRequested) return;
|
|
83448
83592
|
}
|
|
83449
83593
|
const after = await this.deps.participants.analytics.countUnstamped();
|
|
83450
|
-
if (after
|
|
83594
|
+
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.");
|
|
83595
|
+
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.`);
|
|
83451
83596
|
}
|
|
83452
83597
|
async waitForSeal(job, moverJobId) {
|
|
83453
83598
|
const sleep = this.deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
@@ -83796,6 +83941,34 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
83796
83941
|
backfilled: backfill.length,
|
|
83797
83942
|
total: this.locations.size
|
|
83798
83943
|
} });
|
|
83944
|
+
this.reportDisabledDefaults();
|
|
83945
|
+
}
|
|
83946
|
+
/**
|
|
83947
|
+
* Shout about a persisted row that is BOTH the type default and disabled.
|
|
83948
|
+
*
|
|
83949
|
+
* `upsertLocation` cannot produce this (`resolveEnabled` force-enables a
|
|
83950
|
+
* default) and neither can `setDefaultLocations` — but hydrate writes rows
|
|
83951
|
+
* straight into the map without either, so a hand-edited store, a partial
|
|
83952
|
+
* migration or a row written by another build can. It matters because every
|
|
83953
|
+
* bare-type ref resolves through {@link getDefaultLocation}: on such a row
|
|
83954
|
+
* `storage.write({ location: 'eventMedia' })` and the migration coordinator's
|
|
83955
|
+
* drain destination both point at a disk the operator turned off. That is the
|
|
83956
|
+
* one thing this model must never do silently.
|
|
83957
|
+
*
|
|
83958
|
+
* Reported, never repaired: flipping an operator's flag back on at boot would
|
|
83959
|
+
* be the system overruling the switch instead of obeying it. The coordinator
|
|
83960
|
+
* refuses the drain (`storage-migration-coordinator.drain`); the bare-ref read
|
|
83961
|
+
* path is deliberately left working, because narrowing a READ is a worse bug
|
|
83962
|
+
* than the one this line reports.
|
|
83963
|
+
*/
|
|
83964
|
+
reportDisabledDefaults() {
|
|
83965
|
+
for (const loc of this.locations.values()) {
|
|
83966
|
+
if (!loc.isDefault || loc.enabled !== false) continue;
|
|
83967
|
+
this.logger.error("storage-orchestrator: the type DEFAULT is disabled \u2014 every bare-type ref for this type resolves to a location the operator turned off. Enable it, or make another location the default.", { meta: {
|
|
83968
|
+
id: loc.id,
|
|
83969
|
+
type: loc.type
|
|
83970
|
+
} });
|
|
83971
|
+
}
|
|
83799
83972
|
}
|
|
83800
83973
|
/**
|
|
83801
83974
|
* Inject the declaration-driven cardinality source. Called once by the
|
|
@@ -83867,6 +84040,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
83867
84040
|
loaded: this.locations.size,
|
|
83868
84041
|
isSystemUpgraded: upgraded
|
|
83869
84042
|
} });
|
|
84043
|
+
this.reportDisabledDefaults();
|
|
83870
84044
|
}
|
|
83871
84045
|
/**
|
|
83872
84046
|
* Boot backfill (SP1): stamp `nodeId` on every persisted node-local
|
|
@@ -85178,7 +85352,7 @@ var require_system_config_addon = __commonJS({
|
|
|
85178
85352
|
[Symbol.toStringTag]: { value: "Module" }
|
|
85179
85353
|
});
|
|
85180
85354
|
require_chunk_Cek0wNdY();
|
|
85181
|
-
var require_dist10 =
|
|
85355
|
+
var require_dist10 = require_dist_CcvXUhHK();
|
|
85182
85356
|
var SECTION_TITLES = {
|
|
85183
85357
|
server: "Server",
|
|
85184
85358
|
auth: "Authentication"
|
|
@@ -103239,7 +103413,7 @@ var require_winston_logging = __commonJS({
|
|
|
103239
103413
|
[Symbol.toStringTag]: { value: "Module" }
|
|
103240
103414
|
});
|
|
103241
103415
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
103242
|
-
var require_dist10 =
|
|
103416
|
+
var require_dist10 = require_dist_CcvXUhHK();
|
|
103243
103417
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
103244
103418
|
var node_path = __require("path");
|
|
103245
103419
|
node_path = require_chunk.__toESM(node_path);
|
|
@@ -115732,12 +115906,12 @@ var require_dist2 = __commonJS({
|
|
|
115732
115906
|
}
|
|
115733
115907
|
});
|
|
115734
115908
|
|
|
115735
|
-
// ../system/dist/manifest-system-deps-
|
|
115736
|
-
var
|
|
115737
|
-
"../system/dist/manifest-system-deps-
|
|
115909
|
+
// ../system/dist/manifest-system-deps-oJMkWSX-.js
|
|
115910
|
+
var require_manifest_system_deps_oJMkWSX = __commonJS({
|
|
115911
|
+
"../system/dist/manifest-system-deps-oJMkWSX-.js"(exports) {
|
|
115738
115912
|
"use strict";
|
|
115739
115913
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
115740
|
-
|
|
115914
|
+
require_dist_CcvXUhHK();
|
|
115741
115915
|
var node_crypto = __require("crypto");
|
|
115742
115916
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
115743
115917
|
var _camstack_types_node = require_node();
|
|
@@ -119845,6 +120019,174 @@ var require_manifest_system_deps_8boi90D9 = __commonJS({
|
|
|
119845
120019
|
createClient: (nodeId) => new UdsLocalTransportClient(localEndpointPath(nodeId))
|
|
119846
120020
|
};
|
|
119847
120021
|
}
|
|
120022
|
+
var DEFAULT_RETENTION_SECONDS = 300;
|
|
120023
|
+
var DEFAULT_MAX_TRIPLES = 4e3;
|
|
120024
|
+
var PARENT_ROUTED_PROVIDER_ADDON_ID = "(unresolved: parent-routed)";
|
|
120025
|
+
var CapUsageRegistry = class {
|
|
120026
|
+
retentionSeconds;
|
|
120027
|
+
maxTriples;
|
|
120028
|
+
map = /* @__PURE__ */ new Map();
|
|
120029
|
+
triples = 0;
|
|
120030
|
+
droppedTriples = 0;
|
|
120031
|
+
constructor(opts) {
|
|
120032
|
+
this.retentionSeconds = Math.max(1, opts?.retentionSeconds ?? DEFAULT_RETENTION_SECONDS);
|
|
120033
|
+
this.maxTriples = Math.max(1, opts?.maxTriples ?? DEFAULT_MAX_TRIPLES);
|
|
120034
|
+
}
|
|
120035
|
+
/**
|
|
120036
|
+
* Record one observed cap call.
|
|
120037
|
+
*
|
|
120038
|
+
* On the hot path: this runs once per cross-process cap call on hub-main's
|
|
120039
|
+
* event loop, which is the cluster's only queue (D181). Three Map lookups, an
|
|
120040
|
+
* integer increment, and — only when the wall clock has moved on — a bounded
|
|
120041
|
+
* clear. No allocation once a triple exists, and it NEVER throws into the
|
|
120042
|
+
* call it observes.
|
|
120043
|
+
*/
|
|
120044
|
+
recordCall(rec) {
|
|
120045
|
+
try {
|
|
120046
|
+
if (rec.callerAddonId === "" || rec.providerAddonId === "" || rec.capName === "") return;
|
|
120047
|
+
if (!Number.isFinite(rec.atMs) || rec.atMs < 0) return;
|
|
120048
|
+
const window2 = this.windowFor(rec);
|
|
120049
|
+
if (window2 === null) return;
|
|
120050
|
+
const sec = Math.floor(rec.atMs / 1e3);
|
|
120051
|
+
if (window2.lastCallAtMs !== 0) {
|
|
120052
|
+
const lastSec = Math.floor(window2.lastCallAtMs / 1e3);
|
|
120053
|
+
const advanced = Math.min(sec - lastSec, this.retentionSeconds);
|
|
120054
|
+
for (let k = 1; k <= advanced; k++) window2.buckets[(lastSec + k) % this.retentionSeconds] = 0;
|
|
120055
|
+
}
|
|
120056
|
+
const idx = sec % this.retentionSeconds;
|
|
120057
|
+
window2.buckets[idx] = (window2.buckets[idx] ?? 0) + 1;
|
|
120058
|
+
if (rec.atMs > window2.lastCallAtMs) window2.lastCallAtMs = rec.atMs;
|
|
120059
|
+
} catch {
|
|
120060
|
+
}
|
|
120061
|
+
}
|
|
120062
|
+
getGraph(opts) {
|
|
120063
|
+
const windowSeconds = Math.max(1, Math.min(opts.windowSeconds, this.retentionSeconds));
|
|
120064
|
+
const minMs = opts.nowMs - windowSeconds * 1e3;
|
|
120065
|
+
const nowSec = Math.floor(opts.nowMs / 1e3);
|
|
120066
|
+
const out = [];
|
|
120067
|
+
for (const [caller, byProvider] of this.map) for (const [provider, byCap] of byProvider) for (const [capName, window2] of byCap) {
|
|
120068
|
+
if (window2.lastCallAtMs < minMs) continue;
|
|
120069
|
+
const count = this.sumWindow(window2, nowSec, windowSeconds);
|
|
120070
|
+
if (count === 0) continue;
|
|
120071
|
+
const callsPerMin = count / windowSeconds * 60;
|
|
120072
|
+
out.push({
|
|
120073
|
+
callerAddonId: caller,
|
|
120074
|
+
providerAddonId: provider,
|
|
120075
|
+
capName,
|
|
120076
|
+
callsPerMin,
|
|
120077
|
+
lastCallAtMs: window2.lastCallAtMs
|
|
120078
|
+
});
|
|
120079
|
+
}
|
|
120080
|
+
return out;
|
|
120081
|
+
}
|
|
120082
|
+
/** See {@link CapUsageStats}. Cheap — three counters, no scan. */
|
|
120083
|
+
getStats() {
|
|
120084
|
+
return {
|
|
120085
|
+
triples: this.triples,
|
|
120086
|
+
maxTriples: this.maxTriples,
|
|
120087
|
+
droppedTriples: this.droppedTriples
|
|
120088
|
+
};
|
|
120089
|
+
}
|
|
120090
|
+
/** Test / diagnostic helper — drops all recorded calls. */
|
|
120091
|
+
clear() {
|
|
120092
|
+
this.map.clear();
|
|
120093
|
+
this.triples = 0;
|
|
120094
|
+
this.droppedTriples = 0;
|
|
120095
|
+
}
|
|
120096
|
+
/**
|
|
120097
|
+
* Sum the buckets covering `(nowSec - windowSeconds, nowSec]`.
|
|
120098
|
+
*
|
|
120099
|
+
* The scan starts at `lastSec`, never at `nowSec`. `recordCall` clears the
|
|
120100
|
+
* buckets the ring has advanced OVER, which by definition stops at the last
|
|
120101
|
+
* write: the buckets for the seconds since then still hold whatever the
|
|
120102
|
+
* previous pass left in them, and reading `nowSec` down would count it. A
|
|
120103
|
+
* triple that went quiet for a couple of minutes and then made one call would
|
|
120104
|
+
* report the burst it made five minutes earlier.
|
|
120105
|
+
*
|
|
120106
|
+
* No lower bound is needed beyond the window itself: every bucket at or below
|
|
120107
|
+
* `lastSec - retention` was cleared by the advance that reached `lastSec`, so
|
|
120108
|
+
* it reads zero anyway. The loop is bounded by `windowSeconds ≤ retention`.
|
|
120109
|
+
*/
|
|
120110
|
+
sumWindow(window2, nowSec, windowSeconds) {
|
|
120111
|
+
const lastSec = Math.floor(window2.lastCallAtMs / 1e3);
|
|
120112
|
+
const from = Math.min(nowSec, lastSec);
|
|
120113
|
+
const floorSec = nowSec - windowSeconds;
|
|
120114
|
+
let count = 0;
|
|
120115
|
+
for (let s = from; s > floorSec; s--) count += window2.buckets[s % this.retentionSeconds] ?? 0;
|
|
120116
|
+
return count;
|
|
120117
|
+
}
|
|
120118
|
+
/**
|
|
120119
|
+
* The ring for this triple, creating it if the ceiling allows. `null` means
|
|
120120
|
+
* the observation is dropped — counted, never silent (see
|
|
120121
|
+
* {@link CapUsageStats.droppedTriples}).
|
|
120122
|
+
*/
|
|
120123
|
+
windowFor(rec) {
|
|
120124
|
+
let byProvider = this.map.get(rec.callerAddonId);
|
|
120125
|
+
if (!byProvider) {
|
|
120126
|
+
byProvider = /* @__PURE__ */ new Map();
|
|
120127
|
+
this.map.set(rec.callerAddonId, byProvider);
|
|
120128
|
+
}
|
|
120129
|
+
let byCap = byProvider.get(rec.providerAddonId);
|
|
120130
|
+
if (!byCap) {
|
|
120131
|
+
byCap = /* @__PURE__ */ new Map();
|
|
120132
|
+
byProvider.set(rec.providerAddonId, byCap);
|
|
120133
|
+
}
|
|
120134
|
+
const existing = byCap.get(rec.capName);
|
|
120135
|
+
if (existing) return existing;
|
|
120136
|
+
if (this.triples >= this.maxTriples) {
|
|
120137
|
+
this.reclaimExpired(rec.atMs);
|
|
120138
|
+
if (this.triples >= this.maxTriples) {
|
|
120139
|
+
this.droppedTriples++;
|
|
120140
|
+
return null;
|
|
120141
|
+
}
|
|
120142
|
+
byProvider = this.map.get(rec.callerAddonId);
|
|
120143
|
+
if (!byProvider) {
|
|
120144
|
+
byProvider = /* @__PURE__ */ new Map();
|
|
120145
|
+
this.map.set(rec.callerAddonId, byProvider);
|
|
120146
|
+
}
|
|
120147
|
+
byCap = byProvider.get(rec.providerAddonId);
|
|
120148
|
+
if (!byCap) {
|
|
120149
|
+
byCap = /* @__PURE__ */ new Map();
|
|
120150
|
+
byProvider.set(rec.providerAddonId, byCap);
|
|
120151
|
+
}
|
|
120152
|
+
}
|
|
120153
|
+
const created = {
|
|
120154
|
+
buckets: new Uint32Array(this.retentionSeconds),
|
|
120155
|
+
lastCallAtMs: 0
|
|
120156
|
+
};
|
|
120157
|
+
byCap.set(rec.capName, created);
|
|
120158
|
+
this.triples++;
|
|
120159
|
+
return created;
|
|
120160
|
+
}
|
|
120161
|
+
/**
|
|
120162
|
+
* Drop every triple whose whole ring has aged out — its last call is older
|
|
120163
|
+
* than the retention window, so it can contribute to no query. O(triples),
|
|
120164
|
+
* and only reachable when the ceiling is full, so it is amortised away by the
|
|
120165
|
+
* admissions it enables.
|
|
120166
|
+
*/
|
|
120167
|
+
reclaimExpired(nowMs) {
|
|
120168
|
+
const cutoff = nowMs - this.retentionSeconds * 1e3;
|
|
120169
|
+
for (const [caller, byProvider] of this.map) {
|
|
120170
|
+
for (const [provider, byCap] of byProvider) {
|
|
120171
|
+
for (const [capName, window2] of byCap) {
|
|
120172
|
+
if (window2.lastCallAtMs >= cutoff) continue;
|
|
120173
|
+
byCap.delete(capName);
|
|
120174
|
+
this.triples--;
|
|
120175
|
+
}
|
|
120176
|
+
if (byCap.size === 0) byProvider.delete(provider);
|
|
120177
|
+
}
|
|
120178
|
+
if (byProvider.size === 0) this.map.delete(caller);
|
|
120179
|
+
}
|
|
120180
|
+
}
|
|
120181
|
+
};
|
|
120182
|
+
var singleton = null;
|
|
120183
|
+
function getCapUsageRegistry() {
|
|
120184
|
+
if (!singleton) singleton = new CapUsageRegistry();
|
|
120185
|
+
return singleton;
|
|
120186
|
+
}
|
|
120187
|
+
function __resetCapUsageRegistryForTests() {
|
|
120188
|
+
singleton = null;
|
|
120189
|
+
}
|
|
119848
120190
|
function createSharedBusState(retainRecent = false) {
|
|
119849
120191
|
return {
|
|
119850
120192
|
handlers: /* @__PURE__ */ new Map(),
|
|
@@ -120391,6 +120733,8 @@ var require_manifest_system_deps_8boi90D9 = __commonJS({
|
|
|
120391
120733
|
isAddonPinnedCall;
|
|
120392
120734
|
/** See {@link LocalChildRegistryOptions.capTimeoutMs}. */
|
|
120393
120735
|
capTimeoutMs;
|
|
120736
|
+
/** See {@link LocalChildRegistryOptions.capUsageObserver}. */
|
|
120737
|
+
capUsageObserver;
|
|
120394
120738
|
/** Tracks capNames already logged as UDS-routed; one INFO line per capName per process. */
|
|
120395
120739
|
egressRoutedCaps = /* @__PURE__ */ new Set();
|
|
120396
120740
|
/** Active event fan-out mode, read once from `CAMSTACK_UDS_EVENT_FANOUT`. */
|
|
@@ -120422,6 +120766,7 @@ var require_manifest_system_deps_8boi90D9 = __commonJS({
|
|
|
120422
120766
|
this.isAggregatedCollectionMethod = opts.isAggregatedCollectionMethod;
|
|
120423
120767
|
this.isAddonPinnedCall = opts.isAddonPinnedCall;
|
|
120424
120768
|
this.capTimeoutMs = opts.capTimeoutMs;
|
|
120769
|
+
this.capUsageObserver = opts.capUsageObserver;
|
|
120425
120770
|
} else {
|
|
120426
120771
|
this.server = serverOrOptions;
|
|
120427
120772
|
this.onUnownedCall = onUnownedCallArg;
|
|
@@ -120588,6 +120933,30 @@ var require_manifest_system_deps_8boi90D9 = __commonJS({
|
|
|
120588
120933
|
}
|
|
120589
120934
|
return candidates[0];
|
|
120590
120935
|
}
|
|
120936
|
+
/**
|
|
120937
|
+
* Publish one cap-usage observation, if a sink is wired.
|
|
120938
|
+
*
|
|
120939
|
+
* Cost discipline — this is on the hot path (D181: hub-main's event loop is
|
|
120940
|
+
* the cluster's only queue): no sink means one undefined check; with a sink
|
|
120941
|
+
* it is one object literal and one `Date.now()`, and the sink itself is O(1).
|
|
120942
|
+
* Wrapped so a broken observer can never fail the call it observes — the
|
|
120943
|
+
* registry swallows too, and BOTH matter: this catch also covers a sink that
|
|
120944
|
+
* is not the registry.
|
|
120945
|
+
*/
|
|
120946
|
+
recordCapUsage(callerChildId, providerChildId, capName, methodName) {
|
|
120947
|
+
const sink = this.capUsageObserver;
|
|
120948
|
+
if (sink === void 0 || callerChildId === null) return;
|
|
120949
|
+
try {
|
|
120950
|
+
sink({
|
|
120951
|
+
callerAddonId: callerChildId,
|
|
120952
|
+
providerAddonId: providerChildId ?? "(unresolved: parent-routed)",
|
|
120953
|
+
capName,
|
|
120954
|
+
methodName,
|
|
120955
|
+
atMs: Date.now()
|
|
120956
|
+
});
|
|
120957
|
+
} catch {
|
|
120958
|
+
}
|
|
120959
|
+
}
|
|
120591
120960
|
/** First child whose cap manifest contains a descriptor matching `predicate`. */
|
|
120592
120961
|
findChildId(predicate) {
|
|
120593
120962
|
for (const entry of this.children.values()) if (entry.caps.some(predicate)) return entry.childId;
|
|
@@ -120857,7 +121226,9 @@ var require_manifest_system_deps_8boi90D9 = __commonJS({
|
|
|
120857
121226
|
const pinTargetsThisNode = pinnedNodeId !== void 0 && this.ownNodeId !== void 0 && pinnedNodeId === this.ownNodeId;
|
|
120858
121227
|
const aggregated = pinnedNodeId === void 0 && this.isAggregatedCollectionMethod?.(out.capName, out.method) === true;
|
|
120859
121228
|
const addonPinned = pinnedNodeId === void 0 && this.isAddonPinnedCall?.(out.capName, out.method, out.args) === true;
|
|
120860
|
-
|
|
121229
|
+
const target = !(out.native === true) && !aggregated && !addonPinned && (pinnedNodeId === void 0 || pinTargetsThisNode) ? this.resolveChildId(out.capName, out.deviceId) : null;
|
|
121230
|
+
this.recordCapUsage(childId, target, out.capName, out.method);
|
|
121231
|
+
if (target !== null) {
|
|
120861
121232
|
if (!this.egressRoutedCaps.has(out.capName)) {
|
|
120862
121233
|
this.egressRoutedCaps.add(out.capName);
|
|
120863
121234
|
this.logger?.info("routed child egress over UDS", { capName: out.capName });
|
|
@@ -121714,77 +122085,6 @@ var require_manifest_system_deps_8boi90D9 = __commonJS({
|
|
|
121714
122085
|
}
|
|
121715
122086
|
return links;
|
|
121716
122087
|
}
|
|
121717
|
-
var CapUsageRegistry = class {
|
|
121718
|
-
retentionSeconds;
|
|
121719
|
-
map = /* @__PURE__ */ new Map();
|
|
121720
|
-
constructor(opts) {
|
|
121721
|
-
this.retentionSeconds = Math.max(1, opts?.retentionSeconds ?? 300);
|
|
121722
|
-
}
|
|
121723
|
-
recordCall(rec) {
|
|
121724
|
-
try {
|
|
121725
|
-
if (rec.callerAddonId === "" || rec.providerAddonId === "" || rec.capName === "") return;
|
|
121726
|
-
if (!Number.isFinite(rec.atMs) || rec.atMs < 0) return;
|
|
121727
|
-
let byProvider = this.map.get(rec.callerAddonId);
|
|
121728
|
-
if (!byProvider) {
|
|
121729
|
-
byProvider = /* @__PURE__ */ new Map();
|
|
121730
|
-
this.map.set(rec.callerAddonId, byProvider);
|
|
121731
|
-
}
|
|
121732
|
-
let byCap = byProvider.get(rec.providerAddonId);
|
|
121733
|
-
if (!byCap) {
|
|
121734
|
-
byCap = /* @__PURE__ */ new Map();
|
|
121735
|
-
byProvider.set(rec.providerAddonId, byCap);
|
|
121736
|
-
}
|
|
121737
|
-
let window2 = byCap.get(rec.capName);
|
|
121738
|
-
if (!window2) {
|
|
121739
|
-
window2 = {
|
|
121740
|
-
buckets: Array.from({ length: this.retentionSeconds }, () => 0),
|
|
121741
|
-
lastCallAtMs: 0
|
|
121742
|
-
};
|
|
121743
|
-
byCap.set(rec.capName, window2);
|
|
121744
|
-
}
|
|
121745
|
-
const bucketIdx = Math.floor(rec.atMs / 1e3) % this.retentionSeconds;
|
|
121746
|
-
window2.buckets[bucketIdx] = (window2.buckets[bucketIdx] ?? 0) + 1;
|
|
121747
|
-
if (rec.atMs > window2.lastCallAtMs) window2.lastCallAtMs = rec.atMs;
|
|
121748
|
-
} catch {
|
|
121749
|
-
}
|
|
121750
|
-
}
|
|
121751
|
-
getGraph(opts) {
|
|
121752
|
-
const windowSeconds = Math.max(1, Math.min(opts.windowSeconds, this.retentionSeconds));
|
|
121753
|
-
const minMs = opts.nowMs - windowSeconds * 1e3;
|
|
121754
|
-
const out = [];
|
|
121755
|
-
for (const [caller, byProvider] of this.map) for (const [provider, byCap] of byProvider) for (const [capName, window2] of byCap) {
|
|
121756
|
-
if (window2.lastCallAtMs < minMs) continue;
|
|
121757
|
-
const nowBucket = Math.floor(opts.nowMs / 1e3);
|
|
121758
|
-
let count = 0;
|
|
121759
|
-
for (let i = 0; i < windowSeconds; i++) {
|
|
121760
|
-
const b = (nowBucket - i + this.retentionSeconds * 1e6) % this.retentionSeconds;
|
|
121761
|
-
count += window2.buckets[b] ?? 0;
|
|
121762
|
-
}
|
|
121763
|
-
if (count === 0) continue;
|
|
121764
|
-
const callsPerMin = count / windowSeconds * 60;
|
|
121765
|
-
out.push({
|
|
121766
|
-
callerAddonId: caller,
|
|
121767
|
-
providerAddonId: provider,
|
|
121768
|
-
capName,
|
|
121769
|
-
callsPerMin,
|
|
121770
|
-
lastCallAtMs: window2.lastCallAtMs
|
|
121771
|
-
});
|
|
121772
|
-
}
|
|
121773
|
-
return out;
|
|
121774
|
-
}
|
|
121775
|
-
/** Test / diagnostic helper — drops all recorded calls. */
|
|
121776
|
-
clear() {
|
|
121777
|
-
this.map.clear();
|
|
121778
|
-
}
|
|
121779
|
-
};
|
|
121780
|
-
var singleton = null;
|
|
121781
|
-
function getCapUsageRegistry() {
|
|
121782
|
-
if (!singleton) singleton = new CapUsageRegistry();
|
|
121783
|
-
return singleton;
|
|
121784
|
-
}
|
|
121785
|
-
function __resetCapUsageRegistryForTests() {
|
|
121786
|
-
singleton = null;
|
|
121787
|
-
}
|
|
121788
122088
|
function safeExistsSync(path) {
|
|
121789
122089
|
try {
|
|
121790
122090
|
return node_fs.existsSync(path);
|
|
@@ -123220,6 +123520,12 @@ var require_manifest_system_deps_8boi90D9 = __commonJS({
|
|
|
123220
123520
|
return NATIVE_PROVIDER_SERVICE_INFIX;
|
|
123221
123521
|
}
|
|
123222
123522
|
});
|
|
123523
|
+
Object.defineProperty(exports, "PARENT_ROUTED_PROVIDER_ADDON_ID", {
|
|
123524
|
+
enumerable: true,
|
|
123525
|
+
get: function() {
|
|
123526
|
+
return PARENT_ROUTED_PROVIDER_ADDON_ID;
|
|
123527
|
+
}
|
|
123528
|
+
});
|
|
123223
123529
|
Object.defineProperty(exports, "RSS_BUDGET_REANNOUNCE_MIN_MS", {
|
|
123224
123530
|
enumerable: true,
|
|
123225
123531
|
get: function() {
|
|
@@ -127634,7 +127940,7 @@ var require_dist3 = __commonJS({
|
|
|
127634
127940
|
"use strict";
|
|
127635
127941
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
127636
127942
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
127637
|
-
var require_dist10 =
|
|
127943
|
+
var require_dist10 = require_dist_CcvXUhHK();
|
|
127638
127944
|
var require_builtins_alerts_alerts_addon = require_alerts_addon();
|
|
127639
127945
|
require_alerts();
|
|
127640
127946
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
@@ -127660,7 +127966,7 @@ var require_dist3 = __commonJS({
|
|
|
127660
127966
|
var require_builtins_winston_logging_index = require_winston_logging();
|
|
127661
127967
|
var require_file_data_plane = require_file_data_plane_DO8KbxCe();
|
|
127662
127968
|
var require_tls$1 = require_tls_BxQlomxd();
|
|
127663
|
-
var require_manifest_system_deps =
|
|
127969
|
+
var require_manifest_system_deps = require_manifest_system_deps_oJMkWSX();
|
|
127664
127970
|
var require_resource_monitor = require_resource_monitor_CdnzxBLP();
|
|
127665
127971
|
var require_custom_action_registry = require_custom_action_registry_jY0NOZK8();
|
|
127666
127972
|
var zod = require_zod();
|
|
@@ -208146,6 +208452,7 @@ globstar while`, t, d, e, f, m), this.matchOne(t.slice(d), e.slice(f), s)) retur
|
|
|
208146
208452
|
exports.NativeMetricsProvider = require_builtins_native_metrics_native_metrics_addon.NativeMetricsProvider;
|
|
208147
208453
|
exports.NetworkQualityTracker = NetworkQualityTracker;
|
|
208148
208454
|
exports.NotificationService = NotificationService;
|
|
208455
|
+
exports.PARENT_ROUTED_PROVIDER_ADDON_ID = require_manifest_system_deps.PARENT_ROUTED_PROVIDER_ADDON_ID;
|
|
208149
208456
|
Object.defineProperty(exports, "PYTHON_VERSION", {
|
|
208150
208457
|
enumerable: true,
|
|
208151
208458
|
get: function() {
|
|
@@ -209794,6 +210101,21 @@ var require_dist4 = __commonJS({
|
|
|
209794
210101
|
bytesMoved: zod.z.number().int(),
|
|
209795
210102
|
/** Total files discovered up front; null while (or when) unknown. */
|
|
209796
210103
|
filesTotal: zod.z.number().int().nullable(),
|
|
210104
|
+
/**
|
|
210105
|
+
* Rows this run CORRECTED while moving them — a durable mutation the move
|
|
210106
|
+
* made that nobody asked for, so it is reported where the operator reads the
|
|
210107
|
+
* job rather than only in a log line.
|
|
210108
|
+
*
|
|
210109
|
+
* A footage segment records its byte count in its own NAME, and the durable
|
|
210110
|
+
* hour row derives its aggregates from those names. A file that does not
|
|
210111
|
+
* match its name therefore makes the ledger's sums — and with them quota and
|
|
210112
|
+
* pressure eviction — wrong by the difference, and only a rename can fix it.
|
|
210113
|
+
* On 2026-08-30 one such row also stalled a 110 749-file drain permanently.
|
|
210114
|
+
*
|
|
210115
|
+
* Absent on lanes where the question has no meaning: a media blob's size is
|
|
210116
|
+
* in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
|
|
210117
|
+
*/
|
|
210118
|
+
rowsReconciled: zod.z.number().int().nonnegative().optional(),
|
|
209797
210119
|
startedAt: zod.z.number(),
|
|
209798
210120
|
finishedAt: zod.z.number().nullable(),
|
|
209799
210121
|
error: zod.z.string().nullable()
|
|
@@ -209836,11 +210158,18 @@ var require_dist4 = __commonJS({
|
|
|
209836
210158
|
/** Omitted = `move`, the pre-existing behaviour. */
|
|
209837
210159
|
mode: MediaRelocateModeSchema.optional()
|
|
209838
210160
|
});
|
|
209839
|
-
var
|
|
209840
|
-
|
|
209841
|
-
|
|
209842
|
-
total: zod.z.number().int().nonnegative()
|
|
210161
|
+
var UnstampedRowsSchema = zod.z.object({
|
|
210162
|
+
present: zod.z.boolean(),
|
|
210163
|
+
rows: zod.z.number().int().nonnegative().nullable()
|
|
209843
210164
|
});
|
|
210165
|
+
var UnstampedEventMediaCountSchema = zod.z.object({
|
|
210166
|
+
media: UnstampedRowsSchema,
|
|
210167
|
+
retrainFrames: UnstampedRowsSchema,
|
|
210168
|
+
/** True when EITHER collection holds one. The refusal reads this. */
|
|
210169
|
+
anyPresent: zod.z.boolean(),
|
|
210170
|
+
/** Sum across both, or `null` when either lane could not be counted. */
|
|
210171
|
+
total: zod.z.number().int().nonnegative().nullable()
|
|
210172
|
+
}).nullable();
|
|
209844
210173
|
var StorageMigrationMediaMoveInputSchema = RelocateMediaInputSchema.extend({ leaseId: zod.z.string().min(1) });
|
|
209845
210174
|
var StorageMigrationClassSchema = zod.z.enum([
|
|
209846
210175
|
"recordings",
|
|
@@ -209887,6 +210216,10 @@ var require_dist4 = __commonJS({
|
|
|
209887
210216
|
/** The archive census — the **M** of "N of M" (D295). `null` = unknowable. */
|
|
209888
210217
|
filesTotal: zod.z.number().int().nonnegative().nullable(),
|
|
209889
210218
|
bytesMoved: zod.z.number().int().nonnegative(),
|
|
210219
|
+
/** Rows the mover corrected while moving them — see `RelocateJob`. Absent on
|
|
210220
|
+
* a lane that cannot reconcile. A migration that silently rewrote durable
|
|
210221
|
+
* rows would be the same failure as one that silently skipped them. */
|
|
210222
|
+
rowsReconciled: zod.z.number().int().nonnegative().optional(),
|
|
209890
210223
|
/** The MOVER's start, not the migration's: a drain restarted after an addon
|
|
209891
210224
|
* crash gets a new mover, and a rate computed from the migration's start
|
|
209892
210225
|
* would silently average in the time nothing was running. */
|
|
@@ -216211,6 +216544,15 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
216211
216544
|
* calls are sync. Bindings change rarely (only on wrapper toggle or
|
|
216212
216545
|
* device add/remove) — clients invalidate via the
|
|
216213
216546
|
* `capability.binding-changed` event.
|
|
216547
|
+
*
|
|
216548
|
+
* "A single round-trip" describes the CLIENT's side and used not to
|
|
216549
|
+
* describe the server's: until 2026-08-30 the resolver read the persisted
|
|
216550
|
+
* wrapper activations once per device, so answering this cost one
|
|
216551
|
+
* settings-door RPC per device — 1 020 on the live 1 019-device hub, and
|
|
216552
|
+
* it did not return in 240 s against `SystemMirror.init`'s 15 s budget.
|
|
216553
|
+
* The server side is now two reads for the whole fleet. Anything PERIODIC
|
|
216554
|
+
* still belongs on `getBindings` / `getBindingsBatch` (D12); this remains
|
|
216555
|
+
* a warm seed.
|
|
216214
216556
|
*/
|
|
216215
216557
|
getAllBindings: require_sleep.method(zod.z.object({}), zod.z.array(DeviceBindingsForDeviceSchema)),
|
|
216216
216558
|
/**
|
|
@@ -222996,6 +223338,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
222996
223338
|
* happens to stamp it. This count is what the migration planner's
|
|
222997
223339
|
* non-blocking gate reads; `relocateMedia({ mode: 'seal' })` is what drives
|
|
222998
223340
|
* it to zero.
|
|
223341
|
+
*
|
|
223342
|
+
* TWO indexed statements per collection, not a walk. It used to page the
|
|
223343
|
+
* whole collection at 200 rows per RPC ordered by an unindexed column, so
|
|
223344
|
+
* on the live hub — 1 254 576 rows — it hit the 60 s RPC deadline every
|
|
223345
|
+
* time it was called, and the migration it gates could never start. The
|
|
223346
|
+
* cheap question (`present`: is there at least one) is asked first and
|
|
223347
|
+
* separately from the expensive one (`rows`), because only the first has
|
|
223348
|
+
* to be answerable for the gate to do its job.
|
|
223349
|
+
*
|
|
223350
|
+
* **`null` is "not measurable", never zero** — at either level. An
|
|
223351
|
+
* unreadable collection must not read as a sealed one.
|
|
222999
223352
|
*/
|
|
223000
223353
|
countUnstampedEventMedia: require_sleep.method(zod.z.object({}), UnstampedEventMediaCountSchema, { auth: "admin" }),
|
|
223001
223354
|
/**
|
|
@@ -251323,6 +251676,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
251323
251676
|
exports.UnifiedBrokerInfoSchema = BrokerInfoSchema$1;
|
|
251324
251677
|
exports.UnitConversionError = UnitConversionError;
|
|
251325
251678
|
exports.UnstampedEventMediaCountSchema = UnstampedEventMediaCountSchema;
|
|
251679
|
+
exports.UnstampedRowsSchema = UnstampedRowsSchema;
|
|
251326
251680
|
exports.UpdateIntegrationInputSchema = UpdateIntegrationInputSchema;
|
|
251327
251681
|
exports.UpdateStatusSchema = UpdateStatusSchema;
|
|
251328
251682
|
exports.UpdateUserInputSchema = UpdateUserInputSchema;
|
|
@@ -385633,6 +385987,14 @@ var require_main2 = __commonJS({
|
|
|
385633
385987
|
// cap). A pin to ANOTHER node still bypasses the sibling → onUnownedCall.
|
|
385634
385988
|
ownNodeId: agentNodeId,
|
|
385635
385989
|
onUnownedCall
|
|
385990
|
+
// NO `capUsageObserver` here, deliberately. `getCapUsageRegistry()` is a
|
|
385991
|
+
// per-process singleton and the reader (`nodes.getCapUsageGraph`) runs on
|
|
385992
|
+
// hub-main; recording in the agent would produce a map nobody ever reads,
|
|
385993
|
+
// which is exactly the shape that made this graph return `[]` for
|
|
385994
|
+
// months. An agent's addon traffic is a KNOWN blind spot of the graph —
|
|
385995
|
+
// see `LocalChildRegistryOptions.capUsageObserver`. Closing it needs the
|
|
385996
|
+
// caller identity to survive `$hub-cap-fwd`'s envelope, which today it
|
|
385997
|
+
// does not.
|
|
385636
385998
|
});
|
|
385637
385999
|
await agentUdsRegistry.start();
|
|
385638
386000
|
agentUdsRegistry.onChildRegistered((child) => {
|
|
@@ -410443,8 +410805,30 @@ var require_cap_providers = __commonJS({
|
|
|
410443
410805
|
}
|
|
410444
410806
|
return result;
|
|
410445
410807
|
},
|
|
410808
|
+
/**
|
|
410809
|
+
* Observed caller → provider → cap edges over the last `windowSeconds`.
|
|
410810
|
+
*
|
|
410811
|
+
* **Read the blind spots before drawing a conclusion.** This reads
|
|
410812
|
+
* hub-main's `CapUsageRegistry`, written by the hub's `LocalChildRegistry`
|
|
410813
|
+
* on every `cap-call-out` a hub-local forked addon sends. It therefore does
|
|
410814
|
+
* NOT contain: hub-main → hub-main in-process calls (one shared `ctx.api`
|
|
410815
|
+
* client, no per-caller identity), a runner's own co-located calls, or any
|
|
410816
|
+
* traffic originating on an agent. An edge missing here means "not seen on
|
|
410817
|
+
* this plane", never "did not happen" — the full list is on
|
|
410818
|
+
* `LocalChildRegistryOptions.capUsageObserver`.
|
|
410819
|
+
*/
|
|
410446
410820
|
getCapUsageGraph: async (input) => {
|
|
410447
410821
|
const reg = (0, system_1.getCapUsageRegistry)();
|
|
410822
|
+
const stats = reg.getStats();
|
|
410823
|
+
if (stats.droppedTriples > 0) {
|
|
410824
|
+
logger?.warn("cap-usage graph is truncated \u2014 the triple ceiling was reached", {
|
|
410825
|
+
meta: {
|
|
410826
|
+
triples: stats.triples,
|
|
410827
|
+
maxTriples: stats.maxTriples,
|
|
410828
|
+
droppedTriples: stats.droppedTriples
|
|
410829
|
+
}
|
|
410830
|
+
});
|
|
410831
|
+
}
|
|
410448
410832
|
return reg.getGraph({ windowSeconds: input.windowSeconds, nowMs: Date.now() });
|
|
410449
410833
|
},
|
|
410450
410834
|
setProcessLogLevel: async (input) => {
|
|
@@ -421469,6 +421853,14 @@ var require_moleculer_service = __commonJS({
|
|
|
421469
421853
|
// below, so a method that survives 16 minutes across nodes survives it
|
|
421470
421854
|
// across a socket too — and one that hangs is cut off on both.
|
|
421471
421855
|
capTimeoutMs: (capName, method) => this.capabilityService.getRegistry()?.getDefinition(capName)?.methods?.[method]?.timeoutMs,
|
|
421856
|
+
// Feed the cap-usage graph from the one place that sees a forked
|
|
421857
|
+
// addon's outbound `ctx.api` traffic AND runs in the process that reads
|
|
421858
|
+
// it back (`nodes.getCapUsageGraph` → `cap-providers.ts`). The
|
|
421859
|
+
// runner-side observer in `addon-context-factory.ts` writes to the
|
|
421860
|
+
// RUNNER's own module singleton, which hub-main never sees — which is
|
|
421861
|
+
// why the graph answered `[]` on a hub running 64 addons. Blind spots
|
|
421862
|
+
// that remain are listed on `LocalChildRegistryOptions.capUsageObserver`.
|
|
421863
|
+
capUsageObserver: (obs) => (0, system_1.getCapUsageRegistry)().recordCall(obs),
|
|
421472
421864
|
logger: {
|
|
421473
421865
|
info: (msg, meta) => logger.info(msg, meta !== null && meta !== void 0 ? { meta } : void 0)
|
|
421474
421866
|
},
|