camstack 1.2.48 → 1.2.49
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-BVU5JADq.js
|
|
23637
|
+
var require_dist_BVU5JADq = __commonJS({
|
|
23638
|
+
"../system/dist/dist-BVU5JADq.js"(exports) {
|
|
23639
23639
|
"use strict";
|
|
23640
23640
|
var zod = require_zod();
|
|
23641
23641
|
var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
|
|
@@ -25904,6 +25904,46 @@ var require_dist_D3lqzV40 = __commonJS({
|
|
|
25904
25904
|
function logLevelAtMost(level, threshold) {
|
|
25905
25905
|
return LOG_LEVEL_RANK[level] <= LOG_LEVEL_RANK[threshold];
|
|
25906
25906
|
}
|
|
25907
|
+
var LogChannelLevelSchema = zod.z.enum([
|
|
25908
|
+
"info",
|
|
25909
|
+
"warn",
|
|
25910
|
+
"error"
|
|
25911
|
+
]);
|
|
25912
|
+
var LogChannelDescriptorSchema = zod.z.object({
|
|
25913
|
+
/**
|
|
25914
|
+
* Dotted `area.thing`, unique across the workspace. `area` is conventionally
|
|
25915
|
+
* the addon's short name so an operator reading a channel list can tell who
|
|
25916
|
+
* owns it without a second lookup.
|
|
25917
|
+
*/
|
|
25918
|
+
name: zod.z.string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
|
|
25919
|
+
/** One sentence: what the operator will SEE after arming it. */
|
|
25920
|
+
description: zod.z.string().min(1),
|
|
25921
|
+
/** The level its lines are emitted at. Never below `info`. */
|
|
25922
|
+
defaultLevel: LogChannelLevelSchema,
|
|
25923
|
+
/**
|
|
25924
|
+
* Whether this channel can be narrowed to a camera.
|
|
25925
|
+
*
|
|
25926
|
+
* `true` is a PROMISE with two halves, and both must hold: the gate is
|
|
25927
|
+
* consulted with the numeric device id, AND every line the channel admits
|
|
25928
|
+
* carries `tags: { deviceId }` with that same numeric id. The second half is
|
|
25929
|
+
* what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
|
|
25930
|
+
* keeps `deviceId` out of the stream labels for cardinality, so the tag in
|
|
25931
|
+
* the body is the only way to filter.
|
|
25932
|
+
*
|
|
25933
|
+
* A channel whose lines carry the device only in `meta` (or not at all) is
|
|
25934
|
+
* declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
|
|
25935
|
+
* the operator narrows to one camera, sees nothing, and concludes the code
|
|
25936
|
+
* path was never taken.
|
|
25937
|
+
*/
|
|
25938
|
+
perDevice: zod.z.boolean()
|
|
25939
|
+
});
|
|
25940
|
+
var LogChannelWindowSchema = zod.z.object({
|
|
25941
|
+
channel: zod.z.string().min(1),
|
|
25942
|
+
/** Epoch ms the window closes at. */
|
|
25943
|
+
armedUntilMs: zod.z.number(),
|
|
25944
|
+
/** `null` = every camera. A non-empty list narrows to those numeric ids. */
|
|
25945
|
+
deviceIds: zod.z.array(zod.z.number().int()).readonly().nullable()
|
|
25946
|
+
});
|
|
25907
25947
|
var OpsLogDomainSchema = zod.z.enum(["recording", "events"]);
|
|
25908
25948
|
var OpsLogOpSchema = zod.z.enum([
|
|
25909
25949
|
"prune",
|
|
@@ -29461,6 +29501,21 @@ var require_dist_D3lqzV40 = __commonJS({
|
|
|
29461
29501
|
whereBetween: zod.z.record(zod.z.string(), zod.z.tuple([zod.z.unknown(), zod.z.unknown()])).optional(),
|
|
29462
29502
|
whereNot: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
|
|
29463
29503
|
});
|
|
29504
|
+
var AggregateFieldSchema = zod.z.object({
|
|
29505
|
+
/** Result key. */
|
|
29506
|
+
as: zod.z.string().min(1),
|
|
29507
|
+
/** Column to aggregate. Must be a real column of a declared collection. */
|
|
29508
|
+
field: zod.z.string().min(1),
|
|
29509
|
+
op: zod.z.enum([
|
|
29510
|
+
"sum",
|
|
29511
|
+
"min",
|
|
29512
|
+
"max"
|
|
29513
|
+
])
|
|
29514
|
+
});
|
|
29515
|
+
var AggregateResultSchema = zod.z.object({
|
|
29516
|
+
count: zod.z.number().int(),
|
|
29517
|
+
values: zod.z.record(zod.z.string(), zod.z.number().nullable())
|
|
29518
|
+
});
|
|
29464
29519
|
var SettingsRecordSchema = zod.z.object({
|
|
29465
29520
|
id: zod.z.string(),
|
|
29466
29521
|
data: zod.z.record(zod.z.string(), zod.z.unknown())
|
|
@@ -29588,6 +29643,32 @@ var require_dist_D3lqzV40 = __commonJS({
|
|
|
29588
29643
|
collection: zod.z.string(),
|
|
29589
29644
|
filter: QueryFilterSchema.optional()
|
|
29590
29645
|
}), zod.z.number()),
|
|
29646
|
+
/**
|
|
29647
|
+
* `COUNT(*)` and one `SUM` / `MIN` / `MAX` per requested field, in ONE
|
|
29648
|
+
* statement, over the rows `filter` selects.
|
|
29649
|
+
*
|
|
29650
|
+
* Exists because "how much is there" was being answered by materialising
|
|
29651
|
+
* "what is there". The recorder's storage-pressure sweep asked its in-RAM
|
|
29652
|
+
* footage index for bytes/count/oldest/newest across a set of storage
|
|
29653
|
+
* locations twice a minute, and the only way to answer that from a map is
|
|
29654
|
+
* to visit every row — 7.1 M of them on the live hub, ~15 M row visits a
|
|
29655
|
+
* minute on the main thread, which is also why the whole archive had to
|
|
29656
|
+
* stay resident to be visited. The question is a sum; nothing needs to be
|
|
29657
|
+
* materialised to answer it.
|
|
29658
|
+
*
|
|
29659
|
+
* **The engine REFUSES a field it cannot serve**, exactly as
|
|
29660
|
+
* `query.columns` does and unlike a PREDICATE, which is skipped when
|
|
29661
|
+
* unresolvable. A dropped predicate over-matches and the caller sees extra
|
|
29662
|
+
* rows; a dropped aggregate returns a NUMBER that is wrong and looks
|
|
29663
|
+
* exactly like a real one. That asymmetry is what this repo has already
|
|
29664
|
+
* paid for once in `count`.
|
|
29665
|
+
*/
|
|
29666
|
+
aggregate: method(zod.z.object({
|
|
29667
|
+
namespace: zod.z.string().optional(),
|
|
29668
|
+
collection: zod.z.string(),
|
|
29669
|
+
fields: zod.z.array(AggregateFieldSchema).readonly(),
|
|
29670
|
+
filter: QueryFilterSchema.optional()
|
|
29671
|
+
}), AggregateResultSchema),
|
|
29591
29672
|
/** Grouped counts per ((field-origin)/bucketSize) bucket, filtered. */
|
|
29592
29673
|
histogram: method(zod.z.object({
|
|
29593
29674
|
namespace: zod.z.string().optional(),
|
|
@@ -29746,6 +29827,15 @@ var require_dist_D3lqzV40 = __commonJS({
|
|
|
29746
29827
|
collection: zod.z.string(),
|
|
29747
29828
|
filter: QueryFilterSchema.optional()
|
|
29748
29829
|
}), zod.z.number(), { auth: "admin" }),
|
|
29830
|
+
/** `COUNT(*)` plus one SUM/MIN/MAX per field, in one statement. Mirror of
|
|
29831
|
+
* `settings-store.aggregate` — see it for why an unresolvable field is
|
|
29832
|
+
* refused rather than dropped. */
|
|
29833
|
+
aggregate: method(zod.z.object({
|
|
29834
|
+
namespace: zod.z.string().optional(),
|
|
29835
|
+
collection: zod.z.string(),
|
|
29836
|
+
fields: zod.z.array(AggregateFieldSchema).readonly(),
|
|
29837
|
+
filter: QueryFilterSchema.optional()
|
|
29838
|
+
}), AggregateResultSchema, { auth: "admin" }),
|
|
29749
29839
|
/** Grouped counts per ((field-origin)/bucketSize) bucket, filtered. */
|
|
29750
29840
|
histogram: method(zod.z.object({
|
|
29751
29841
|
namespace: zod.z.string().optional(),
|
|
@@ -30857,6 +30947,39 @@ var require_dist_D3lqzV40 = __commonJS({
|
|
|
30857
30947
|
/** List children of a parent device (by parent numeric id). */
|
|
30858
30948
|
getChildren: method(zod.z.object({ parentDeviceId: zod.z.number() }), zod.z.array(DeviceInfoSchema)),
|
|
30859
30949
|
/**
|
|
30950
|
+
* `getChildren` for a NAMED SET of parents, in one call.
|
|
30951
|
+
*
|
|
30952
|
+
* The accessory reconcile in `device-cap-proxy.ts` asks this question once
|
|
30953
|
+
* per registered device — every `BaseDevice` inherits a
|
|
30954
|
+
* `getAccessoryChildren()` that returns `[]`, so even a leaf accessory
|
|
30955
|
+
* pays a round-trip to learn it has nothing to prune. Measured on the live
|
|
30956
|
+
* hub 2026-08-27 over a 120-second boot window, fleet of 1 017 devices:
|
|
30957
|
+
* `DeviceRowStore.list < DeviceRowStore.listByParent < getChildren` at
|
|
30958
|
+
* **1 024 calls** returning **919 rows in total** — 1 024 RPCs and 1 024
|
|
30959
|
+
* indexed scans to move less than one row each. `listByParentMany`
|
|
30960
|
+
* collapses the scans; this collapses the RPCs.
|
|
30961
|
+
*
|
|
30962
|
+
* Keyed by parent id as a STRING — a JSON object cannot key by number
|
|
30963
|
+
* (same reason as `getDeviceStatusAggregateBatch`). The per-parent value
|
|
30964
|
+
* is exactly what `getChildren` returns for that parent.
|
|
30965
|
+
*
|
|
30966
|
+
* A parent with no children — or one the fleet does not know — is ABSENT
|
|
30967
|
+
* from the record, never an invented empty row: the same contract as
|
|
30968
|
+
* `DeviceRowStore.getMany`/`listByParentMany`. An EMPTY `parentDeviceIds`
|
|
30969
|
+
* reads nothing at all rather than degrading to "every device".
|
|
30970
|
+
*
|
|
30971
|
+
* `parentDeviceIds` is capped at {@link DEVICE_CHILDREN_BATCH_MAX} — see
|
|
30972
|
+
* that constant for why. A caller with more parents than that sends more
|
|
30973
|
+
* than one call; it never sends one pathological one.
|
|
30974
|
+
*
|
|
30975
|
+
* Version skew: this is a NEW method, not a new field on `getChildren`, so
|
|
30976
|
+
* a hub that predates it answers NOT_FOUND rather than silently stripping
|
|
30977
|
+
* an unknown input key and answering a DIFFERENT question. The kernel-side
|
|
30978
|
+
* loader degrades to per-parent `getChildren` on that error — see
|
|
30979
|
+
* `children-batch-loader.ts`.
|
|
30980
|
+
*/
|
|
30981
|
+
getChildrenBatch: method(zod.z.object({ parentDeviceIds: zod.z.array(zod.z.number()).max(256) }), zod.z.record(zod.z.string(), zod.z.array(DeviceInfoSchema))),
|
|
30982
|
+
/**
|
|
30860
30983
|
* Resolve the devices LINKED to a camera — the single policy authority
|
|
30861
30984
|
* both consumers call (viewer devices panel + pipeline-analytics event
|
|
30862
30985
|
* kinds/ingest). Device-tree children are ALWAYS included; mode 'auto'
|
|
@@ -31901,6 +32024,38 @@ var require_dist_D3lqzV40 = __commonJS({
|
|
|
31901
32024
|
})
|
|
31902
32025
|
}
|
|
31903
32026
|
};
|
|
32027
|
+
var LogChannelApplyResultSchema = zod.z.object({
|
|
32028
|
+
/** How many declared channels are armed in this process after the call. */
|
|
32029
|
+
armed: zod.z.number().int().min(0),
|
|
32030
|
+
/**
|
|
32031
|
+
* Names the document armed that this process does not declare. Reported
|
|
32032
|
+
* rather than swallowed: a name here is either a typo or an addon that has
|
|
32033
|
+
* not booted, and both deserve a line instead of silence.
|
|
32034
|
+
*/
|
|
32035
|
+
unknown: zod.z.array(zod.z.string()).readonly()
|
|
32036
|
+
});
|
|
32037
|
+
var logChannelsCapability = {
|
|
32038
|
+
name: "log-channels",
|
|
32039
|
+
scope: "system",
|
|
32040
|
+
mode: "collection",
|
|
32041
|
+
internal: true,
|
|
32042
|
+
methods: {
|
|
32043
|
+
/** The channels this addon declares. Inert: no value, no state. */
|
|
32044
|
+
list: method(zod.z.void(), zod.z.array(LogChannelDescriptorSchema).readonly()),
|
|
32045
|
+
/**
|
|
32046
|
+
* Refresh this process's mirror from the document's FULL set of armed
|
|
32047
|
+
* windows.
|
|
32048
|
+
*
|
|
32049
|
+
* Full and not incremental on purpose: the document is the authority, so a
|
|
32050
|
+
* channel it does not name is disarmed here. An incremental apply would
|
|
32051
|
+
* let a disarm get lost in transit and leave a channel running that
|
|
32052
|
+
* nobody can see is running.
|
|
32053
|
+
*/
|
|
32054
|
+
apply: method(zod.z.object({ windows: zod.z.array(LogChannelWindowSchema).readonly() }), LogChannelApplyResultSchema, { kind: "mutation" })
|
|
32055
|
+
},
|
|
32056
|
+
/** In-process only — enumerated through `addons.listCapabilityProviders`. */
|
|
32057
|
+
mount: { kind: "skip" }
|
|
32058
|
+
};
|
|
31904
32059
|
var LogLevelSchema = zod.z.enum([
|
|
31905
32060
|
"debug",
|
|
31906
32061
|
"info",
|
|
@@ -47226,6 +47381,14 @@ var require_dist_D3lqzV40 = __commonJS({
|
|
|
47226
47381
|
scope: LoggingScopeKindSchema,
|
|
47227
47382
|
/** The node this layer speaks for; `null` on the cluster layer. */
|
|
47228
47383
|
nodeId: zod.z.string().nullable(),
|
|
47384
|
+
/**
|
|
47385
|
+
* The declared channel this layer speaks for; `null` on every layer but
|
|
47386
|
+
* `component`. Never folded into `nodeId`: a component level is CLUSTER-WIDE
|
|
47387
|
+
* by design — the convention this repo settled on is one orchestrator-wide
|
|
47388
|
+
* setting, never per node (D52) — so a component layer that carried a node
|
|
47389
|
+
* would invite a per-node copy of a value that has no per-node meaning.
|
|
47390
|
+
*/
|
|
47391
|
+
component: zod.z.string().nullable(),
|
|
47229
47392
|
/** Explicitly set here, or `null` when this layer inherits. */
|
|
47230
47393
|
level: LogLevelSchema$1.nullable()
|
|
47231
47394
|
});
|
|
@@ -47251,6 +47414,38 @@ var require_dist_D3lqzV40 = __commonJS({
|
|
|
47251
47414
|
/** Cadence of the diagnostic's aggregated report line. Clamped by the server. */
|
|
47252
47415
|
reportEveryMs: zod.z.number().int().positive().optional()
|
|
47253
47416
|
});
|
|
47417
|
+
var LogChannelWindowStateSchema = zod.z.object({
|
|
47418
|
+
channel: zod.z.string(),
|
|
47419
|
+
armed: zod.z.boolean(),
|
|
47420
|
+
/** Epoch ms the window closes at. 0 when disarmed. */
|
|
47421
|
+
armedUntilMs: zod.z.number(),
|
|
47422
|
+
/** Ms left before it expires on its own. 0 when disarmed. */
|
|
47423
|
+
remainingMs: zod.z.number(),
|
|
47424
|
+
/**
|
|
47425
|
+
* The cameras it is narrowed to, or `null` for every camera.
|
|
47426
|
+
*
|
|
47427
|
+
* A channel declared `perDevice: false` can only ever report `null` here:
|
|
47428
|
+
* its lines do not carry `tags: { deviceId }`, so narrowing them would
|
|
47429
|
+
* produce a filter that silently matches nothing. The server REFUSES such a
|
|
47430
|
+
* patch rather than quietly widening it — ignoring the request would teach
|
|
47431
|
+
* the operator that per-camera filtering works on that channel when it does
|
|
47432
|
+
* not.
|
|
47433
|
+
*/
|
|
47434
|
+
deviceIds: zod.z.array(zod.z.number().int()).readonly().nullable()
|
|
47435
|
+
});
|
|
47436
|
+
var LogChannelWindowPatchSchema = zod.z.object({
|
|
47437
|
+
channel: zod.z.string().min(1),
|
|
47438
|
+
armMs: zod.z.number().int().min(0),
|
|
47439
|
+
/**
|
|
47440
|
+
* Narrow to these numeric device ids. Absent or `null` = every camera.
|
|
47441
|
+
*
|
|
47442
|
+
* Numeric because the repo's own rule makes it possible: every log line
|
|
47443
|
+
* about a device carries `tags: { deviceId }` with the numeric id. That rule
|
|
47444
|
+
* was paid for with a 22% thumbnail gap and a 3-hour media blackout both
|
|
47445
|
+
* diagnosed by hand, and this is the first thing that collects on it.
|
|
47446
|
+
*/
|
|
47447
|
+
deviceIds: zod.z.array(zod.z.number().int()).readonly().nullable().optional()
|
|
47448
|
+
});
|
|
47254
47449
|
var LoggingSettingsPatchSchema = zod.z.object({
|
|
47255
47450
|
/**
|
|
47256
47451
|
* Absent leaves the level untouched. `null` CLEARS the explicit value at the
|
|
@@ -47261,19 +47456,50 @@ var require_dist_D3lqzV40 = __commonJS({
|
|
|
47261
47456
|
* Only the diagnostics NAMED here change. An armed window that is not listed
|
|
47262
47457
|
* keeps running — a patch is never a full replacement.
|
|
47263
47458
|
*/
|
|
47264
|
-
diagnostics: zod.z.array(DiagnosticWindowPatchSchema).readonly().optional()
|
|
47459
|
+
diagnostics: zod.z.array(DiagnosticWindowPatchSchema).readonly().optional(),
|
|
47460
|
+
/**
|
|
47461
|
+
* Only the channels NAMED here change. An armed channel that is not listed
|
|
47462
|
+
* keeps running — same rule as `diagnostics`, because a patch that silently
|
|
47463
|
+
* disarmed the channels it did not mention would make the Levels page and
|
|
47464
|
+
* the Diagnostics page fight over the same value.
|
|
47465
|
+
*/
|
|
47466
|
+
channels: zod.z.array(LogChannelWindowPatchSchema).readonly().optional()
|
|
47467
|
+
});
|
|
47468
|
+
var GetLoggingSettingsInputSchema = zod.z.object({
|
|
47469
|
+
scopeNodeId: zod.z.string().optional(),
|
|
47470
|
+
/**
|
|
47471
|
+
* The declared CHANNEL this document is addressed at, when the caller wants
|
|
47472
|
+
* the `component` layer. Absent = the node/cluster hierarchy only.
|
|
47473
|
+
*
|
|
47474
|
+
* Naming it separately rather than overloading `scopeNodeId` keeps the two
|
|
47475
|
+
* axes from collapsing: a component level is cluster-wide, a node level is
|
|
47476
|
+
* not, and one selector for both would make "which of these two did I just
|
|
47477
|
+
* set" unanswerable — the exact ambiguity `explicit` exists to remove.
|
|
47478
|
+
*/
|
|
47479
|
+
scopeComponent: zod.z.string().optional()
|
|
47265
47480
|
});
|
|
47266
|
-
var GetLoggingSettingsInputSchema = zod.z.object({ scopeNodeId: zod.z.string().optional() });
|
|
47267
47481
|
var SetLoggingSettingsInputSchema = zod.z.object({
|
|
47268
47482
|
scopeNodeId: zod.z.string().optional(),
|
|
47483
|
+
scopeComponent: zod.z.string().optional(),
|
|
47269
47484
|
patch: LoggingSettingsPatchSchema
|
|
47270
47485
|
});
|
|
47271
47486
|
var LoggingSettingsStateSchema = zod.z.object({
|
|
47272
47487
|
/** The layer this document was read at. `null` = the cluster layer. */
|
|
47273
47488
|
scopeNodeId: zod.z.string().nullable(),
|
|
47489
|
+
/** The channel this document was read at. `null` = no component layer. */
|
|
47490
|
+
scopeComponent: zod.z.string().nullable(),
|
|
47274
47491
|
effective: LoggingEffectiveSchema,
|
|
47275
47492
|
explicit: LoggingExplicitSchema,
|
|
47276
47493
|
activeWindows: zod.z.array(DiagnosticWindowSchema).readonly(),
|
|
47494
|
+
/**
|
|
47495
|
+
* Every channel the cluster's addons DECLARE, gathered from the
|
|
47496
|
+
* `log-channels` providers. Not stored anywhere: assembled per read, so a
|
|
47497
|
+
* channel added by a redeployed addon appears without anybody editing a
|
|
47498
|
+
* list, and a channel whose addon is gone stops being offered.
|
|
47499
|
+
*/
|
|
47500
|
+
channels: zod.z.array(LogChannelDescriptorSchema).readonly(),
|
|
47501
|
+
/** The channels ARMED right now, each with its deadline. */
|
|
47502
|
+
activeChannels: zod.z.array(LogChannelWindowStateSchema).readonly(),
|
|
47277
47503
|
persisted: zod.z.boolean()
|
|
47278
47504
|
});
|
|
47279
47505
|
var systemCapability = {
|
|
@@ -48752,6 +48978,7 @@ var require_dist_D3lqzV40 = __commonJS({
|
|
|
48752
48978
|
llmRuntimeCapability,
|
|
48753
48979
|
localNetworkCapability,
|
|
48754
48980
|
lockControlCapability,
|
|
48981
|
+
logChannelsCapability,
|
|
48755
48982
|
logDestinationCapability,
|
|
48756
48983
|
loginMethodCapability,
|
|
48757
48984
|
mediaPlayerCapability,
|
|
@@ -49718,6 +49945,12 @@ var require_dist_D3lqzV40 = __commonJS({
|
|
|
49718
49945
|
addonId: null,
|
|
49719
49946
|
access: "view"
|
|
49720
49947
|
},
|
|
49948
|
+
"dataStoreProvider.aggregate": {
|
|
49949
|
+
capName: "data-store-provider",
|
|
49950
|
+
capScope: "system",
|
|
49951
|
+
addonId: null,
|
|
49952
|
+
access: "view"
|
|
49953
|
+
},
|
|
49721
49954
|
"dataStoreProvider.count": {
|
|
49722
49955
|
capName: "data-store-provider",
|
|
49723
49956
|
capScope: "system",
|
|
@@ -50132,6 +50365,12 @@ var require_dist_D3lqzV40 = __commonJS({
|
|
|
50132
50365
|
addonId: null,
|
|
50133
50366
|
access: "view"
|
|
50134
50367
|
},
|
|
50368
|
+
"deviceManager.getChildrenBatch": {
|
|
50369
|
+
capName: "device-manager",
|
|
50370
|
+
capScope: "system",
|
|
50371
|
+
addonId: null,
|
|
50372
|
+
access: "view"
|
|
50373
|
+
},
|
|
50135
50374
|
"deviceManager.getConfigSchema": {
|
|
50136
50375
|
capName: "device-manager",
|
|
50137
50376
|
capScope: "system",
|
|
@@ -51182,6 +51421,18 @@ var require_dist_D3lqzV40 = __commonJS({
|
|
|
51182
51421
|
addonId: null,
|
|
51183
51422
|
access: "create"
|
|
51184
51423
|
},
|
|
51424
|
+
"logChannels.apply": {
|
|
51425
|
+
capName: "log-channels",
|
|
51426
|
+
capScope: "system",
|
|
51427
|
+
addonId: null,
|
|
51428
|
+
access: "create"
|
|
51429
|
+
},
|
|
51430
|
+
"logChannels.list": {
|
|
51431
|
+
capName: "log-channels",
|
|
51432
|
+
capScope: "system",
|
|
51433
|
+
addonId: null,
|
|
51434
|
+
access: "view"
|
|
51435
|
+
},
|
|
51185
51436
|
"logDestination.query": {
|
|
51186
51437
|
capName: "log-destination",
|
|
51187
51438
|
capScope: "system",
|
|
@@ -53336,6 +53587,12 @@ var require_dist_D3lqzV40 = __commonJS({
|
|
|
53336
53587
|
addonId: null,
|
|
53337
53588
|
access: "create"
|
|
53338
53589
|
},
|
|
53590
|
+
"settingsStore.aggregate": {
|
|
53591
|
+
capName: "settings-store",
|
|
53592
|
+
capScope: "system",
|
|
53593
|
+
addonId: null,
|
|
53594
|
+
access: "view"
|
|
53595
|
+
},
|
|
53339
53596
|
"settingsStore.count": {
|
|
53340
53597
|
capName: "settings-store",
|
|
53341
53598
|
capScope: "system",
|
|
@@ -54915,6 +55172,11 @@ var require_dist_D3lqzV40 = __commonJS({
|
|
|
54915
55172
|
form: "single",
|
|
54916
55173
|
optional: false
|
|
54917
55174
|
}],
|
|
55175
|
+
"deviceManager.getChildrenBatch": [{
|
|
55176
|
+
name: "parentDeviceIds",
|
|
55177
|
+
form: "array",
|
|
55178
|
+
optional: false
|
|
55179
|
+
}],
|
|
54918
55180
|
"deviceManager.getConfigSchema": [{
|
|
54919
55181
|
name: "deviceId",
|
|
54920
55182
|
form: "single",
|
|
@@ -57730,7 +57992,7 @@ var require_alerts_addon = __commonJS({
|
|
|
57730
57992
|
[Symbol.toStringTag]: { value: "Module" }
|
|
57731
57993
|
});
|
|
57732
57994
|
require_chunk_Cek0wNdY();
|
|
57733
|
-
var require_dist10 =
|
|
57995
|
+
var require_dist10 = require_dist_BVU5JADq();
|
|
57734
57996
|
function selectExpired(alerts, cutoffMs) {
|
|
57735
57997
|
return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
|
|
57736
57998
|
}
|
|
@@ -58549,7 +58811,7 @@ var require_console_logging = __commonJS({
|
|
|
58549
58811
|
[Symbol.toStringTag]: { value: "Module" }
|
|
58550
58812
|
});
|
|
58551
58813
|
require_chunk_Cek0wNdY();
|
|
58552
|
-
var require_dist10 =
|
|
58814
|
+
var require_dist10 = require_dist_BVU5JADq();
|
|
58553
58815
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
58554
58816
|
var LEVEL_RANK = {
|
|
58555
58817
|
debug: 0,
|
|
@@ -58643,7 +58905,7 @@ var require_core_blocks_addon = __commonJS({
|
|
|
58643
58905
|
"use strict";
|
|
58644
58906
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
58645
58907
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
58646
|
-
var require_dist10 =
|
|
58908
|
+
var require_dist10 = require_dist_BVU5JADq();
|
|
58647
58909
|
var node_crypto = __require("crypto");
|
|
58648
58910
|
var node_fs_promises = __require("fs/promises");
|
|
58649
58911
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
@@ -59540,11 +59802,11 @@ var require_core_blocks = __commonJS({
|
|
|
59540
59802
|
}
|
|
59541
59803
|
});
|
|
59542
59804
|
|
|
59543
|
-
// ../system/dist/retired-settings-keys-
|
|
59544
|
-
var
|
|
59545
|
-
"../system/dist/retired-settings-keys-
|
|
59805
|
+
// ../system/dist/retired-settings-keys-_PLI9w0k.js
|
|
59806
|
+
var require_retired_settings_keys_PLI9w0k = __commonJS({
|
|
59807
|
+
"../system/dist/retired-settings-keys-_PLI9w0k.js"(exports) {
|
|
59546
59808
|
"use strict";
|
|
59547
|
-
var require_dist10 =
|
|
59809
|
+
var require_dist10 = require_dist_BVU5JADq();
|
|
59548
59810
|
function settingsStoreIsAuthoritativeHere(env) {
|
|
59549
59811
|
const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
|
|
59550
59812
|
return raw === "" || raw === "hub";
|
|
@@ -61587,8 +61849,8 @@ var require_device_manager_addon = __commonJS({
|
|
|
61587
61849
|
[Symbol.toStringTag]: { value: "Module" }
|
|
61588
61850
|
});
|
|
61589
61851
|
require_chunk_Cek0wNdY();
|
|
61590
|
-
var require_dist10 =
|
|
61591
|
-
var require_retired_settings_keys =
|
|
61852
|
+
var require_dist10 = require_dist_BVU5JADq();
|
|
61853
|
+
var require_retired_settings_keys = require_retired_settings_keys_PLI9w0k();
|
|
61592
61854
|
var node_crypto = __require("crypto");
|
|
61593
61855
|
var _camstack_types_node = require_node();
|
|
61594
61856
|
var JOB_HISTORY = 20;
|
|
@@ -62750,30 +63012,16 @@ var require_device_manager_addon = __commonJS({
|
|
|
62750
63012
|
...sourceInfoGetDevice !== void 0 ? { sourceInfo: sourceInfoGetDevice } : {}
|
|
62751
63013
|
};
|
|
62752
63014
|
}
|
|
62753
|
-
async function
|
|
62754
|
-
const { parentDeviceId } = input;
|
|
62755
|
-
let ownerAddonId = null;
|
|
62756
|
-
if (pctx.registry) {
|
|
62757
|
-
if (pctx.registry.getById(parentDeviceId)) ownerAddonId = pctx.registry.getAddonId(parentDeviceId);
|
|
62758
|
-
}
|
|
62759
|
-
if (!ownerAddonId) {
|
|
62760
|
-
const persisted = await pctx.metaStore.resolvePersistedById(parentDeviceId);
|
|
62761
|
-
if (!persisted) return [];
|
|
62762
|
-
ownerAddonId = persisted.addonId;
|
|
62763
|
-
}
|
|
63015
|
+
async function projectChildren(pctx, parentDeviceId, ownerAddonId, childRows, liveChildren) {
|
|
62764
63016
|
const results = [];
|
|
62765
63017
|
const seen = /* @__PURE__ */ new Set();
|
|
62766
|
-
const childRows = await pctx.metaStore.rows.listByParent(parentDeviceId);
|
|
62767
63018
|
const rowById = /* @__PURE__ */ new Map();
|
|
62768
63019
|
for (const row of childRows) rowById.set(row.meta.id, row);
|
|
62769
|
-
|
|
62770
|
-
const
|
|
62771
|
-
|
|
62772
|
-
|
|
62773
|
-
|
|
62774
|
-
results.push(toDeviceInfo(ownerAddonId, device, row?.metadata ?? null, row?.meta ?? null));
|
|
62775
|
-
seen.add(key);
|
|
62776
|
-
}
|
|
63020
|
+
for (const device of liveChildren) {
|
|
63021
|
+
const key = String(device.id);
|
|
63022
|
+
const row = rowById.get(device.id);
|
|
63023
|
+
results.push(toDeviceInfo(ownerAddonId, device, row?.metadata ?? null, row?.meta ?? null));
|
|
63024
|
+
seen.add(key);
|
|
62777
63025
|
}
|
|
62778
63026
|
for (const row of childRows) {
|
|
62779
63027
|
const m = row.meta;
|
|
@@ -62809,6 +63057,58 @@ var require_device_manager_addon = __commonJS({
|
|
|
62809
63057
|
}
|
|
62810
63058
|
return results;
|
|
62811
63059
|
}
|
|
63060
|
+
function liveOwnerOf(pctx, parentDeviceId) {
|
|
63061
|
+
if (!pctx.registry) return null;
|
|
63062
|
+
if (!pctx.registry.getById(parentDeviceId)) return null;
|
|
63063
|
+
return pctx.registry.getAddonId(parentDeviceId);
|
|
63064
|
+
}
|
|
63065
|
+
async function getChildren(pctx, input) {
|
|
63066
|
+
const { parentDeviceId } = input;
|
|
63067
|
+
let ownerAddonId = liveOwnerOf(pctx, parentDeviceId);
|
|
63068
|
+
if (!ownerAddonId) {
|
|
63069
|
+
const persisted = await pctx.metaStore.resolvePersistedById(parentDeviceId);
|
|
63070
|
+
if (!persisted) return [];
|
|
63071
|
+
ownerAddonId = persisted.addonId;
|
|
63072
|
+
}
|
|
63073
|
+
const childRows = await pctx.metaStore.rows.listByParent(parentDeviceId);
|
|
63074
|
+
const liveChildren = pctx.registry?.getChildren(parentDeviceId) ?? [];
|
|
63075
|
+
return [...await projectChildren(pctx, parentDeviceId, ownerAddonId, childRows, liveChildren)];
|
|
63076
|
+
}
|
|
63077
|
+
async function getChildrenBatch(pctx, input) {
|
|
63078
|
+
const parentIds = [...new Set(input.parentDeviceIds)];
|
|
63079
|
+
const out = {};
|
|
63080
|
+
if (parentIds.length === 0) return out;
|
|
63081
|
+
const owners = /* @__PURE__ */ new Map();
|
|
63082
|
+
const unownedIds = [];
|
|
63083
|
+
for (const parentId of parentIds) {
|
|
63084
|
+
const live = liveOwnerOf(pctx, parentId);
|
|
63085
|
+
if (live !== null) owners.set(parentId, live);
|
|
63086
|
+
else unownedIds.push(parentId);
|
|
63087
|
+
}
|
|
63088
|
+
if (unownedIds.length > 0) {
|
|
63089
|
+
const rows = await pctx.metaStore.rows.getMany(unownedIds);
|
|
63090
|
+
for (const [parentId, row] of rows) owners.set(parentId, row.meta.addonId);
|
|
63091
|
+
}
|
|
63092
|
+
const requested = new Set(parentIds);
|
|
63093
|
+
const rowsByParent = await pctx.metaStore.rows.listByParentMany([...owners.keys()]);
|
|
63094
|
+
const liveByParent = /* @__PURE__ */ new Map();
|
|
63095
|
+
for (const device of pctx.registry?.getAll() ?? []) {
|
|
63096
|
+
const parentId = device.parentDeviceId;
|
|
63097
|
+
if (parentId === null || parentId === void 0) continue;
|
|
63098
|
+
if (!requested.has(parentId)) continue;
|
|
63099
|
+
const bucket = liveByParent.get(parentId);
|
|
63100
|
+
if (bucket === void 0) liveByParent.set(parentId, [device]);
|
|
63101
|
+
else bucket.push(device);
|
|
63102
|
+
}
|
|
63103
|
+
for (const [parentId, ownerAddonId] of owners) {
|
|
63104
|
+
const childRows = rowsByParent.get(parentId) ?? [];
|
|
63105
|
+
const liveChildren = liveByParent.get(parentId) ?? [];
|
|
63106
|
+
if (childRows.length === 0 && liveChildren.length === 0) continue;
|
|
63107
|
+
const projected = await projectChildren(pctx, parentId, ownerAddonId, childRows, liveChildren);
|
|
63108
|
+
if (projected.length > 0) out[String(parentId)] = [...projected];
|
|
63109
|
+
}
|
|
63110
|
+
return out;
|
|
63111
|
+
}
|
|
62812
63112
|
async function getStreamSources(pctx, input) {
|
|
62813
63113
|
const { deviceId } = input;
|
|
62814
63114
|
if (pctx.registry) {
|
|
@@ -64255,16 +64555,17 @@ var require_device_manager_addon = __commonJS({
|
|
|
64255
64555
|
async function collectDescendants(metaStore, rootId) {
|
|
64256
64556
|
const out = [];
|
|
64257
64557
|
const visited = /* @__PURE__ */ new Set([rootId]);
|
|
64258
|
-
|
|
64259
|
-
while (
|
|
64260
|
-
const
|
|
64261
|
-
|
|
64262
|
-
for (const row of
|
|
64558
|
+
let frontier = [rootId];
|
|
64559
|
+
while (frontier.length > 0) {
|
|
64560
|
+
const byParent = await metaStore.rows.listByParentMany(frontier);
|
|
64561
|
+
const next = [];
|
|
64562
|
+
for (const parentId of frontier) for (const row of byParent.get(parentId) ?? []) {
|
|
64263
64563
|
if (visited.has(row.meta.id)) continue;
|
|
64264
64564
|
visited.add(row.meta.id);
|
|
64265
64565
|
out.push(row.meta);
|
|
64266
|
-
|
|
64566
|
+
next.push(row.meta.id);
|
|
64267
64567
|
}
|
|
64568
|
+
frontier = next;
|
|
64268
64569
|
}
|
|
64269
64570
|
return out;
|
|
64270
64571
|
}
|
|
@@ -65323,6 +65624,46 @@ var require_device_manager_addon = __commonJS({
|
|
|
65323
65624
|
limit: DEVICE_ROWS_FLEET_LIMIT
|
|
65324
65625
|
});
|
|
65325
65626
|
}
|
|
65627
|
+
/**
|
|
65628
|
+
* Direct children of MANY devices, in ONE query.
|
|
65629
|
+
*
|
|
65630
|
+
* `listByParent` called once per parent is the same shape `get` was before
|
|
65631
|
+
* {@link getMany}: an indexed lookup this store makes cheap, repeated until
|
|
65632
|
+
* it isn't. Measured on the live hub 2026-08-27, 120-second boot window:
|
|
65633
|
+
* `DeviceRowStore.list < DeviceRowStore.listByParent < getChildren` at
|
|
65634
|
+
* **1 024 calls** — one per device — for 919 rows in total. The BFS in
|
|
65635
|
+
* `device-meta-actions.ts#collectDescendants` walked a container tree the
|
|
65636
|
+
* same way, one query per node.
|
|
65637
|
+
*
|
|
65638
|
+
* `whereIn` on the indexed `parentDeviceId` column collapses a level of that
|
|
65639
|
+
* walk into a single statement. Contract, verbatim from {@link getMany}: an
|
|
65640
|
+
* EMPTY `parentDeviceIds` reads nothing at all rather than degrading to
|
|
65641
|
+
* "everything", and a parent with no children — or one the fleet does not
|
|
65642
|
+
* know — is simply ABSENT from the map, never an invented empty row.
|
|
65643
|
+
*/
|
|
65644
|
+
async listByParentMany(parentDeviceIds) {
|
|
65645
|
+
const out = /* @__PURE__ */ new Map();
|
|
65646
|
+
const unique = [...new Set(parentDeviceIds)];
|
|
65647
|
+
if (unique.length === 0) return out;
|
|
65648
|
+
const rows = await this.list({
|
|
65649
|
+
whereIn: { parentDeviceId: unique },
|
|
65650
|
+
orderBy: {
|
|
65651
|
+
field: "deviceId",
|
|
65652
|
+
direction: "asc"
|
|
65653
|
+
},
|
|
65654
|
+
limit: DEVICE_ROWS_FLEET_LIMIT
|
|
65655
|
+
});
|
|
65656
|
+
const byParent = /* @__PURE__ */ new Map();
|
|
65657
|
+
for (const row of rows) {
|
|
65658
|
+
const parent = row.meta.parentDeviceId;
|
|
65659
|
+
if (parent === null || parent === void 0) continue;
|
|
65660
|
+
const bucket = byParent.get(parent);
|
|
65661
|
+
if (bucket === void 0) byParent.set(parent, [row]);
|
|
65662
|
+
else bucket.push(row);
|
|
65663
|
+
}
|
|
65664
|
+
for (const [parent, bucket] of byParent) out.set(parent, bucket);
|
|
65665
|
+
return out;
|
|
65666
|
+
}
|
|
65326
65667
|
/** How many devices the fleet holds. Used to tell "empty store" from "gone device". */
|
|
65327
65668
|
async count() {
|
|
65328
65669
|
await this.declare();
|
|
@@ -66056,6 +66397,7 @@ var require_device_manager_addon = __commonJS({
|
|
|
66056
66397
|
listAll: (input) => listAll(pctx, input),
|
|
66057
66398
|
getDevice: (input) => getDevice(pctx, input),
|
|
66058
66399
|
getChildren: (input) => getChildren(pctx, input),
|
|
66400
|
+
getChildrenBatch: (input) => getChildrenBatch(pctx, input),
|
|
66059
66401
|
getLinkedDevices: (input) => getLinkedDevices(pctx, input),
|
|
66060
66402
|
getLinkedDevicesBatch: (input) => getLinkedDevicesBatch(pctx, input),
|
|
66061
66403
|
getDeviceSettingsContribution: (input) => buildLinkedDevicesContribution(pctx, input.deviceId),
|
|
@@ -66257,7 +66599,7 @@ var require_hub_forwarder = __commonJS({
|
|
|
66257
66599
|
[Symbol.toStringTag]: { value: "Module" }
|
|
66258
66600
|
});
|
|
66259
66601
|
require_chunk_Cek0wNdY();
|
|
66260
|
-
var require_dist10 =
|
|
66602
|
+
var require_dist10 = require_dist_BVU5JADq();
|
|
66261
66603
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
66262
66604
|
var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
|
|
66263
66605
|
var HubForwarderDestination = class {
|
|
@@ -66394,7 +66736,7 @@ var require_liveness_monitor_addon = __commonJS({
|
|
|
66394
66736
|
"use strict";
|
|
66395
66737
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
66396
66738
|
require_chunk_Cek0wNdY();
|
|
66397
|
-
var require_dist10 =
|
|
66739
|
+
var require_dist10 = require_dist_BVU5JADq();
|
|
66398
66740
|
var NO_DEVICES = "liveness:no-devices";
|
|
66399
66741
|
var ALL_OFFLINE = "liveness:all-devices-offline";
|
|
66400
66742
|
var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
|
|
@@ -66584,7 +66926,7 @@ var require_local_auth_addon = __commonJS({
|
|
|
66584
66926
|
[Symbol.toStringTag]: { value: "Module" }
|
|
66585
66927
|
});
|
|
66586
66928
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
66587
|
-
var require_dist10 =
|
|
66929
|
+
var require_dist10 = require_dist_BVU5JADq();
|
|
66588
66930
|
var node_crypto = __require("crypto");
|
|
66589
66931
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
66590
66932
|
var crypto$1 = __require("crypto");
|
|
@@ -74268,7 +74610,7 @@ var require_loki_logging = __commonJS({
|
|
|
74268
74610
|
[Symbol.toStringTag]: { value: "Module" }
|
|
74269
74611
|
});
|
|
74270
74612
|
require_chunk_Cek0wNdY();
|
|
74271
|
-
var require_dist10 =
|
|
74613
|
+
var require_dist10 = require_dist_BVU5JADq();
|
|
74272
74614
|
function sanitizeLabelName(raw) {
|
|
74273
74615
|
const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
|
|
74274
74616
|
return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
|
|
@@ -74833,7 +75175,7 @@ var require_native_metrics_addon = __commonJS({
|
|
|
74833
75175
|
[Symbol.toStringTag]: { value: "Module" }
|
|
74834
75176
|
});
|
|
74835
75177
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
74836
|
-
var require_dist10 =
|
|
75178
|
+
var require_dist10 = require_dist_BVU5JADq();
|
|
74837
75179
|
var node_child_process = __require("child_process");
|
|
74838
75180
|
var node_util = __require("util");
|
|
74839
75181
|
var node_os = __require("os");
|
|
@@ -75775,7 +76117,7 @@ var require_filesystem_storage_addon = __commonJS({
|
|
|
75775
76117
|
[Symbol.toStringTag]: { value: "Module" }
|
|
75776
76118
|
});
|
|
75777
76119
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
75778
|
-
var require_dist10 =
|
|
76120
|
+
var require_dist10 = require_dist_BVU5JADq();
|
|
75779
76121
|
var node_crypto = __require("crypto");
|
|
75780
76122
|
var node_fs_promises = __require("fs/promises");
|
|
75781
76123
|
var node_path = __require("path");
|
|
@@ -76891,8 +77233,8 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
76891
77233
|
[Symbol.toStringTag]: { value: "Module" }
|
|
76892
77234
|
});
|
|
76893
77235
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
76894
|
-
var require_dist10 =
|
|
76895
|
-
var require_retired_settings_keys =
|
|
77236
|
+
var require_dist10 = require_dist_BVU5JADq();
|
|
77237
|
+
var require_retired_settings_keys = require_retired_settings_keys_PLI9w0k();
|
|
76896
77238
|
var node_crypto = __require("crypto");
|
|
76897
77239
|
var node_fs = __require("fs");
|
|
76898
77240
|
var node_module = __require("module");
|
|
@@ -77733,6 +78075,46 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
77733
78075
|
params
|
|
77734
78076
|
}, () => this.getDb().prepare(sql).get(...params))?.cnt ?? 0;
|
|
77735
78077
|
}
|
|
78078
|
+
/**
|
|
78079
|
+
* `COUNT(*)` and one SUM/MIN/MAX per requested field, in ONE statement.
|
|
78080
|
+
*
|
|
78081
|
+
* Result slots are aliased POSITIONALLY (`a0`, `a1`, …) and mapped back by
|
|
78082
|
+
* index: `as` is caller text and must never reach SQL as an identifier, and
|
|
78083
|
+
* the column name is validated through {@link fieldExprFor} exactly like a
|
|
78084
|
+
* projection — an unresolvable field THROWS rather than being dropped, because
|
|
78085
|
+
* a missing aggregate comes back as a number that looks real.
|
|
78086
|
+
*
|
|
78087
|
+
* `select` mode on the filter, so this agrees with `query` and `count` about
|
|
78088
|
+
* which rows it is talking about.
|
|
78089
|
+
*/
|
|
78090
|
+
async aggregate({ namespace, collection, fields, filter }) {
|
|
78091
|
+
const scoped = this.scopedName(namespace, collection);
|
|
78092
|
+
const decl = this.requireDeclared(scoped);
|
|
78093
|
+
const shape = this.shapeOf(decl);
|
|
78094
|
+
const selects = ['COUNT(*) AS "n"'];
|
|
78095
|
+
fields.forEach((f, i) => {
|
|
78096
|
+
const col = fieldExprFor(f.field, shape);
|
|
78097
|
+
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`);
|
|
78098
|
+
selects.push(`${f.op.toUpperCase()}(${col}) AS "a${i}"`);
|
|
78099
|
+
});
|
|
78100
|
+
const { whereSql, params } = compileFilter$1(filter, shape, "select", (v) => this.serializeColumnValue(v));
|
|
78101
|
+
const sql = `SELECT ${selects.join(", ")} FROM "${scoped}"${whereSql}`;
|
|
78102
|
+
const row = this.measured({
|
|
78103
|
+
op: "aggregate",
|
|
78104
|
+
collection: scoped,
|
|
78105
|
+
sql,
|
|
78106
|
+
params
|
|
78107
|
+
}, () => this.getDb().prepare(sql).get(...params));
|
|
78108
|
+
const values = {};
|
|
78109
|
+
fields.forEach((f, i) => {
|
|
78110
|
+
const raw = row?.[`a${i}`];
|
|
78111
|
+
values[f.as] = typeof raw === "number" && Number.isFinite(raw) ? raw : null;
|
|
78112
|
+
});
|
|
78113
|
+
return {
|
|
78114
|
+
count: row?.["n"] ?? 0,
|
|
78115
|
+
values
|
|
78116
|
+
};
|
|
78117
|
+
}
|
|
77736
78118
|
async histogram({ namespace, collection, field, bucketSize, origin, filter }) {
|
|
77737
78119
|
const scoped = this.scopedName(namespace, collection);
|
|
77738
78120
|
const decl = this.requireDeclared(scoped);
|
|
@@ -79086,7 +79468,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
79086
79468
|
[Symbol.toStringTag]: { value: "Module" }
|
|
79087
79469
|
});
|
|
79088
79470
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
79089
|
-
var require_dist10 =
|
|
79471
|
+
var require_dist10 = require_dist_BVU5JADq();
|
|
79090
79472
|
var node_crypto = __require("crypto");
|
|
79091
79473
|
var node_fs_promises = __require("fs/promises");
|
|
79092
79474
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
@@ -79655,6 +80037,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
79655
80037
|
deleteWhere: async (input) => (await engine()).deleteWhere(input),
|
|
79656
80038
|
updateWhere: async (input) => (await engine()).updateWhere(input),
|
|
79657
80039
|
count: async (input) => (await engine()).count(input),
|
|
80040
|
+
aggregate: async (input) => (await engine()).aggregate(input),
|
|
79658
80041
|
histogram: async (input) => (await engine()).histogram(input),
|
|
79659
80042
|
isEmpty: async (input) => (await engine()).isEmpty(input),
|
|
79660
80043
|
declareCollection: async (input) => (await engine()).declareCollection(input)
|
|
@@ -80965,7 +81348,7 @@ var require_system_config_addon = __commonJS({
|
|
|
80965
81348
|
[Symbol.toStringTag]: { value: "Module" }
|
|
80966
81349
|
});
|
|
80967
81350
|
require_chunk_Cek0wNdY();
|
|
80968
|
-
var require_dist10 =
|
|
81351
|
+
var require_dist10 = require_dist_BVU5JADq();
|
|
80969
81352
|
var SECTION_TITLES = {
|
|
80970
81353
|
server: "Server",
|
|
80971
81354
|
auth: "Authentication"
|
|
@@ -99026,7 +99409,7 @@ var require_winston_logging = __commonJS({
|
|
|
99026
99409
|
[Symbol.toStringTag]: { value: "Module" }
|
|
99027
99410
|
});
|
|
99028
99411
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
99029
|
-
var require_dist10 =
|
|
99412
|
+
var require_dist10 = require_dist_BVU5JADq();
|
|
99030
99413
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
99031
99414
|
var node_path = __require("path");
|
|
99032
99415
|
node_path = require_chunk.__toESM(node_path);
|
|
@@ -104814,6 +105197,7 @@ var require_addon = __commonJS({
|
|
|
104814
105197
|
"listProfiles",
|
|
104815
105198
|
"listRuntimeNodes"
|
|
104816
105199
|
],
|
|
105200
|
+
"log-channels": ["list"],
|
|
104817
105201
|
"log-destination": ["query"],
|
|
104818
105202
|
"login-method": ["getLoginMethods"],
|
|
104819
105203
|
"mqtt-broker": ["listBrokers"],
|
|
@@ -111401,11 +111785,12 @@ var require_dist2 = __commonJS({
|
|
|
111401
111785
|
}
|
|
111402
111786
|
});
|
|
111403
111787
|
|
|
111404
|
-
// ../system/dist/manifest-python-deps-
|
|
111405
|
-
var
|
|
111406
|
-
"../system/dist/manifest-python-deps-
|
|
111788
|
+
// ../system/dist/manifest-python-deps-GjlyPjm0.js
|
|
111789
|
+
var require_manifest_python_deps_GjlyPjm0 = __commonJS({
|
|
111790
|
+
"../system/dist/manifest-python-deps-GjlyPjm0.js"(exports) {
|
|
111407
111791
|
"use strict";
|
|
111408
111792
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
111793
|
+
require_dist_BVU5JADq();
|
|
111409
111794
|
var node_crypto = __require("crypto");
|
|
111410
111795
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
111411
111796
|
var _camstack_types_node = require_node();
|
|
@@ -112479,6 +112864,105 @@ var require_manifest_python_deps_B_mCU6gz = __commonJS({
|
|
|
112479
112864
|
});
|
|
112480
112865
|
return true;
|
|
112481
112866
|
}
|
|
112867
|
+
function parseChildRows(value) {
|
|
112868
|
+
if (!Array.isArray(value)) return null;
|
|
112869
|
+
const rows = [];
|
|
112870
|
+
for (const entry of value) {
|
|
112871
|
+
if (typeof entry !== "object" || entry === null) return null;
|
|
112872
|
+
const id = Reflect.get(entry, "id");
|
|
112873
|
+
const stableId = Reflect.get(entry, "stableId");
|
|
112874
|
+
const name = Reflect.get(entry, "name");
|
|
112875
|
+
const linkDeviceId = Reflect.get(entry, "linkDeviceId");
|
|
112876
|
+
if (typeof id !== "number" || typeof stableId !== "string" || typeof name !== "string") return null;
|
|
112877
|
+
rows.push({
|
|
112878
|
+
id,
|
|
112879
|
+
stableId,
|
|
112880
|
+
name,
|
|
112881
|
+
...typeof linkDeviceId === "number" || linkDeviceId === null ? { linkDeviceId } : {}
|
|
112882
|
+
});
|
|
112883
|
+
}
|
|
112884
|
+
return rows;
|
|
112885
|
+
}
|
|
112886
|
+
function parseChildrenByParent(value) {
|
|
112887
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
112888
|
+
const out = /* @__PURE__ */ new Map();
|
|
112889
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
112890
|
+
const parentDeviceId = Number(key);
|
|
112891
|
+
if (!Number.isInteger(parentDeviceId)) return null;
|
|
112892
|
+
const rows = parseChildRows(entry);
|
|
112893
|
+
if (rows === null) return null;
|
|
112894
|
+
out.set(parentDeviceId, rows);
|
|
112895
|
+
}
|
|
112896
|
+
return out;
|
|
112897
|
+
}
|
|
112898
|
+
function looksLikeUnknownMethod(err) {
|
|
112899
|
+
const lowered = (err instanceof Error ? err.message : String(err)).toLowerCase();
|
|
112900
|
+
return lowered.includes("not_found") || lowered.includes("no procedure") || lowered.includes("unknown method") || lowered.includes("no such method") || lowered.includes("not available");
|
|
112901
|
+
}
|
|
112902
|
+
function createChildrenBatchLoader(opts) {
|
|
112903
|
+
const maxBatch = Math.max(1, opts.maxBatch ?? 256);
|
|
112904
|
+
let pending = [];
|
|
112905
|
+
let flushScheduled = false;
|
|
112906
|
+
let flushing = false;
|
|
112907
|
+
let batchUnsupported = false;
|
|
112908
|
+
const answerOneByOne = async (waiters) => {
|
|
112909
|
+
await Promise.all(waiters.map(async (waiter) => {
|
|
112910
|
+
try {
|
|
112911
|
+
waiter.resolve(await opts.fetchOne(waiter.parentDeviceId));
|
|
112912
|
+
} catch (err) {
|
|
112913
|
+
waiter.reject(err);
|
|
112914
|
+
}
|
|
112915
|
+
}));
|
|
112916
|
+
};
|
|
112917
|
+
const runBatch = async (waiters) => {
|
|
112918
|
+
const parentDeviceIds = [...new Set(waiters.map((w) => w.parentDeviceId))];
|
|
112919
|
+
try {
|
|
112920
|
+
const byParent = await opts.fetchBatch(parentDeviceIds);
|
|
112921
|
+
for (const waiter of waiters) waiter.resolve(byParent.get(waiter.parentDeviceId) ?? []);
|
|
112922
|
+
} catch (err) {
|
|
112923
|
+
const unknown = looksLikeUnknownMethod(err);
|
|
112924
|
+
if (unknown) batchUnsupported = true;
|
|
112925
|
+
opts.logger.warn(unknown ? "device-manager.getChildrenBatch is not available on this hub \u2014 falling back to per-parent getChildren for the process lifetime" : "device-manager.getChildrenBatch failed \u2014 answering this batch per parent", { meta: {
|
|
112926
|
+
parents: parentDeviceIds.length,
|
|
112927
|
+
error: err instanceof Error ? err.message : String(err)
|
|
112928
|
+
} });
|
|
112929
|
+
await answerOneByOne(waiters);
|
|
112930
|
+
}
|
|
112931
|
+
};
|
|
112932
|
+
const flush = async () => {
|
|
112933
|
+
flushing = true;
|
|
112934
|
+
try {
|
|
112935
|
+
while (pending.length > 0) {
|
|
112936
|
+
const batch = pending.slice(0, maxBatch);
|
|
112937
|
+
pending = pending.slice(maxBatch);
|
|
112938
|
+
if (batchUnsupported) await answerOneByOne(batch);
|
|
112939
|
+
else await runBatch(batch);
|
|
112940
|
+
}
|
|
112941
|
+
} finally {
|
|
112942
|
+
flushing = false;
|
|
112943
|
+
}
|
|
112944
|
+
};
|
|
112945
|
+
const scheduleFlush = () => {
|
|
112946
|
+
if (flushScheduled || flushing) return;
|
|
112947
|
+
flushScheduled = true;
|
|
112948
|
+
setTimeout(() => {
|
|
112949
|
+
flushScheduled = false;
|
|
112950
|
+
flush().catch((err) => {
|
|
112951
|
+
opts.logger.warn("children batch flush threw", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
112952
|
+
});
|
|
112953
|
+
}, 0);
|
|
112954
|
+
};
|
|
112955
|
+
return { load(parentDeviceId) {
|
|
112956
|
+
return new Promise((resolve, reject) => {
|
|
112957
|
+
pending.push({
|
|
112958
|
+
parentDeviceId,
|
|
112959
|
+
resolve,
|
|
112960
|
+
reject
|
|
112961
|
+
});
|
|
112962
|
+
scheduleFlush();
|
|
112963
|
+
});
|
|
112964
|
+
} };
|
|
112965
|
+
}
|
|
112482
112966
|
var ACCESSORY_SPAWN_CONCURRENCY = 12;
|
|
112483
112967
|
async function runBounded(items, limit, worker2) {
|
|
112484
112968
|
const effectiveLimit = Math.max(1, Math.min(limit, items.length));
|
|
@@ -112686,6 +113170,19 @@ var require_manifest_python_deps_B_mCU6gz = __commonJS({
|
|
|
112686
113170
|
api
|
|
112687
113171
|
});
|
|
112688
113172
|
let selfApi;
|
|
113173
|
+
const childrenLoader = createChildrenBatchLoader({
|
|
113174
|
+
logger: opts.logger,
|
|
113175
|
+
fetchBatch: async (parentDeviceIds) => {
|
|
113176
|
+
const parsed = parseChildrenByParent(await callDeviceManager(api, "getChildrenBatch", { parentDeviceIds }));
|
|
113177
|
+
if (parsed === null) throw new Error("getChildrenBatch returned a shape this kernel cannot read");
|
|
113178
|
+
return parsed;
|
|
113179
|
+
},
|
|
113180
|
+
fetchOne: async (parentDeviceId) => {
|
|
113181
|
+
const parsed = parseChildRows(await callDeviceManager(api, "getChildren", { parentDeviceId }));
|
|
113182
|
+
if (parsed === null) throw new Error("getChildren returned a shape this kernel cannot read");
|
|
113183
|
+
return parsed;
|
|
113184
|
+
}
|
|
113185
|
+
});
|
|
112689
113186
|
const deviceRebuildFactories = /* @__PURE__ */ new Map();
|
|
112690
113187
|
const buildContext = (stableId, id, parentDeviceId = null, initialRuntimeState = {}, persistedConfig = {}, deviceMeta = null) => {
|
|
112691
113188
|
let runtimeStateRef = null;
|
|
@@ -113097,7 +113594,7 @@ var require_manifest_python_deps_B_mCU6gz = __commonJS({
|
|
|
113097
113594
|
return;
|
|
113098
113595
|
}
|
|
113099
113596
|
try {
|
|
113100
|
-
const currentChildren = await
|
|
113597
|
+
const currentChildren = await childrenLoader.load(device.id);
|
|
113101
113598
|
for (const child of currentChildren) {
|
|
113102
113599
|
if (child.linkDeviceId !== device.id) continue;
|
|
113103
113600
|
if (expected.has(child.stableId)) continue;
|
|
@@ -122274,7 +122771,7 @@ var require_dist3 = __commonJS({
|
|
|
122274
122771
|
"use strict";
|
|
122275
122772
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
122276
122773
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
122277
|
-
var require_dist10 =
|
|
122774
|
+
var require_dist10 = require_dist_BVU5JADq();
|
|
122278
122775
|
var require_builtins_alerts_alerts_addon = require_alerts_addon();
|
|
122279
122776
|
require_alerts();
|
|
122280
122777
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
@@ -122300,7 +122797,7 @@ var require_dist3 = __commonJS({
|
|
|
122300
122797
|
var require_builtins_winston_logging_index = require_winston_logging();
|
|
122301
122798
|
var require_file_data_plane = require_file_data_plane_DO8KbxCe();
|
|
122302
122799
|
var require_tls$1 = require_tls_u8QCJCFE();
|
|
122303
|
-
var require_manifest_python_deps =
|
|
122800
|
+
var require_manifest_python_deps = require_manifest_python_deps_GjlyPjm0();
|
|
122304
122801
|
var require_resource_monitor = require_resource_monitor_CdnzxBLP();
|
|
122305
122802
|
var require_custom_action_registry = require_custom_action_registry_jY0NOZK8();
|
|
122306
122803
|
var zod = require_zod();
|
|
@@ -203840,6 +204337,253 @@ var require_dist4 = __commonJS({
|
|
|
203840
204337
|
function logLevelAtMost(level, threshold) {
|
|
203841
204338
|
return LOG_LEVEL_RANK[level] <= LOG_LEVEL_RANK[threshold];
|
|
203842
204339
|
}
|
|
204340
|
+
var LogChannelLevelSchema = zod.z.enum([
|
|
204341
|
+
"info",
|
|
204342
|
+
"warn",
|
|
204343
|
+
"error"
|
|
204344
|
+
]);
|
|
204345
|
+
var LogChannelDescriptorSchema = zod.z.object({
|
|
204346
|
+
/**
|
|
204347
|
+
* Dotted `area.thing`, unique across the workspace. `area` is conventionally
|
|
204348
|
+
* the addon's short name so an operator reading a channel list can tell who
|
|
204349
|
+
* owns it without a second lookup.
|
|
204350
|
+
*/
|
|
204351
|
+
name: zod.z.string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
|
|
204352
|
+
/** One sentence: what the operator will SEE after arming it. */
|
|
204353
|
+
description: zod.z.string().min(1),
|
|
204354
|
+
/** The level its lines are emitted at. Never below `info`. */
|
|
204355
|
+
defaultLevel: LogChannelLevelSchema,
|
|
204356
|
+
/**
|
|
204357
|
+
* Whether this channel can be narrowed to a camera.
|
|
204358
|
+
*
|
|
204359
|
+
* `true` is a PROMISE with two halves, and both must hold: the gate is
|
|
204360
|
+
* consulted with the numeric device id, AND every line the channel admits
|
|
204361
|
+
* carries `tags: { deviceId }` with that same numeric id. The second half is
|
|
204362
|
+
* what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
|
|
204363
|
+
* keeps `deviceId` out of the stream labels for cardinality, so the tag in
|
|
204364
|
+
* the body is the only way to filter.
|
|
204365
|
+
*
|
|
204366
|
+
* A channel whose lines carry the device only in `meta` (or not at all) is
|
|
204367
|
+
* declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
|
|
204368
|
+
* the operator narrows to one camera, sees nothing, and concludes the code
|
|
204369
|
+
* path was never taken.
|
|
204370
|
+
*/
|
|
204371
|
+
perDevice: zod.z.boolean()
|
|
204372
|
+
});
|
|
204373
|
+
var LogChannelWindowSchema = zod.z.object({
|
|
204374
|
+
channel: zod.z.string().min(1),
|
|
204375
|
+
/** Epoch ms the window closes at. */
|
|
204376
|
+
armedUntilMs: zod.z.number(),
|
|
204377
|
+
/** `null` = every camera. A non-empty list narrows to those numeric ids. */
|
|
204378
|
+
deviceIds: zod.z.array(zod.z.number().int()).readonly().nullable()
|
|
204379
|
+
});
|
|
204380
|
+
var LogChannelGate = class {
|
|
204381
|
+
descriptor;
|
|
204382
|
+
/**
|
|
204383
|
+
* HOT PATH GUARD. A plain data FIELD, and it must stay one.
|
|
204384
|
+
*
|
|
204385
|
+
* `log-channel.spec.ts` asserts the property descriptor has no getter and
|
|
204386
|
+
* booby-traps the device set, so turning this into an accessor — or reading
|
|
204387
|
+
* anything before it — fails the spec instead of taxing every line the
|
|
204388
|
+
* process emits.
|
|
204389
|
+
*/
|
|
204390
|
+
on = false;
|
|
204391
|
+
/** `null` while armed for every camera. Never read while `on` is false. */
|
|
204392
|
+
devices = null;
|
|
204393
|
+
level;
|
|
204394
|
+
closesAtMs = 0;
|
|
204395
|
+
constructor(descriptor) {
|
|
204396
|
+
this.descriptor = descriptor;
|
|
204397
|
+
this.level = descriptor.defaultLevel;
|
|
204398
|
+
}
|
|
204399
|
+
/** Epoch ms this channel disarms itself at. 0 when disarmed. */
|
|
204400
|
+
get armedUntilMs() {
|
|
204401
|
+
return this.on ? this.closesAtMs : 0;
|
|
204402
|
+
}
|
|
204403
|
+
/**
|
|
204404
|
+
* Does this channel want a line about `deviceId`?
|
|
204405
|
+
*
|
|
204406
|
+
* Call it only behind `gate.on &&`. On its own it is still correct — the
|
|
204407
|
+
* guard is repeated inside — but the point of the prefix is that a disarmed
|
|
204408
|
+
* channel must not pay the call at all.
|
|
204409
|
+
*/
|
|
204410
|
+
wants(deviceId) {
|
|
204411
|
+
if (!this.on) return false;
|
|
204412
|
+
return this.devices === null || this.devices.has(deviceId);
|
|
204413
|
+
}
|
|
204414
|
+
/**
|
|
204415
|
+
* Emit one line on this channel, at the channel's declared level.
|
|
204416
|
+
*
|
|
204417
|
+
* The channel name is added as `tags.logChannel` so LogQL can select the
|
|
204418
|
+
* channel without matching on the message text, and whatever `tags` the
|
|
204419
|
+
* caller passed — `deviceId` above all — is preserved.
|
|
204420
|
+
*/
|
|
204421
|
+
log(logger, message, extras) {
|
|
204422
|
+
if (!this.on) return;
|
|
204423
|
+
const tags = {
|
|
204424
|
+
...extras.tags,
|
|
204425
|
+
logChannel: this.descriptor.name
|
|
204426
|
+
};
|
|
204427
|
+
const line = {
|
|
204428
|
+
...extras,
|
|
204429
|
+
tags
|
|
204430
|
+
};
|
|
204431
|
+
if (this.level === "error") logger.error(message, line);
|
|
204432
|
+
else if (this.level === "warn") logger.warn(message, line);
|
|
204433
|
+
else logger.info(message, line);
|
|
204434
|
+
}
|
|
204435
|
+
/**
|
|
204436
|
+
* Arm (or RE-arm, restarting) this channel. Off the hot path only.
|
|
204437
|
+
*
|
|
204438
|
+
* An empty `deviceIds` list is treated as "every camera" rather than "no
|
|
204439
|
+
* camera": a window that matches nothing is indistinguishable from a
|
|
204440
|
+
* disarmed one, and the operator who asked for it would wait for lines that
|
|
204441
|
+
* can never come.
|
|
204442
|
+
*/
|
|
204443
|
+
arm(window2) {
|
|
204444
|
+
const ids = window2.deviceIds;
|
|
204445
|
+
this.devices = ids === null || ids.length === 0 ? null : new Set(ids);
|
|
204446
|
+
this.closesAtMs = window2.armedUntilMs;
|
|
204447
|
+
this.on = true;
|
|
204448
|
+
}
|
|
204449
|
+
/** Disarm. Off the hot path only. */
|
|
204450
|
+
disarm() {
|
|
204451
|
+
this.on = false;
|
|
204452
|
+
this.devices = null;
|
|
204453
|
+
this.closesAtMs = 0;
|
|
204454
|
+
}
|
|
204455
|
+
};
|
|
204456
|
+
var LogChannelRegistry = class {
|
|
204457
|
+
gates = /* @__PURE__ */ new Map();
|
|
204458
|
+
/**
|
|
204459
|
+
* Declare a channel and get its gate.
|
|
204460
|
+
*
|
|
204461
|
+
* A duplicate name throws. Two declarations of one name is a programming
|
|
204462
|
+
* error, not a merge: the operator would arm one and the other would stay
|
|
204463
|
+
* dark, which is the dead-knob shape (D62) with an extra step.
|
|
204464
|
+
*/
|
|
204465
|
+
declare(descriptor) {
|
|
204466
|
+
const parsed = LogChannelDescriptorSchema.parse(descriptor);
|
|
204467
|
+
if (this.gates.get(parsed.name) !== void 0) throw new Error(`log channel "${parsed.name}" is already declared in this process \u2014 two declarations of one name is a programming error, not a merge`);
|
|
204468
|
+
const gate = new LogChannelGate(parsed);
|
|
204469
|
+
this.gates.set(parsed.name, gate);
|
|
204470
|
+
return gate;
|
|
204471
|
+
}
|
|
204472
|
+
/** The declarations, sorted by name so a list is stable to read and diff. */
|
|
204473
|
+
list() {
|
|
204474
|
+
return [...this.gates.values()].map((gate) => gate.descriptor).sort((a, b) => a.name.localeCompare(b.name));
|
|
204475
|
+
}
|
|
204476
|
+
/** The gate for a declared channel, or `undefined`. */
|
|
204477
|
+
gate(name) {
|
|
204478
|
+
return this.gates.get(name);
|
|
204479
|
+
}
|
|
204480
|
+
/**
|
|
204481
|
+
* Apply the FULL set of armed windows. Off the hot path.
|
|
204482
|
+
*
|
|
204483
|
+
* Full, not incremental, and that is the whole design: the document is the
|
|
204484
|
+
* authority, so a channel the document does not name is disarmed here. An
|
|
204485
|
+
* incremental apply would let a disarm get lost in transit and leave a
|
|
204486
|
+
* channel running that nobody can see is running.
|
|
204487
|
+
*
|
|
204488
|
+
* A window already past its deadline is ignored rather than armed — a
|
|
204489
|
+
* restore that re-armed an expired window would make a forgotten diagnostic
|
|
204490
|
+
* immortal across restarts.
|
|
204491
|
+
*
|
|
204492
|
+
* Returns the names it could not place, so the caller can log them: a
|
|
204493
|
+
* channel named in the document that this process does not declare is
|
|
204494
|
+
* either a typo or an addon that has not booted yet, and both deserve a
|
|
204495
|
+
* line rather than silence.
|
|
204496
|
+
*/
|
|
204497
|
+
apply(windows, nowMs) {
|
|
204498
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
204499
|
+
const unknown = [];
|
|
204500
|
+
for (const window2 of windows) {
|
|
204501
|
+
if (window2.armedUntilMs <= nowMs) continue;
|
|
204502
|
+
if (!this.gates.has(window2.channel)) {
|
|
204503
|
+
unknown.push(window2.channel);
|
|
204504
|
+
continue;
|
|
204505
|
+
}
|
|
204506
|
+
wanted.set(window2.channel, window2);
|
|
204507
|
+
}
|
|
204508
|
+
for (const [name, gate] of this.gates) {
|
|
204509
|
+
const window2 = wanted.get(name);
|
|
204510
|
+
if (window2 === void 0) gate.disarm();
|
|
204511
|
+
else gate.arm(window2);
|
|
204512
|
+
}
|
|
204513
|
+
return unknown;
|
|
204514
|
+
}
|
|
204515
|
+
/**
|
|
204516
|
+
* Disarm whatever has run out. Called on a timer, NEVER from a log path — a
|
|
204517
|
+
* diagnostic that adds a `Date.now()` to the path it is measuring measures
|
|
204518
|
+
* itself.
|
|
204519
|
+
*
|
|
204520
|
+
* Returns the names it closed, so the caller can write the one line that
|
|
204521
|
+
* says a window ended and stops "it went quiet" from reading as "the branch
|
|
204522
|
+
* was not taken".
|
|
204523
|
+
*/
|
|
204524
|
+
tick(nowMs) {
|
|
204525
|
+
const closed = [];
|
|
204526
|
+
for (const [name, gate] of this.gates) if (gate.on && gate.armedUntilMs <= nowMs) {
|
|
204527
|
+
gate.disarm();
|
|
204528
|
+
closed.push(name);
|
|
204529
|
+
}
|
|
204530
|
+
return closed;
|
|
204531
|
+
}
|
|
204532
|
+
/** The channels armed right now, as the document would describe them. */
|
|
204533
|
+
armed() {
|
|
204534
|
+
const out = [];
|
|
204535
|
+
for (const [name, gate] of this.gates) if (gate.on) out.push({
|
|
204536
|
+
channel: name,
|
|
204537
|
+
armedUntilMs: gate.armedUntilMs,
|
|
204538
|
+
deviceIds: null
|
|
204539
|
+
});
|
|
204540
|
+
return out;
|
|
204541
|
+
}
|
|
204542
|
+
};
|
|
204543
|
+
var instance = null;
|
|
204544
|
+
function getLogChannelRegistry() {
|
|
204545
|
+
instance ??= new LogChannelRegistry();
|
|
204546
|
+
return instance;
|
|
204547
|
+
}
|
|
204548
|
+
function declareLogChannel(descriptor) {
|
|
204549
|
+
return getLogChannelRegistry().declare(descriptor);
|
|
204550
|
+
}
|
|
204551
|
+
function __resetLogChannelRegistryForTests() {
|
|
204552
|
+
instance = null;
|
|
204553
|
+
}
|
|
204554
|
+
var LOG_CHANNEL_TICK_MS = 5e3;
|
|
204555
|
+
function createLogChannelsProvider(logger, options = {}) {
|
|
204556
|
+
const registry = getLogChannelRegistry();
|
|
204557
|
+
const now = options.now ?? Date.now;
|
|
204558
|
+
const tickMs = options.tickMs ?? 5e3;
|
|
204559
|
+
const timer = setInterval(() => {
|
|
204560
|
+
const closed = registry.tick(now());
|
|
204561
|
+
for (const name of closed) logger.info("log channel window closed", {
|
|
204562
|
+
tags: { logChannel: name },
|
|
204563
|
+
meta: { channel: name }
|
|
204564
|
+
});
|
|
204565
|
+
}, tickMs);
|
|
204566
|
+
timer.unref?.();
|
|
204567
|
+
return {
|
|
204568
|
+
list: () => registry.list(),
|
|
204569
|
+
apply: (input) => {
|
|
204570
|
+
const unknown = registry.apply(input.windows, now());
|
|
204571
|
+
const armed = registry.armed();
|
|
204572
|
+
logger.info("log channels applied", { meta: {
|
|
204573
|
+
armed: armed.map((window2) => window2.channel),
|
|
204574
|
+
unknown,
|
|
204575
|
+
declared: registry.list().length
|
|
204576
|
+
} });
|
|
204577
|
+
return {
|
|
204578
|
+
armed: armed.length,
|
|
204579
|
+
unknown
|
|
204580
|
+
};
|
|
204581
|
+
},
|
|
204582
|
+
stop: () => {
|
|
204583
|
+
clearInterval(timer);
|
|
204584
|
+
}
|
|
204585
|
+
};
|
|
204586
|
+
}
|
|
203843
204587
|
var OpsLogDomainSchema = zod.z.enum(["recording", "events"]);
|
|
203844
204588
|
var OpsLogOpSchema = zod.z.enum([
|
|
203845
204589
|
"prune",
|
|
@@ -208416,6 +209160,21 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
208416
209160
|
whereBetween: zod.z.record(zod.z.string(), zod.z.tuple([zod.z.unknown(), zod.z.unknown()])).optional(),
|
|
208417
209161
|
whereNot: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
|
|
208418
209162
|
});
|
|
209163
|
+
var AggregateFieldSchema = zod.z.object({
|
|
209164
|
+
/** Result key. */
|
|
209165
|
+
as: zod.z.string().min(1),
|
|
209166
|
+
/** Column to aggregate. Must be a real column of a declared collection. */
|
|
209167
|
+
field: zod.z.string().min(1),
|
|
209168
|
+
op: zod.z.enum([
|
|
209169
|
+
"sum",
|
|
209170
|
+
"min",
|
|
209171
|
+
"max"
|
|
209172
|
+
])
|
|
209173
|
+
});
|
|
209174
|
+
var AggregateResultSchema = zod.z.object({
|
|
209175
|
+
count: zod.z.number().int(),
|
|
209176
|
+
values: zod.z.record(zod.z.string(), zod.z.number().nullable())
|
|
209177
|
+
});
|
|
208419
209178
|
var SettingsRecordSchema = zod.z.object({
|
|
208420
209179
|
id: zod.z.string(),
|
|
208421
209180
|
data: zod.z.record(zod.z.string(), zod.z.unknown())
|
|
@@ -208543,6 +209302,32 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
208543
209302
|
collection: zod.z.string(),
|
|
208544
209303
|
filter: QueryFilterSchema.optional()
|
|
208545
209304
|
}), zod.z.number()),
|
|
209305
|
+
/**
|
|
209306
|
+
* `COUNT(*)` and one `SUM` / `MIN` / `MAX` per requested field, in ONE
|
|
209307
|
+
* statement, over the rows `filter` selects.
|
|
209308
|
+
*
|
|
209309
|
+
* Exists because "how much is there" was being answered by materialising
|
|
209310
|
+
* "what is there". The recorder's storage-pressure sweep asked its in-RAM
|
|
209311
|
+
* footage index for bytes/count/oldest/newest across a set of storage
|
|
209312
|
+
* locations twice a minute, and the only way to answer that from a map is
|
|
209313
|
+
* to visit every row — 7.1 M of them on the live hub, ~15 M row visits a
|
|
209314
|
+
* minute on the main thread, which is also why the whole archive had to
|
|
209315
|
+
* stay resident to be visited. The question is a sum; nothing needs to be
|
|
209316
|
+
* materialised to answer it.
|
|
209317
|
+
*
|
|
209318
|
+
* **The engine REFUSES a field it cannot serve**, exactly as
|
|
209319
|
+
* `query.columns` does and unlike a PREDICATE, which is skipped when
|
|
209320
|
+
* unresolvable. A dropped predicate over-matches and the caller sees extra
|
|
209321
|
+
* rows; a dropped aggregate returns a NUMBER that is wrong and looks
|
|
209322
|
+
* exactly like a real one. That asymmetry is what this repo has already
|
|
209323
|
+
* paid for once in `count`.
|
|
209324
|
+
*/
|
|
209325
|
+
aggregate: require_sleep.method(zod.z.object({
|
|
209326
|
+
namespace: zod.z.string().optional(),
|
|
209327
|
+
collection: zod.z.string(),
|
|
209328
|
+
fields: zod.z.array(AggregateFieldSchema).readonly(),
|
|
209329
|
+
filter: QueryFilterSchema.optional()
|
|
209330
|
+
}), AggregateResultSchema),
|
|
208546
209331
|
/** Grouped counts per ((field-origin)/bucketSize) bucket, filtered. */
|
|
208547
209332
|
histogram: require_sleep.method(zod.z.object({
|
|
208548
209333
|
namespace: zod.z.string().optional(),
|
|
@@ -208701,6 +209486,15 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
208701
209486
|
collection: zod.z.string(),
|
|
208702
209487
|
filter: QueryFilterSchema.optional()
|
|
208703
209488
|
}), zod.z.number(), { auth: "admin" }),
|
|
209489
|
+
/** `COUNT(*)` plus one SUM/MIN/MAX per field, in one statement. Mirror of
|
|
209490
|
+
* `settings-store.aggregate` — see it for why an unresolvable field is
|
|
209491
|
+
* refused rather than dropped. */
|
|
209492
|
+
aggregate: require_sleep.method(zod.z.object({
|
|
209493
|
+
namespace: zod.z.string().optional(),
|
|
209494
|
+
collection: zod.z.string(),
|
|
209495
|
+
fields: zod.z.array(AggregateFieldSchema).readonly(),
|
|
209496
|
+
filter: QueryFilterSchema.optional()
|
|
209497
|
+
}), AggregateResultSchema, { auth: "admin" }),
|
|
208704
209498
|
/** Grouped counts per ((field-origin)/bucketSize) bucket, filtered. */
|
|
208705
209499
|
histogram: require_sleep.method(zod.z.object({
|
|
208706
209500
|
namespace: zod.z.string().optional(),
|
|
@@ -209415,6 +210209,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
209415
210209
|
})
|
|
209416
210210
|
}
|
|
209417
210211
|
};
|
|
210212
|
+
var DEVICE_CHILDREN_BATCH_MAX = 256;
|
|
209418
210213
|
var ChildLayoutEntrySchema = zod.z.object({
|
|
209419
210214
|
childKey: zod.z.string(),
|
|
209420
210215
|
section: zod.z.string(),
|
|
@@ -209896,6 +210691,39 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
209896
210691
|
/** List children of a parent device (by parent numeric id). */
|
|
209897
210692
|
getChildren: require_sleep.method(zod.z.object({ parentDeviceId: zod.z.number() }), zod.z.array(DeviceInfoSchema)),
|
|
209898
210693
|
/**
|
|
210694
|
+
* `getChildren` for a NAMED SET of parents, in one call.
|
|
210695
|
+
*
|
|
210696
|
+
* The accessory reconcile in `device-cap-proxy.ts` asks this question once
|
|
210697
|
+
* per registered device — every `BaseDevice` inherits a
|
|
210698
|
+
* `getAccessoryChildren()` that returns `[]`, so even a leaf accessory
|
|
210699
|
+
* pays a round-trip to learn it has nothing to prune. Measured on the live
|
|
210700
|
+
* hub 2026-08-27 over a 120-second boot window, fleet of 1 017 devices:
|
|
210701
|
+
* `DeviceRowStore.list < DeviceRowStore.listByParent < getChildren` at
|
|
210702
|
+
* **1 024 calls** returning **919 rows in total** — 1 024 RPCs and 1 024
|
|
210703
|
+
* indexed scans to move less than one row each. `listByParentMany`
|
|
210704
|
+
* collapses the scans; this collapses the RPCs.
|
|
210705
|
+
*
|
|
210706
|
+
* Keyed by parent id as a STRING — a JSON object cannot key by number
|
|
210707
|
+
* (same reason as `getDeviceStatusAggregateBatch`). The per-parent value
|
|
210708
|
+
* is exactly what `getChildren` returns for that parent.
|
|
210709
|
+
*
|
|
210710
|
+
* A parent with no children — or one the fleet does not know — is ABSENT
|
|
210711
|
+
* from the record, never an invented empty row: the same contract as
|
|
210712
|
+
* `DeviceRowStore.getMany`/`listByParentMany`. An EMPTY `parentDeviceIds`
|
|
210713
|
+
* reads nothing at all rather than degrading to "every device".
|
|
210714
|
+
*
|
|
210715
|
+
* `parentDeviceIds` is capped at {@link DEVICE_CHILDREN_BATCH_MAX} — see
|
|
210716
|
+
* that constant for why. A caller with more parents than that sends more
|
|
210717
|
+
* than one call; it never sends one pathological one.
|
|
210718
|
+
*
|
|
210719
|
+
* Version skew: this is a NEW method, not a new field on `getChildren`, so
|
|
210720
|
+
* a hub that predates it answers NOT_FOUND rather than silently stripping
|
|
210721
|
+
* an unknown input key and answering a DIFFERENT question. The kernel-side
|
|
210722
|
+
* loader degrades to per-parent `getChildren` on that error — see
|
|
210723
|
+
* `children-batch-loader.ts`.
|
|
210724
|
+
*/
|
|
210725
|
+
getChildrenBatch: require_sleep.method(zod.z.object({ parentDeviceIds: zod.z.array(zod.z.number()).max(256) }), zod.z.record(zod.z.string(), zod.z.array(DeviceInfoSchema))),
|
|
210726
|
+
/**
|
|
209899
210727
|
* Resolve the devices LINKED to a camera — the single policy authority
|
|
209900
210728
|
* both consumers call (viewer devices panel + pipeline-analytics event
|
|
209901
210729
|
* kinds/ingest). Device-tree children are ALWAYS included; mode 'auto'
|
|
@@ -210947,6 +211775,38 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
210947
211775
|
})
|
|
210948
211776
|
}
|
|
210949
211777
|
};
|
|
211778
|
+
var LogChannelApplyResultSchema = zod.z.object({
|
|
211779
|
+
/** How many declared channels are armed in this process after the call. */
|
|
211780
|
+
armed: zod.z.number().int().min(0),
|
|
211781
|
+
/**
|
|
211782
|
+
* Names the document armed that this process does not declare. Reported
|
|
211783
|
+
* rather than swallowed: a name here is either a typo or an addon that has
|
|
211784
|
+
* not booted, and both deserve a line instead of silence.
|
|
211785
|
+
*/
|
|
211786
|
+
unknown: zod.z.array(zod.z.string()).readonly()
|
|
211787
|
+
});
|
|
211788
|
+
var logChannelsCapability = {
|
|
211789
|
+
name: "log-channels",
|
|
211790
|
+
scope: "system",
|
|
211791
|
+
mode: "collection",
|
|
211792
|
+
internal: true,
|
|
211793
|
+
methods: {
|
|
211794
|
+
/** The channels this addon declares. Inert: no value, no state. */
|
|
211795
|
+
list: require_sleep.method(zod.z.void(), zod.z.array(LogChannelDescriptorSchema).readonly()),
|
|
211796
|
+
/**
|
|
211797
|
+
* Refresh this process's mirror from the document's FULL set of armed
|
|
211798
|
+
* windows.
|
|
211799
|
+
*
|
|
211800
|
+
* Full and not incremental on purpose: the document is the authority, so a
|
|
211801
|
+
* channel it does not name is disarmed here. An incremental apply would
|
|
211802
|
+
* let a disarm get lost in transit and leave a channel running that
|
|
211803
|
+
* nobody can see is running.
|
|
211804
|
+
*/
|
|
211805
|
+
apply: require_sleep.method(zod.z.object({ windows: zod.z.array(LogChannelWindowSchema).readonly() }), LogChannelApplyResultSchema, { kind: "mutation" })
|
|
211806
|
+
},
|
|
211807
|
+
/** In-process only — enumerated through `addons.listCapabilityProviders`. */
|
|
211808
|
+
mount: { kind: "skip" }
|
|
211809
|
+
};
|
|
210950
211810
|
var LogLevelSchema = zod.z.enum([
|
|
210951
211811
|
"debug",
|
|
210952
211812
|
"info",
|
|
@@ -227266,6 +228126,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
227266
228126
|
scope: LoggingScopeKindSchema,
|
|
227267
228127
|
/** The node this layer speaks for; `null` on the cluster layer. */
|
|
227268
228128
|
nodeId: zod.z.string().nullable(),
|
|
228129
|
+
/**
|
|
228130
|
+
* The declared channel this layer speaks for; `null` on every layer but
|
|
228131
|
+
* `component`. Never folded into `nodeId`: a component level is CLUSTER-WIDE
|
|
228132
|
+
* by design — the convention this repo settled on is one orchestrator-wide
|
|
228133
|
+
* setting, never per node (D52) — so a component layer that carried a node
|
|
228134
|
+
* would invite a per-node copy of a value that has no per-node meaning.
|
|
228135
|
+
*/
|
|
228136
|
+
component: zod.z.string().nullable(),
|
|
227269
228137
|
/** Explicitly set here, or `null` when this layer inherits. */
|
|
227270
228138
|
level: LogLevelSchema$1.nullable()
|
|
227271
228139
|
});
|
|
@@ -227291,6 +228159,38 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
227291
228159
|
/** Cadence of the diagnostic's aggregated report line. Clamped by the server. */
|
|
227292
228160
|
reportEveryMs: zod.z.number().int().positive().optional()
|
|
227293
228161
|
});
|
|
228162
|
+
var LogChannelWindowStateSchema = zod.z.object({
|
|
228163
|
+
channel: zod.z.string(),
|
|
228164
|
+
armed: zod.z.boolean(),
|
|
228165
|
+
/** Epoch ms the window closes at. 0 when disarmed. */
|
|
228166
|
+
armedUntilMs: zod.z.number(),
|
|
228167
|
+
/** Ms left before it expires on its own. 0 when disarmed. */
|
|
228168
|
+
remainingMs: zod.z.number(),
|
|
228169
|
+
/**
|
|
228170
|
+
* The cameras it is narrowed to, or `null` for every camera.
|
|
228171
|
+
*
|
|
228172
|
+
* A channel declared `perDevice: false` can only ever report `null` here:
|
|
228173
|
+
* its lines do not carry `tags: { deviceId }`, so narrowing them would
|
|
228174
|
+
* produce a filter that silently matches nothing. The server REFUSES such a
|
|
228175
|
+
* patch rather than quietly widening it — ignoring the request would teach
|
|
228176
|
+
* the operator that per-camera filtering works on that channel when it does
|
|
228177
|
+
* not.
|
|
228178
|
+
*/
|
|
228179
|
+
deviceIds: zod.z.array(zod.z.number().int()).readonly().nullable()
|
|
228180
|
+
});
|
|
228181
|
+
var LogChannelWindowPatchSchema = zod.z.object({
|
|
228182
|
+
channel: zod.z.string().min(1),
|
|
228183
|
+
armMs: zod.z.number().int().min(0),
|
|
228184
|
+
/**
|
|
228185
|
+
* Narrow to these numeric device ids. Absent or `null` = every camera.
|
|
228186
|
+
*
|
|
228187
|
+
* Numeric because the repo's own rule makes it possible: every log line
|
|
228188
|
+
* about a device carries `tags: { deviceId }` with the numeric id. That rule
|
|
228189
|
+
* was paid for with a 22% thumbnail gap and a 3-hour media blackout both
|
|
228190
|
+
* diagnosed by hand, and this is the first thing that collects on it.
|
|
228191
|
+
*/
|
|
228192
|
+
deviceIds: zod.z.array(zod.z.number().int()).readonly().nullable().optional()
|
|
228193
|
+
});
|
|
227294
228194
|
var LoggingSettingsPatchSchema = zod.z.object({
|
|
227295
228195
|
/**
|
|
227296
228196
|
* Absent leaves the level untouched. `null` CLEARS the explicit value at the
|
|
@@ -227301,19 +228201,50 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
227301
228201
|
* Only the diagnostics NAMED here change. An armed window that is not listed
|
|
227302
228202
|
* keeps running — a patch is never a full replacement.
|
|
227303
228203
|
*/
|
|
227304
|
-
diagnostics: zod.z.array(DiagnosticWindowPatchSchema).readonly().optional()
|
|
228204
|
+
diagnostics: zod.z.array(DiagnosticWindowPatchSchema).readonly().optional(),
|
|
228205
|
+
/**
|
|
228206
|
+
* Only the channels NAMED here change. An armed channel that is not listed
|
|
228207
|
+
* keeps running — same rule as `diagnostics`, because a patch that silently
|
|
228208
|
+
* disarmed the channels it did not mention would make the Levels page and
|
|
228209
|
+
* the Diagnostics page fight over the same value.
|
|
228210
|
+
*/
|
|
228211
|
+
channels: zod.z.array(LogChannelWindowPatchSchema).readonly().optional()
|
|
228212
|
+
});
|
|
228213
|
+
var GetLoggingSettingsInputSchema = zod.z.object({
|
|
228214
|
+
scopeNodeId: zod.z.string().optional(),
|
|
228215
|
+
/**
|
|
228216
|
+
* The declared CHANNEL this document is addressed at, when the caller wants
|
|
228217
|
+
* the `component` layer. Absent = the node/cluster hierarchy only.
|
|
228218
|
+
*
|
|
228219
|
+
* Naming it separately rather than overloading `scopeNodeId` keeps the two
|
|
228220
|
+
* axes from collapsing: a component level is cluster-wide, a node level is
|
|
228221
|
+
* not, and one selector for both would make "which of these two did I just
|
|
228222
|
+
* set" unanswerable — the exact ambiguity `explicit` exists to remove.
|
|
228223
|
+
*/
|
|
228224
|
+
scopeComponent: zod.z.string().optional()
|
|
227305
228225
|
});
|
|
227306
|
-
var GetLoggingSettingsInputSchema = zod.z.object({ scopeNodeId: zod.z.string().optional() });
|
|
227307
228226
|
var SetLoggingSettingsInputSchema = zod.z.object({
|
|
227308
228227
|
scopeNodeId: zod.z.string().optional(),
|
|
228228
|
+
scopeComponent: zod.z.string().optional(),
|
|
227309
228229
|
patch: LoggingSettingsPatchSchema
|
|
227310
228230
|
});
|
|
227311
228231
|
var LoggingSettingsStateSchema = zod.z.object({
|
|
227312
228232
|
/** The layer this document was read at. `null` = the cluster layer. */
|
|
227313
228233
|
scopeNodeId: zod.z.string().nullable(),
|
|
228234
|
+
/** The channel this document was read at. `null` = no component layer. */
|
|
228235
|
+
scopeComponent: zod.z.string().nullable(),
|
|
227314
228236
|
effective: LoggingEffectiveSchema,
|
|
227315
228237
|
explicit: LoggingExplicitSchema,
|
|
227316
228238
|
activeWindows: zod.z.array(DiagnosticWindowSchema).readonly(),
|
|
228239
|
+
/**
|
|
228240
|
+
* Every channel the cluster's addons DECLARE, gathered from the
|
|
228241
|
+
* `log-channels` providers. Not stored anywhere: assembled per read, so a
|
|
228242
|
+
* channel added by a redeployed addon appears without anybody editing a
|
|
228243
|
+
* list, and a channel whose addon is gone stops being offered.
|
|
228244
|
+
*/
|
|
228245
|
+
channels: zod.z.array(LogChannelDescriptorSchema).readonly(),
|
|
228246
|
+
/** The channels ARMED right now, each with its deadline. */
|
|
228247
|
+
activeChannels: zod.z.array(LogChannelWindowStateSchema).readonly(),
|
|
227317
228248
|
persisted: zod.z.boolean()
|
|
227318
228249
|
});
|
|
227319
228250
|
var systemCapability = {
|
|
@@ -230945,6 +231876,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
230945
231876
|
llmRuntime: "llm-runtime",
|
|
230946
231877
|
localNetwork: "local-network",
|
|
230947
231878
|
lockControl: "lock-control",
|
|
231879
|
+
logChannels: "log-channels",
|
|
230948
231880
|
logDestination: "log-destination",
|
|
230949
231881
|
loginMethod: "login-method",
|
|
230950
231882
|
mediaPlayer: "media-player",
|
|
@@ -231315,6 +232247,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
231315
232247
|
key: "lockControl",
|
|
231316
232248
|
name: "lock-control"
|
|
231317
232249
|
},
|
|
232250
|
+
{
|
|
232251
|
+
key: "logChannels",
|
|
232252
|
+
name: "log-channels"
|
|
232253
|
+
},
|
|
231318
232254
|
{
|
|
231319
232255
|
key: "logDestination",
|
|
231320
232256
|
name: "log-destination"
|
|
@@ -231694,6 +232630,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
231694
232630
|
llmRuntimeCapability,
|
|
231695
232631
|
localNetworkCapability,
|
|
231696
232632
|
lockControlCapability,
|
|
232633
|
+
logChannelsCapability,
|
|
231697
232634
|
logDestinationCapability,
|
|
231698
232635
|
loginMethodCapability,
|
|
231699
232636
|
mediaPlayerCapability,
|
|
@@ -232660,6 +233597,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
232660
233597
|
addonId: null,
|
|
232661
233598
|
access: "view"
|
|
232662
233599
|
},
|
|
233600
|
+
"dataStoreProvider.aggregate": {
|
|
233601
|
+
capName: "data-store-provider",
|
|
233602
|
+
capScope: "system",
|
|
233603
|
+
addonId: null,
|
|
233604
|
+
access: "view"
|
|
233605
|
+
},
|
|
232663
233606
|
"dataStoreProvider.count": {
|
|
232664
233607
|
capName: "data-store-provider",
|
|
232665
233608
|
capScope: "system",
|
|
@@ -233074,6 +234017,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
233074
234017
|
addonId: null,
|
|
233075
234018
|
access: "view"
|
|
233076
234019
|
},
|
|
234020
|
+
"deviceManager.getChildrenBatch": {
|
|
234021
|
+
capName: "device-manager",
|
|
234022
|
+
capScope: "system",
|
|
234023
|
+
addonId: null,
|
|
234024
|
+
access: "view"
|
|
234025
|
+
},
|
|
233077
234026
|
"deviceManager.getConfigSchema": {
|
|
233078
234027
|
capName: "device-manager",
|
|
233079
234028
|
capScope: "system",
|
|
@@ -234124,6 +235073,18 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
234124
235073
|
addonId: null,
|
|
234125
235074
|
access: "create"
|
|
234126
235075
|
},
|
|
235076
|
+
"logChannels.apply": {
|
|
235077
|
+
capName: "log-channels",
|
|
235078
|
+
capScope: "system",
|
|
235079
|
+
addonId: null,
|
|
235080
|
+
access: "create"
|
|
235081
|
+
},
|
|
235082
|
+
"logChannels.list": {
|
|
235083
|
+
capName: "log-channels",
|
|
235084
|
+
capScope: "system",
|
|
235085
|
+
addonId: null,
|
|
235086
|
+
access: "view"
|
|
235087
|
+
},
|
|
234127
235088
|
"logDestination.query": {
|
|
234128
235089
|
capName: "log-destination",
|
|
234129
235090
|
capScope: "system",
|
|
@@ -236278,6 +237239,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
236278
237239
|
addonId: null,
|
|
236279
237240
|
access: "create"
|
|
236280
237241
|
},
|
|
237242
|
+
"settingsStore.aggregate": {
|
|
237243
|
+
capName: "settings-store",
|
|
237244
|
+
capScope: "system",
|
|
237245
|
+
addonId: null,
|
|
237246
|
+
access: "view"
|
|
237247
|
+
},
|
|
236281
237248
|
"settingsStore.count": {
|
|
236282
237249
|
capName: "settings-store",
|
|
236283
237250
|
capScope: "system",
|
|
@@ -237626,6 +238593,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
237626
238593
|
"llm-runtime",
|
|
237627
238594
|
"local-network",
|
|
237628
238595
|
"lock-control",
|
|
238596
|
+
"log-channels",
|
|
237629
238597
|
"log-destination",
|
|
237630
238598
|
"login-method",
|
|
237631
238599
|
"media-player",
|
|
@@ -237782,6 +238750,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
237782
238750
|
"llm",
|
|
237783
238751
|
"llm-runtime",
|
|
237784
238752
|
"local-network",
|
|
238753
|
+
"log-channels",
|
|
237785
238754
|
"log-destination",
|
|
237786
238755
|
"login-method",
|
|
237787
238756
|
"mesh-network",
|
|
@@ -238109,6 +239078,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
238109
239078
|
form: "single",
|
|
238110
239079
|
optional: false
|
|
238111
239080
|
}],
|
|
239081
|
+
"deviceManager.getChildrenBatch": [{
|
|
239082
|
+
name: "parentDeviceIds",
|
|
239083
|
+
form: "array",
|
|
239084
|
+
optional: false
|
|
239085
|
+
}],
|
|
238112
239086
|
"deviceManager.getConfigSchema": [{
|
|
238113
239087
|
name: "deviceId",
|
|
238114
239088
|
form: "single",
|
|
@@ -239583,6 +240557,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
239583
240557
|
"deviceManager.getBindings",
|
|
239584
240558
|
"deviceManager.getBindingsBatch",
|
|
239585
240559
|
"deviceManager.getChildren",
|
|
240560
|
+
"deviceManager.getChildrenBatch",
|
|
239586
240561
|
"deviceManager.getConfigSchema",
|
|
239587
240562
|
"deviceManager.getDevice",
|
|
239588
240563
|
"deviceManager.getDeviceAggregate",
|
|
@@ -240275,6 +241250,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
240275
241250
|
listPersistedByAddon: (input) => dispatch("deviceManager", "listPersistedByAddon", "query", input),
|
|
240276
241251
|
listAll: (input) => dispatch("deviceManager", "listAll", "query", input),
|
|
240277
241252
|
getChildren: (input) => dispatch("deviceManager", "getChildren", "query", input),
|
|
241253
|
+
getChildrenBatch: (input) => dispatch("deviceManager", "getChildrenBatch", "query", input),
|
|
240278
241254
|
getLinkedDevicesBatch: (input) => dispatch("deviceManager", "getLinkedDevicesBatch", "query", input),
|
|
240279
241255
|
removeByIntegration: (input) => dispatch("deviceManager", "removeByIntegration", "mutation", input),
|
|
240280
241256
|
getBindingsBatch: (input) => dispatch("deviceManager", "getBindingsBatch", "query", input),
|
|
@@ -240589,6 +241565,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
240589
241565
|
deleteWhere: (input) => dispatch("settingsStore", "deleteWhere", "mutation", input),
|
|
240590
241566
|
updateWhere: (input) => dispatch("settingsStore", "updateWhere", "mutation", input),
|
|
240591
241567
|
count: (input) => dispatch("settingsStore", "count", "query", input),
|
|
241568
|
+
aggregate: (input) => dispatch("settingsStore", "aggregate", "query", input),
|
|
240592
241569
|
histogram: (input) => dispatch("settingsStore", "histogram", "query", input),
|
|
240593
241570
|
isEmpty: (input) => dispatch("settingsStore", "isEmpty", "query", input),
|
|
240594
241571
|
declareCollection: (input) => dispatch("settingsStore", "declareCollection", "mutation", input)
|
|
@@ -243598,6 +244575,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
243598
244575
|
exports.DETECTION_PIPELINE_CAP_NAME = DETECTION_PIPELINE_CAP_NAME;
|
|
243599
244576
|
exports.DEVICE_BACKEND_TO_FORMAT = DEVICE_BACKEND_TO_FORMAT;
|
|
243600
244577
|
exports.DEVICE_CAP_NAMES = DEVICE_CAP_NAMES;
|
|
244578
|
+
exports.DEVICE_CHILDREN_BATCH_MAX = DEVICE_CHILDREN_BATCH_MAX;
|
|
243601
244579
|
exports.DEVICE_PROFILES = DEVICE_PROFILES;
|
|
243602
244580
|
exports.DEVICE_SCOPED_CAPS = require_sleep.DEVICE_SCOPED_CAPS;
|
|
243603
244581
|
exports.DEVICE_SETTINGS_CONTRIBUTION_METHODS = require_sleep.DEVICE_SETTINGS_CONTRIBUTION_METHODS;
|
|
@@ -243748,6 +244726,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
243748
244726
|
exports.IntercomStatusSchema = IntercomStatusSchema;
|
|
243749
244727
|
exports.KNOWN_CAP_NAMES = KNOWN_CAP_NAMES;
|
|
243750
244728
|
exports.KeyEventSchema = KeyEventSchema;
|
|
244729
|
+
exports.LOG_CHANNEL_TICK_MS = LOG_CHANNEL_TICK_MS;
|
|
243751
244730
|
exports.LOG_LEVEL_RANK = LOG_LEVEL_RANK;
|
|
243752
244731
|
exports.LabelAttributionSchema = LabelAttributionSchema;
|
|
243753
244732
|
exports.LabelDefinitionSchema = LabelDefinitionSchema;
|
|
@@ -243783,6 +244762,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
243783
244762
|
exports.LocationStatSchema = LocationStatSchema;
|
|
243784
244763
|
exports.LockControlStatusSchema = LockControlStatusSchema;
|
|
243785
244764
|
exports.LockStateSchema = LockStateSchema;
|
|
244765
|
+
exports.LogChannelApplyResultSchema = LogChannelApplyResultSchema;
|
|
244766
|
+
exports.LogChannelDescriptorSchema = LogChannelDescriptorSchema;
|
|
244767
|
+
exports.LogChannelGate = LogChannelGate;
|
|
244768
|
+
exports.LogChannelLevelSchema = LogChannelLevelSchema;
|
|
244769
|
+
exports.LogChannelRegistry = LogChannelRegistry;
|
|
244770
|
+
exports.LogChannelWindowPatchSchema = LogChannelWindowPatchSchema;
|
|
244771
|
+
exports.LogChannelWindowSchema = LogChannelWindowSchema;
|
|
244772
|
+
exports.LogChannelWindowStateSchema = LogChannelWindowStateSchema;
|
|
243786
244773
|
exports.LogEntrySchema = LogEntrySchema;
|
|
243787
244774
|
exports.LogLevelSchema = LogLevelSchema;
|
|
243788
244775
|
exports.LogStreamEntrySchema = LogStreamEntrySchema;
|
|
@@ -244333,6 +245320,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
244333
245320
|
exports.ZoneRulesArraySchema = ZoneRulesArraySchema;
|
|
244334
245321
|
exports.ZoneSchema = ZoneSchema;
|
|
244335
245322
|
exports.ZoneScopeBreakdownSchema = ZoneScopeBreakdownSchema;
|
|
245323
|
+
exports.__resetLogChannelRegistryForTests = __resetLogChannelRegistryForTests;
|
|
244336
245324
|
exports.accessoriesCapability = accessoriesCapability;
|
|
244337
245325
|
exports.accessoryStableId = accessoryStableId;
|
|
244338
245326
|
exports.addonPagesCapability = addonPagesCapability;
|
|
@@ -244429,6 +245417,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
244429
245417
|
exports.createExpressionScope = createExpressionScope;
|
|
244430
245418
|
exports.createHwAccelCache = createHwAccelCache;
|
|
244431
245419
|
exports.createLazyTrpcSource = require_sleep.createLazyTrpcSource;
|
|
245420
|
+
exports.createLogChannelsProvider = createLogChannelsProvider;
|
|
244432
245421
|
exports.createMirrorSource = require_sleep.createMirrorSource;
|
|
244433
245422
|
exports.createRuntimeStateBridge = createRuntimeStateBridge;
|
|
244434
245423
|
exports.createSliceHandle = require_sleep.createSliceHandle;
|
|
@@ -244438,6 +245427,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
244438
245427
|
exports.dataStoreProviderCapability = dataStoreProviderCapability;
|
|
244439
245428
|
exports.dayNightCapability = dayNightCapability;
|
|
244440
245429
|
exports.declarationOwnerNodeId = declarationOwnerNodeId;
|
|
245430
|
+
exports.declareLogChannel = declareLogChannel;
|
|
244441
245431
|
exports.decodeVectorBase64 = decodeVectorBase64;
|
|
244442
245432
|
exports.decoderCapability = decoderCapability;
|
|
244443
245433
|
exports.defaultDeliveryForSection = defaultDeliveryForSection;
|
|
@@ -244500,6 +245490,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
244500
245490
|
exports.getAudioMacroClassIds = getAudioMacroClassIds;
|
|
244501
245491
|
exports.getByPath = getByPath;
|
|
244502
245492
|
exports.getCapsByProviderKind = getCapsByProviderKind;
|
|
245493
|
+
exports.getLogChannelRegistry = getLogChannelRegistry;
|
|
244503
245494
|
exports.getTaxonomyEntry = getTaxonomyEntry;
|
|
244504
245495
|
exports.hasMotionTrigger = hasMotionTrigger;
|
|
244505
245496
|
exports.hfModelUrl = hfModelUrl;
|
|
@@ -244553,6 +245544,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
244553
245544
|
exports.locationSimilarity = locationSimilarity;
|
|
244554
245545
|
exports.lockControlCapability = lockControlCapability;
|
|
244555
245546
|
exports.logBannerArgs = require_canonical_hash.logBannerArgs;
|
|
245547
|
+
exports.logChannelsCapability = logChannelsCapability;
|
|
244556
245548
|
exports.logDestinationCapability = logDestinationCapability;
|
|
244557
245549
|
exports.logLevelAtMost = logLevelAtMost;
|
|
244558
245550
|
exports.loginMethodCapability = loginMethodCapability;
|
|
@@ -401844,17 +402836,55 @@ var require_logging_settings = __commonJS({
|
|
|
401844
402836
|
"../../server/backend/dist/api/core/logging-settings.js"(exports) {
|
|
401845
402837
|
"use strict";
|
|
401846
402838
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
401847
|
-
exports.LoggingSettingsService = exports.HUB_NODE_ID = exports.LOGGING_SETTINGS_KEY = void 0;
|
|
402839
|
+
exports.LoggingSettingsService = exports.MAX_CHANNEL_WINDOW_MS = exports.EMPTY_LOG_CHANNEL_PLANE = exports.HUB_NODE_ID = exports.LOGGING_SETTINGS_KEY = void 0;
|
|
402840
|
+
exports.buildLogChannelPlane = buildLogChannelPlane;
|
|
401848
402841
|
exports.toLoggingSettingsRecord = toLoggingSettingsRecord;
|
|
401849
402842
|
exports.resolveLevel = resolveLevel;
|
|
401850
402843
|
exports.describeLayers = describeLayers;
|
|
401851
402844
|
exports.mergeLevel = mergeLevel;
|
|
402845
|
+
exports.validateChannelPatch = validateChannelPatch;
|
|
402846
|
+
exports.mergeChannelWindows = mergeChannelWindows;
|
|
402847
|
+
exports.liveChannelWindows = liveChannelWindows;
|
|
402848
|
+
exports.describeChannelWindows = describeChannelWindows;
|
|
401852
402849
|
var types_1 = require_dist4();
|
|
401853
402850
|
var system_1 = require_dist3();
|
|
401854
402851
|
exports.LOGGING_SETTINGS_KEY = "logging-settings";
|
|
401855
402852
|
var SYSTEM_SETTINGS_COLLECTION = "system-settings";
|
|
401856
402853
|
exports.HUB_NODE_ID = "hub";
|
|
401857
402854
|
var LOG_LEVELS = ["debug", "info", "warn", "error"];
|
|
402855
|
+
exports.EMPTY_LOG_CHANNEL_PLANE = {
|
|
402856
|
+
declarations: async () => [],
|
|
402857
|
+
apply: async () => {
|
|
402858
|
+
}
|
|
402859
|
+
};
|
|
402860
|
+
function buildLogChannelPlane(source, onProviderError) {
|
|
402861
|
+
return {
|
|
402862
|
+
declarations: async () => {
|
|
402863
|
+
const seen = /* @__PURE__ */ new Map();
|
|
402864
|
+
for (const [addonId, provider] of source.entries()) {
|
|
402865
|
+
try {
|
|
402866
|
+
for (const descriptor of await provider.list()) {
|
|
402867
|
+
if (!seen.has(descriptor.name))
|
|
402868
|
+
seen.set(descriptor.name, descriptor);
|
|
402869
|
+
}
|
|
402870
|
+
} catch (err) {
|
|
402871
|
+
onProviderError?.(addonId, "list", (0, types_1.errMsg)(err));
|
|
402872
|
+
}
|
|
402873
|
+
}
|
|
402874
|
+
return [...seen.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
402875
|
+
},
|
|
402876
|
+
apply: async (windows) => {
|
|
402877
|
+
for (const [addonId, provider] of source.entries()) {
|
|
402878
|
+
try {
|
|
402879
|
+
await provider.apply({ windows });
|
|
402880
|
+
} catch (err) {
|
|
402881
|
+
onProviderError?.(addonId, "apply", (0, types_1.errMsg)(err));
|
|
402882
|
+
}
|
|
402883
|
+
}
|
|
402884
|
+
}
|
|
402885
|
+
};
|
|
402886
|
+
}
|
|
402887
|
+
exports.MAX_CHANNEL_WINDOW_MS = 30 * 6e4;
|
|
401858
402888
|
var LoggingSettingsService = class {
|
|
401859
402889
|
store;
|
|
401860
402890
|
gate;
|
|
@@ -401862,6 +402892,7 @@ var require_logging_settings = __commonJS({
|
|
|
401862
402892
|
localNodeId;
|
|
401863
402893
|
logger;
|
|
401864
402894
|
now;
|
|
402895
|
+
channels;
|
|
401865
402896
|
constructor(deps) {
|
|
401866
402897
|
this.store = deps.store;
|
|
401867
402898
|
this.gate = deps.gate;
|
|
@@ -401869,6 +402900,7 @@ var require_logging_settings = __commonJS({
|
|
|
401869
402900
|
this.localNodeId = deps.localNodeId ?? exports.HUB_NODE_ID;
|
|
401870
402901
|
this.logger = deps.logger;
|
|
401871
402902
|
this.now = deps.now ?? Date.now;
|
|
402903
|
+
this.channels = deps.channels ?? exports.EMPTY_LOG_CHANNEL_PLANE;
|
|
401872
402904
|
}
|
|
401873
402905
|
/**
|
|
401874
402906
|
* The document as it stands, resolved for `nodeId` (absent = the cluster).
|
|
@@ -401877,9 +402909,9 @@ var require_logging_settings = __commonJS({
|
|
|
401877
402909
|
* and, crucially, does not touch the mirror. What the operator sees is
|
|
401878
402910
|
* honestly labelled as un-persisted; what the hub does is unchanged.
|
|
401879
402911
|
*/
|
|
401880
|
-
async get(scopeNodeId) {
|
|
402912
|
+
async get(scopeNodeId, scopeComponent) {
|
|
401881
402913
|
const record = await this.read();
|
|
401882
|
-
return this.describe(record, scopeNodeId ?? null);
|
|
402914
|
+
return this.describe(record, scopeNodeId ?? null, scopeComponent ?? null);
|
|
401883
402915
|
}
|
|
401884
402916
|
/**
|
|
401885
402917
|
* Apply a patch. Fields absent from it are untouched.
|
|
@@ -401889,28 +402921,47 @@ var require_logging_settings = __commonJS({
|
|
|
401889
402921
|
* must still give the operator a live measurement, and must say so rather
|
|
401890
402922
|
* than reporting a durable arm that is not durable.
|
|
401891
402923
|
*/
|
|
401892
|
-
async set(patch, scopeNodeId) {
|
|
402924
|
+
async set(patch, scopeNodeId, scopeComponent) {
|
|
401893
402925
|
await this.applyDiagnostics(patch);
|
|
401894
402926
|
const scope = scopeNodeId ?? null;
|
|
402927
|
+
const component = scopeComponent ?? null;
|
|
401895
402928
|
const stored = await this.read();
|
|
401896
402929
|
const levelChanged = patch.level !== void 0;
|
|
401897
|
-
|
|
402930
|
+
const channelsChanged = patch.channels !== void 0;
|
|
402931
|
+
let persisted = true;
|
|
402932
|
+
let next = stored ?? {};
|
|
401898
402933
|
if (levelChanged) {
|
|
401899
|
-
|
|
401900
|
-
|
|
402934
|
+
next = mergeLevel(next, scope, component, patch.level ?? null, this.now());
|
|
402935
|
+
}
|
|
402936
|
+
if (channelsChanged) {
|
|
402937
|
+
const declared = await this.channels.declarations();
|
|
402938
|
+
const rejected = validateChannelPatch(patch.channels ?? [], declared);
|
|
402939
|
+
if (rejected.length > 0) {
|
|
402940
|
+
this.logger?.warn("log channel patch refused - nothing was armed or disarmed", {
|
|
402941
|
+
meta: { reasons: rejected }
|
|
402942
|
+
});
|
|
402943
|
+
throw new Error(`log channel patch refused: ${rejected.join("; ")}`);
|
|
402944
|
+
}
|
|
402945
|
+
next = mergeChannelWindows(next, patch.channels ?? [], this.now());
|
|
402946
|
+
}
|
|
402947
|
+
if (levelChanged || channelsChanged) {
|
|
402948
|
+
persisted = await this.write(next);
|
|
401901
402949
|
this.refreshMirror(next);
|
|
402950
|
+
await this.pushChannels(next);
|
|
401902
402951
|
this.logger?.info("logging settings written", {
|
|
401903
402952
|
meta: {
|
|
401904
|
-
scope: scope === null ? "cluster" : "node",
|
|
402953
|
+
scope: component !== null ? "component" : scope === null ? "cluster" : "node",
|
|
401905
402954
|
nodeId: scope,
|
|
401906
|
-
|
|
401907
|
-
|
|
402955
|
+
component,
|
|
402956
|
+
level: levelChanged ? patch.level ?? null : void 0,
|
|
402957
|
+
channels: (patch.channels ?? []).map((c) => `${c.channel}:${c.armMs}`),
|
|
402958
|
+
persisted
|
|
401908
402959
|
}
|
|
401909
402960
|
});
|
|
401910
|
-
const state = await this.describe(next, scope);
|
|
401911
|
-
return { ...state, persisted: state.persisted &&
|
|
402961
|
+
const state = await this.describe(next, scope, component);
|
|
402962
|
+
return { ...state, persisted: state.persisted && persisted };
|
|
401912
402963
|
}
|
|
401913
|
-
return this.describe(stored, scope);
|
|
402964
|
+
return this.describe(stored, scope, component);
|
|
401914
402965
|
}
|
|
401915
402966
|
/**
|
|
401916
402967
|
* Re-establish the mirror and re-arm any diagnostic window that outlived the
|
|
@@ -401923,8 +402974,10 @@ var require_logging_settings = __commonJS({
|
|
|
401923
402974
|
*/
|
|
401924
402975
|
async restore() {
|
|
401925
402976
|
const record = await this.read();
|
|
401926
|
-
if (record !== null)
|
|
402977
|
+
if (record !== null) {
|
|
401927
402978
|
this.refreshMirror(record);
|
|
402979
|
+
await this.pushChannels(record);
|
|
402980
|
+
}
|
|
401928
402981
|
await this.requestCensus.restore();
|
|
401929
402982
|
}
|
|
401930
402983
|
// ── internals ─────────────────────────────────────────────────────
|
|
@@ -401942,16 +402995,55 @@ var require_logging_settings = __commonJS({
|
|
|
401942
402995
|
});
|
|
401943
402996
|
}
|
|
401944
402997
|
}
|
|
401945
|
-
|
|
401946
|
-
|
|
402998
|
+
/**
|
|
402999
|
+
* Push the live windows to every declared gate.
|
|
403000
|
+
*
|
|
403001
|
+
* A failure here is logged and swallowed: the document is written and the
|
|
403002
|
+
* operator is told what it says, and a runner that could not be reached
|
|
403003
|
+
* simply keeps the mirror it has. Throwing would turn one unreachable addon
|
|
403004
|
+
* into a failed write for the whole cluster.
|
|
403005
|
+
*/
|
|
403006
|
+
async pushChannels(record) {
|
|
403007
|
+
try {
|
|
403008
|
+
await this.channels.apply(liveChannelWindows(record, this.now()));
|
|
403009
|
+
} catch (err) {
|
|
403010
|
+
this.logger?.warn("log channel windows written but NOT pushed to every gate", {
|
|
403011
|
+
meta: { error: (0, types_1.errMsg)(err) }
|
|
403012
|
+
});
|
|
403013
|
+
}
|
|
403014
|
+
}
|
|
403015
|
+
async describe(record, scopeNodeId, scopeComponent) {
|
|
403016
|
+
const resolved = resolveLevel(record ?? {}, scopeNodeId, scopeComponent);
|
|
401947
403017
|
return {
|
|
401948
403018
|
scopeNodeId,
|
|
403019
|
+
scopeComponent,
|
|
401949
403020
|
effective: { level: resolved.level, levelSource: resolved.source },
|
|
401950
|
-
explicit: { layers: describeLayers(record ?? {}, scopeNodeId) },
|
|
403021
|
+
explicit: { layers: describeLayers(record ?? {}, scopeNodeId, scopeComponent) },
|
|
401951
403022
|
activeWindows: await this.describeWindows(),
|
|
403023
|
+
channels: await this.declaredChannels(),
|
|
403024
|
+
activeChannels: describeChannelWindows(record ?? {}, this.now()),
|
|
401952
403025
|
persisted: record !== null
|
|
401953
403026
|
};
|
|
401954
403027
|
}
|
|
403028
|
+
/**
|
|
403029
|
+
* The declarations, assembled per read.
|
|
403030
|
+
*
|
|
403031
|
+
* Not cached and not stored: a channel added by a redeployed addon has to
|
|
403032
|
+
* appear without anybody editing a list, and a channel whose addon is gone
|
|
403033
|
+
* has to stop being offered. A read that throws yields the EMPTY list and
|
|
403034
|
+
* says so — "unknown" is the truth about a set of addons nobody reached, and
|
|
403035
|
+
* it changes no armed window (D49/D224).
|
|
403036
|
+
*/
|
|
403037
|
+
async declaredChannels() {
|
|
403038
|
+
try {
|
|
403039
|
+
return await this.channels.declarations();
|
|
403040
|
+
} catch (err) {
|
|
403041
|
+
this.logger?.warn("log channel declarations unreadable - reporting none", {
|
|
403042
|
+
meta: { error: (0, types_1.errMsg)(err) }
|
|
403043
|
+
});
|
|
403044
|
+
return [];
|
|
403045
|
+
}
|
|
403046
|
+
}
|
|
401955
403047
|
/**
|
|
401956
403048
|
* The armed windows, with their deadline.
|
|
401957
403049
|
*
|
|
@@ -402015,14 +403107,21 @@ var require_logging_settings = __commonJS({
|
|
|
402015
403107
|
return {};
|
|
402016
403108
|
const clusterLevel = asLogLevel(Reflect.get(value, "clusterLevel"));
|
|
402017
403109
|
const nodeLevels = asNodeLevels(Reflect.get(value, "nodeLevels"));
|
|
403110
|
+
const componentLevels = asNodeLevels(Reflect.get(value, "componentLevels"));
|
|
403111
|
+
const channelWindows = asChannelWindows(Reflect.get(value, "channelWindows"));
|
|
402018
403112
|
const updatedAt = Reflect.get(value, "updatedAt");
|
|
402019
403113
|
return {
|
|
402020
403114
|
...clusterLevel !== null ? { clusterLevel } : {},
|
|
402021
403115
|
...nodeLevels !== null ? { nodeLevels } : {},
|
|
403116
|
+
...componentLevels !== null ? { componentLevels } : {},
|
|
403117
|
+
...channelWindows !== null ? { channelWindows } : {},
|
|
402022
403118
|
...typeof updatedAt === "number" && Number.isFinite(updatedAt) ? { updatedAt } : {}
|
|
402023
403119
|
};
|
|
402024
403120
|
}
|
|
402025
|
-
function resolveLevel(record, nodeId) {
|
|
403121
|
+
function resolveLevel(record, nodeId, component = null) {
|
|
403122
|
+
const componentLevel = component === null ? void 0 : record.componentLevels?.[component];
|
|
403123
|
+
if (componentLevel !== void 0)
|
|
403124
|
+
return { level: componentLevel, source: "component" };
|
|
402026
403125
|
const nodeLevel = nodeId === null ? void 0 : record.nodeLevels?.[nodeId];
|
|
402027
403126
|
if (nodeLevel !== void 0)
|
|
402028
403127
|
return { level: nodeLevel, source: "node" };
|
|
@@ -402030,17 +403129,37 @@ var require_logging_settings = __commonJS({
|
|
|
402030
403129
|
return { level: record.clusterLevel, source: "cluster" };
|
|
402031
403130
|
return { level: system_1.DEFAULT_LOG_LEVEL, source: "default" };
|
|
402032
403131
|
}
|
|
402033
|
-
function describeLayers(record, nodeId) {
|
|
402034
|
-
const
|
|
402035
|
-
scope: "cluster",
|
|
402036
|
-
|
|
402037
|
-
|
|
402038
|
-
|
|
402039
|
-
|
|
402040
|
-
|
|
402041
|
-
|
|
403132
|
+
function describeLayers(record, nodeId, component = null) {
|
|
403133
|
+
const layers = [
|
|
403134
|
+
{ scope: "cluster", nodeId: null, component: null, level: record.clusterLevel ?? null }
|
|
403135
|
+
];
|
|
403136
|
+
if (nodeId !== null) {
|
|
403137
|
+
layers.push({
|
|
403138
|
+
scope: "node",
|
|
403139
|
+
nodeId,
|
|
403140
|
+
component: null,
|
|
403141
|
+
level: record.nodeLevels?.[nodeId] ?? null
|
|
403142
|
+
});
|
|
403143
|
+
}
|
|
403144
|
+
if (component !== null) {
|
|
403145
|
+
layers.push({
|
|
403146
|
+
scope: "component",
|
|
403147
|
+
nodeId: null,
|
|
403148
|
+
component,
|
|
403149
|
+
level: record.componentLevels?.[component] ?? null
|
|
403150
|
+
});
|
|
403151
|
+
}
|
|
403152
|
+
return layers;
|
|
402042
403153
|
}
|
|
402043
|
-
function mergeLevel(record, nodeId, next, updatedAt) {
|
|
403154
|
+
function mergeLevel(record, nodeId, component, next, updatedAt) {
|
|
403155
|
+
if (component !== null) {
|
|
403156
|
+
const { [component]: _clearedComponent, ...otherComponents } = record.componentLevels ?? {};
|
|
403157
|
+
const componentLevels = {
|
|
403158
|
+
...otherComponents,
|
|
403159
|
+
...next !== null ? { [component]: next } : {}
|
|
403160
|
+
};
|
|
403161
|
+
return { ...record, componentLevels, updatedAt };
|
|
403162
|
+
}
|
|
402044
403163
|
if (nodeId === null) {
|
|
402045
403164
|
const { clusterLevel: _dropped, ...rest } = record;
|
|
402046
403165
|
return { ...rest, ...next !== null ? { clusterLevel: next } : {}, updatedAt };
|
|
@@ -402056,6 +403175,81 @@ var require_logging_settings = __commonJS({
|
|
|
402056
403175
|
updatedAt
|
|
402057
403176
|
};
|
|
402058
403177
|
}
|
|
403178
|
+
function validateChannelPatch(patches, declared) {
|
|
403179
|
+
const byName = new Map(declared.map((d) => [d.name, d]));
|
|
403180
|
+
const reasons = [];
|
|
403181
|
+
for (const patch of patches) {
|
|
403182
|
+
const descriptor = byName.get(patch.channel);
|
|
403183
|
+
if (descriptor === void 0) {
|
|
403184
|
+
const known = [...byName.keys()].sort().slice(0, 8).join(", ");
|
|
403185
|
+
reasons.push(`"${patch.channel}" is not declared by any addon on this cluster` + (known.length > 0 ? ` (declared: ${known})` : ""));
|
|
403186
|
+
continue;
|
|
403187
|
+
}
|
|
403188
|
+
const ids = patch.deviceIds;
|
|
403189
|
+
if (ids !== void 0 && ids !== null && ids.length > 0 && !descriptor.perDevice) {
|
|
403190
|
+
reasons.push(`"${patch.channel}" is declared perDevice: false \u2014 its lines carry no tags.deviceId, so narrowing it to a camera would match nothing`);
|
|
403191
|
+
}
|
|
403192
|
+
}
|
|
403193
|
+
return reasons;
|
|
403194
|
+
}
|
|
403195
|
+
function mergeChannelWindows(record, patches, nowMs) {
|
|
403196
|
+
const windows = { ...record.channelWindows };
|
|
403197
|
+
for (const patch of patches) {
|
|
403198
|
+
if (patch.armMs <= 0) {
|
|
403199
|
+
delete windows[patch.channel];
|
|
403200
|
+
continue;
|
|
403201
|
+
}
|
|
403202
|
+
const ids = patch.deviceIds;
|
|
403203
|
+
windows[patch.channel] = {
|
|
403204
|
+
armedUntilMs: nowMs + Math.min(patch.armMs, exports.MAX_CHANNEL_WINDOW_MS),
|
|
403205
|
+
deviceIds: ids === void 0 || ids === null || ids.length === 0 ? null : [...ids]
|
|
403206
|
+
};
|
|
403207
|
+
}
|
|
403208
|
+
return { ...record, channelWindows: windows, updatedAt: nowMs };
|
|
403209
|
+
}
|
|
403210
|
+
function liveChannelWindows(record, nowMs) {
|
|
403211
|
+
const out = [];
|
|
403212
|
+
for (const [channel, window2] of Object.entries(record.channelWindows ?? {})) {
|
|
403213
|
+
if (window2.armedUntilMs <= nowMs)
|
|
403214
|
+
continue;
|
|
403215
|
+
out.push({ channel, armedUntilMs: window2.armedUntilMs, deviceIds: window2.deviceIds });
|
|
403216
|
+
}
|
|
403217
|
+
return out.sort((a, b) => a.channel.localeCompare(b.channel));
|
|
403218
|
+
}
|
|
403219
|
+
function describeChannelWindows(record, nowMs) {
|
|
403220
|
+
const out = [];
|
|
403221
|
+
for (const [channel, window2] of Object.entries(record.channelWindows ?? {})) {
|
|
403222
|
+
const armed = window2.armedUntilMs > nowMs;
|
|
403223
|
+
out.push({
|
|
403224
|
+
channel,
|
|
403225
|
+
armed,
|
|
403226
|
+
armedUntilMs: armed ? window2.armedUntilMs : 0,
|
|
403227
|
+
remainingMs: armed ? window2.armedUntilMs - nowMs : 0,
|
|
403228
|
+
deviceIds: armed ? window2.deviceIds : null
|
|
403229
|
+
});
|
|
403230
|
+
}
|
|
403231
|
+
return out.sort((a, b) => a.channel.localeCompare(b.channel));
|
|
403232
|
+
}
|
|
403233
|
+
function asChannelWindows(value) {
|
|
403234
|
+
if (typeof value !== "object" || value === null)
|
|
403235
|
+
return null;
|
|
403236
|
+
const out = {};
|
|
403237
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
403238
|
+
if (typeof raw !== "object" || raw === null)
|
|
403239
|
+
continue;
|
|
403240
|
+
const armedUntilMs = Reflect.get(raw, "armedUntilMs");
|
|
403241
|
+
if (typeof armedUntilMs !== "number" || !Number.isFinite(armedUntilMs))
|
|
403242
|
+
continue;
|
|
403243
|
+
out[key] = { armedUntilMs, deviceIds: asDeviceIds(Reflect.get(raw, "deviceIds")) };
|
|
403244
|
+
}
|
|
403245
|
+
return out;
|
|
403246
|
+
}
|
|
403247
|
+
function asDeviceIds(value) {
|
|
403248
|
+
if (!Array.isArray(value))
|
|
403249
|
+
return null;
|
|
403250
|
+
const ids = value.filter((id) => typeof id === "number" && Number.isInteger(id));
|
|
403251
|
+
return ids.length > 0 ? ids : null;
|
|
403252
|
+
}
|
|
402059
403253
|
function asLogLevel(value) {
|
|
402060
403254
|
return typeof value === "string" && isLogLevel(value) ? value : null;
|
|
402061
403255
|
}
|
|
@@ -402692,10 +403886,14 @@ var require_cap_providers = __commonJS({
|
|
|
402692
403886
|
store,
|
|
402693
403887
|
...logger !== void 0 ? { logger } : {}
|
|
402694
403888
|
});
|
|
403889
|
+
const channels = (0, logging_settings_js_1.buildLogChannelPlane)({ entries: () => registry?.getCollectionEntries("log-channels") ?? [] }, (addonId, phase, error) => {
|
|
403890
|
+
logger?.warn("log-channels provider unreachable", { meta: { addonId, phase, error } });
|
|
403891
|
+
});
|
|
402695
403892
|
const loggingSettings = new logging_settings_js_1.LoggingSettingsService({
|
|
402696
403893
|
store,
|
|
402697
403894
|
gate: (0, system_1.getLoggingGate)(),
|
|
402698
403895
|
requestCensus,
|
|
403896
|
+
channels,
|
|
402699
403897
|
...logger !== void 0 ? { logger } : {}
|
|
402700
403898
|
});
|
|
402701
403899
|
return {
|
|
@@ -402729,8 +403927,8 @@ var require_cap_providers = __commonJS({
|
|
|
402729
403927
|
setSiteLocation: async (input) => siteLocation.set(input),
|
|
402730
403928
|
detectSiteLocation: async () => siteLocation.detect(),
|
|
402731
403929
|
getRequestCensus: async () => requestCensus.status(),
|
|
402732
|
-
getLoggingSettings: async (input) => loggingSettings.get(input.scopeNodeId),
|
|
402733
|
-
setLoggingSettings: async (input) => loggingSettings.set(input.patch, input.scopeNodeId)
|
|
403930
|
+
getLoggingSettings: async (input) => loggingSettings.get(input.scopeNodeId, input.scopeComponent),
|
|
403931
|
+
setLoggingSettings: async (input) => loggingSettings.set(input.patch, input.scopeNodeId, input.scopeComponent)
|
|
402734
403932
|
};
|
|
402735
403933
|
}
|
|
402736
403934
|
function buildSiteLocationService(registry, logger) {
|
|
@@ -405277,6 +406475,7 @@ var require_device_config_secret_redaction = __commonJS({
|
|
|
405277
406475
|
exports.NON_ADMIN_CONFIG_REDACTED_METHODS = exports.REDACTED_SECRET = void 0;
|
|
405278
406476
|
exports.isSecretConfigKey = isSecretConfigKey;
|
|
405279
406477
|
exports.redactDeviceInfoSecrets = redactDeviceInfoSecrets;
|
|
406478
|
+
exports.redactDeviceInfoRecordSecrets = redactDeviceInfoRecordSecrets;
|
|
405280
406479
|
exports.redactSettingsSections = redactSettingsSections;
|
|
405281
406480
|
exports.redactSettingsAggregate = redactSettingsAggregate;
|
|
405282
406481
|
exports.redactConfigEntries = redactConfigEntries;
|
|
@@ -405314,6 +406513,14 @@ var require_device_config_secret_redaction = __commonJS({
|
|
|
405314
406513
|
return data;
|
|
405315
406514
|
return { ...data, config: redactRecord(config) };
|
|
405316
406515
|
}
|
|
406516
|
+
function redactDeviceInfoRecordSecrets(data) {
|
|
406517
|
+
if (!isRecord(data))
|
|
406518
|
+
return data;
|
|
406519
|
+
const out = {};
|
|
406520
|
+
for (const [key, value] of Object.entries(data))
|
|
406521
|
+
out[key] = redactDeviceInfoSecrets(value);
|
|
406522
|
+
return out;
|
|
406523
|
+
}
|
|
405317
406524
|
function redactField(field) {
|
|
405318
406525
|
if (!isRecord(field))
|
|
405319
406526
|
return field;
|
|
@@ -405372,6 +406579,7 @@ var require_device_config_secret_redaction = __commonJS({
|
|
|
405372
406579
|
["deviceManager.getDevice", redactDeviceInfoSecrets],
|
|
405373
406580
|
["deviceManager.listAll", redactDeviceInfoSecrets],
|
|
405374
406581
|
["deviceManager.getChildren", redactDeviceInfoSecrets],
|
|
406582
|
+
["deviceManager.getChildrenBatch", redactDeviceInfoRecordSecrets],
|
|
405375
406583
|
["deviceManager.getDeviceSettingsAggregate", redactSettingsSections],
|
|
405376
406584
|
["deviceManager.getSettingsSchema", redactSettingsSections],
|
|
405377
406585
|
["deviceManager.getDeviceAggregate", redactSettingsAggregate],
|
|
@@ -408962,6 +410170,234 @@ var require_addon_settings_provider = __commonJS({
|
|
|
408962
410170
|
}
|
|
408963
410171
|
});
|
|
408964
410172
|
|
|
410173
|
+
// ../../server/backend/dist/core/addon/single-flight-refresh.js
|
|
410174
|
+
var require_single_flight_refresh = __commonJS({
|
|
410175
|
+
"../../server/backend/dist/core/addon/single-flight-refresh.js"(exports) {
|
|
410176
|
+
"use strict";
|
|
410177
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
410178
|
+
exports.createSingleFlightRefresh = createSingleFlightRefresh;
|
|
410179
|
+
function widenScope(a, b) {
|
|
410180
|
+
if (a === null || b === null)
|
|
410181
|
+
return null;
|
|
410182
|
+
const out = new Set(a);
|
|
410183
|
+
for (const id of b)
|
|
410184
|
+
out.add(id);
|
|
410185
|
+
return out;
|
|
410186
|
+
}
|
|
410187
|
+
function createSingleFlightRefresh(run) {
|
|
410188
|
+
let inFlight = null;
|
|
410189
|
+
let pendingScope;
|
|
410190
|
+
let pendingPromise = null;
|
|
410191
|
+
let releasePending = () => {
|
|
410192
|
+
};
|
|
410193
|
+
const start = (scope) => {
|
|
410194
|
+
const active = (async () => {
|
|
410195
|
+
try {
|
|
410196
|
+
await run(scope);
|
|
410197
|
+
} finally {
|
|
410198
|
+
inFlight = null;
|
|
410199
|
+
if (pendingScope !== void 0) {
|
|
410200
|
+
const next = pendingScope;
|
|
410201
|
+
pendingScope = void 0;
|
|
410202
|
+
const release = releasePending;
|
|
410203
|
+
releasePending = () => {
|
|
410204
|
+
};
|
|
410205
|
+
pendingPromise = null;
|
|
410206
|
+
void start(next).finally(release);
|
|
410207
|
+
}
|
|
410208
|
+
}
|
|
410209
|
+
})();
|
|
410210
|
+
inFlight = active;
|
|
410211
|
+
return active;
|
|
410212
|
+
};
|
|
410213
|
+
return {
|
|
410214
|
+
request: async (scope) => {
|
|
410215
|
+
if (scope !== null && scope.size === 0)
|
|
410216
|
+
return;
|
|
410217
|
+
if (inFlight === null) {
|
|
410218
|
+
await start(scope);
|
|
410219
|
+
return;
|
|
410220
|
+
}
|
|
410221
|
+
pendingScope = pendingScope === void 0 ? scope : widenScope(pendingScope, scope);
|
|
410222
|
+
if (pendingPromise === null) {
|
|
410223
|
+
pendingPromise = new Promise((resolve) => {
|
|
410224
|
+
releasePending = resolve;
|
|
410225
|
+
});
|
|
410226
|
+
}
|
|
410227
|
+
const wait = pendingPromise;
|
|
410228
|
+
if (wait !== null)
|
|
410229
|
+
await wait;
|
|
410230
|
+
}
|
|
410231
|
+
};
|
|
410232
|
+
}
|
|
410233
|
+
}
|
|
410234
|
+
});
|
|
410235
|
+
|
|
410236
|
+
// ../../server/backend/dist/core/addon/device-meta-mirror.js
|
|
410237
|
+
var require_device_meta_mirror = __commonJS({
|
|
410238
|
+
"../../server/backend/dist/core/addon/device-meta-mirror.js"(exports) {
|
|
410239
|
+
"use strict";
|
|
410240
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
410241
|
+
exports.DeviceMetaMirror = exports.MIRROR_IRRELEVANT_META_FIELDS = void 0;
|
|
410242
|
+
exports.classifyDeviceEvent = classifyDeviceEvent;
|
|
410243
|
+
var types_1 = require_dist4();
|
|
410244
|
+
var single_flight_refresh_js_1 = require_single_flight_refresh();
|
|
410245
|
+
exports.MIRROR_IRRELEVANT_META_FIELDS = /* @__PURE__ */ new Set([
|
|
410246
|
+
"name",
|
|
410247
|
+
"disabled",
|
|
410248
|
+
"metadata",
|
|
410249
|
+
"display",
|
|
410250
|
+
"role",
|
|
410251
|
+
"integrationId",
|
|
410252
|
+
"linkDeviceId",
|
|
410253
|
+
"primaryChildEntityId",
|
|
410254
|
+
"childLayout"
|
|
410255
|
+
]);
|
|
410256
|
+
function classifyDeviceEvent(data) {
|
|
410257
|
+
if (data === null || typeof data !== "object")
|
|
410258
|
+
return { kind: "fleet" };
|
|
410259
|
+
const field = Reflect.get(data, "field");
|
|
410260
|
+
if (typeof field === "string" && exports.MIRROR_IRRELEVANT_META_FIELDS.has(field)) {
|
|
410261
|
+
return { kind: "none" };
|
|
410262
|
+
}
|
|
410263
|
+
const deviceId = Reflect.get(data, "deviceId");
|
|
410264
|
+
if (typeof deviceId !== "number" || !Number.isFinite(deviceId))
|
|
410265
|
+
return { kind: "fleet" };
|
|
410266
|
+
return { kind: "device", deviceId };
|
|
410267
|
+
}
|
|
410268
|
+
var MAX_ANCESTOR_HOPS = 8;
|
|
410269
|
+
var DeviceMetaMirror = class {
|
|
410270
|
+
read;
|
|
410271
|
+
live;
|
|
410272
|
+
logger;
|
|
410273
|
+
parents = /* @__PURE__ */ new Map();
|
|
410274
|
+
meta = /* @__PURE__ */ new Map();
|
|
410275
|
+
gate;
|
|
410276
|
+
constructor(read, live, logger) {
|
|
410277
|
+
this.read = read;
|
|
410278
|
+
this.live = live;
|
|
410279
|
+
this.logger = logger;
|
|
410280
|
+
this.gate = (0, single_flight_refresh_js_1.createSingleFlightRefresh)((scope) => this.refresh(scope));
|
|
410281
|
+
}
|
|
410282
|
+
/** Ask for a refresh covering exactly what the event changed. */
|
|
410283
|
+
onDeviceEvent(data) {
|
|
410284
|
+
const scope = classifyDeviceEvent(data);
|
|
410285
|
+
if (scope.kind === "none")
|
|
410286
|
+
return;
|
|
410287
|
+
void this.gate.request(scope.kind === "fleet" ? null : /* @__PURE__ */ new Set([scope.deviceId]));
|
|
410288
|
+
}
|
|
410289
|
+
/** Warm (or re-warm) the whole fleet — the boot sweep, and the D8 reconcile
|
|
410290
|
+
* that repairs anything a dropped event would have left behind. */
|
|
410291
|
+
async refreshAll() {
|
|
410292
|
+
await this.gate.request(null);
|
|
410293
|
+
}
|
|
410294
|
+
/** Parent of a device: mirror first (covers forked devices), then the live
|
|
410295
|
+
* hub registry (covers a hub-local device before the first warm). */
|
|
410296
|
+
parentOf(deviceId) {
|
|
410297
|
+
const mirrored = this.parents.get(deviceId);
|
|
410298
|
+
if (mirrored !== void 0)
|
|
410299
|
+
return mirrored;
|
|
410300
|
+
return this.live.parentDeviceId(deviceId);
|
|
410301
|
+
}
|
|
410302
|
+
/** Persisted `DeviceType` string, or null when unknown. */
|
|
410303
|
+
typeOf(deviceId) {
|
|
410304
|
+
const mirrored = this.meta.get(deviceId);
|
|
410305
|
+
if (mirrored !== void 0)
|
|
410306
|
+
return mirrored.type;
|
|
410307
|
+
return this.live.type(deviceId);
|
|
410308
|
+
}
|
|
410309
|
+
/** Persisted operator `location` label, or null when unset/unknown. */
|
|
410310
|
+
locationOf(deviceId) {
|
|
410311
|
+
const mirrored = this.meta.get(deviceId);
|
|
410312
|
+
if (mirrored !== void 0)
|
|
410313
|
+
return mirrored.location;
|
|
410314
|
+
return this.live.location(deviceId);
|
|
410315
|
+
}
|
|
410316
|
+
/** The whole mirrored fleet — fuels fleet-wide selector expansion. */
|
|
410317
|
+
list() {
|
|
410318
|
+
const out = [];
|
|
410319
|
+
for (const [id, m] of this.meta) {
|
|
410320
|
+
out.push({
|
|
410321
|
+
id,
|
|
410322
|
+
type: m.type,
|
|
410323
|
+
location: m.location,
|
|
410324
|
+
parentDeviceId: this.parents.get(id) ?? null
|
|
410325
|
+
});
|
|
410326
|
+
}
|
|
410327
|
+
return out;
|
|
410328
|
+
}
|
|
410329
|
+
/** Ancestor chain (parent, grandparent, …), bounded. Empty for a top-level
|
|
410330
|
+
* device or one the hub has never heard of. */
|
|
410331
|
+
ancestorsOf(deviceId) {
|
|
410332
|
+
const out = [];
|
|
410333
|
+
let current = deviceId;
|
|
410334
|
+
for (let hop = 0; hop < MAX_ANCESTOR_HOPS; hop++) {
|
|
410335
|
+
const parent = this.parentOf(current);
|
|
410336
|
+
if (parent === null || parent === current)
|
|
410337
|
+
break;
|
|
410338
|
+
out.push(parent);
|
|
410339
|
+
current = parent;
|
|
410340
|
+
}
|
|
410341
|
+
return out;
|
|
410342
|
+
}
|
|
410343
|
+
async refresh(scope) {
|
|
410344
|
+
try {
|
|
410345
|
+
const ids = scope === null ? null : [...scope];
|
|
410346
|
+
const rows = await this.read(ids);
|
|
410347
|
+
if (!Array.isArray(rows))
|
|
410348
|
+
return;
|
|
410349
|
+
const nextParents = /* @__PURE__ */ new Map();
|
|
410350
|
+
const nextMeta = /* @__PURE__ */ new Map();
|
|
410351
|
+
for (const row of rows) {
|
|
410352
|
+
if (row === null || typeof row !== "object")
|
|
410353
|
+
continue;
|
|
410354
|
+
const id = Reflect.get(row, "id");
|
|
410355
|
+
if (typeof id !== "number")
|
|
410356
|
+
continue;
|
|
410357
|
+
const parent = Reflect.get(row, "parentDeviceId");
|
|
410358
|
+
if (typeof parent === "number")
|
|
410359
|
+
nextParents.set(id, parent);
|
|
410360
|
+
const type = Reflect.get(row, "type");
|
|
410361
|
+
const location = Reflect.get(row, "location");
|
|
410362
|
+
nextMeta.set(id, {
|
|
410363
|
+
type: typeof type === "string" ? type : "",
|
|
410364
|
+
location: typeof location === "string" ? location : null
|
|
410365
|
+
});
|
|
410366
|
+
}
|
|
410367
|
+
if (ids === null) {
|
|
410368
|
+
this.parents.clear();
|
|
410369
|
+
for (const [k, v] of nextParents)
|
|
410370
|
+
this.parents.set(k, v);
|
|
410371
|
+
this.meta.clear();
|
|
410372
|
+
for (const [k, v] of nextMeta)
|
|
410373
|
+
this.meta.set(k, v);
|
|
410374
|
+
return;
|
|
410375
|
+
}
|
|
410376
|
+
for (const id of ids) {
|
|
410377
|
+
const m = nextMeta.get(id);
|
|
410378
|
+
if (m === void 0) {
|
|
410379
|
+
this.parents.delete(id);
|
|
410380
|
+
this.meta.delete(id);
|
|
410381
|
+
continue;
|
|
410382
|
+
}
|
|
410383
|
+
this.meta.set(id, m);
|
|
410384
|
+
const parent = nextParents.get(id);
|
|
410385
|
+
if (parent === void 0)
|
|
410386
|
+
this.parents.delete(id);
|
|
410387
|
+
else
|
|
410388
|
+
this.parents.set(id, parent);
|
|
410389
|
+
}
|
|
410390
|
+
} catch (err) {
|
|
410391
|
+
this.logger.debug("device-meta mirror refresh failed \u2014 keeping previous", {
|
|
410392
|
+
meta: { error: (0, types_1.errMsg)(err), scope: scope === null ? "fleet" : scope.size }
|
|
410393
|
+
});
|
|
410394
|
+
}
|
|
410395
|
+
}
|
|
410396
|
+
};
|
|
410397
|
+
exports.DeviceMetaMirror = DeviceMetaMirror;
|
|
410398
|
+
}
|
|
410399
|
+
});
|
|
410400
|
+
|
|
408965
410401
|
// ../../server/backend/dist/core/addon/integration-visibility.js
|
|
408966
410402
|
var require_integration_visibility = __commonJS({
|
|
408967
410403
|
"../../server/backend/dist/core/addon/integration-visibility.js"(exports) {
|
|
@@ -409221,58 +410657,6 @@ var require_runner_spawn_fanout = __commonJS({
|
|
|
409221
410657
|
}
|
|
409222
410658
|
});
|
|
409223
410659
|
|
|
409224
|
-
// ../../server/backend/dist/core/addon/single-flight-refresh.js
|
|
409225
|
-
var require_single_flight_refresh = __commonJS({
|
|
409226
|
-
"../../server/backend/dist/core/addon/single-flight-refresh.js"(exports) {
|
|
409227
|
-
"use strict";
|
|
409228
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
409229
|
-
exports.createSingleFlightRefresh = createSingleFlightRefresh;
|
|
409230
|
-
function createSingleFlightRefresh(run) {
|
|
409231
|
-
let inFlight = null;
|
|
409232
|
-
let pending = false;
|
|
409233
|
-
let pendingPromise = null;
|
|
409234
|
-
let releasePending = () => {
|
|
409235
|
-
};
|
|
409236
|
-
const start = () => {
|
|
409237
|
-
const active = (async () => {
|
|
409238
|
-
try {
|
|
409239
|
-
await run();
|
|
409240
|
-
} finally {
|
|
409241
|
-
inFlight = null;
|
|
409242
|
-
if (pending) {
|
|
409243
|
-
pending = false;
|
|
409244
|
-
const release = releasePending;
|
|
409245
|
-
releasePending = () => {
|
|
409246
|
-
};
|
|
409247
|
-
pendingPromise = null;
|
|
409248
|
-
void start().finally(release);
|
|
409249
|
-
}
|
|
409250
|
-
}
|
|
409251
|
-
})();
|
|
409252
|
-
inFlight = active;
|
|
409253
|
-
return active;
|
|
409254
|
-
};
|
|
409255
|
-
return {
|
|
409256
|
-
request: async () => {
|
|
409257
|
-
if (inFlight === null) {
|
|
409258
|
-
await start();
|
|
409259
|
-
return;
|
|
409260
|
-
}
|
|
409261
|
-
if (!pending) {
|
|
409262
|
-
pending = true;
|
|
409263
|
-
pendingPromise = new Promise((resolve) => {
|
|
409264
|
-
releasePending = resolve;
|
|
409265
|
-
});
|
|
409266
|
-
}
|
|
409267
|
-
const wait = pendingPromise;
|
|
409268
|
-
if (wait !== null)
|
|
409269
|
-
await wait;
|
|
409270
|
-
}
|
|
409271
|
-
};
|
|
409272
|
-
}
|
|
409273
|
-
}
|
|
409274
|
-
});
|
|
409275
|
-
|
|
409276
410660
|
// ../../server/backend/dist/core/addon/addon-registry.service.js
|
|
409277
410661
|
var require_addon_registry_service = __commonJS({
|
|
409278
410662
|
"../../server/backend/dist/core/addon/addon-registry.service.js"(exports) {
|
|
@@ -409329,13 +410713,13 @@ var require_addon_registry_service = __commonJS({
|
|
|
409329
410713
|
var addon_call_gateway_js_1 = require_addon_call_gateway();
|
|
409330
410714
|
var addon_row_manifest_1 = require_addon_row_manifest();
|
|
409331
410715
|
var addon_settings_provider_js_1 = require_addon_settings_provider();
|
|
410716
|
+
var device_meta_mirror_js_1 = require_device_meta_mirror();
|
|
409332
410717
|
var integration_visibility_js_1 = require_integration_visibility();
|
|
409333
410718
|
var package_dir_utils_1 = require_package_dir_utils();
|
|
409334
410719
|
var prune_misplaced_addons_js_1 = require_prune_misplaced_addons();
|
|
409335
410720
|
var require_cache_js_1 = require_require_cache();
|
|
409336
410721
|
var runner_convergence_1 = require_runner_convergence();
|
|
409337
410722
|
var runner_spawn_fanout_js_1 = require_runner_spawn_fanout();
|
|
409338
|
-
var single_flight_refresh_js_1 = require_single_flight_refresh();
|
|
409339
410723
|
function shouldEvictMissingOnDisk(entry, id, onDiskIds) {
|
|
409340
410724
|
return entry.source === "installed" && entry.packageName !== "@camstack/system" && !onDiskIds.has(id);
|
|
409341
410725
|
}
|
|
@@ -409542,6 +410926,14 @@ var require_addon_registry_service = __commonJS({
|
|
|
409542
410926
|
this.streamProbe = streamProbe;
|
|
409543
410927
|
this.logger = this.loggingService.createLogger("AddonRegistry");
|
|
409544
410928
|
this.addonLoader = new system_1.AddonLoader(this.loggingService.createLogger("AddonLoader"));
|
|
410929
|
+
this.deviceMirror = new device_meta_mirror_js_1.DeviceMetaMirror(async (deviceIds) => this.getBrokerApi().deviceManager.listAll.query({
|
|
410930
|
+
projection: "slim",
|
|
410931
|
+
...deviceIds === null ? {} : { deviceIds: [...deviceIds] }
|
|
410932
|
+
}), {
|
|
410933
|
+
parentDeviceId: (deviceId) => this.deviceRegistry.getById(deviceId)?.parentDeviceId ?? null,
|
|
410934
|
+
type: (deviceId) => this.deviceRegistry.getById(deviceId)?.type ?? null,
|
|
410935
|
+
location: (deviceId) => this.deviceRegistry.getById(deviceId)?.location ?? null
|
|
410936
|
+
}, this.logger);
|
|
409545
410937
|
this.healthMonitor = new system_1.AddonHealthMonitor({
|
|
409546
410938
|
eventBus: this.eventBusService,
|
|
409547
410939
|
logger: this.loggingService.createLogger("AddonHealthMonitor"),
|
|
@@ -410103,56 +411495,22 @@ var require_addon_registry_service = __commonJS({
|
|
|
410103
411495
|
// fleet — forked devices included. This is a hub-process, SYNCHRONOUSLY
|
|
410104
411496
|
// readable mirror of that parentage, refreshed OFF the request path on every
|
|
410105
411497
|
// device-meta lifecycle event — never a per-call DB query.
|
|
410106
|
-
|
|
410107
|
-
|
|
410108
|
-
|
|
410109
|
-
|
|
410110
|
-
|
|
410111
|
-
* matcher resolves a selector against a device's type/location synchronously,
|
|
410112
|
-
* off the request path (D49). Empty for a device the hub has never heard of.
|
|
410113
|
-
*/
|
|
410114
|
-
deviceMetaMirror = /* @__PURE__ */ new Map();
|
|
410115
|
-
/** Parent of a device: the persisted mirror first (covers forked devices),
|
|
410116
|
-
* falling back to the live hub registry (covers a hub-local device before the
|
|
410117
|
-
* first mirror warm). null when top-level or unknown. */
|
|
410118
|
-
parentOfDevice(deviceId) {
|
|
410119
|
-
const mirrored = this.deviceParentMirror.get(deviceId);
|
|
410120
|
-
if (mirrored !== void 0)
|
|
410121
|
-
return mirrored;
|
|
410122
|
-
return this.deviceRegistry.getById(deviceId)?.parentDeviceId ?? null;
|
|
410123
|
-
}
|
|
411498
|
+
//
|
|
411499
|
+
// The mirror itself, and the rule that decides how much of the fleet one
|
|
411500
|
+
// event may re-read, live in `device-meta-mirror.ts` — with the 4 904-call /
|
|
411501
|
+
// 4 987 368-row boot census that made the rule necessary.
|
|
411502
|
+
deviceMirror;
|
|
410124
411503
|
/** Persisted `DeviceType` string of a device, or null when unknown. Mirror
|
|
410125
411504
|
* first (covers forked devices), then the live hub registry. */
|
|
410126
|
-
getPersistedType = (deviceId) =>
|
|
410127
|
-
const mirrored = this.deviceMetaMirror.get(deviceId);
|
|
410128
|
-
if (mirrored !== void 0)
|
|
410129
|
-
return mirrored.type;
|
|
410130
|
-
return this.deviceRegistry.getById(deviceId)?.type ?? null;
|
|
410131
|
-
};
|
|
411505
|
+
getPersistedType = (deviceId) => this.deviceMirror.typeOf(deviceId);
|
|
410132
411506
|
/** Persisted operator `location` label of a device, or null when unset/unknown. */
|
|
410133
|
-
getPersistedLocation = (deviceId) =>
|
|
410134
|
-
const mirrored = this.deviceMetaMirror.get(deviceId);
|
|
410135
|
-
if (mirrored !== void 0)
|
|
410136
|
-
return mirrored.location;
|
|
410137
|
-
return this.deviceRegistry.getById(deviceId)?.location ?? null;
|
|
410138
|
-
};
|
|
411507
|
+
getPersistedLocation = (deviceId) => this.deviceMirror.locationOf(deviceId);
|
|
410139
411508
|
/**
|
|
410140
411509
|
* The whole persisted fleet, slim — id + type + location + parentDeviceId.
|
|
410141
411510
|
* Fuels the FLEET-wide selector expansion (response projection + `auth.me`
|
|
410142
411511
|
* counts). Read synchronously off the mirror, never a per-call DB query.
|
|
410143
411512
|
*/
|
|
410144
|
-
getPersistedDeviceList = () =>
|
|
410145
|
-
const out = [];
|
|
410146
|
-
for (const [id, m] of this.deviceMetaMirror) {
|
|
410147
|
-
out.push({
|
|
410148
|
-
id,
|
|
410149
|
-
type: m.type,
|
|
410150
|
-
location: m.location,
|
|
410151
|
-
parentDeviceId: this.deviceParentMirror.get(id) ?? null
|
|
410152
|
-
});
|
|
410153
|
-
}
|
|
410154
|
-
return out;
|
|
410155
|
-
};
|
|
411513
|
+
getPersistedDeviceList = () => this.deviceMirror.list();
|
|
410156
411514
|
/**
|
|
410157
411515
|
* Ancestor chain (parent, grandparent, …) of a device, bounded to 8 hops
|
|
410158
411516
|
* (defence-in-depth against a corrupt registry cycle). Synchronous — it is
|
|
@@ -410160,80 +411518,31 @@ var require_addon_registry_service = __commonJS({
|
|
|
410160
411518
|
* a FORKED camera covers its accessory children. Empty for a top-level device
|
|
410161
411519
|
* or one the hub has never heard of.
|
|
410162
411520
|
*/
|
|
410163
|
-
getPersistedAncestors = (deviceId) =>
|
|
410164
|
-
|
|
410165
|
-
|
|
410166
|
-
|
|
410167
|
-
|
|
410168
|
-
|
|
410169
|
-
|
|
410170
|
-
|
|
410171
|
-
|
|
410172
|
-
|
|
410173
|
-
|
|
410174
|
-
};
|
|
410175
|
-
/** One full-fleet rebuild in flight, at most one queued behind it. Every
|
|
410176
|
-
* event below asks for the SAME total rebuild, so running it once per event
|
|
410177
|
-
* was ~1 000 rebuilds of a 974-row mirror on a boot — each one a 625 KB read
|
|
410178
|
-
* and parse on hub-main's event loop. No timer: the first event still
|
|
410179
|
-
* refreshes immediately, because this mirror backs scope enforcement.
|
|
410180
|
-
* See `single-flight-refresh.ts`. */
|
|
410181
|
-
deviceMirrorGate = (0, single_flight_refresh_js_1.createSingleFlightRefresh)(() => this.refreshDeviceParentMirror());
|
|
410182
|
-
/** Subscribe the mirror to every device-meta lifecycle event, and warm it once
|
|
410183
|
-
* the addon set (device-manager included) is up. */
|
|
411521
|
+
getPersistedAncestors = (deviceId) => this.deviceMirror.ancestorsOf(deviceId);
|
|
411522
|
+
/**
|
|
411523
|
+
* Subscribe the mirror to every device-meta lifecycle event, and warm it once
|
|
411524
|
+
* the addon set (device-manager included) is up.
|
|
411525
|
+
*
|
|
411526
|
+
* The device events ask for a SCOPED refresh — the one device they name, or
|
|
411527
|
+
* nothing at all when they name a field the mirror does not hold.
|
|
411528
|
+
* `SystemAddonsReady` is the only fleet-wide read, and it is also the D8
|
|
411529
|
+
* reconcile: whatever a dropped event would have left stale, the warm
|
|
411530
|
+
* repairs.
|
|
411531
|
+
*/
|
|
410184
411532
|
wireDeviceParentMirror() {
|
|
410185
|
-
const refresh = () => {
|
|
410186
|
-
void this.deviceMirrorGate.request();
|
|
410187
|
-
};
|
|
410188
411533
|
for (const category of [
|
|
410189
411534
|
types_1.EventCategory.DeviceMetaChanged,
|
|
410190
411535
|
types_1.EventCategory.DeviceRegistered,
|
|
410191
411536
|
types_1.EventCategory.DeviceUnregistered,
|
|
410192
|
-
types_1.EventCategory.DeviceProvisioned
|
|
410193
|
-
types_1.EventCategory.SystemAddonsReady
|
|
411537
|
+
types_1.EventCategory.DeviceProvisioned
|
|
410194
411538
|
]) {
|
|
410195
|
-
this.eventBusService.subscribe({ category },
|
|
410196
|
-
|
|
410197
|
-
}
|
|
410198
|
-
/** Rebuild the parent mirror from the device-manager's persisted fleet
|
|
410199
|
-
* (`listAll`, slim projection — no config blob). Off the request path; a
|
|
410200
|
-
* failed read KEEPS the previous mirror (D49 — a read that fails changes
|
|
410201
|
-
* nothing, and must never look like an unbind that destroys inheritance). */
|
|
410202
|
-
async refreshDeviceParentMirror() {
|
|
410203
|
-
try {
|
|
410204
|
-
const api = this.getBrokerApi();
|
|
410205
|
-
const rows = await api.deviceManager.listAll.query({ projection: "slim" });
|
|
410206
|
-
if (!Array.isArray(rows))
|
|
410207
|
-
return;
|
|
410208
|
-
const nextParents = /* @__PURE__ */ new Map();
|
|
410209
|
-
const nextMeta = /* @__PURE__ */ new Map();
|
|
410210
|
-
for (const row of rows) {
|
|
410211
|
-
if (row === null || typeof row !== "object")
|
|
410212
|
-
continue;
|
|
410213
|
-
const id = Reflect.get(row, "id");
|
|
410214
|
-
if (typeof id !== "number")
|
|
410215
|
-
continue;
|
|
410216
|
-
const parent = Reflect.get(row, "parentDeviceId");
|
|
410217
|
-
if (typeof parent === "number")
|
|
410218
|
-
nextParents.set(id, parent);
|
|
410219
|
-
const type = Reflect.get(row, "type");
|
|
410220
|
-
const location = Reflect.get(row, "location");
|
|
410221
|
-
nextMeta.set(id, {
|
|
410222
|
-
type: typeof type === "string" ? type : "",
|
|
410223
|
-
location: typeof location === "string" ? location : null
|
|
410224
|
-
});
|
|
410225
|
-
}
|
|
410226
|
-
this.deviceParentMirror.clear();
|
|
410227
|
-
for (const [k, v] of nextParents)
|
|
410228
|
-
this.deviceParentMirror.set(k, v);
|
|
410229
|
-
this.deviceMetaMirror.clear();
|
|
410230
|
-
for (const [k, v] of nextMeta)
|
|
410231
|
-
this.deviceMetaMirror.set(k, v);
|
|
410232
|
-
} catch (err) {
|
|
410233
|
-
this.logger.debug("device-parent mirror refresh failed \u2014 keeping previous", {
|
|
410234
|
-
meta: { error: (0, types_1.errMsg)(err) }
|
|
411539
|
+
this.eventBusService.subscribe({ category }, (event) => {
|
|
411540
|
+
this.deviceMirror.onDeviceEvent(event.data);
|
|
410235
411541
|
});
|
|
410236
411542
|
}
|
|
411543
|
+
this.eventBusService.subscribe({ category: types_1.EventCategory.SystemAddonsReady }, () => {
|
|
411544
|
+
void this.deviceMirror.refreshAll();
|
|
411545
|
+
});
|
|
410237
411546
|
}
|
|
410238
411547
|
/** Load persisted collection disabled-lists from settings-store into the registry */
|
|
410239
411548
|
loadCollectionPreferences() {
|