@camstack/addon-provider-dreame 0.2.32 → 0.2.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/addon.js +409 -30
  2. package/dist/addon.mjs +409 -30
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -57973,6 +57973,111 @@ var CameraSwitchGroupSchema = object({
57973
57973
  fetchedAt: number()
57974
57974
  });
57975
57975
  /**
57976
+ * Per-component log CHANNELS — the gate a hot path consults, and the registry
57977
+ * an addon declares its channels in.
57978
+ *
57979
+ * ## Two axes, deliberately separated
57980
+ *
57981
+ * - **DECLARATION** — which channels exist. Only the addon knows:
57982
+ * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
57983
+ * baichuan/handshake. A hand-wired central list rots at the first addition,
57984
+ * and rots silently. So a channel is declared where it is consulted, and the
57985
+ * `log-channels` capability enumerates the declarations.
57986
+ * - **VALUE** — at which level, for which scope, until when. That stays ONE
57987
+ * thing: the logging settings document on the `system` cap. Two authorities
57988
+ * over the values is the exact defect
57989
+ * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
57990
+ * remove; re-introducing it from the cure side would be grotesque.
57991
+ *
57992
+ * Nothing in this file reads a clock, an env var or a store. The registry is
57993
+ * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
57994
+ * the hot path with a value somebody actually read, and by
57995
+ * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
57996
+ * never reaches here, so it can neither disarm an armed channel nor arm a
57997
+ * disarmed one (D49).
57998
+ *
57999
+ * ## The canonical call shape
58000
+ *
58001
+ * ```ts
58002
+ * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
58003
+ * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
58004
+ * }
58005
+ * ```
58006
+ *
58007
+ * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
58008
+ * read. Disarmed, a call site costs one load and one branch, and the `extras`
58009
+ * object literal is never constructed because it lives inside the branch. It
58010
+ * is the same shape already proven in production at `stream-broker.ts:1650`,
58011
+ * and the same discipline `LoggingGate.allowsDestination` uses for the
58012
+ * destination floor (measured at 1.93 ns/call when off).
58013
+ *
58014
+ * ## Why a channel emits at `info`
58015
+ *
58016
+ * `loki-logging.addon.ts` pins the destination default at `info` and
58017
+ * `loki-destination.ts` drops everything below it, so a line emitted at
58018
+ * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
58019
+ * minutes. A diagnostic that cannot be read an hour later is worse than no
58020
+ * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
58021
+ * emits at the channel's declared level, whose schema floor is `info`.
58022
+ */
58023
+ /**
58024
+ * The level a channel writes at once armed.
58025
+ *
58026
+ * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
58027
+ * not leave the process for Loki, and the whole point of arming a channel is
58028
+ * to read it later.
58029
+ */
58030
+ var LogChannelLevelSchema = _enum([
58031
+ "info",
58032
+ "warn",
58033
+ "error"
58034
+ ]);
58035
+ /**
58036
+ * What an addon declares about one channel. No value, no state — a
58037
+ * declaration is inert.
58038
+ */
58039
+ var LogChannelDescriptorSchema = object({
58040
+ /**
58041
+ * Dotted `area.thing`, unique across the workspace. `area` is conventionally
58042
+ * the addon's short name so an operator reading a channel list can tell who
58043
+ * owns it without a second lookup.
58044
+ */
58045
+ name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
58046
+ /** One sentence: what the operator will SEE after arming it. */
58047
+ description: string().min(1),
58048
+ /** The level its lines are emitted at. Never below `info`. */
58049
+ defaultLevel: LogChannelLevelSchema,
58050
+ /**
58051
+ * Whether this channel can be narrowed to a camera.
58052
+ *
58053
+ * `true` is a PROMISE with two halves, and both must hold: the gate is
58054
+ * consulted with the numeric device id, AND every line the channel admits
58055
+ * carries `tags: { deviceId }` with that same numeric id. The second half is
58056
+ * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
58057
+ * keeps `deviceId` out of the stream labels for cardinality, so the tag in
58058
+ * the body is the only way to filter.
58059
+ *
58060
+ * A channel whose lines carry the device only in `meta` (or not at all) is
58061
+ * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
58062
+ * the operator narrows to one camera, sees nothing, and concludes the code
58063
+ * path was never taken.
58064
+ */
58065
+ perDevice: boolean()
58066
+ });
58067
+ /**
58068
+ * An armed window over one channel, as the document hands it to a mirror.
58069
+ *
58070
+ * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
58071
+ * expires by itself, which is the one failure a boolean cannot avoid.
58072
+ */
58073
+ var LogChannelWindowSchema = object({
58074
+ channel: string().min(1),
58075
+ /** Epoch ms the window closes at. */
58076
+ armedUntilMs: number(),
58077
+ /** `null` = every camera. A non-empty list narrows to those numeric ids. */
58078
+ deviceIds: array(number().int()).readonly().nullable()
58079
+ });
58080
+ /**
57976
58081
  * Ops-log — the durable, append-only operations audit shared by the
57977
58082
  * recordings and events management surfaces.
57978
58083
  *
@@ -61593,6 +61698,35 @@ var MutationFilterSchema = object({
61593
61698
  whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
61594
61699
  whereNot: record(string(), unknown()).optional()
61595
61700
  });
61701
+ /**
61702
+ * One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
61703
+ *
61704
+ * `as` names the slot in the result, so the SAME column may be asked twice with
61705
+ * two operations (`MIN(startMs)` and `MAX(startMs)` in one round trip) — which
61706
+ * a `Record<column, op>` shape could not express.
61707
+ */
61708
+ var AggregateFieldSchema = object({
61709
+ /** Result key. */
61710
+ as: string().min(1),
61711
+ /** Column to aggregate. Must be a real column of a declared collection. */
61712
+ field: string().min(1),
61713
+ op: _enum([
61714
+ "sum",
61715
+ "min",
61716
+ "max"
61717
+ ])
61718
+ });
61719
+ /**
61720
+ * `COUNT(*)` plus one number per requested field.
61721
+ *
61722
+ * `null` means NO ROW MATCHED, never zero: a `SUM` over an empty set and a sum
61723
+ * that really is 0 are different facts, and an accounting caller that renders
61724
+ * "0 bytes, oldest = 0" for "nothing here" reports a lie about a disk.
61725
+ */
61726
+ var AggregateResultSchema = object({
61727
+ count: number().int(),
61728
+ values: record(string(), number().nullable())
61729
+ });
61596
61730
  /** A single stored record: `{ id, data }`. */
61597
61731
  var SettingsRecordSchema = object({
61598
61732
  id: string(),
@@ -61677,6 +61811,11 @@ method(object({
61677
61811
  collection: string(),
61678
61812
  filter: QueryFilterSchema.optional()
61679
61813
  }), number()), method(object({
61814
+ namespace: string().optional(),
61815
+ collection: string(),
61816
+ fields: array(AggregateFieldSchema).readonly(),
61817
+ filter: QueryFilterSchema.optional()
61818
+ }), AggregateResultSchema), method(object({
61680
61819
  namespace: string().optional(),
61681
61820
  collection: string(),
61682
61821
  field: string(),
@@ -61793,6 +61932,11 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
61793
61932
  collection: string(),
61794
61933
  filter: QueryFilterSchema.optional()
61795
61934
  }), number(), { auth: "admin" }), method(object({
61935
+ namespace: string().optional(),
61936
+ collection: string(),
61937
+ fields: array(AggregateFieldSchema).readonly(),
61938
+ filter: QueryFilterSchema.optional()
61939
+ }), AggregateResultSchema, { auth: "admin" }), method(object({
61796
61940
  namespace: string().optional(),
61797
61941
  collection: string(),
61798
61942
  field: string(),
@@ -62533,24 +62677,6 @@ var deviceProviderCapability = {
62533
62677
  })
62534
62678
  }
62535
62679
  };
62536
- /**
62537
- * Device Manager capability — hub-side singleton that unifies device persistence,
62538
- * live registry access, and all management operations into a single tRPC surface.
62539
- *
62540
- * Replaces:
62541
- * - `device-persistence` capability (persistence methods absorbed here)
62542
- * - `device-management.router.ts` (deleted in Phase 2)
62543
- * - `device-ops.router.ts` (compat layer — deleted; device-provider ops absorbed here)
62544
- *
62545
- * All device provider addons (rtsp, onvif, frigate, …) are hub-local: they may
62546
- * fork into separate processes but never run on remote cluster agents. Therefore:
62547
- * - No nodeId routing needed — this is a pure hub singleton.
62548
- * - The hub's DeviceRegistry is the single source of truth for all live devices.
62549
- * - No shadow registry or cross-node aggregation required.
62550
- *
62551
- * Forked workers register devices back to the hub via `ctx.devices`
62552
- * (DeviceManagerApi → ctx.api.deviceManager.registerDevice), same as today.
62553
- */
62554
62680
  /** One child-placement directive on a container's `childLayout`. Structurally
62555
62681
  * identical to `ChildLayoutEntry` in `device-management.ts` — the cap wire
62556
62682
  * shape for the same field. The child is identified by its re-sync-stable
@@ -62919,7 +63045,7 @@ method(object({
62919
63045
  * it answers today and the caller filters as it already does.
62920
63046
  */
62921
63047
  deviceIds: array(number()).optional()
62922
- }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
63048
+ }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ parentDeviceIds: array(number()).max(256) }), record(string(), array(DeviceInfoSchema))), method(object({ deviceId: number() }), object({
62923
63049
  mode: LinkedDevicesModeSchema,
62924
63050
  devices: array(LinkedDeviceSchema)
62925
63051
  })), method(object({ deviceIds: array(number()) }), array(LinkedDevicesForDeviceSchema)), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
@@ -63643,6 +63769,59 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
63643
63769
  kind: "mutation",
63644
63770
  auth: "admin"
63645
63771
  });
63772
+ /**
63773
+ * `log-channels` — the capability an addon DECLARES its diagnostic channels
63774
+ * through. It stores nothing.
63775
+ *
63776
+ * ## Why a capability at all, and why this shape
63777
+ *
63778
+ * Which channels exist is knowledge only the addon has: `stream-broker` knows
63779
+ * webrtc/ICE/RTP, `provider-reolink` knows baichuan/handshake. A central list
63780
+ * maintained by hand rots at the first addition and rots INVISIBLY — nothing
63781
+ * fails, an operator just never sees the channel somebody added. So the list
63782
+ * is assembled from declarations at runtime.
63783
+ *
63784
+ * The shape is copied from `log-destination.cap.ts`, which already does
63785
+ * exactly this job: `mode: 'collection'`, `internal: true`,
63786
+ * `mount: { kind: 'skip' }` — no tRPC route, no generated hooks — while
63787
+ * `addons.listCapabilityProviders` still enumerates it, and the hub's
63788
+ * `CapabilityRegistry` still holds an RPC proxy per provider so a forked
63789
+ * runner's declarations reach hub-main over the transport that already exists.
63790
+ * No new UDS message, no second registry.
63791
+ *
63792
+ * ## What it deliberately does NOT own
63793
+ *
63794
+ * The VALUES — which channel is armed, for which cameras, until when — live in
63795
+ * ONE place: the logging settings document on the `system` cap
63796
+ * (`getLoggingSettings` / `setLoggingSettings`). Two authorities over the same
63797
+ * value is the defect the plan behind this work exists to remove, and
63798
+ * `setRequestCensus` was retired (D245) rather than allowed to be a second
63799
+ * one. {@link logChannelsCapability} therefore has no getter for a level, no
63800
+ * setter for a window and no persistence of any kind.
63801
+ *
63802
+ * ## Why `apply` is here even so
63803
+ *
63804
+ * The gate lives in the addon's PROCESS; the document lives in hub-main. Some
63805
+ * seam has to carry the value from the authority to the mirror, and a channel
63806
+ * that cannot be reached is precisely the dead knob this whole slice exists to
63807
+ * make impossible (D62 — `audioThresholdDbfs`, the HA entities with no source).
63808
+ * `apply` is that seam and nothing more: it writes an in-memory mirror, it
63809
+ * persists nothing, it is never the source of a value, and it is called only
63810
+ * with a set the hub actually read (D49 — a read that fails does not call it
63811
+ * at all, so no channel is silently disarmed by a bad read).
63812
+ */
63813
+ /** What `apply` reports back — enough to log, not enough to be a second state. */
63814
+ var LogChannelApplyResultSchema = object({
63815
+ /** How many declared channels are armed in this process after the call. */
63816
+ armed: number().int().min(0),
63817
+ /**
63818
+ * Names the document armed that this process does not declare. Reported
63819
+ * rather than swallowed: a name here is either a typo or an addon that has
63820
+ * not booted, and both deserve a line instead of silence.
63821
+ */
63822
+ unknown: array(string()).readonly()
63823
+ });
63824
+ method(_void(), array(LogChannelDescriptorSchema).readonly()), method(object({ windows: array(LogChannelWindowSchema).readonly() }), LogChannelApplyResultSchema, { kind: "mutation" });
63646
63825
  var LogLevelSchema = _enum([
63647
63826
  "debug",
63648
63827
  "info",
@@ -78950,17 +79129,60 @@ var SetSiteLocationInputSchema = object({
78950
79129
  longitude: number().min(-180).max(180)
78951
79130
  }).nullable();
78952
79131
  /**
78953
- * One `(procedure, user-agent, ip, principal)` tuple of the HTTP request
79132
+ * The TRANSPORT a call arrived on.
79133
+ *
79134
+ * Every counted call carries exactly one of these, and `unknown` is a PLANE
79135
+ * rather than a gap: a plane that cannot attribute a call declares it here, so
79136
+ * the call lands in a named bucket instead of vanishing. `planes` summing to
79137
+ * `procedureCalls` is what makes "the sum of the planes explains the total"
79138
+ * checkable rather than asserted.
79139
+ *
79140
+ * - `http` — the Fastify tRPC plugin (`/trpc/*`), one context per request.
79141
+ * - `ws` — `applyWSSHandler`, counted per OPERATION rather than per
79142
+ * connection; the viewer talks to the hub over `wsLink`
79143
+ * exclusively, so this is the plane the HTTP census could not see.
79144
+ * - `mesh` — the in-process `$core-caps` bridge (`createCaller`), which
79145
+ * never touches a socket and therefore never touched a census.
79146
+ * - `unknown` — counted, plane undecidable. No hook produces it today, and
79147
+ * that is exactly what its `0` asserts: every plane the hub has can name
79148
+ * itself. It is an output bucket, never a knob — a call that arrives on a
79149
+ * plane nobody instrumented lands here instead of vanishing from the total.
79150
+ */
79151
+ var TransportPlaneSchema = _enum([
79152
+ "http",
79153
+ "ws",
79154
+ "mesh",
79155
+ "unknown"
79156
+ ]);
79157
+ /**
79158
+ * Calls per plane. Every key is always present, `0` included — an absent plane
79159
+ * reads as "not instrumented", which is the one thing this census must never
79160
+ * make an operator wonder about.
79161
+ */
79162
+ var TransportPlaneCountsSchema = object({
79163
+ http: number(),
79164
+ ws: number(),
79165
+ mesh: number(),
79166
+ unknown: number()
79167
+ });
79168
+ /**
79169
+ * One `(plane, procedure, user-agent, ip, principal)` tuple of the transport
78954
79170
  * census. `principal` is the DERIVED identity (`apocaliss92 (admin)`,
78955
79171
  * `scoped:1a2b3c4d (scoped-token)`, `anonymous`) that the tRPC error log
78956
79172
  * already prints - never a token, never an `Authorization` header.
79173
+ *
79174
+ * `subscriptions` is counted APART from `calls`: a subscription is opened once
79175
+ * and lives for hours, so folding it into a call count makes one long-lived
79176
+ * stream look like a storm.
78957
79177
  */
78958
79178
  var RequestCensusGroupSchema = object({
79179
+ plane: TransportPlaneSchema,
78959
79180
  procedure: string(),
78960
79181
  userAgent: string(),
78961
79182
  ip: string(),
78962
79183
  principal: string(),
78963
79184
  calls: number(),
79185
+ subscriptions: number(),
78964
79186
  perMin: number()
78965
79187
  });
78966
79188
  /**
@@ -78973,6 +79195,14 @@ var RequestCensusGroupSchema = object({
78973
79195
  var RequestCensusProcedureSchema = object({
78974
79196
  procedure: string(),
78975
79197
  calls: number(),
79198
+ /**
79199
+ * The same total, split by transport. THIS is the row that answers the
79200
+ * question the census exists for: one look at `deviceManager.listAll` says
79201
+ * which plane carried the 4 960, without joining two log lines by eye.
79202
+ */
79203
+ planes: TransportPlaneCountsSchema,
79204
+ /** Subscription STARTS on this procedure. Never folded into `calls`. */
79205
+ subscriptions: number(),
78976
79206
  perMin: number()
78977
79207
  });
78978
79208
  /**
@@ -79000,14 +79230,45 @@ var RequestCensusStatusSchema = object({
79000
79230
  */
79001
79231
  procedureCalls: number(),
79002
79232
  /**
79233
+ * `procedureCalls` split by transport. The four keys sum to
79234
+ * `procedureCalls` by construction - {@link RequestCensusSnapshotSchema}'s
79235
+ * `planesExplainTotal` is that identity, checked rather than assumed.
79236
+ */
79237
+ planes: TransportPlaneCountsSchema,
79238
+ /**
79239
+ * True iff `planes` sums to `procedureCalls`. False means a call was counted
79240
+ * on no plane at all - which is a RESULT (a plane is missing from the
79241
+ * instrument), not a failure, and it has to be visible to be read as one.
79242
+ */
79243
+ planesExplainTotal: boolean(),
79244
+ /**
79003
79245
  * tRPC WebSocket connections opened during the window. NOT calls - the WS
79004
- * transport resolves one context per connection - but the number that says
79005
- * whether a plane this census cannot see was busy while HTTP was quiet.
79246
+ * adapter resolves one context per connection - kept because a plane's call
79247
+ * count of zero against 37 open connections says something different from a
79248
+ * plane with no connections at all.
79006
79249
  */
79007
79250
  wsConnections: number(),
79251
+ /**
79252
+ * Client frames the WS plane looked at. `wsMessages` far above
79253
+ * `planes.ws + subscriptions` means most traffic is not operations
79254
+ * (keepalives, connection params) - which is itself an answer.
79255
+ */
79256
+ wsMessages: number(),
79257
+ /**
79258
+ * Subscription STARTS across every plane, excluded from `procedureCalls` on
79259
+ * purpose: one live-events stream opened at boot and held for six hours is
79260
+ * one subscription, and counting it as a call would let a quiet plane
79261
+ * masquerade as the storm.
79262
+ */
79263
+ subscriptions: number(),
79264
+ /** `subscription.stop` frames. Starts minus stops is what is still open. */
79265
+ subscriptionStops: number(),
79008
79266
  distinctGroups: number(),
79009
- /** Calls counted in the totals whose group attribution was shed at the
79010
- * cardinality bound. */
79267
+ /**
79268
+ * Operations counted in the totals whose CALLER attribution was shed at the
79269
+ * cardinality bound. Unrelated to the `unknown` PLANE: these calls know
79270
+ * which transport they arrived on, they just lost their group row.
79271
+ */
79011
79272
  unattributedCalls: number(),
79012
79273
  procedures: array(RequestCensusProcedureSchema).readonly(),
79013
79274
  groups: array(RequestCensusGroupSchema).readonly()
@@ -79030,10 +79291,11 @@ var DiagnosticIdSchema = _enum(["request-census"]);
79030
79291
  * The layers of the level hierarchy, general → specific. The most specific
79031
79292
  * layer that carries an explicit value wins.
79032
79293
  *
79033
- * `component` is DECLARED and not yet resolvable: the per-component channels
79034
- * are a later slice of the same plan, and a `levelSource` enum that has to
79035
- * grow later would force every consumer of this document to change with it.
79036
- * Nothing returns `component` today.
79294
+ * `component` became RESOLVABLE on 2026-08-27: a component is a declared log
79295
+ * CHANNEL (`stream-broker.webrtc`, `provider-reolink.baichuan`), named by
79296
+ * `scopeComponent`. It was declared-but-dark in the first slice precisely so
79297
+ * that turning it on would not force every consumer of this document to widen
79298
+ * a `levelSource` enum — which is what has now not happened.
79037
79299
  */
79038
79300
  var LoggingScopeKindSchema = _enum([
79039
79301
  "cluster",
@@ -79060,6 +79322,14 @@ var LoggingLevelLayerSchema = object({
79060
79322
  scope: LoggingScopeKindSchema,
79061
79323
  /** The node this layer speaks for; `null` on the cluster layer. */
79062
79324
  nodeId: string().nullable(),
79325
+ /**
79326
+ * The declared channel this layer speaks for; `null` on every layer but
79327
+ * `component`. Never folded into `nodeId`: a component level is CLUSTER-WIDE
79328
+ * by design — the convention this repo settled on is one orchestrator-wide
79329
+ * setting, never per node (D52) — so a component layer that carried a node
79330
+ * would invite a per-node copy of a value that has no per-node meaning.
79331
+ */
79332
+ component: string().nullable(),
79063
79333
  /** Explicitly set here, or `null` when this layer inherits. */
79064
79334
  level: LogLevelSchema$1.nullable()
79065
79335
  });
@@ -79101,6 +79371,49 @@ var DiagnosticWindowPatchSchema = object({
79101
79371
  reportEveryMs: number().int().positive().optional()
79102
79372
  });
79103
79373
  /**
79374
+ * A channel ARMED, as the document reports it.
79375
+ *
79376
+ * `armMs` is not echoed back: what an operator needs to see is the deadline
79377
+ * and the time left, because a diagnostic left running is itself an incident
79378
+ * and "armed for 10 minutes" said an hour ago is not an answer.
79379
+ */
79380
+ var LogChannelWindowStateSchema = object({
79381
+ channel: string(),
79382
+ armed: boolean(),
79383
+ /** Epoch ms the window closes at. 0 when disarmed. */
79384
+ armedUntilMs: number(),
79385
+ /** Ms left before it expires on its own. 0 when disarmed. */
79386
+ remainingMs: number(),
79387
+ /**
79388
+ * The cameras it is narrowed to, or `null` for every camera.
79389
+ *
79390
+ * A channel declared `perDevice: false` can only ever report `null` here:
79391
+ * its lines do not carry `tags: { deviceId }`, so narrowing them would
79392
+ * produce a filter that silently matches nothing. The server REFUSES such a
79393
+ * patch rather than quietly widening it — ignoring the request would teach
79394
+ * the operator that per-camera filtering works on that channel when it does
79395
+ * not.
79396
+ */
79397
+ deviceIds: array(number().int()).readonly().nullable()
79398
+ });
79399
+ /**
79400
+ * `armMs: 0` DISARMS. Same grammar as {@link DiagnosticWindowPatchSchema}, and
79401
+ * for the same reason: a channel is a window with a deadline, never a switch.
79402
+ */
79403
+ var LogChannelWindowPatchSchema = object({
79404
+ channel: string().min(1),
79405
+ armMs: number().int().min(0),
79406
+ /**
79407
+ * Narrow to these numeric device ids. Absent or `null` = every camera.
79408
+ *
79409
+ * Numeric because the repo's own rule makes it possible: every log line
79410
+ * about a device carries `tags: { deviceId }` with the numeric id. That rule
79411
+ * was paid for with a 22% thumbnail gap and a 3-hour media blackout both
79412
+ * diagnosed by hand, and this is the first thing that collects on it.
79413
+ */
79414
+ deviceIds: array(number().int()).readonly().nullable().optional()
79415
+ });
79416
+ /**
79104
79417
  * A PATCH, and patches MERGE.
79105
79418
  *
79106
79419
  * A field absent from the patch is left exactly as it was — arming a
@@ -79119,7 +79432,14 @@ var LoggingSettingsPatchSchema = object({
79119
79432
  * Only the diagnostics NAMED here change. An armed window that is not listed
79120
79433
  * keeps running — a patch is never a full replacement.
79121
79434
  */
79122
- diagnostics: array(DiagnosticWindowPatchSchema).readonly().optional()
79435
+ diagnostics: array(DiagnosticWindowPatchSchema).readonly().optional(),
79436
+ /**
79437
+ * Only the channels NAMED here change. An armed channel that is not listed
79438
+ * keeps running — same rule as `diagnostics`, because a patch that silently
79439
+ * disarmed the channels it did not mention would make the Levels page and
79440
+ * the Diagnostics page fight over the same value.
79441
+ */
79442
+ channels: array(LogChannelWindowPatchSchema).readonly().optional()
79123
79443
  });
79124
79444
  /**
79125
79445
  * Which LAYER of the hierarchy is addressed. Absent = the cluster layer.
@@ -79132,9 +79452,22 @@ var LoggingSettingsPatchSchema = object({
79132
79452
  * authority over the whole hierarchy and answers for every layer, so the
79133
79453
  * layer selector needs a name the transport does not already own.
79134
79454
  */
79135
- var GetLoggingSettingsInputSchema = object({ scopeNodeId: string().optional() });
79455
+ var GetLoggingSettingsInputSchema = object({
79456
+ scopeNodeId: string().optional(),
79457
+ /**
79458
+ * The declared CHANNEL this document is addressed at, when the caller wants
79459
+ * the `component` layer. Absent = the node/cluster hierarchy only.
79460
+ *
79461
+ * Naming it separately rather than overloading `scopeNodeId` keeps the two
79462
+ * axes from collapsing: a component level is cluster-wide, a node level is
79463
+ * not, and one selector for both would make "which of these two did I just
79464
+ * set" unanswerable — the exact ambiguity `explicit` exists to remove.
79465
+ */
79466
+ scopeComponent: string().optional()
79467
+ });
79136
79468
  var SetLoggingSettingsInputSchema = object({
79137
79469
  scopeNodeId: string().optional(),
79470
+ scopeComponent: string().optional(),
79138
79471
  patch: LoggingSettingsPatchSchema
79139
79472
  });
79140
79473
  /**
@@ -79149,9 +79482,20 @@ var SetLoggingSettingsInputSchema = object({
79149
79482
  var LoggingSettingsStateSchema = object({
79150
79483
  /** The layer this document was read at. `null` = the cluster layer. */
79151
79484
  scopeNodeId: string().nullable(),
79485
+ /** The channel this document was read at. `null` = no component layer. */
79486
+ scopeComponent: string().nullable(),
79152
79487
  effective: LoggingEffectiveSchema,
79153
79488
  explicit: LoggingExplicitSchema,
79154
79489
  activeWindows: array(DiagnosticWindowSchema).readonly(),
79490
+ /**
79491
+ * Every channel the cluster's addons DECLARE, gathered from the
79492
+ * `log-channels` providers. Not stored anywhere: assembled per read, so a
79493
+ * channel added by a redeployed addon appears without anybody editing a
79494
+ * list, and a channel whose addon is gone stops being offered.
79495
+ */
79496
+ channels: array(LogChannelDescriptorSchema).readonly(),
79497
+ /** The channels ARMED right now, each with its deadline. */
79498
+ activeChannels: array(LogChannelWindowStateSchema).readonly(),
79155
79499
  persisted: boolean()
79156
79500
  });
79157
79501
  method(_void(), FeatureManifestSchema), method(_void(), HealthStatusSchema), method(_void(), FeatureManifestSchema), method(_void(), array(NetworkAddressSchema).readonly()), method(_void(), unknown().nullable(), { auth: "admin" }), method(record(string(), unknown()), _null(), {
@@ -82136,6 +82480,12 @@ Object.freeze({
82136
82480
  addonId: null,
82137
82481
  access: "view"
82138
82482
  },
82483
+ "dataStoreProvider.aggregate": {
82484
+ capName: "data-store-provider",
82485
+ capScope: "system",
82486
+ addonId: null,
82487
+ access: "view"
82488
+ },
82139
82489
  "dataStoreProvider.count": {
82140
82490
  capName: "data-store-provider",
82141
82491
  capScope: "system",
@@ -82550,6 +82900,12 @@ Object.freeze({
82550
82900
  addonId: null,
82551
82901
  access: "view"
82552
82902
  },
82903
+ "deviceManager.getChildrenBatch": {
82904
+ capName: "device-manager",
82905
+ capScope: "system",
82906
+ addonId: null,
82907
+ access: "view"
82908
+ },
82553
82909
  "deviceManager.getConfigSchema": {
82554
82910
  capName: "device-manager",
82555
82911
  capScope: "system",
@@ -83600,6 +83956,18 @@ Object.freeze({
83600
83956
  addonId: null,
83601
83957
  access: "create"
83602
83958
  },
83959
+ "logChannels.apply": {
83960
+ capName: "log-channels",
83961
+ capScope: "system",
83962
+ addonId: null,
83963
+ access: "create"
83964
+ },
83965
+ "logChannels.list": {
83966
+ capName: "log-channels",
83967
+ capScope: "system",
83968
+ addonId: null,
83969
+ access: "view"
83970
+ },
83603
83971
  "logDestination.query": {
83604
83972
  capName: "log-destination",
83605
83973
  capScope: "system",
@@ -85754,6 +86122,12 @@ Object.freeze({
85754
86122
  addonId: null,
85755
86123
  access: "create"
85756
86124
  },
86125
+ "settingsStore.aggregate": {
86126
+ capName: "settings-store",
86127
+ capScope: "system",
86128
+ addonId: null,
86129
+ access: "view"
86130
+ },
85757
86131
  "settingsStore.count": {
85758
86132
  capName: "settings-store",
85759
86133
  capScope: "system",
@@ -87333,6 +87707,11 @@ Object.freeze({
87333
87707
  form: "single",
87334
87708
  optional: false
87335
87709
  }],
87710
+ "deviceManager.getChildrenBatch": [{
87711
+ name: "parentDeviceIds",
87712
+ form: "array",
87713
+ optional: false
87714
+ }],
87336
87715
  "deviceManager.getConfigSchema": [{
87337
87716
  name: "deviceId",
87338
87717
  form: "single",
package/dist/addon.mjs CHANGED
@@ -57973,6 +57973,111 @@ var CameraSwitchGroupSchema = object({
57973
57973
  fetchedAt: number()
57974
57974
  });
57975
57975
  /**
57976
+ * Per-component log CHANNELS — the gate a hot path consults, and the registry
57977
+ * an addon declares its channels in.
57978
+ *
57979
+ * ## Two axes, deliberately separated
57980
+ *
57981
+ * - **DECLARATION** — which channels exist. Only the addon knows:
57982
+ * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
57983
+ * baichuan/handshake. A hand-wired central list rots at the first addition,
57984
+ * and rots silently. So a channel is declared where it is consulted, and the
57985
+ * `log-channels` capability enumerates the declarations.
57986
+ * - **VALUE** — at which level, for which scope, until when. That stays ONE
57987
+ * thing: the logging settings document on the `system` cap. Two authorities
57988
+ * over the values is the exact defect
57989
+ * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
57990
+ * remove; re-introducing it from the cure side would be grotesque.
57991
+ *
57992
+ * Nothing in this file reads a clock, an env var or a store. The registry is
57993
+ * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
57994
+ * the hot path with a value somebody actually read, and by
57995
+ * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
57996
+ * never reaches here, so it can neither disarm an armed channel nor arm a
57997
+ * disarmed one (D49).
57998
+ *
57999
+ * ## The canonical call shape
58000
+ *
58001
+ * ```ts
58002
+ * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
58003
+ * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
58004
+ * }
58005
+ * ```
58006
+ *
58007
+ * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
58008
+ * read. Disarmed, a call site costs one load and one branch, and the `extras`
58009
+ * object literal is never constructed because it lives inside the branch. It
58010
+ * is the same shape already proven in production at `stream-broker.ts:1650`,
58011
+ * and the same discipline `LoggingGate.allowsDestination` uses for the
58012
+ * destination floor (measured at 1.93 ns/call when off).
58013
+ *
58014
+ * ## Why a channel emits at `info`
58015
+ *
58016
+ * `loki-logging.addon.ts` pins the destination default at `info` and
58017
+ * `loki-destination.ts` drops everything below it, so a line emitted at
58018
+ * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
58019
+ * minutes. A diagnostic that cannot be read an hour later is worse than no
58020
+ * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
58021
+ * emits at the channel's declared level, whose schema floor is `info`.
58022
+ */
58023
+ /**
58024
+ * The level a channel writes at once armed.
58025
+ *
58026
+ * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
58027
+ * not leave the process for Loki, and the whole point of arming a channel is
58028
+ * to read it later.
58029
+ */
58030
+ var LogChannelLevelSchema = _enum([
58031
+ "info",
58032
+ "warn",
58033
+ "error"
58034
+ ]);
58035
+ /**
58036
+ * What an addon declares about one channel. No value, no state — a
58037
+ * declaration is inert.
58038
+ */
58039
+ var LogChannelDescriptorSchema = object({
58040
+ /**
58041
+ * Dotted `area.thing`, unique across the workspace. `area` is conventionally
58042
+ * the addon's short name so an operator reading a channel list can tell who
58043
+ * owns it without a second lookup.
58044
+ */
58045
+ name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
58046
+ /** One sentence: what the operator will SEE after arming it. */
58047
+ description: string().min(1),
58048
+ /** The level its lines are emitted at. Never below `info`. */
58049
+ defaultLevel: LogChannelLevelSchema,
58050
+ /**
58051
+ * Whether this channel can be narrowed to a camera.
58052
+ *
58053
+ * `true` is a PROMISE with two halves, and both must hold: the gate is
58054
+ * consulted with the numeric device id, AND every line the channel admits
58055
+ * carries `tags: { deviceId }` with that same numeric id. The second half is
58056
+ * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
58057
+ * keeps `deviceId` out of the stream labels for cardinality, so the tag in
58058
+ * the body is the only way to filter.
58059
+ *
58060
+ * A channel whose lines carry the device only in `meta` (or not at all) is
58061
+ * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
58062
+ * the operator narrows to one camera, sees nothing, and concludes the code
58063
+ * path was never taken.
58064
+ */
58065
+ perDevice: boolean()
58066
+ });
58067
+ /**
58068
+ * An armed window over one channel, as the document hands it to a mirror.
58069
+ *
58070
+ * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
58071
+ * expires by itself, which is the one failure a boolean cannot avoid.
58072
+ */
58073
+ var LogChannelWindowSchema = object({
58074
+ channel: string().min(1),
58075
+ /** Epoch ms the window closes at. */
58076
+ armedUntilMs: number(),
58077
+ /** `null` = every camera. A non-empty list narrows to those numeric ids. */
58078
+ deviceIds: array(number().int()).readonly().nullable()
58079
+ });
58080
+ /**
57976
58081
  * Ops-log — the durable, append-only operations audit shared by the
57977
58082
  * recordings and events management surfaces.
57978
58083
  *
@@ -61593,6 +61698,35 @@ var MutationFilterSchema = object({
61593
61698
  whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
61594
61699
  whereNot: record(string(), unknown()).optional()
61595
61700
  });
61701
+ /**
61702
+ * One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
61703
+ *
61704
+ * `as` names the slot in the result, so the SAME column may be asked twice with
61705
+ * two operations (`MIN(startMs)` and `MAX(startMs)` in one round trip) — which
61706
+ * a `Record<column, op>` shape could not express.
61707
+ */
61708
+ var AggregateFieldSchema = object({
61709
+ /** Result key. */
61710
+ as: string().min(1),
61711
+ /** Column to aggregate. Must be a real column of a declared collection. */
61712
+ field: string().min(1),
61713
+ op: _enum([
61714
+ "sum",
61715
+ "min",
61716
+ "max"
61717
+ ])
61718
+ });
61719
+ /**
61720
+ * `COUNT(*)` plus one number per requested field.
61721
+ *
61722
+ * `null` means NO ROW MATCHED, never zero: a `SUM` over an empty set and a sum
61723
+ * that really is 0 are different facts, and an accounting caller that renders
61724
+ * "0 bytes, oldest = 0" for "nothing here" reports a lie about a disk.
61725
+ */
61726
+ var AggregateResultSchema = object({
61727
+ count: number().int(),
61728
+ values: record(string(), number().nullable())
61729
+ });
61596
61730
  /** A single stored record: `{ id, data }`. */
61597
61731
  var SettingsRecordSchema = object({
61598
61732
  id: string(),
@@ -61677,6 +61811,11 @@ method(object({
61677
61811
  collection: string(),
61678
61812
  filter: QueryFilterSchema.optional()
61679
61813
  }), number()), method(object({
61814
+ namespace: string().optional(),
61815
+ collection: string(),
61816
+ fields: array(AggregateFieldSchema).readonly(),
61817
+ filter: QueryFilterSchema.optional()
61818
+ }), AggregateResultSchema), method(object({
61680
61819
  namespace: string().optional(),
61681
61820
  collection: string(),
61682
61821
  field: string(),
@@ -61793,6 +61932,11 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
61793
61932
  collection: string(),
61794
61933
  filter: QueryFilterSchema.optional()
61795
61934
  }), number(), { auth: "admin" }), method(object({
61935
+ namespace: string().optional(),
61936
+ collection: string(),
61937
+ fields: array(AggregateFieldSchema).readonly(),
61938
+ filter: QueryFilterSchema.optional()
61939
+ }), AggregateResultSchema, { auth: "admin" }), method(object({
61796
61940
  namespace: string().optional(),
61797
61941
  collection: string(),
61798
61942
  field: string(),
@@ -62533,24 +62677,6 @@ var deviceProviderCapability = {
62533
62677
  })
62534
62678
  }
62535
62679
  };
62536
- /**
62537
- * Device Manager capability — hub-side singleton that unifies device persistence,
62538
- * live registry access, and all management operations into a single tRPC surface.
62539
- *
62540
- * Replaces:
62541
- * - `device-persistence` capability (persistence methods absorbed here)
62542
- * - `device-management.router.ts` (deleted in Phase 2)
62543
- * - `device-ops.router.ts` (compat layer — deleted; device-provider ops absorbed here)
62544
- *
62545
- * All device provider addons (rtsp, onvif, frigate, …) are hub-local: they may
62546
- * fork into separate processes but never run on remote cluster agents. Therefore:
62547
- * - No nodeId routing needed — this is a pure hub singleton.
62548
- * - The hub's DeviceRegistry is the single source of truth for all live devices.
62549
- * - No shadow registry or cross-node aggregation required.
62550
- *
62551
- * Forked workers register devices back to the hub via `ctx.devices`
62552
- * (DeviceManagerApi → ctx.api.deviceManager.registerDevice), same as today.
62553
- */
62554
62680
  /** One child-placement directive on a container's `childLayout`. Structurally
62555
62681
  * identical to `ChildLayoutEntry` in `device-management.ts` — the cap wire
62556
62682
  * shape for the same field. The child is identified by its re-sync-stable
@@ -62919,7 +63045,7 @@ method(object({
62919
63045
  * it answers today and the caller filters as it already does.
62920
63046
  */
62921
63047
  deviceIds: array(number()).optional()
62922
- }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
63048
+ }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ parentDeviceIds: array(number()).max(256) }), record(string(), array(DeviceInfoSchema))), method(object({ deviceId: number() }), object({
62923
63049
  mode: LinkedDevicesModeSchema,
62924
63050
  devices: array(LinkedDeviceSchema)
62925
63051
  })), method(object({ deviceIds: array(number()) }), array(LinkedDevicesForDeviceSchema)), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
@@ -63643,6 +63769,59 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
63643
63769
  kind: "mutation",
63644
63770
  auth: "admin"
63645
63771
  });
63772
+ /**
63773
+ * `log-channels` — the capability an addon DECLARES its diagnostic channels
63774
+ * through. It stores nothing.
63775
+ *
63776
+ * ## Why a capability at all, and why this shape
63777
+ *
63778
+ * Which channels exist is knowledge only the addon has: `stream-broker` knows
63779
+ * webrtc/ICE/RTP, `provider-reolink` knows baichuan/handshake. A central list
63780
+ * maintained by hand rots at the first addition and rots INVISIBLY — nothing
63781
+ * fails, an operator just never sees the channel somebody added. So the list
63782
+ * is assembled from declarations at runtime.
63783
+ *
63784
+ * The shape is copied from `log-destination.cap.ts`, which already does
63785
+ * exactly this job: `mode: 'collection'`, `internal: true`,
63786
+ * `mount: { kind: 'skip' }` — no tRPC route, no generated hooks — while
63787
+ * `addons.listCapabilityProviders` still enumerates it, and the hub's
63788
+ * `CapabilityRegistry` still holds an RPC proxy per provider so a forked
63789
+ * runner's declarations reach hub-main over the transport that already exists.
63790
+ * No new UDS message, no second registry.
63791
+ *
63792
+ * ## What it deliberately does NOT own
63793
+ *
63794
+ * The VALUES — which channel is armed, for which cameras, until when — live in
63795
+ * ONE place: the logging settings document on the `system` cap
63796
+ * (`getLoggingSettings` / `setLoggingSettings`). Two authorities over the same
63797
+ * value is the defect the plan behind this work exists to remove, and
63798
+ * `setRequestCensus` was retired (D245) rather than allowed to be a second
63799
+ * one. {@link logChannelsCapability} therefore has no getter for a level, no
63800
+ * setter for a window and no persistence of any kind.
63801
+ *
63802
+ * ## Why `apply` is here even so
63803
+ *
63804
+ * The gate lives in the addon's PROCESS; the document lives in hub-main. Some
63805
+ * seam has to carry the value from the authority to the mirror, and a channel
63806
+ * that cannot be reached is precisely the dead knob this whole slice exists to
63807
+ * make impossible (D62 — `audioThresholdDbfs`, the HA entities with no source).
63808
+ * `apply` is that seam and nothing more: it writes an in-memory mirror, it
63809
+ * persists nothing, it is never the source of a value, and it is called only
63810
+ * with a set the hub actually read (D49 — a read that fails does not call it
63811
+ * at all, so no channel is silently disarmed by a bad read).
63812
+ */
63813
+ /** What `apply` reports back — enough to log, not enough to be a second state. */
63814
+ var LogChannelApplyResultSchema = object({
63815
+ /** How many declared channels are armed in this process after the call. */
63816
+ armed: number().int().min(0),
63817
+ /**
63818
+ * Names the document armed that this process does not declare. Reported
63819
+ * rather than swallowed: a name here is either a typo or an addon that has
63820
+ * not booted, and both deserve a line instead of silence.
63821
+ */
63822
+ unknown: array(string()).readonly()
63823
+ });
63824
+ method(_void(), array(LogChannelDescriptorSchema).readonly()), method(object({ windows: array(LogChannelWindowSchema).readonly() }), LogChannelApplyResultSchema, { kind: "mutation" });
63646
63825
  var LogLevelSchema = _enum([
63647
63826
  "debug",
63648
63827
  "info",
@@ -78950,17 +79129,60 @@ var SetSiteLocationInputSchema = object({
78950
79129
  longitude: number().min(-180).max(180)
78951
79130
  }).nullable();
78952
79131
  /**
78953
- * One `(procedure, user-agent, ip, principal)` tuple of the HTTP request
79132
+ * The TRANSPORT a call arrived on.
79133
+ *
79134
+ * Every counted call carries exactly one of these, and `unknown` is a PLANE
79135
+ * rather than a gap: a plane that cannot attribute a call declares it here, so
79136
+ * the call lands in a named bucket instead of vanishing. `planes` summing to
79137
+ * `procedureCalls` is what makes "the sum of the planes explains the total"
79138
+ * checkable rather than asserted.
79139
+ *
79140
+ * - `http` — the Fastify tRPC plugin (`/trpc/*`), one context per request.
79141
+ * - `ws` — `applyWSSHandler`, counted per OPERATION rather than per
79142
+ * connection; the viewer talks to the hub over `wsLink`
79143
+ * exclusively, so this is the plane the HTTP census could not see.
79144
+ * - `mesh` — the in-process `$core-caps` bridge (`createCaller`), which
79145
+ * never touches a socket and therefore never touched a census.
79146
+ * - `unknown` — counted, plane undecidable. No hook produces it today, and
79147
+ * that is exactly what its `0` asserts: every plane the hub has can name
79148
+ * itself. It is an output bucket, never a knob — a call that arrives on a
79149
+ * plane nobody instrumented lands here instead of vanishing from the total.
79150
+ */
79151
+ var TransportPlaneSchema = _enum([
79152
+ "http",
79153
+ "ws",
79154
+ "mesh",
79155
+ "unknown"
79156
+ ]);
79157
+ /**
79158
+ * Calls per plane. Every key is always present, `0` included — an absent plane
79159
+ * reads as "not instrumented", which is the one thing this census must never
79160
+ * make an operator wonder about.
79161
+ */
79162
+ var TransportPlaneCountsSchema = object({
79163
+ http: number(),
79164
+ ws: number(),
79165
+ mesh: number(),
79166
+ unknown: number()
79167
+ });
79168
+ /**
79169
+ * One `(plane, procedure, user-agent, ip, principal)` tuple of the transport
78954
79170
  * census. `principal` is the DERIVED identity (`apocaliss92 (admin)`,
78955
79171
  * `scoped:1a2b3c4d (scoped-token)`, `anonymous`) that the tRPC error log
78956
79172
  * already prints - never a token, never an `Authorization` header.
79173
+ *
79174
+ * `subscriptions` is counted APART from `calls`: a subscription is opened once
79175
+ * and lives for hours, so folding it into a call count makes one long-lived
79176
+ * stream look like a storm.
78957
79177
  */
78958
79178
  var RequestCensusGroupSchema = object({
79179
+ plane: TransportPlaneSchema,
78959
79180
  procedure: string(),
78960
79181
  userAgent: string(),
78961
79182
  ip: string(),
78962
79183
  principal: string(),
78963
79184
  calls: number(),
79185
+ subscriptions: number(),
78964
79186
  perMin: number()
78965
79187
  });
78966
79188
  /**
@@ -78973,6 +79195,14 @@ var RequestCensusGroupSchema = object({
78973
79195
  var RequestCensusProcedureSchema = object({
78974
79196
  procedure: string(),
78975
79197
  calls: number(),
79198
+ /**
79199
+ * The same total, split by transport. THIS is the row that answers the
79200
+ * question the census exists for: one look at `deviceManager.listAll` says
79201
+ * which plane carried the 4 960, without joining two log lines by eye.
79202
+ */
79203
+ planes: TransportPlaneCountsSchema,
79204
+ /** Subscription STARTS on this procedure. Never folded into `calls`. */
79205
+ subscriptions: number(),
78976
79206
  perMin: number()
78977
79207
  });
78978
79208
  /**
@@ -79000,14 +79230,45 @@ var RequestCensusStatusSchema = object({
79000
79230
  */
79001
79231
  procedureCalls: number(),
79002
79232
  /**
79233
+ * `procedureCalls` split by transport. The four keys sum to
79234
+ * `procedureCalls` by construction - {@link RequestCensusSnapshotSchema}'s
79235
+ * `planesExplainTotal` is that identity, checked rather than assumed.
79236
+ */
79237
+ planes: TransportPlaneCountsSchema,
79238
+ /**
79239
+ * True iff `planes` sums to `procedureCalls`. False means a call was counted
79240
+ * on no plane at all - which is a RESULT (a plane is missing from the
79241
+ * instrument), not a failure, and it has to be visible to be read as one.
79242
+ */
79243
+ planesExplainTotal: boolean(),
79244
+ /**
79003
79245
  * tRPC WebSocket connections opened during the window. NOT calls - the WS
79004
- * transport resolves one context per connection - but the number that says
79005
- * whether a plane this census cannot see was busy while HTTP was quiet.
79246
+ * adapter resolves one context per connection - kept because a plane's call
79247
+ * count of zero against 37 open connections says something different from a
79248
+ * plane with no connections at all.
79006
79249
  */
79007
79250
  wsConnections: number(),
79251
+ /**
79252
+ * Client frames the WS plane looked at. `wsMessages` far above
79253
+ * `planes.ws + subscriptions` means most traffic is not operations
79254
+ * (keepalives, connection params) - which is itself an answer.
79255
+ */
79256
+ wsMessages: number(),
79257
+ /**
79258
+ * Subscription STARTS across every plane, excluded from `procedureCalls` on
79259
+ * purpose: one live-events stream opened at boot and held for six hours is
79260
+ * one subscription, and counting it as a call would let a quiet plane
79261
+ * masquerade as the storm.
79262
+ */
79263
+ subscriptions: number(),
79264
+ /** `subscription.stop` frames. Starts minus stops is what is still open. */
79265
+ subscriptionStops: number(),
79008
79266
  distinctGroups: number(),
79009
- /** Calls counted in the totals whose group attribution was shed at the
79010
- * cardinality bound. */
79267
+ /**
79268
+ * Operations counted in the totals whose CALLER attribution was shed at the
79269
+ * cardinality bound. Unrelated to the `unknown` PLANE: these calls know
79270
+ * which transport they arrived on, they just lost their group row.
79271
+ */
79011
79272
  unattributedCalls: number(),
79012
79273
  procedures: array(RequestCensusProcedureSchema).readonly(),
79013
79274
  groups: array(RequestCensusGroupSchema).readonly()
@@ -79030,10 +79291,11 @@ var DiagnosticIdSchema = _enum(["request-census"]);
79030
79291
  * The layers of the level hierarchy, general → specific. The most specific
79031
79292
  * layer that carries an explicit value wins.
79032
79293
  *
79033
- * `component` is DECLARED and not yet resolvable: the per-component channels
79034
- * are a later slice of the same plan, and a `levelSource` enum that has to
79035
- * grow later would force every consumer of this document to change with it.
79036
- * Nothing returns `component` today.
79294
+ * `component` became RESOLVABLE on 2026-08-27: a component is a declared log
79295
+ * CHANNEL (`stream-broker.webrtc`, `provider-reolink.baichuan`), named by
79296
+ * `scopeComponent`. It was declared-but-dark in the first slice precisely so
79297
+ * that turning it on would not force every consumer of this document to widen
79298
+ * a `levelSource` enum — which is what has now not happened.
79037
79299
  */
79038
79300
  var LoggingScopeKindSchema = _enum([
79039
79301
  "cluster",
@@ -79060,6 +79322,14 @@ var LoggingLevelLayerSchema = object({
79060
79322
  scope: LoggingScopeKindSchema,
79061
79323
  /** The node this layer speaks for; `null` on the cluster layer. */
79062
79324
  nodeId: string().nullable(),
79325
+ /**
79326
+ * The declared channel this layer speaks for; `null` on every layer but
79327
+ * `component`. Never folded into `nodeId`: a component level is CLUSTER-WIDE
79328
+ * by design — the convention this repo settled on is one orchestrator-wide
79329
+ * setting, never per node (D52) — so a component layer that carried a node
79330
+ * would invite a per-node copy of a value that has no per-node meaning.
79331
+ */
79332
+ component: string().nullable(),
79063
79333
  /** Explicitly set here, or `null` when this layer inherits. */
79064
79334
  level: LogLevelSchema$1.nullable()
79065
79335
  });
@@ -79101,6 +79371,49 @@ var DiagnosticWindowPatchSchema = object({
79101
79371
  reportEveryMs: number().int().positive().optional()
79102
79372
  });
79103
79373
  /**
79374
+ * A channel ARMED, as the document reports it.
79375
+ *
79376
+ * `armMs` is not echoed back: what an operator needs to see is the deadline
79377
+ * and the time left, because a diagnostic left running is itself an incident
79378
+ * and "armed for 10 minutes" said an hour ago is not an answer.
79379
+ */
79380
+ var LogChannelWindowStateSchema = object({
79381
+ channel: string(),
79382
+ armed: boolean(),
79383
+ /** Epoch ms the window closes at. 0 when disarmed. */
79384
+ armedUntilMs: number(),
79385
+ /** Ms left before it expires on its own. 0 when disarmed. */
79386
+ remainingMs: number(),
79387
+ /**
79388
+ * The cameras it is narrowed to, or `null` for every camera.
79389
+ *
79390
+ * A channel declared `perDevice: false` can only ever report `null` here:
79391
+ * its lines do not carry `tags: { deviceId }`, so narrowing them would
79392
+ * produce a filter that silently matches nothing. The server REFUSES such a
79393
+ * patch rather than quietly widening it — ignoring the request would teach
79394
+ * the operator that per-camera filtering works on that channel when it does
79395
+ * not.
79396
+ */
79397
+ deviceIds: array(number().int()).readonly().nullable()
79398
+ });
79399
+ /**
79400
+ * `armMs: 0` DISARMS. Same grammar as {@link DiagnosticWindowPatchSchema}, and
79401
+ * for the same reason: a channel is a window with a deadline, never a switch.
79402
+ */
79403
+ var LogChannelWindowPatchSchema = object({
79404
+ channel: string().min(1),
79405
+ armMs: number().int().min(0),
79406
+ /**
79407
+ * Narrow to these numeric device ids. Absent or `null` = every camera.
79408
+ *
79409
+ * Numeric because the repo's own rule makes it possible: every log line
79410
+ * about a device carries `tags: { deviceId }` with the numeric id. That rule
79411
+ * was paid for with a 22% thumbnail gap and a 3-hour media blackout both
79412
+ * diagnosed by hand, and this is the first thing that collects on it.
79413
+ */
79414
+ deviceIds: array(number().int()).readonly().nullable().optional()
79415
+ });
79416
+ /**
79104
79417
  * A PATCH, and patches MERGE.
79105
79418
  *
79106
79419
  * A field absent from the patch is left exactly as it was — arming a
@@ -79119,7 +79432,14 @@ var LoggingSettingsPatchSchema = object({
79119
79432
  * Only the diagnostics NAMED here change. An armed window that is not listed
79120
79433
  * keeps running — a patch is never a full replacement.
79121
79434
  */
79122
- diagnostics: array(DiagnosticWindowPatchSchema).readonly().optional()
79435
+ diagnostics: array(DiagnosticWindowPatchSchema).readonly().optional(),
79436
+ /**
79437
+ * Only the channels NAMED here change. An armed channel that is not listed
79438
+ * keeps running — same rule as `diagnostics`, because a patch that silently
79439
+ * disarmed the channels it did not mention would make the Levels page and
79440
+ * the Diagnostics page fight over the same value.
79441
+ */
79442
+ channels: array(LogChannelWindowPatchSchema).readonly().optional()
79123
79443
  });
79124
79444
  /**
79125
79445
  * Which LAYER of the hierarchy is addressed. Absent = the cluster layer.
@@ -79132,9 +79452,22 @@ var LoggingSettingsPatchSchema = object({
79132
79452
  * authority over the whole hierarchy and answers for every layer, so the
79133
79453
  * layer selector needs a name the transport does not already own.
79134
79454
  */
79135
- var GetLoggingSettingsInputSchema = object({ scopeNodeId: string().optional() });
79455
+ var GetLoggingSettingsInputSchema = object({
79456
+ scopeNodeId: string().optional(),
79457
+ /**
79458
+ * The declared CHANNEL this document is addressed at, when the caller wants
79459
+ * the `component` layer. Absent = the node/cluster hierarchy only.
79460
+ *
79461
+ * Naming it separately rather than overloading `scopeNodeId` keeps the two
79462
+ * axes from collapsing: a component level is cluster-wide, a node level is
79463
+ * not, and one selector for both would make "which of these two did I just
79464
+ * set" unanswerable — the exact ambiguity `explicit` exists to remove.
79465
+ */
79466
+ scopeComponent: string().optional()
79467
+ });
79136
79468
  var SetLoggingSettingsInputSchema = object({
79137
79469
  scopeNodeId: string().optional(),
79470
+ scopeComponent: string().optional(),
79138
79471
  patch: LoggingSettingsPatchSchema
79139
79472
  });
79140
79473
  /**
@@ -79149,9 +79482,20 @@ var SetLoggingSettingsInputSchema = object({
79149
79482
  var LoggingSettingsStateSchema = object({
79150
79483
  /** The layer this document was read at. `null` = the cluster layer. */
79151
79484
  scopeNodeId: string().nullable(),
79485
+ /** The channel this document was read at. `null` = no component layer. */
79486
+ scopeComponent: string().nullable(),
79152
79487
  effective: LoggingEffectiveSchema,
79153
79488
  explicit: LoggingExplicitSchema,
79154
79489
  activeWindows: array(DiagnosticWindowSchema).readonly(),
79490
+ /**
79491
+ * Every channel the cluster's addons DECLARE, gathered from the
79492
+ * `log-channels` providers. Not stored anywhere: assembled per read, so a
79493
+ * channel added by a redeployed addon appears without anybody editing a
79494
+ * list, and a channel whose addon is gone stops being offered.
79495
+ */
79496
+ channels: array(LogChannelDescriptorSchema).readonly(),
79497
+ /** The channels ARMED right now, each with its deadline. */
79498
+ activeChannels: array(LogChannelWindowStateSchema).readonly(),
79155
79499
  persisted: boolean()
79156
79500
  });
79157
79501
  method(_void(), FeatureManifestSchema), method(_void(), HealthStatusSchema), method(_void(), FeatureManifestSchema), method(_void(), array(NetworkAddressSchema).readonly()), method(_void(), unknown().nullable(), { auth: "admin" }), method(record(string(), unknown()), _null(), {
@@ -82136,6 +82480,12 @@ Object.freeze({
82136
82480
  addonId: null,
82137
82481
  access: "view"
82138
82482
  },
82483
+ "dataStoreProvider.aggregate": {
82484
+ capName: "data-store-provider",
82485
+ capScope: "system",
82486
+ addonId: null,
82487
+ access: "view"
82488
+ },
82139
82489
  "dataStoreProvider.count": {
82140
82490
  capName: "data-store-provider",
82141
82491
  capScope: "system",
@@ -82550,6 +82900,12 @@ Object.freeze({
82550
82900
  addonId: null,
82551
82901
  access: "view"
82552
82902
  },
82903
+ "deviceManager.getChildrenBatch": {
82904
+ capName: "device-manager",
82905
+ capScope: "system",
82906
+ addonId: null,
82907
+ access: "view"
82908
+ },
82553
82909
  "deviceManager.getConfigSchema": {
82554
82910
  capName: "device-manager",
82555
82911
  capScope: "system",
@@ -83600,6 +83956,18 @@ Object.freeze({
83600
83956
  addonId: null,
83601
83957
  access: "create"
83602
83958
  },
83959
+ "logChannels.apply": {
83960
+ capName: "log-channels",
83961
+ capScope: "system",
83962
+ addonId: null,
83963
+ access: "create"
83964
+ },
83965
+ "logChannels.list": {
83966
+ capName: "log-channels",
83967
+ capScope: "system",
83968
+ addonId: null,
83969
+ access: "view"
83970
+ },
83603
83971
  "logDestination.query": {
83604
83972
  capName: "log-destination",
83605
83973
  capScope: "system",
@@ -85754,6 +86122,12 @@ Object.freeze({
85754
86122
  addonId: null,
85755
86123
  access: "create"
85756
86124
  },
86125
+ "settingsStore.aggregate": {
86126
+ capName: "settings-store",
86127
+ capScope: "system",
86128
+ addonId: null,
86129
+ access: "view"
86130
+ },
85757
86131
  "settingsStore.count": {
85758
86132
  capName: "settings-store",
85759
86133
  capScope: "system",
@@ -87333,6 +87707,11 @@ Object.freeze({
87333
87707
  form: "single",
87334
87708
  optional: false
87335
87709
  }],
87710
+ "deviceManager.getChildrenBatch": [{
87711
+ name: "parentDeviceIds",
87712
+ form: "array",
87713
+ optional: false
87714
+ }],
87336
87715
  "deviceManager.getConfigSchema": [{
87337
87716
  name: "deviceId",
87338
87717
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-dreame",
3
- "version": "0.2.32",
3
+ "version": "0.2.34",
4
4
  "description": "Dreame robot-vacuum / lawn-mower device-provider addon for CamStack — wraps the @apocaliss92/nodedreame Dreamehome cloud client",
5
5
  "keywords": [
6
6
  "camstack",