@camstack/addon-decoder-ffmpeg 1.2.31 → 1.2.33

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/index.js +409 -30
  2. package/dist/index.mjs +409 -30
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7469,6 +7469,111 @@ var CameraSwitchGroupSchema = object({
7469
7469
  fetchedAt: number()
7470
7470
  });
7471
7471
  /**
7472
+ * Per-component log CHANNELS — the gate a hot path consults, and the registry
7473
+ * an addon declares its channels in.
7474
+ *
7475
+ * ## Two axes, deliberately separated
7476
+ *
7477
+ * - **DECLARATION** — which channels exist. Only the addon knows:
7478
+ * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
7479
+ * baichuan/handshake. A hand-wired central list rots at the first addition,
7480
+ * and rots silently. So a channel is declared where it is consulted, and the
7481
+ * `log-channels` capability enumerates the declarations.
7482
+ * - **VALUE** — at which level, for which scope, until when. That stays ONE
7483
+ * thing: the logging settings document on the `system` cap. Two authorities
7484
+ * over the values is the exact defect
7485
+ * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
7486
+ * remove; re-introducing it from the cure side would be grotesque.
7487
+ *
7488
+ * Nothing in this file reads a clock, an env var or a store. The registry is
7489
+ * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
7490
+ * the hot path with a value somebody actually read, and by
7491
+ * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
7492
+ * never reaches here, so it can neither disarm an armed channel nor arm a
7493
+ * disarmed one (D49).
7494
+ *
7495
+ * ## The canonical call shape
7496
+ *
7497
+ * ```ts
7498
+ * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
7499
+ * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
7500
+ * }
7501
+ * ```
7502
+ *
7503
+ * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
7504
+ * read. Disarmed, a call site costs one load and one branch, and the `extras`
7505
+ * object literal is never constructed because it lives inside the branch. It
7506
+ * is the same shape already proven in production at `stream-broker.ts:1650`,
7507
+ * and the same discipline `LoggingGate.allowsDestination` uses for the
7508
+ * destination floor (measured at 1.93 ns/call when off).
7509
+ *
7510
+ * ## Why a channel emits at `info`
7511
+ *
7512
+ * `loki-logging.addon.ts` pins the destination default at `info` and
7513
+ * `loki-destination.ts` drops everything below it, so a line emitted at
7514
+ * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
7515
+ * minutes. A diagnostic that cannot be read an hour later is worse than no
7516
+ * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
7517
+ * emits at the channel's declared level, whose schema floor is `info`.
7518
+ */
7519
+ /**
7520
+ * The level a channel writes at once armed.
7521
+ *
7522
+ * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
7523
+ * not leave the process for Loki, and the whole point of arming a channel is
7524
+ * to read it later.
7525
+ */
7526
+ var LogChannelLevelSchema = _enum([
7527
+ "info",
7528
+ "warn",
7529
+ "error"
7530
+ ]);
7531
+ /**
7532
+ * What an addon declares about one channel. No value, no state — a
7533
+ * declaration is inert.
7534
+ */
7535
+ var LogChannelDescriptorSchema = object({
7536
+ /**
7537
+ * Dotted `area.thing`, unique across the workspace. `area` is conventionally
7538
+ * the addon's short name so an operator reading a channel list can tell who
7539
+ * owns it without a second lookup.
7540
+ */
7541
+ name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
7542
+ /** One sentence: what the operator will SEE after arming it. */
7543
+ description: string().min(1),
7544
+ /** The level its lines are emitted at. Never below `info`. */
7545
+ defaultLevel: LogChannelLevelSchema,
7546
+ /**
7547
+ * Whether this channel can be narrowed to a camera.
7548
+ *
7549
+ * `true` is a PROMISE with two halves, and both must hold: the gate is
7550
+ * consulted with the numeric device id, AND every line the channel admits
7551
+ * carries `tags: { deviceId }` with that same numeric id. The second half is
7552
+ * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
7553
+ * keeps `deviceId` out of the stream labels for cardinality, so the tag in
7554
+ * the body is the only way to filter.
7555
+ *
7556
+ * A channel whose lines carry the device only in `meta` (or not at all) is
7557
+ * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
7558
+ * the operator narrows to one camera, sees nothing, and concludes the code
7559
+ * path was never taken.
7560
+ */
7561
+ perDevice: boolean()
7562
+ });
7563
+ /**
7564
+ * An armed window over one channel, as the document hands it to a mirror.
7565
+ *
7566
+ * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
7567
+ * expires by itself, which is the one failure a boolean cannot avoid.
7568
+ */
7569
+ var LogChannelWindowSchema = object({
7570
+ channel: string().min(1),
7571
+ /** Epoch ms the window closes at. */
7572
+ armedUntilMs: number(),
7573
+ /** `null` = every camera. A non-empty list narrows to those numeric ids. */
7574
+ deviceIds: array(number().int()).readonly().nullable()
7575
+ });
7576
+ /**
7472
7577
  * Ops-log — the durable, append-only operations audit shared by the
7473
7578
  * recordings and events management surfaces.
7474
7579
  *
@@ -11024,6 +11129,35 @@ var MutationFilterSchema = object({
11024
11129
  whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
11025
11130
  whereNot: record(string(), unknown()).optional()
11026
11131
  });
11132
+ /**
11133
+ * One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
11134
+ *
11135
+ * `as` names the slot in the result, so the SAME column may be asked twice with
11136
+ * two operations (`MIN(startMs)` and `MAX(startMs)` in one round trip) — which
11137
+ * a `Record<column, op>` shape could not express.
11138
+ */
11139
+ var AggregateFieldSchema = object({
11140
+ /** Result key. */
11141
+ as: string().min(1),
11142
+ /** Column to aggregate. Must be a real column of a declared collection. */
11143
+ field: string().min(1),
11144
+ op: _enum([
11145
+ "sum",
11146
+ "min",
11147
+ "max"
11148
+ ])
11149
+ });
11150
+ /**
11151
+ * `COUNT(*)` plus one number per requested field.
11152
+ *
11153
+ * `null` means NO ROW MATCHED, never zero: a `SUM` over an empty set and a sum
11154
+ * that really is 0 are different facts, and an accounting caller that renders
11155
+ * "0 bytes, oldest = 0" for "nothing here" reports a lie about a disk.
11156
+ */
11157
+ var AggregateResultSchema = object({
11158
+ count: number().int(),
11159
+ values: record(string(), number().nullable())
11160
+ });
11027
11161
  /** A single stored record: `{ id, data }`. */
11028
11162
  var SettingsRecordSchema = object({
11029
11163
  id: string(),
@@ -11108,6 +11242,11 @@ method(object({
11108
11242
  collection: string(),
11109
11243
  filter: QueryFilterSchema.optional()
11110
11244
  }), number()), method(object({
11245
+ namespace: string().optional(),
11246
+ collection: string(),
11247
+ fields: array(AggregateFieldSchema).readonly(),
11248
+ filter: QueryFilterSchema.optional()
11249
+ }), AggregateResultSchema), method(object({
11111
11250
  namespace: string().optional(),
11112
11251
  collection: string(),
11113
11252
  field: string(),
@@ -11224,6 +11363,11 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
11224
11363
  collection: string(),
11225
11364
  filter: QueryFilterSchema.optional()
11226
11365
  }), number(), { auth: "admin" }), method(object({
11366
+ namespace: string().optional(),
11367
+ collection: string(),
11368
+ fields: array(AggregateFieldSchema).readonly(),
11369
+ filter: QueryFilterSchema.optional()
11370
+ }), AggregateResultSchema, { auth: "admin" }), method(object({
11227
11371
  namespace: string().optional(),
11228
11372
  collection: string(),
11229
11373
  field: string(),
@@ -11878,24 +12022,6 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
11878
12022
  kind: "mutation",
11879
12023
  auth: "admin"
11880
12024
  });
11881
- /**
11882
- * Device Manager capability — hub-side singleton that unifies device persistence,
11883
- * live registry access, and all management operations into a single tRPC surface.
11884
- *
11885
- * Replaces:
11886
- * - `device-persistence` capability (persistence methods absorbed here)
11887
- * - `device-management.router.ts` (deleted in Phase 2)
11888
- * - `device-ops.router.ts` (compat layer — deleted; device-provider ops absorbed here)
11889
- *
11890
- * All device provider addons (rtsp, onvif, frigate, …) are hub-local: they may
11891
- * fork into separate processes but never run on remote cluster agents. Therefore:
11892
- * - No nodeId routing needed — this is a pure hub singleton.
11893
- * - The hub's DeviceRegistry is the single source of truth for all live devices.
11894
- * - No shadow registry or cross-node aggregation required.
11895
- *
11896
- * Forked workers register devices back to the hub via `ctx.devices`
11897
- * (DeviceManagerApi → ctx.api.deviceManager.registerDevice), same as today.
11898
- */
11899
12025
  /** One child-placement directive on a container's `childLayout`. Structurally
11900
12026
  * identical to `ChildLayoutEntry` in `device-management.ts` — the cap wire
11901
12027
  * shape for the same field. The child is identified by its re-sync-stable
@@ -12264,7 +12390,7 @@ method(object({
12264
12390
  * it answers today and the caller filters as it already does.
12265
12391
  */
12266
12392
  deviceIds: array(number()).optional()
12267
- }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
12393
+ }), 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({
12268
12394
  mode: LinkedDevicesModeSchema,
12269
12395
  devices: array(LinkedDeviceSchema)
12270
12396
  })), 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({
@@ -12988,6 +13114,59 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
12988
13114
  kind: "mutation",
12989
13115
  auth: "admin"
12990
13116
  });
13117
+ /**
13118
+ * `log-channels` — the capability an addon DECLARES its diagnostic channels
13119
+ * through. It stores nothing.
13120
+ *
13121
+ * ## Why a capability at all, and why this shape
13122
+ *
13123
+ * Which channels exist is knowledge only the addon has: `stream-broker` knows
13124
+ * webrtc/ICE/RTP, `provider-reolink` knows baichuan/handshake. A central list
13125
+ * maintained by hand rots at the first addition and rots INVISIBLY — nothing
13126
+ * fails, an operator just never sees the channel somebody added. So the list
13127
+ * is assembled from declarations at runtime.
13128
+ *
13129
+ * The shape is copied from `log-destination.cap.ts`, which already does
13130
+ * exactly this job: `mode: 'collection'`, `internal: true`,
13131
+ * `mount: { kind: 'skip' }` — no tRPC route, no generated hooks — while
13132
+ * `addons.listCapabilityProviders` still enumerates it, and the hub's
13133
+ * `CapabilityRegistry` still holds an RPC proxy per provider so a forked
13134
+ * runner's declarations reach hub-main over the transport that already exists.
13135
+ * No new UDS message, no second registry.
13136
+ *
13137
+ * ## What it deliberately does NOT own
13138
+ *
13139
+ * The VALUES — which channel is armed, for which cameras, until when — live in
13140
+ * ONE place: the logging settings document on the `system` cap
13141
+ * (`getLoggingSettings` / `setLoggingSettings`). Two authorities over the same
13142
+ * value is the defect the plan behind this work exists to remove, and
13143
+ * `setRequestCensus` was retired (D245) rather than allowed to be a second
13144
+ * one. {@link logChannelsCapability} therefore has no getter for a level, no
13145
+ * setter for a window and no persistence of any kind.
13146
+ *
13147
+ * ## Why `apply` is here even so
13148
+ *
13149
+ * The gate lives in the addon's PROCESS; the document lives in hub-main. Some
13150
+ * seam has to carry the value from the authority to the mirror, and a channel
13151
+ * that cannot be reached is precisely the dead knob this whole slice exists to
13152
+ * make impossible (D62 — `audioThresholdDbfs`, the HA entities with no source).
13153
+ * `apply` is that seam and nothing more: it writes an in-memory mirror, it
13154
+ * persists nothing, it is never the source of a value, and it is called only
13155
+ * with a set the hub actually read (D49 — a read that fails does not call it
13156
+ * at all, so no channel is silently disarmed by a bad read).
13157
+ */
13158
+ /** What `apply` reports back — enough to log, not enough to be a second state. */
13159
+ var LogChannelApplyResultSchema = object({
13160
+ /** How many declared channels are armed in this process after the call. */
13161
+ armed: number().int().min(0),
13162
+ /**
13163
+ * Names the document armed that this process does not declare. Reported
13164
+ * rather than swallowed: a name here is either a typo or an addon that has
13165
+ * not booted, and both deserve a line instead of silence.
13166
+ */
13167
+ unknown: array(string()).readonly()
13168
+ });
13169
+ method(_void(), array(LogChannelDescriptorSchema).readonly()), method(object({ windows: array(LogChannelWindowSchema).readonly() }), LogChannelApplyResultSchema, { kind: "mutation" });
12991
13170
  var LogLevelSchema = _enum([
12992
13171
  "debug",
12993
13172
  "info",
@@ -26243,17 +26422,60 @@ var SetSiteLocationInputSchema = object({
26243
26422
  longitude: number().min(-180).max(180)
26244
26423
  }).nullable();
26245
26424
  /**
26246
- * One `(procedure, user-agent, ip, principal)` tuple of the HTTP request
26425
+ * The TRANSPORT a call arrived on.
26426
+ *
26427
+ * Every counted call carries exactly one of these, and `unknown` is a PLANE
26428
+ * rather than a gap: a plane that cannot attribute a call declares it here, so
26429
+ * the call lands in a named bucket instead of vanishing. `planes` summing to
26430
+ * `procedureCalls` is what makes "the sum of the planes explains the total"
26431
+ * checkable rather than asserted.
26432
+ *
26433
+ * - `http` — the Fastify tRPC plugin (`/trpc/*`), one context per request.
26434
+ * - `ws` — `applyWSSHandler`, counted per OPERATION rather than per
26435
+ * connection; the viewer talks to the hub over `wsLink`
26436
+ * exclusively, so this is the plane the HTTP census could not see.
26437
+ * - `mesh` — the in-process `$core-caps` bridge (`createCaller`), which
26438
+ * never touches a socket and therefore never touched a census.
26439
+ * - `unknown` — counted, plane undecidable. No hook produces it today, and
26440
+ * that is exactly what its `0` asserts: every plane the hub has can name
26441
+ * itself. It is an output bucket, never a knob — a call that arrives on a
26442
+ * plane nobody instrumented lands here instead of vanishing from the total.
26443
+ */
26444
+ var TransportPlaneSchema = _enum([
26445
+ "http",
26446
+ "ws",
26447
+ "mesh",
26448
+ "unknown"
26449
+ ]);
26450
+ /**
26451
+ * Calls per plane. Every key is always present, `0` included — an absent plane
26452
+ * reads as "not instrumented", which is the one thing this census must never
26453
+ * make an operator wonder about.
26454
+ */
26455
+ var TransportPlaneCountsSchema = object({
26456
+ http: number(),
26457
+ ws: number(),
26458
+ mesh: number(),
26459
+ unknown: number()
26460
+ });
26461
+ /**
26462
+ * One `(plane, procedure, user-agent, ip, principal)` tuple of the transport
26247
26463
  * census. `principal` is the DERIVED identity (`apocaliss92 (admin)`,
26248
26464
  * `scoped:1a2b3c4d (scoped-token)`, `anonymous`) that the tRPC error log
26249
26465
  * already prints - never a token, never an `Authorization` header.
26466
+ *
26467
+ * `subscriptions` is counted APART from `calls`: a subscription is opened once
26468
+ * and lives for hours, so folding it into a call count makes one long-lived
26469
+ * stream look like a storm.
26250
26470
  */
26251
26471
  var RequestCensusGroupSchema = object({
26472
+ plane: TransportPlaneSchema,
26252
26473
  procedure: string(),
26253
26474
  userAgent: string(),
26254
26475
  ip: string(),
26255
26476
  principal: string(),
26256
26477
  calls: number(),
26478
+ subscriptions: number(),
26257
26479
  perMin: number()
26258
26480
  });
26259
26481
  /**
@@ -26266,6 +26488,14 @@ var RequestCensusGroupSchema = object({
26266
26488
  var RequestCensusProcedureSchema = object({
26267
26489
  procedure: string(),
26268
26490
  calls: number(),
26491
+ /**
26492
+ * The same total, split by transport. THIS is the row that answers the
26493
+ * question the census exists for: one look at `deviceManager.listAll` says
26494
+ * which plane carried the 4 960, without joining two log lines by eye.
26495
+ */
26496
+ planes: TransportPlaneCountsSchema,
26497
+ /** Subscription STARTS on this procedure. Never folded into `calls`. */
26498
+ subscriptions: number(),
26269
26499
  perMin: number()
26270
26500
  });
26271
26501
  /**
@@ -26293,14 +26523,45 @@ var RequestCensusStatusSchema = object({
26293
26523
  */
26294
26524
  procedureCalls: number(),
26295
26525
  /**
26526
+ * `procedureCalls` split by transport. The four keys sum to
26527
+ * `procedureCalls` by construction - {@link RequestCensusSnapshotSchema}'s
26528
+ * `planesExplainTotal` is that identity, checked rather than assumed.
26529
+ */
26530
+ planes: TransportPlaneCountsSchema,
26531
+ /**
26532
+ * True iff `planes` sums to `procedureCalls`. False means a call was counted
26533
+ * on no plane at all - which is a RESULT (a plane is missing from the
26534
+ * instrument), not a failure, and it has to be visible to be read as one.
26535
+ */
26536
+ planesExplainTotal: boolean(),
26537
+ /**
26296
26538
  * tRPC WebSocket connections opened during the window. NOT calls - the WS
26297
- * transport resolves one context per connection - but the number that says
26298
- * whether a plane this census cannot see was busy while HTTP was quiet.
26539
+ * adapter resolves one context per connection - kept because a plane's call
26540
+ * count of zero against 37 open connections says something different from a
26541
+ * plane with no connections at all.
26299
26542
  */
26300
26543
  wsConnections: number(),
26544
+ /**
26545
+ * Client frames the WS plane looked at. `wsMessages` far above
26546
+ * `planes.ws + subscriptions` means most traffic is not operations
26547
+ * (keepalives, connection params) - which is itself an answer.
26548
+ */
26549
+ wsMessages: number(),
26550
+ /**
26551
+ * Subscription STARTS across every plane, excluded from `procedureCalls` on
26552
+ * purpose: one live-events stream opened at boot and held for six hours is
26553
+ * one subscription, and counting it as a call would let a quiet plane
26554
+ * masquerade as the storm.
26555
+ */
26556
+ subscriptions: number(),
26557
+ /** `subscription.stop` frames. Starts minus stops is what is still open. */
26558
+ subscriptionStops: number(),
26301
26559
  distinctGroups: number(),
26302
- /** Calls counted in the totals whose group attribution was shed at the
26303
- * cardinality bound. */
26560
+ /**
26561
+ * Operations counted in the totals whose CALLER attribution was shed at the
26562
+ * cardinality bound. Unrelated to the `unknown` PLANE: these calls know
26563
+ * which transport they arrived on, they just lost their group row.
26564
+ */
26304
26565
  unattributedCalls: number(),
26305
26566
  procedures: array(RequestCensusProcedureSchema).readonly(),
26306
26567
  groups: array(RequestCensusGroupSchema).readonly()
@@ -26323,10 +26584,11 @@ var DiagnosticIdSchema = _enum(["request-census"]);
26323
26584
  * The layers of the level hierarchy, general → specific. The most specific
26324
26585
  * layer that carries an explicit value wins.
26325
26586
  *
26326
- * `component` is DECLARED and not yet resolvable: the per-component channels
26327
- * are a later slice of the same plan, and a `levelSource` enum that has to
26328
- * grow later would force every consumer of this document to change with it.
26329
- * Nothing returns `component` today.
26587
+ * `component` became RESOLVABLE on 2026-08-27: a component is a declared log
26588
+ * CHANNEL (`stream-broker.webrtc`, `provider-reolink.baichuan`), named by
26589
+ * `scopeComponent`. It was declared-but-dark in the first slice precisely so
26590
+ * that turning it on would not force every consumer of this document to widen
26591
+ * a `levelSource` enum — which is what has now not happened.
26330
26592
  */
26331
26593
  var LoggingScopeKindSchema = _enum([
26332
26594
  "cluster",
@@ -26353,6 +26615,14 @@ var LoggingLevelLayerSchema = object({
26353
26615
  scope: LoggingScopeKindSchema,
26354
26616
  /** The node this layer speaks for; `null` on the cluster layer. */
26355
26617
  nodeId: string().nullable(),
26618
+ /**
26619
+ * The declared channel this layer speaks for; `null` on every layer but
26620
+ * `component`. Never folded into `nodeId`: a component level is CLUSTER-WIDE
26621
+ * by design — the convention this repo settled on is one orchestrator-wide
26622
+ * setting, never per node (D52) — so a component layer that carried a node
26623
+ * would invite a per-node copy of a value that has no per-node meaning.
26624
+ */
26625
+ component: string().nullable(),
26356
26626
  /** Explicitly set here, or `null` when this layer inherits. */
26357
26627
  level: LogLevelSchema$1.nullable()
26358
26628
  });
@@ -26394,6 +26664,49 @@ var DiagnosticWindowPatchSchema = object({
26394
26664
  reportEveryMs: number().int().positive().optional()
26395
26665
  });
26396
26666
  /**
26667
+ * A channel ARMED, as the document reports it.
26668
+ *
26669
+ * `armMs` is not echoed back: what an operator needs to see is the deadline
26670
+ * and the time left, because a diagnostic left running is itself an incident
26671
+ * and "armed for 10 minutes" said an hour ago is not an answer.
26672
+ */
26673
+ var LogChannelWindowStateSchema = object({
26674
+ channel: string(),
26675
+ armed: boolean(),
26676
+ /** Epoch ms the window closes at. 0 when disarmed. */
26677
+ armedUntilMs: number(),
26678
+ /** Ms left before it expires on its own. 0 when disarmed. */
26679
+ remainingMs: number(),
26680
+ /**
26681
+ * The cameras it is narrowed to, or `null` for every camera.
26682
+ *
26683
+ * A channel declared `perDevice: false` can only ever report `null` here:
26684
+ * its lines do not carry `tags: { deviceId }`, so narrowing them would
26685
+ * produce a filter that silently matches nothing. The server REFUSES such a
26686
+ * patch rather than quietly widening it — ignoring the request would teach
26687
+ * the operator that per-camera filtering works on that channel when it does
26688
+ * not.
26689
+ */
26690
+ deviceIds: array(number().int()).readonly().nullable()
26691
+ });
26692
+ /**
26693
+ * `armMs: 0` DISARMS. Same grammar as {@link DiagnosticWindowPatchSchema}, and
26694
+ * for the same reason: a channel is a window with a deadline, never a switch.
26695
+ */
26696
+ var LogChannelWindowPatchSchema = object({
26697
+ channel: string().min(1),
26698
+ armMs: number().int().min(0),
26699
+ /**
26700
+ * Narrow to these numeric device ids. Absent or `null` = every camera.
26701
+ *
26702
+ * Numeric because the repo's own rule makes it possible: every log line
26703
+ * about a device carries `tags: { deviceId }` with the numeric id. That rule
26704
+ * was paid for with a 22% thumbnail gap and a 3-hour media blackout both
26705
+ * diagnosed by hand, and this is the first thing that collects on it.
26706
+ */
26707
+ deviceIds: array(number().int()).readonly().nullable().optional()
26708
+ });
26709
+ /**
26397
26710
  * A PATCH, and patches MERGE.
26398
26711
  *
26399
26712
  * A field absent from the patch is left exactly as it was — arming a
@@ -26412,7 +26725,14 @@ var LoggingSettingsPatchSchema = object({
26412
26725
  * Only the diagnostics NAMED here change. An armed window that is not listed
26413
26726
  * keeps running — a patch is never a full replacement.
26414
26727
  */
26415
- diagnostics: array(DiagnosticWindowPatchSchema).readonly().optional()
26728
+ diagnostics: array(DiagnosticWindowPatchSchema).readonly().optional(),
26729
+ /**
26730
+ * Only the channels NAMED here change. An armed channel that is not listed
26731
+ * keeps running — same rule as `diagnostics`, because a patch that silently
26732
+ * disarmed the channels it did not mention would make the Levels page and
26733
+ * the Diagnostics page fight over the same value.
26734
+ */
26735
+ channels: array(LogChannelWindowPatchSchema).readonly().optional()
26416
26736
  });
26417
26737
  /**
26418
26738
  * Which LAYER of the hierarchy is addressed. Absent = the cluster layer.
@@ -26425,9 +26745,22 @@ var LoggingSettingsPatchSchema = object({
26425
26745
  * authority over the whole hierarchy and answers for every layer, so the
26426
26746
  * layer selector needs a name the transport does not already own.
26427
26747
  */
26428
- var GetLoggingSettingsInputSchema = object({ scopeNodeId: string().optional() });
26748
+ var GetLoggingSettingsInputSchema = object({
26749
+ scopeNodeId: string().optional(),
26750
+ /**
26751
+ * The declared CHANNEL this document is addressed at, when the caller wants
26752
+ * the `component` layer. Absent = the node/cluster hierarchy only.
26753
+ *
26754
+ * Naming it separately rather than overloading `scopeNodeId` keeps the two
26755
+ * axes from collapsing: a component level is cluster-wide, a node level is
26756
+ * not, and one selector for both would make "which of these two did I just
26757
+ * set" unanswerable — the exact ambiguity `explicit` exists to remove.
26758
+ */
26759
+ scopeComponent: string().optional()
26760
+ });
26429
26761
  var SetLoggingSettingsInputSchema = object({
26430
26762
  scopeNodeId: string().optional(),
26763
+ scopeComponent: string().optional(),
26431
26764
  patch: LoggingSettingsPatchSchema
26432
26765
  });
26433
26766
  /**
@@ -26442,9 +26775,20 @@ var SetLoggingSettingsInputSchema = object({
26442
26775
  var LoggingSettingsStateSchema = object({
26443
26776
  /** The layer this document was read at. `null` = the cluster layer. */
26444
26777
  scopeNodeId: string().nullable(),
26778
+ /** The channel this document was read at. `null` = no component layer. */
26779
+ scopeComponent: string().nullable(),
26445
26780
  effective: LoggingEffectiveSchema,
26446
26781
  explicit: LoggingExplicitSchema,
26447
26782
  activeWindows: array(DiagnosticWindowSchema).readonly(),
26783
+ /**
26784
+ * Every channel the cluster's addons DECLARE, gathered from the
26785
+ * `log-channels` providers. Not stored anywhere: assembled per read, so a
26786
+ * channel added by a redeployed addon appears without anybody editing a
26787
+ * list, and a channel whose addon is gone stops being offered.
26788
+ */
26789
+ channels: array(LogChannelDescriptorSchema).readonly(),
26790
+ /** The channels ARMED right now, each with its deadline. */
26791
+ activeChannels: array(LogChannelWindowStateSchema).readonly(),
26448
26792
  persisted: boolean()
26449
26793
  });
26450
26794
  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(), {
@@ -28021,6 +28365,12 @@ Object.freeze({
28021
28365
  addonId: null,
28022
28366
  access: "view"
28023
28367
  },
28368
+ "dataStoreProvider.aggregate": {
28369
+ capName: "data-store-provider",
28370
+ capScope: "system",
28371
+ addonId: null,
28372
+ access: "view"
28373
+ },
28024
28374
  "dataStoreProvider.count": {
28025
28375
  capName: "data-store-provider",
28026
28376
  capScope: "system",
@@ -28435,6 +28785,12 @@ Object.freeze({
28435
28785
  addonId: null,
28436
28786
  access: "view"
28437
28787
  },
28788
+ "deviceManager.getChildrenBatch": {
28789
+ capName: "device-manager",
28790
+ capScope: "system",
28791
+ addonId: null,
28792
+ access: "view"
28793
+ },
28438
28794
  "deviceManager.getConfigSchema": {
28439
28795
  capName: "device-manager",
28440
28796
  capScope: "system",
@@ -29485,6 +29841,18 @@ Object.freeze({
29485
29841
  addonId: null,
29486
29842
  access: "create"
29487
29843
  },
29844
+ "logChannels.apply": {
29845
+ capName: "log-channels",
29846
+ capScope: "system",
29847
+ addonId: null,
29848
+ access: "create"
29849
+ },
29850
+ "logChannels.list": {
29851
+ capName: "log-channels",
29852
+ capScope: "system",
29853
+ addonId: null,
29854
+ access: "view"
29855
+ },
29488
29856
  "logDestination.query": {
29489
29857
  capName: "log-destination",
29490
29858
  capScope: "system",
@@ -31639,6 +32007,12 @@ Object.freeze({
31639
32007
  addonId: null,
31640
32008
  access: "create"
31641
32009
  },
32010
+ "settingsStore.aggregate": {
32011
+ capName: "settings-store",
32012
+ capScope: "system",
32013
+ addonId: null,
32014
+ access: "view"
32015
+ },
31642
32016
  "settingsStore.count": {
31643
32017
  capName: "settings-store",
31644
32018
  capScope: "system",
@@ -33218,6 +33592,11 @@ Object.freeze({
33218
33592
  form: "single",
33219
33593
  optional: false
33220
33594
  }],
33595
+ "deviceManager.getChildrenBatch": [{
33596
+ name: "parentDeviceIds",
33597
+ form: "array",
33598
+ optional: false
33599
+ }],
33221
33600
  "deviceManager.getConfigSchema": [{
33222
33601
  name: "deviceId",
33223
33602
  form: "single",
package/dist/index.mjs CHANGED
@@ -7465,6 +7465,111 @@ var CameraSwitchGroupSchema = object({
7465
7465
  fetchedAt: number()
7466
7466
  });
7467
7467
  /**
7468
+ * Per-component log CHANNELS — the gate a hot path consults, and the registry
7469
+ * an addon declares its channels in.
7470
+ *
7471
+ * ## Two axes, deliberately separated
7472
+ *
7473
+ * - **DECLARATION** — which channels exist. Only the addon knows:
7474
+ * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
7475
+ * baichuan/handshake. A hand-wired central list rots at the first addition,
7476
+ * and rots silently. So a channel is declared where it is consulted, and the
7477
+ * `log-channels` capability enumerates the declarations.
7478
+ * - **VALUE** — at which level, for which scope, until when. That stays ONE
7479
+ * thing: the logging settings document on the `system` cap. Two authorities
7480
+ * over the values is the exact defect
7481
+ * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
7482
+ * remove; re-introducing it from the cure side would be grotesque.
7483
+ *
7484
+ * Nothing in this file reads a clock, an env var or a store. The registry is
7485
+ * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
7486
+ * the hot path with a value somebody actually read, and by
7487
+ * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
7488
+ * never reaches here, so it can neither disarm an armed channel nor arm a
7489
+ * disarmed one (D49).
7490
+ *
7491
+ * ## The canonical call shape
7492
+ *
7493
+ * ```ts
7494
+ * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
7495
+ * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
7496
+ * }
7497
+ * ```
7498
+ *
7499
+ * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
7500
+ * read. Disarmed, a call site costs one load and one branch, and the `extras`
7501
+ * object literal is never constructed because it lives inside the branch. It
7502
+ * is the same shape already proven in production at `stream-broker.ts:1650`,
7503
+ * and the same discipline `LoggingGate.allowsDestination` uses for the
7504
+ * destination floor (measured at 1.93 ns/call when off).
7505
+ *
7506
+ * ## Why a channel emits at `info`
7507
+ *
7508
+ * `loki-logging.addon.ts` pins the destination default at `info` and
7509
+ * `loki-destination.ts` drops everything below it, so a line emitted at
7510
+ * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
7511
+ * minutes. A diagnostic that cannot be read an hour later is worse than no
7512
+ * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
7513
+ * emits at the channel's declared level, whose schema floor is `info`.
7514
+ */
7515
+ /**
7516
+ * The level a channel writes at once armed.
7517
+ *
7518
+ * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
7519
+ * not leave the process for Loki, and the whole point of arming a channel is
7520
+ * to read it later.
7521
+ */
7522
+ var LogChannelLevelSchema = _enum([
7523
+ "info",
7524
+ "warn",
7525
+ "error"
7526
+ ]);
7527
+ /**
7528
+ * What an addon declares about one channel. No value, no state — a
7529
+ * declaration is inert.
7530
+ */
7531
+ var LogChannelDescriptorSchema = object({
7532
+ /**
7533
+ * Dotted `area.thing`, unique across the workspace. `area` is conventionally
7534
+ * the addon's short name so an operator reading a channel list can tell who
7535
+ * owns it without a second lookup.
7536
+ */
7537
+ name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
7538
+ /** One sentence: what the operator will SEE after arming it. */
7539
+ description: string().min(1),
7540
+ /** The level its lines are emitted at. Never below `info`. */
7541
+ defaultLevel: LogChannelLevelSchema,
7542
+ /**
7543
+ * Whether this channel can be narrowed to a camera.
7544
+ *
7545
+ * `true` is a PROMISE with two halves, and both must hold: the gate is
7546
+ * consulted with the numeric device id, AND every line the channel admits
7547
+ * carries `tags: { deviceId }` with that same numeric id. The second half is
7548
+ * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
7549
+ * keeps `deviceId` out of the stream labels for cardinality, so the tag in
7550
+ * the body is the only way to filter.
7551
+ *
7552
+ * A channel whose lines carry the device only in `meta` (or not at all) is
7553
+ * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
7554
+ * the operator narrows to one camera, sees nothing, and concludes the code
7555
+ * path was never taken.
7556
+ */
7557
+ perDevice: boolean()
7558
+ });
7559
+ /**
7560
+ * An armed window over one channel, as the document hands it to a mirror.
7561
+ *
7562
+ * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
7563
+ * expires by itself, which is the one failure a boolean cannot avoid.
7564
+ */
7565
+ var LogChannelWindowSchema = object({
7566
+ channel: string().min(1),
7567
+ /** Epoch ms the window closes at. */
7568
+ armedUntilMs: number(),
7569
+ /** `null` = every camera. A non-empty list narrows to those numeric ids. */
7570
+ deviceIds: array(number().int()).readonly().nullable()
7571
+ });
7572
+ /**
7468
7573
  * Ops-log — the durable, append-only operations audit shared by the
7469
7574
  * recordings and events management surfaces.
7470
7575
  *
@@ -11020,6 +11125,35 @@ var MutationFilterSchema = object({
11020
11125
  whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
11021
11126
  whereNot: record(string(), unknown()).optional()
11022
11127
  });
11128
+ /**
11129
+ * One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
11130
+ *
11131
+ * `as` names the slot in the result, so the SAME column may be asked twice with
11132
+ * two operations (`MIN(startMs)` and `MAX(startMs)` in one round trip) — which
11133
+ * a `Record<column, op>` shape could not express.
11134
+ */
11135
+ var AggregateFieldSchema = object({
11136
+ /** Result key. */
11137
+ as: string().min(1),
11138
+ /** Column to aggregate. Must be a real column of a declared collection. */
11139
+ field: string().min(1),
11140
+ op: _enum([
11141
+ "sum",
11142
+ "min",
11143
+ "max"
11144
+ ])
11145
+ });
11146
+ /**
11147
+ * `COUNT(*)` plus one number per requested field.
11148
+ *
11149
+ * `null` means NO ROW MATCHED, never zero: a `SUM` over an empty set and a sum
11150
+ * that really is 0 are different facts, and an accounting caller that renders
11151
+ * "0 bytes, oldest = 0" for "nothing here" reports a lie about a disk.
11152
+ */
11153
+ var AggregateResultSchema = object({
11154
+ count: number().int(),
11155
+ values: record(string(), number().nullable())
11156
+ });
11023
11157
  /** A single stored record: `{ id, data }`. */
11024
11158
  var SettingsRecordSchema = object({
11025
11159
  id: string(),
@@ -11104,6 +11238,11 @@ method(object({
11104
11238
  collection: string(),
11105
11239
  filter: QueryFilterSchema.optional()
11106
11240
  }), number()), method(object({
11241
+ namespace: string().optional(),
11242
+ collection: string(),
11243
+ fields: array(AggregateFieldSchema).readonly(),
11244
+ filter: QueryFilterSchema.optional()
11245
+ }), AggregateResultSchema), method(object({
11107
11246
  namespace: string().optional(),
11108
11247
  collection: string(),
11109
11248
  field: string(),
@@ -11220,6 +11359,11 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
11220
11359
  collection: string(),
11221
11360
  filter: QueryFilterSchema.optional()
11222
11361
  }), number(), { auth: "admin" }), method(object({
11362
+ namespace: string().optional(),
11363
+ collection: string(),
11364
+ fields: array(AggregateFieldSchema).readonly(),
11365
+ filter: QueryFilterSchema.optional()
11366
+ }), AggregateResultSchema, { auth: "admin" }), method(object({
11223
11367
  namespace: string().optional(),
11224
11368
  collection: string(),
11225
11369
  field: string(),
@@ -11874,24 +12018,6 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
11874
12018
  kind: "mutation",
11875
12019
  auth: "admin"
11876
12020
  });
11877
- /**
11878
- * Device Manager capability — hub-side singleton that unifies device persistence,
11879
- * live registry access, and all management operations into a single tRPC surface.
11880
- *
11881
- * Replaces:
11882
- * - `device-persistence` capability (persistence methods absorbed here)
11883
- * - `device-management.router.ts` (deleted in Phase 2)
11884
- * - `device-ops.router.ts` (compat layer — deleted; device-provider ops absorbed here)
11885
- *
11886
- * All device provider addons (rtsp, onvif, frigate, …) are hub-local: they may
11887
- * fork into separate processes but never run on remote cluster agents. Therefore:
11888
- * - No nodeId routing needed — this is a pure hub singleton.
11889
- * - The hub's DeviceRegistry is the single source of truth for all live devices.
11890
- * - No shadow registry or cross-node aggregation required.
11891
- *
11892
- * Forked workers register devices back to the hub via `ctx.devices`
11893
- * (DeviceManagerApi → ctx.api.deviceManager.registerDevice), same as today.
11894
- */
11895
12021
  /** One child-placement directive on a container's `childLayout`. Structurally
11896
12022
  * identical to `ChildLayoutEntry` in `device-management.ts` — the cap wire
11897
12023
  * shape for the same field. The child is identified by its re-sync-stable
@@ -12260,7 +12386,7 @@ method(object({
12260
12386
  * it answers today and the caller filters as it already does.
12261
12387
  */
12262
12388
  deviceIds: array(number()).optional()
12263
- }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
12389
+ }), 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({
12264
12390
  mode: LinkedDevicesModeSchema,
12265
12391
  devices: array(LinkedDeviceSchema)
12266
12392
  })), 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({
@@ -12984,6 +13110,59 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
12984
13110
  kind: "mutation",
12985
13111
  auth: "admin"
12986
13112
  });
13113
+ /**
13114
+ * `log-channels` — the capability an addon DECLARES its diagnostic channels
13115
+ * through. It stores nothing.
13116
+ *
13117
+ * ## Why a capability at all, and why this shape
13118
+ *
13119
+ * Which channels exist is knowledge only the addon has: `stream-broker` knows
13120
+ * webrtc/ICE/RTP, `provider-reolink` knows baichuan/handshake. A central list
13121
+ * maintained by hand rots at the first addition and rots INVISIBLY — nothing
13122
+ * fails, an operator just never sees the channel somebody added. So the list
13123
+ * is assembled from declarations at runtime.
13124
+ *
13125
+ * The shape is copied from `log-destination.cap.ts`, which already does
13126
+ * exactly this job: `mode: 'collection'`, `internal: true`,
13127
+ * `mount: { kind: 'skip' }` — no tRPC route, no generated hooks — while
13128
+ * `addons.listCapabilityProviders` still enumerates it, and the hub's
13129
+ * `CapabilityRegistry` still holds an RPC proxy per provider so a forked
13130
+ * runner's declarations reach hub-main over the transport that already exists.
13131
+ * No new UDS message, no second registry.
13132
+ *
13133
+ * ## What it deliberately does NOT own
13134
+ *
13135
+ * The VALUES — which channel is armed, for which cameras, until when — live in
13136
+ * ONE place: the logging settings document on the `system` cap
13137
+ * (`getLoggingSettings` / `setLoggingSettings`). Two authorities over the same
13138
+ * value is the defect the plan behind this work exists to remove, and
13139
+ * `setRequestCensus` was retired (D245) rather than allowed to be a second
13140
+ * one. {@link logChannelsCapability} therefore has no getter for a level, no
13141
+ * setter for a window and no persistence of any kind.
13142
+ *
13143
+ * ## Why `apply` is here even so
13144
+ *
13145
+ * The gate lives in the addon's PROCESS; the document lives in hub-main. Some
13146
+ * seam has to carry the value from the authority to the mirror, and a channel
13147
+ * that cannot be reached is precisely the dead knob this whole slice exists to
13148
+ * make impossible (D62 — `audioThresholdDbfs`, the HA entities with no source).
13149
+ * `apply` is that seam and nothing more: it writes an in-memory mirror, it
13150
+ * persists nothing, it is never the source of a value, and it is called only
13151
+ * with a set the hub actually read (D49 — a read that fails does not call it
13152
+ * at all, so no channel is silently disarmed by a bad read).
13153
+ */
13154
+ /** What `apply` reports back — enough to log, not enough to be a second state. */
13155
+ var LogChannelApplyResultSchema = object({
13156
+ /** How many declared channels are armed in this process after the call. */
13157
+ armed: number().int().min(0),
13158
+ /**
13159
+ * Names the document armed that this process does not declare. Reported
13160
+ * rather than swallowed: a name here is either a typo or an addon that has
13161
+ * not booted, and both deserve a line instead of silence.
13162
+ */
13163
+ unknown: array(string()).readonly()
13164
+ });
13165
+ method(_void(), array(LogChannelDescriptorSchema).readonly()), method(object({ windows: array(LogChannelWindowSchema).readonly() }), LogChannelApplyResultSchema, { kind: "mutation" });
12987
13166
  var LogLevelSchema = _enum([
12988
13167
  "debug",
12989
13168
  "info",
@@ -26239,17 +26418,60 @@ var SetSiteLocationInputSchema = object({
26239
26418
  longitude: number().min(-180).max(180)
26240
26419
  }).nullable();
26241
26420
  /**
26242
- * One `(procedure, user-agent, ip, principal)` tuple of the HTTP request
26421
+ * The TRANSPORT a call arrived on.
26422
+ *
26423
+ * Every counted call carries exactly one of these, and `unknown` is a PLANE
26424
+ * rather than a gap: a plane that cannot attribute a call declares it here, so
26425
+ * the call lands in a named bucket instead of vanishing. `planes` summing to
26426
+ * `procedureCalls` is what makes "the sum of the planes explains the total"
26427
+ * checkable rather than asserted.
26428
+ *
26429
+ * - `http` — the Fastify tRPC plugin (`/trpc/*`), one context per request.
26430
+ * - `ws` — `applyWSSHandler`, counted per OPERATION rather than per
26431
+ * connection; the viewer talks to the hub over `wsLink`
26432
+ * exclusively, so this is the plane the HTTP census could not see.
26433
+ * - `mesh` — the in-process `$core-caps` bridge (`createCaller`), which
26434
+ * never touches a socket and therefore never touched a census.
26435
+ * - `unknown` — counted, plane undecidable. No hook produces it today, and
26436
+ * that is exactly what its `0` asserts: every plane the hub has can name
26437
+ * itself. It is an output bucket, never a knob — a call that arrives on a
26438
+ * plane nobody instrumented lands here instead of vanishing from the total.
26439
+ */
26440
+ var TransportPlaneSchema = _enum([
26441
+ "http",
26442
+ "ws",
26443
+ "mesh",
26444
+ "unknown"
26445
+ ]);
26446
+ /**
26447
+ * Calls per plane. Every key is always present, `0` included — an absent plane
26448
+ * reads as "not instrumented", which is the one thing this census must never
26449
+ * make an operator wonder about.
26450
+ */
26451
+ var TransportPlaneCountsSchema = object({
26452
+ http: number(),
26453
+ ws: number(),
26454
+ mesh: number(),
26455
+ unknown: number()
26456
+ });
26457
+ /**
26458
+ * One `(plane, procedure, user-agent, ip, principal)` tuple of the transport
26243
26459
  * census. `principal` is the DERIVED identity (`apocaliss92 (admin)`,
26244
26460
  * `scoped:1a2b3c4d (scoped-token)`, `anonymous`) that the tRPC error log
26245
26461
  * already prints - never a token, never an `Authorization` header.
26462
+ *
26463
+ * `subscriptions` is counted APART from `calls`: a subscription is opened once
26464
+ * and lives for hours, so folding it into a call count makes one long-lived
26465
+ * stream look like a storm.
26246
26466
  */
26247
26467
  var RequestCensusGroupSchema = object({
26468
+ plane: TransportPlaneSchema,
26248
26469
  procedure: string(),
26249
26470
  userAgent: string(),
26250
26471
  ip: string(),
26251
26472
  principal: string(),
26252
26473
  calls: number(),
26474
+ subscriptions: number(),
26253
26475
  perMin: number()
26254
26476
  });
26255
26477
  /**
@@ -26262,6 +26484,14 @@ var RequestCensusGroupSchema = object({
26262
26484
  var RequestCensusProcedureSchema = object({
26263
26485
  procedure: string(),
26264
26486
  calls: number(),
26487
+ /**
26488
+ * The same total, split by transport. THIS is the row that answers the
26489
+ * question the census exists for: one look at `deviceManager.listAll` says
26490
+ * which plane carried the 4 960, without joining two log lines by eye.
26491
+ */
26492
+ planes: TransportPlaneCountsSchema,
26493
+ /** Subscription STARTS on this procedure. Never folded into `calls`. */
26494
+ subscriptions: number(),
26265
26495
  perMin: number()
26266
26496
  });
26267
26497
  /**
@@ -26289,14 +26519,45 @@ var RequestCensusStatusSchema = object({
26289
26519
  */
26290
26520
  procedureCalls: number(),
26291
26521
  /**
26522
+ * `procedureCalls` split by transport. The four keys sum to
26523
+ * `procedureCalls` by construction - {@link RequestCensusSnapshotSchema}'s
26524
+ * `planesExplainTotal` is that identity, checked rather than assumed.
26525
+ */
26526
+ planes: TransportPlaneCountsSchema,
26527
+ /**
26528
+ * True iff `planes` sums to `procedureCalls`. False means a call was counted
26529
+ * on no plane at all - which is a RESULT (a plane is missing from the
26530
+ * instrument), not a failure, and it has to be visible to be read as one.
26531
+ */
26532
+ planesExplainTotal: boolean(),
26533
+ /**
26292
26534
  * tRPC WebSocket connections opened during the window. NOT calls - the WS
26293
- * transport resolves one context per connection - but the number that says
26294
- * whether a plane this census cannot see was busy while HTTP was quiet.
26535
+ * adapter resolves one context per connection - kept because a plane's call
26536
+ * count of zero against 37 open connections says something different from a
26537
+ * plane with no connections at all.
26295
26538
  */
26296
26539
  wsConnections: number(),
26540
+ /**
26541
+ * Client frames the WS plane looked at. `wsMessages` far above
26542
+ * `planes.ws + subscriptions` means most traffic is not operations
26543
+ * (keepalives, connection params) - which is itself an answer.
26544
+ */
26545
+ wsMessages: number(),
26546
+ /**
26547
+ * Subscription STARTS across every plane, excluded from `procedureCalls` on
26548
+ * purpose: one live-events stream opened at boot and held for six hours is
26549
+ * one subscription, and counting it as a call would let a quiet plane
26550
+ * masquerade as the storm.
26551
+ */
26552
+ subscriptions: number(),
26553
+ /** `subscription.stop` frames. Starts minus stops is what is still open. */
26554
+ subscriptionStops: number(),
26297
26555
  distinctGroups: number(),
26298
- /** Calls counted in the totals whose group attribution was shed at the
26299
- * cardinality bound. */
26556
+ /**
26557
+ * Operations counted in the totals whose CALLER attribution was shed at the
26558
+ * cardinality bound. Unrelated to the `unknown` PLANE: these calls know
26559
+ * which transport they arrived on, they just lost their group row.
26560
+ */
26300
26561
  unattributedCalls: number(),
26301
26562
  procedures: array(RequestCensusProcedureSchema).readonly(),
26302
26563
  groups: array(RequestCensusGroupSchema).readonly()
@@ -26319,10 +26580,11 @@ var DiagnosticIdSchema = _enum(["request-census"]);
26319
26580
  * The layers of the level hierarchy, general → specific. The most specific
26320
26581
  * layer that carries an explicit value wins.
26321
26582
  *
26322
- * `component` is DECLARED and not yet resolvable: the per-component channels
26323
- * are a later slice of the same plan, and a `levelSource` enum that has to
26324
- * grow later would force every consumer of this document to change with it.
26325
- * Nothing returns `component` today.
26583
+ * `component` became RESOLVABLE on 2026-08-27: a component is a declared log
26584
+ * CHANNEL (`stream-broker.webrtc`, `provider-reolink.baichuan`), named by
26585
+ * `scopeComponent`. It was declared-but-dark in the first slice precisely so
26586
+ * that turning it on would not force every consumer of this document to widen
26587
+ * a `levelSource` enum — which is what has now not happened.
26326
26588
  */
26327
26589
  var LoggingScopeKindSchema = _enum([
26328
26590
  "cluster",
@@ -26349,6 +26611,14 @@ var LoggingLevelLayerSchema = object({
26349
26611
  scope: LoggingScopeKindSchema,
26350
26612
  /** The node this layer speaks for; `null` on the cluster layer. */
26351
26613
  nodeId: string().nullable(),
26614
+ /**
26615
+ * The declared channel this layer speaks for; `null` on every layer but
26616
+ * `component`. Never folded into `nodeId`: a component level is CLUSTER-WIDE
26617
+ * by design — the convention this repo settled on is one orchestrator-wide
26618
+ * setting, never per node (D52) — so a component layer that carried a node
26619
+ * would invite a per-node copy of a value that has no per-node meaning.
26620
+ */
26621
+ component: string().nullable(),
26352
26622
  /** Explicitly set here, or `null` when this layer inherits. */
26353
26623
  level: LogLevelSchema$1.nullable()
26354
26624
  });
@@ -26390,6 +26660,49 @@ var DiagnosticWindowPatchSchema = object({
26390
26660
  reportEveryMs: number().int().positive().optional()
26391
26661
  });
26392
26662
  /**
26663
+ * A channel ARMED, as the document reports it.
26664
+ *
26665
+ * `armMs` is not echoed back: what an operator needs to see is the deadline
26666
+ * and the time left, because a diagnostic left running is itself an incident
26667
+ * and "armed for 10 minutes" said an hour ago is not an answer.
26668
+ */
26669
+ var LogChannelWindowStateSchema = object({
26670
+ channel: string(),
26671
+ armed: boolean(),
26672
+ /** Epoch ms the window closes at. 0 when disarmed. */
26673
+ armedUntilMs: number(),
26674
+ /** Ms left before it expires on its own. 0 when disarmed. */
26675
+ remainingMs: number(),
26676
+ /**
26677
+ * The cameras it is narrowed to, or `null` for every camera.
26678
+ *
26679
+ * A channel declared `perDevice: false` can only ever report `null` here:
26680
+ * its lines do not carry `tags: { deviceId }`, so narrowing them would
26681
+ * produce a filter that silently matches nothing. The server REFUSES such a
26682
+ * patch rather than quietly widening it — ignoring the request would teach
26683
+ * the operator that per-camera filtering works on that channel when it does
26684
+ * not.
26685
+ */
26686
+ deviceIds: array(number().int()).readonly().nullable()
26687
+ });
26688
+ /**
26689
+ * `armMs: 0` DISARMS. Same grammar as {@link DiagnosticWindowPatchSchema}, and
26690
+ * for the same reason: a channel is a window with a deadline, never a switch.
26691
+ */
26692
+ var LogChannelWindowPatchSchema = object({
26693
+ channel: string().min(1),
26694
+ armMs: number().int().min(0),
26695
+ /**
26696
+ * Narrow to these numeric device ids. Absent or `null` = every camera.
26697
+ *
26698
+ * Numeric because the repo's own rule makes it possible: every log line
26699
+ * about a device carries `tags: { deviceId }` with the numeric id. That rule
26700
+ * was paid for with a 22% thumbnail gap and a 3-hour media blackout both
26701
+ * diagnosed by hand, and this is the first thing that collects on it.
26702
+ */
26703
+ deviceIds: array(number().int()).readonly().nullable().optional()
26704
+ });
26705
+ /**
26393
26706
  * A PATCH, and patches MERGE.
26394
26707
  *
26395
26708
  * A field absent from the patch is left exactly as it was — arming a
@@ -26408,7 +26721,14 @@ var LoggingSettingsPatchSchema = object({
26408
26721
  * Only the diagnostics NAMED here change. An armed window that is not listed
26409
26722
  * keeps running — a patch is never a full replacement.
26410
26723
  */
26411
- diagnostics: array(DiagnosticWindowPatchSchema).readonly().optional()
26724
+ diagnostics: array(DiagnosticWindowPatchSchema).readonly().optional(),
26725
+ /**
26726
+ * Only the channels NAMED here change. An armed channel that is not listed
26727
+ * keeps running — same rule as `diagnostics`, because a patch that silently
26728
+ * disarmed the channels it did not mention would make the Levels page and
26729
+ * the Diagnostics page fight over the same value.
26730
+ */
26731
+ channels: array(LogChannelWindowPatchSchema).readonly().optional()
26412
26732
  });
26413
26733
  /**
26414
26734
  * Which LAYER of the hierarchy is addressed. Absent = the cluster layer.
@@ -26421,9 +26741,22 @@ var LoggingSettingsPatchSchema = object({
26421
26741
  * authority over the whole hierarchy and answers for every layer, so the
26422
26742
  * layer selector needs a name the transport does not already own.
26423
26743
  */
26424
- var GetLoggingSettingsInputSchema = object({ scopeNodeId: string().optional() });
26744
+ var GetLoggingSettingsInputSchema = object({
26745
+ scopeNodeId: string().optional(),
26746
+ /**
26747
+ * The declared CHANNEL this document is addressed at, when the caller wants
26748
+ * the `component` layer. Absent = the node/cluster hierarchy only.
26749
+ *
26750
+ * Naming it separately rather than overloading `scopeNodeId` keeps the two
26751
+ * axes from collapsing: a component level is cluster-wide, a node level is
26752
+ * not, and one selector for both would make "which of these two did I just
26753
+ * set" unanswerable — the exact ambiguity `explicit` exists to remove.
26754
+ */
26755
+ scopeComponent: string().optional()
26756
+ });
26425
26757
  var SetLoggingSettingsInputSchema = object({
26426
26758
  scopeNodeId: string().optional(),
26759
+ scopeComponent: string().optional(),
26427
26760
  patch: LoggingSettingsPatchSchema
26428
26761
  });
26429
26762
  /**
@@ -26438,9 +26771,20 @@ var SetLoggingSettingsInputSchema = object({
26438
26771
  var LoggingSettingsStateSchema = object({
26439
26772
  /** The layer this document was read at. `null` = the cluster layer. */
26440
26773
  scopeNodeId: string().nullable(),
26774
+ /** The channel this document was read at. `null` = no component layer. */
26775
+ scopeComponent: string().nullable(),
26441
26776
  effective: LoggingEffectiveSchema,
26442
26777
  explicit: LoggingExplicitSchema,
26443
26778
  activeWindows: array(DiagnosticWindowSchema).readonly(),
26779
+ /**
26780
+ * Every channel the cluster's addons DECLARE, gathered from the
26781
+ * `log-channels` providers. Not stored anywhere: assembled per read, so a
26782
+ * channel added by a redeployed addon appears without anybody editing a
26783
+ * list, and a channel whose addon is gone stops being offered.
26784
+ */
26785
+ channels: array(LogChannelDescriptorSchema).readonly(),
26786
+ /** The channels ARMED right now, each with its deadline. */
26787
+ activeChannels: array(LogChannelWindowStateSchema).readonly(),
26444
26788
  persisted: boolean()
26445
26789
  });
26446
26790
  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(), {
@@ -28017,6 +28361,12 @@ Object.freeze({
28017
28361
  addonId: null,
28018
28362
  access: "view"
28019
28363
  },
28364
+ "dataStoreProvider.aggregate": {
28365
+ capName: "data-store-provider",
28366
+ capScope: "system",
28367
+ addonId: null,
28368
+ access: "view"
28369
+ },
28020
28370
  "dataStoreProvider.count": {
28021
28371
  capName: "data-store-provider",
28022
28372
  capScope: "system",
@@ -28431,6 +28781,12 @@ Object.freeze({
28431
28781
  addonId: null,
28432
28782
  access: "view"
28433
28783
  },
28784
+ "deviceManager.getChildrenBatch": {
28785
+ capName: "device-manager",
28786
+ capScope: "system",
28787
+ addonId: null,
28788
+ access: "view"
28789
+ },
28434
28790
  "deviceManager.getConfigSchema": {
28435
28791
  capName: "device-manager",
28436
28792
  capScope: "system",
@@ -29481,6 +29837,18 @@ Object.freeze({
29481
29837
  addonId: null,
29482
29838
  access: "create"
29483
29839
  },
29840
+ "logChannels.apply": {
29841
+ capName: "log-channels",
29842
+ capScope: "system",
29843
+ addonId: null,
29844
+ access: "create"
29845
+ },
29846
+ "logChannels.list": {
29847
+ capName: "log-channels",
29848
+ capScope: "system",
29849
+ addonId: null,
29850
+ access: "view"
29851
+ },
29484
29852
  "logDestination.query": {
29485
29853
  capName: "log-destination",
29486
29854
  capScope: "system",
@@ -31635,6 +32003,12 @@ Object.freeze({
31635
32003
  addonId: null,
31636
32004
  access: "create"
31637
32005
  },
32006
+ "settingsStore.aggregate": {
32007
+ capName: "settings-store",
32008
+ capScope: "system",
32009
+ addonId: null,
32010
+ access: "view"
32011
+ },
31638
32012
  "settingsStore.count": {
31639
32013
  capName: "settings-store",
31640
32014
  capScope: "system",
@@ -33214,6 +33588,11 @@ Object.freeze({
33214
33588
  form: "single",
33215
33589
  optional: false
33216
33590
  }],
33591
+ "deviceManager.getChildrenBatch": [{
33592
+ name: "parentDeviceIds",
33593
+ form: "array",
33594
+ optional: false
33595
+ }],
33217
33596
  "deviceManager.getConfigSchema": [{
33218
33597
  name: "deviceId",
33219
33598
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-decoder-ffmpeg",
3
- "version": "1.2.31",
3
+ "version": "1.2.33",
4
4
  "description": "Standalone ffmpeg-subprocess decoder fallback addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",