@camstack/addon-export-hap 1.2.89 → 1.2.90

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.
@@ -8281,111 +8281,6 @@ var CameraSwitchGroupSchema = object({
8281
8281
  fetchedAt: number()
8282
8282
  });
8283
8283
  /**
8284
- * Per-component log CHANNELS — the gate a hot path consults, and the registry
8285
- * an addon declares its channels in.
8286
- *
8287
- * ## Two axes, deliberately separated
8288
- *
8289
- * - **DECLARATION** — which channels exist. Only the addon knows:
8290
- * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
8291
- * baichuan/handshake. A hand-wired central list rots at the first addition,
8292
- * and rots silently. So a channel is declared where it is consulted, and the
8293
- * `log-channels` capability enumerates the declarations.
8294
- * - **VALUE** — at which level, for which scope, until when. That stays ONE
8295
- * thing: the logging settings document on the `system` cap. Two authorities
8296
- * over the values is the exact defect
8297
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
8298
- * remove; re-introducing it from the cure side would be grotesque.
8299
- *
8300
- * Nothing in this file reads a clock, an env var or a store. The registry is
8301
- * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
8302
- * the hot path with a value somebody actually read, and by
8303
- * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
8304
- * never reaches here, so it can neither disarm an armed channel nor arm a
8305
- * disarmed one (D49).
8306
- *
8307
- * ## The canonical call shape
8308
- *
8309
- * ```ts
8310
- * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
8311
- * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
8312
- * }
8313
- * ```
8314
- *
8315
- * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
8316
- * read. Disarmed, a call site costs one load and one branch, and the `extras`
8317
- * object literal is never constructed because it lives inside the branch. It
8318
- * is the same shape already proven in production at `stream-broker.ts:1650`,
8319
- * and the same discipline `LoggingGate.allowsDestination` uses for the
8320
- * destination floor (measured at 1.93 ns/call when off).
8321
- *
8322
- * ## Why a channel emits at `info`
8323
- *
8324
- * `loki-logging.addon.ts` pins the destination default at `info` and
8325
- * `loki-destination.ts` drops everything below it, so a line emitted at
8326
- * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
8327
- * minutes. A diagnostic that cannot be read an hour later is worse than no
8328
- * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
8329
- * emits at the channel's declared level, whose schema floor is `info`.
8330
- */
8331
- /**
8332
- * The level a channel writes at once armed.
8333
- *
8334
- * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
8335
- * not leave the process for Loki, and the whole point of arming a channel is
8336
- * to read it later.
8337
- */
8338
- var LogChannelLevelSchema = _enum([
8339
- "info",
8340
- "warn",
8341
- "error"
8342
- ]);
8343
- /**
8344
- * What an addon declares about one channel. No value, no state — a
8345
- * declaration is inert.
8346
- */
8347
- var LogChannelDescriptorSchema = object({
8348
- /**
8349
- * Dotted `area.thing`, unique across the workspace. `area` is conventionally
8350
- * the addon's short name so an operator reading a channel list can tell who
8351
- * owns it without a second lookup.
8352
- */
8353
- name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
8354
- /** One sentence: what the operator will SEE after arming it. */
8355
- description: string().min(1),
8356
- /** The level its lines are emitted at. Never below `info`. */
8357
- defaultLevel: LogChannelLevelSchema,
8358
- /**
8359
- * Whether this channel can be narrowed to a camera.
8360
- *
8361
- * `true` is a PROMISE with two halves, and both must hold: the gate is
8362
- * consulted with the numeric device id, AND every line the channel admits
8363
- * carries `tags: { deviceId }` with that same numeric id. The second half is
8364
- * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
8365
- * keeps `deviceId` out of the stream labels for cardinality, so the tag in
8366
- * the body is the only way to filter.
8367
- *
8368
- * A channel whose lines carry the device only in `meta` (or not at all) is
8369
- * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
8370
- * the operator narrows to one camera, sees nothing, and concludes the code
8371
- * path was never taken.
8372
- */
8373
- perDevice: boolean()
8374
- });
8375
- /**
8376
- * An armed window over one channel, as the document hands it to a mirror.
8377
- *
8378
- * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
8379
- * expires by itself, which is the one failure a boolean cannot avoid.
8380
- */
8381
- var LogChannelWindowSchema = object({
8382
- channel: string().min(1),
8383
- /** Epoch ms the window closes at. */
8384
- armedUntilMs: number(),
8385
- /** `null` = every camera. A non-empty list narrows to those numeric ids. */
8386
- deviceIds: array(number().int()).readonly().nullable()
8387
- });
8388
- /**
8389
8284
  * Ops-log — the durable, append-only operations audit shared by the
8390
8285
  * recordings and events management surfaces.
8391
8286
  *
@@ -9331,8 +9226,11 @@ var StorageLocationTypeSchema = string().regex(/^[a-z][a-zA-Z0-9-]*$/);
9331
9226
  * `STORAGE_LOCATION_CARDINALITY` map has been removed.
9332
9227
  *
9333
9228
  * `id` is a stable namespaced string of the form `<type>:<slug>`.
9334
- * The default location for a type uses `id === <type>:default` by
9335
- * convention (the bare type ref like `'backups'` resolves to it).
9229
+ * The seed names its first instance `<type>:default` — a NAME, not a flag.
9230
+ * There is no default location any more (D383): `enabled` is the whole write
9231
+ * model, and a bare type ref resolves to the sole location of the type, or —
9232
+ * transitionally, only while legacy NULL-stamped rows exist — to the row whose
9233
+ * slug is `default`.
9336
9234
  *
9337
9235
  * `isSystem` is a legacy persisted flag. Seed still creates the initial
9338
9236
  * `<type>:default` locations; the flag is no longer a lock, a badge, or a
@@ -9353,21 +9251,20 @@ var StorageLocationSchema = object({
9353
9251
  * flag at upsert time, not here (the schema is provider-agnostic).
9354
9252
  */
9355
9253
  nodeId: string().optional(),
9356
- isDefault: boolean().default(false),
9357
9254
  isSystem: boolean().default(false),
9358
9255
  /**
9359
- * Operator opt-in: whether consumers that BALANCE across several locations
9360
- * of a type may write here. Recordings reads it today; event media and
9361
- * backups are the next consumers, which is why the flag lives on the
9362
- * location rather than in any one addon's store — nothing has to be
9363
- * extended to add the next consumer.
9256
+ * THE write switch, and the only one (D383). `enabled: true` means every
9257
+ * consumer that chooses a write target for this type may write here, and all
9258
+ * enabled locations of a type are used TOGETHER; `false` means read-only —
9259
+ * still read, still played back, still age-swept, still drained, never
9260
+ * written.
9364
9261
  *
9365
- * OPTIONAL, and ABSENT MEANS ACTIVE. Every location persisted before the
9366
- * flag existed reads back with no flag and keeps working exactly as before;
9367
- * that is the whole compat story, and it is why no migration ships with it.
9368
- * A newly CREATED sibling is stamped `false` by the orchestrator (creating a
9369
- * disk must not silently start writing to it); the default of a type is
9370
- * always stamped `true`.
9262
+ * OPTIONAL only for the wire: an upsert that omits it means "leave what is
9263
+ * stored" on an update and "born inert unless it is the first location of its
9264
+ * type" on a create. On a PERSISTED row absence is legacy and it means
9265
+ * enabled — {@link isLocationEnabled} is the one place that says so, and the
9266
+ * orchestrator stamps every flagless row `true` once at hydrate so absence
9267
+ * stops existing rather than being re-derived on every read.
9371
9268
  */
9372
9269
  enabled: boolean().optional(),
9373
9270
  /** COMPUTED at read time by the orchestrator (statfs of the backing volume
@@ -9380,10 +9277,12 @@ var StorageLocationSchema = object({
9380
9277
  createdAt: number(),
9381
9278
  updatedAt: number()
9382
9279
  });
9280
+ object({ isDefault: boolean().optional() });
9383
9281
  /**
9384
9282
  * Reference accepted by consumer-facing `api.storage.*` calls.
9385
9283
  * Either:
9386
- * - a `StorageLocationType` (e.g. `'backups'`) → orchestrator resolves to the default of that type
9284
+ * - a `StorageLocationType` (e.g. `'backups'`) → the sole location of that type
9285
+ * (transitionally, the `<type>:default`-slugged row when several exist)
9387
9286
  * - a fully-qualified id (e.g. `'backups:nas-01'`) → addresses a specific instance
9388
9287
  *
9389
9288
  * The orchestrator's `resolveRef(ref)` handles both cases.
@@ -9559,6 +9458,111 @@ var DecoderSessionConfigSchema = object({
9559
9458
  */
9560
9459
  debug: boolean().optional()
9561
9460
  });
9461
+ /**
9462
+ * Per-component log CHANNELS — the gate a hot path consults, and the registry
9463
+ * an addon declares its channels in.
9464
+ *
9465
+ * ## Two axes, deliberately separated
9466
+ *
9467
+ * - **DECLARATION** — which channels exist. Only the addon knows:
9468
+ * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
9469
+ * baichuan/handshake. A hand-wired central list rots at the first addition,
9470
+ * and rots silently. So a channel is declared where it is consulted, and the
9471
+ * `log-channels` capability enumerates the declarations.
9472
+ * - **VALUE** — at which level, for which scope, until when. That stays ONE
9473
+ * thing: the logging settings document on the `system` cap. Two authorities
9474
+ * over the values is the exact defect
9475
+ * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
9476
+ * remove; re-introducing it from the cure side would be grotesque.
9477
+ *
9478
+ * Nothing in this file reads a clock, an env var or a store. The registry is
9479
+ * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
9480
+ * the hot path with a value somebody actually read, and by
9481
+ * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
9482
+ * never reaches here, so it can neither disarm an armed channel nor arm a
9483
+ * disarmed one (D49).
9484
+ *
9485
+ * ## The canonical call shape
9486
+ *
9487
+ * ```ts
9488
+ * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
9489
+ * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
9490
+ * }
9491
+ * ```
9492
+ *
9493
+ * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
9494
+ * read. Disarmed, a call site costs one load and one branch, and the `extras`
9495
+ * object literal is never constructed because it lives inside the branch. It
9496
+ * is the same shape already proven in production at `stream-broker.ts:1650`,
9497
+ * and the same discipline `LoggingGate.allowsDestination` uses for the
9498
+ * destination floor (measured at 1.93 ns/call when off).
9499
+ *
9500
+ * ## Why a channel emits at `info`
9501
+ *
9502
+ * `loki-logging.addon.ts` pins the destination default at `info` and
9503
+ * `loki-destination.ts` drops everything below it, so a line emitted at
9504
+ * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
9505
+ * minutes. A diagnostic that cannot be read an hour later is worse than no
9506
+ * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
9507
+ * emits at the channel's declared level, whose schema floor is `info`.
9508
+ */
9509
+ /**
9510
+ * The level a channel writes at once armed.
9511
+ *
9512
+ * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
9513
+ * not leave the process for Loki, and the whole point of arming a channel is
9514
+ * to read it later.
9515
+ */
9516
+ var LogChannelLevelSchema = _enum([
9517
+ "info",
9518
+ "warn",
9519
+ "error"
9520
+ ]);
9521
+ /**
9522
+ * What an addon declares about one channel. No value, no state — a
9523
+ * declaration is inert.
9524
+ */
9525
+ var LogChannelDescriptorSchema = object({
9526
+ /**
9527
+ * Dotted `area.thing`, unique across the workspace. `area` is conventionally
9528
+ * the addon's short name so an operator reading a channel list can tell who
9529
+ * owns it without a second lookup.
9530
+ */
9531
+ name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
9532
+ /** One sentence: what the operator will SEE after arming it. */
9533
+ description: string().min(1),
9534
+ /** The level its lines are emitted at. Never below `info`. */
9535
+ defaultLevel: LogChannelLevelSchema,
9536
+ /**
9537
+ * Whether this channel can be narrowed to a camera.
9538
+ *
9539
+ * `true` is a PROMISE with two halves, and both must hold: the gate is
9540
+ * consulted with the numeric device id, AND every line the channel admits
9541
+ * carries `tags: { deviceId }` with that same numeric id. The second half is
9542
+ * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
9543
+ * keeps `deviceId` out of the stream labels for cardinality, so the tag in
9544
+ * the body is the only way to filter.
9545
+ *
9546
+ * A channel whose lines carry the device only in `meta` (or not at all) is
9547
+ * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
9548
+ * the operator narrows to one camera, sees nothing, and concludes the code
9549
+ * path was never taken.
9550
+ */
9551
+ perDevice: boolean()
9552
+ });
9553
+ /**
9554
+ * An armed window over one channel, as the document hands it to a mirror.
9555
+ *
9556
+ * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
9557
+ * expires by itself, which is the one failure a boolean cannot avoid.
9558
+ */
9559
+ var LogChannelWindowSchema = object({
9560
+ channel: string().min(1),
9561
+ /** Epoch ms the window closes at. */
9562
+ armedUntilMs: number(),
9563
+ /** `null` = every camera. A non-empty list narrows to those numeric ids. */
9564
+ deviceIds: array(number().int()).readonly().nullable()
9565
+ });
9562
9566
  function pickPreferredRtspEntry(entries, pref, deviceId, options = {}) {
9563
9567
  if (entries.length === 0) return null;
9564
9568
  const prefix = `${deviceId}/`;
@@ -22358,7 +22362,7 @@ method(object({
22358
22362
  downloadId: string(),
22359
22363
  offset: number(),
22360
22364
  length: number()
22361
- }), _instanceof(Uint8Array)), method(object({ downloadId: string() }), _void(), { kind: "mutation" }), method(object({ type: StorageLocationTypeSchema.optional() }), array(StorageLocationSchema).readonly()), method(object({ type: StorageLocationTypeSchema }), StorageLocationSchema.nullable()), method(_void(), array(StorageLocationDeclarationSchema).readonly()), method(StorageLocationSchema.omit({
22365
+ }), _instanceof(Uint8Array)), method(object({ downloadId: string() }), _void(), { kind: "mutation" }), method(object({ type: StorageLocationTypeSchema.optional() }), array(StorageLocationSchema).readonly()), method(_void(), array(StorageLocationDeclarationSchema).readonly()), method(StorageLocationSchema.omit({
22362
22366
  createdAt: true,
22363
22367
  updatedAt: true
22364
22368
  }), StorageLocationSchema, {
@@ -35280,12 +35284,6 @@ Object.freeze({
35280
35284
  addonId: null,
35281
35285
  access: "view"
35282
35286
  },
35283
- "storage.getDefaultLocation": {
35284
- capName: "storage",
35285
- capScope: "system",
35286
- addonId: null,
35287
- access: "view"
35288
- },
35289
35287
  "storage.list": {
35290
35288
  capName: "storage",
35291
35289
  capScope: "system",
@@ -8269,111 +8269,6 @@ var CameraSwitchGroupSchema = object({
8269
8269
  fetchedAt: number()
8270
8270
  });
8271
8271
  /**
8272
- * Per-component log CHANNELS — the gate a hot path consults, and the registry
8273
- * an addon declares its channels in.
8274
- *
8275
- * ## Two axes, deliberately separated
8276
- *
8277
- * - **DECLARATION** — which channels exist. Only the addon knows:
8278
- * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
8279
- * baichuan/handshake. A hand-wired central list rots at the first addition,
8280
- * and rots silently. So a channel is declared where it is consulted, and the
8281
- * `log-channels` capability enumerates the declarations.
8282
- * - **VALUE** — at which level, for which scope, until when. That stays ONE
8283
- * thing: the logging settings document on the `system` cap. Two authorities
8284
- * over the values is the exact defect
8285
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
8286
- * remove; re-introducing it from the cure side would be grotesque.
8287
- *
8288
- * Nothing in this file reads a clock, an env var or a store. The registry is
8289
- * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
8290
- * the hot path with a value somebody actually read, and by
8291
- * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
8292
- * never reaches here, so it can neither disarm an armed channel nor arm a
8293
- * disarmed one (D49).
8294
- *
8295
- * ## The canonical call shape
8296
- *
8297
- * ```ts
8298
- * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
8299
- * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
8300
- * }
8301
- * ```
8302
- *
8303
- * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
8304
- * read. Disarmed, a call site costs one load and one branch, and the `extras`
8305
- * object literal is never constructed because it lives inside the branch. It
8306
- * is the same shape already proven in production at `stream-broker.ts:1650`,
8307
- * and the same discipline `LoggingGate.allowsDestination` uses for the
8308
- * destination floor (measured at 1.93 ns/call when off).
8309
- *
8310
- * ## Why a channel emits at `info`
8311
- *
8312
- * `loki-logging.addon.ts` pins the destination default at `info` and
8313
- * `loki-destination.ts` drops everything below it, so a line emitted at
8314
- * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
8315
- * minutes. A diagnostic that cannot be read an hour later is worse than no
8316
- * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
8317
- * emits at the channel's declared level, whose schema floor is `info`.
8318
- */
8319
- /**
8320
- * The level a channel writes at once armed.
8321
- *
8322
- * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
8323
- * not leave the process for Loki, and the whole point of arming a channel is
8324
- * to read it later.
8325
- */
8326
- var LogChannelLevelSchema = _enum([
8327
- "info",
8328
- "warn",
8329
- "error"
8330
- ]);
8331
- /**
8332
- * What an addon declares about one channel. No value, no state — a
8333
- * declaration is inert.
8334
- */
8335
- var LogChannelDescriptorSchema = object({
8336
- /**
8337
- * Dotted `area.thing`, unique across the workspace. `area` is conventionally
8338
- * the addon's short name so an operator reading a channel list can tell who
8339
- * owns it without a second lookup.
8340
- */
8341
- name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
8342
- /** One sentence: what the operator will SEE after arming it. */
8343
- description: string().min(1),
8344
- /** The level its lines are emitted at. Never below `info`. */
8345
- defaultLevel: LogChannelLevelSchema,
8346
- /**
8347
- * Whether this channel can be narrowed to a camera.
8348
- *
8349
- * `true` is a PROMISE with two halves, and both must hold: the gate is
8350
- * consulted with the numeric device id, AND every line the channel admits
8351
- * carries `tags: { deviceId }` with that same numeric id. The second half is
8352
- * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
8353
- * keeps `deviceId` out of the stream labels for cardinality, so the tag in
8354
- * the body is the only way to filter.
8355
- *
8356
- * A channel whose lines carry the device only in `meta` (or not at all) is
8357
- * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
8358
- * the operator narrows to one camera, sees nothing, and concludes the code
8359
- * path was never taken.
8360
- */
8361
- perDevice: boolean()
8362
- });
8363
- /**
8364
- * An armed window over one channel, as the document hands it to a mirror.
8365
- *
8366
- * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
8367
- * expires by itself, which is the one failure a boolean cannot avoid.
8368
- */
8369
- var LogChannelWindowSchema = object({
8370
- channel: string().min(1),
8371
- /** Epoch ms the window closes at. */
8372
- armedUntilMs: number(),
8373
- /** `null` = every camera. A non-empty list narrows to those numeric ids. */
8374
- deviceIds: array(number().int()).readonly().nullable()
8375
- });
8376
- /**
8377
8272
  * Ops-log — the durable, append-only operations audit shared by the
8378
8273
  * recordings and events management surfaces.
8379
8274
  *
@@ -9319,8 +9214,11 @@ var StorageLocationTypeSchema = string().regex(/^[a-z][a-zA-Z0-9-]*$/);
9319
9214
  * `STORAGE_LOCATION_CARDINALITY` map has been removed.
9320
9215
  *
9321
9216
  * `id` is a stable namespaced string of the form `<type>:<slug>`.
9322
- * The default location for a type uses `id === <type>:default` by
9323
- * convention (the bare type ref like `'backups'` resolves to it).
9217
+ * The seed names its first instance `<type>:default` — a NAME, not a flag.
9218
+ * There is no default location any more (D383): `enabled` is the whole write
9219
+ * model, and a bare type ref resolves to the sole location of the type, or —
9220
+ * transitionally, only while legacy NULL-stamped rows exist — to the row whose
9221
+ * slug is `default`.
9324
9222
  *
9325
9223
  * `isSystem` is a legacy persisted flag. Seed still creates the initial
9326
9224
  * `<type>:default` locations; the flag is no longer a lock, a badge, or a
@@ -9341,21 +9239,20 @@ var StorageLocationSchema = object({
9341
9239
  * flag at upsert time, not here (the schema is provider-agnostic).
9342
9240
  */
9343
9241
  nodeId: string().optional(),
9344
- isDefault: boolean().default(false),
9345
9242
  isSystem: boolean().default(false),
9346
9243
  /**
9347
- * Operator opt-in: whether consumers that BALANCE across several locations
9348
- * of a type may write here. Recordings reads it today; event media and
9349
- * backups are the next consumers, which is why the flag lives on the
9350
- * location rather than in any one addon's store — nothing has to be
9351
- * extended to add the next consumer.
9244
+ * THE write switch, and the only one (D383). `enabled: true` means every
9245
+ * consumer that chooses a write target for this type may write here, and all
9246
+ * enabled locations of a type are used TOGETHER; `false` means read-only —
9247
+ * still read, still played back, still age-swept, still drained, never
9248
+ * written.
9352
9249
  *
9353
- * OPTIONAL, and ABSENT MEANS ACTIVE. Every location persisted before the
9354
- * flag existed reads back with no flag and keeps working exactly as before;
9355
- * that is the whole compat story, and it is why no migration ships with it.
9356
- * A newly CREATED sibling is stamped `false` by the orchestrator (creating a
9357
- * disk must not silently start writing to it); the default of a type is
9358
- * always stamped `true`.
9250
+ * OPTIONAL only for the wire: an upsert that omits it means "leave what is
9251
+ * stored" on an update and "born inert unless it is the first location of its
9252
+ * type" on a create. On a PERSISTED row absence is legacy and it means
9253
+ * enabled — {@link isLocationEnabled} is the one place that says so, and the
9254
+ * orchestrator stamps every flagless row `true` once at hydrate so absence
9255
+ * stops existing rather than being re-derived on every read.
9359
9256
  */
9360
9257
  enabled: boolean().optional(),
9361
9258
  /** COMPUTED at read time by the orchestrator (statfs of the backing volume
@@ -9368,10 +9265,12 @@ var StorageLocationSchema = object({
9368
9265
  createdAt: number(),
9369
9266
  updatedAt: number()
9370
9267
  });
9268
+ object({ isDefault: boolean().optional() });
9371
9269
  /**
9372
9270
  * Reference accepted by consumer-facing `api.storage.*` calls.
9373
9271
  * Either:
9374
- * - a `StorageLocationType` (e.g. `'backups'`) → orchestrator resolves to the default of that type
9272
+ * - a `StorageLocationType` (e.g. `'backups'`) → the sole location of that type
9273
+ * (transitionally, the `<type>:default`-slugged row when several exist)
9375
9274
  * - a fully-qualified id (e.g. `'backups:nas-01'`) → addresses a specific instance
9376
9275
  *
9377
9276
  * The orchestrator's `resolveRef(ref)` handles both cases.
@@ -9547,6 +9446,111 @@ var DecoderSessionConfigSchema = object({
9547
9446
  */
9548
9447
  debug: boolean().optional()
9549
9448
  });
9449
+ /**
9450
+ * Per-component log CHANNELS — the gate a hot path consults, and the registry
9451
+ * an addon declares its channels in.
9452
+ *
9453
+ * ## Two axes, deliberately separated
9454
+ *
9455
+ * - **DECLARATION** — which channels exist. Only the addon knows:
9456
+ * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
9457
+ * baichuan/handshake. A hand-wired central list rots at the first addition,
9458
+ * and rots silently. So a channel is declared where it is consulted, and the
9459
+ * `log-channels` capability enumerates the declarations.
9460
+ * - **VALUE** — at which level, for which scope, until when. That stays ONE
9461
+ * thing: the logging settings document on the `system` cap. Two authorities
9462
+ * over the values is the exact defect
9463
+ * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
9464
+ * remove; re-introducing it from the cure side would be grotesque.
9465
+ *
9466
+ * Nothing in this file reads a clock, an env var or a store. The registry is
9467
+ * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
9468
+ * the hot path with a value somebody actually read, and by
9469
+ * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
9470
+ * never reaches here, so it can neither disarm an armed channel nor arm a
9471
+ * disarmed one (D49).
9472
+ *
9473
+ * ## The canonical call shape
9474
+ *
9475
+ * ```ts
9476
+ * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
9477
+ * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
9478
+ * }
9479
+ * ```
9480
+ *
9481
+ * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
9482
+ * read. Disarmed, a call site costs one load and one branch, and the `extras`
9483
+ * object literal is never constructed because it lives inside the branch. It
9484
+ * is the same shape already proven in production at `stream-broker.ts:1650`,
9485
+ * and the same discipline `LoggingGate.allowsDestination` uses for the
9486
+ * destination floor (measured at 1.93 ns/call when off).
9487
+ *
9488
+ * ## Why a channel emits at `info`
9489
+ *
9490
+ * `loki-logging.addon.ts` pins the destination default at `info` and
9491
+ * `loki-destination.ts` drops everything below it, so a line emitted at
9492
+ * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
9493
+ * minutes. A diagnostic that cannot be read an hour later is worse than no
9494
+ * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
9495
+ * emits at the channel's declared level, whose schema floor is `info`.
9496
+ */
9497
+ /**
9498
+ * The level a channel writes at once armed.
9499
+ *
9500
+ * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
9501
+ * not leave the process for Loki, and the whole point of arming a channel is
9502
+ * to read it later.
9503
+ */
9504
+ var LogChannelLevelSchema = _enum([
9505
+ "info",
9506
+ "warn",
9507
+ "error"
9508
+ ]);
9509
+ /**
9510
+ * What an addon declares about one channel. No value, no state — a
9511
+ * declaration is inert.
9512
+ */
9513
+ var LogChannelDescriptorSchema = object({
9514
+ /**
9515
+ * Dotted `area.thing`, unique across the workspace. `area` is conventionally
9516
+ * the addon's short name so an operator reading a channel list can tell who
9517
+ * owns it without a second lookup.
9518
+ */
9519
+ name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
9520
+ /** One sentence: what the operator will SEE after arming it. */
9521
+ description: string().min(1),
9522
+ /** The level its lines are emitted at. Never below `info`. */
9523
+ defaultLevel: LogChannelLevelSchema,
9524
+ /**
9525
+ * Whether this channel can be narrowed to a camera.
9526
+ *
9527
+ * `true` is a PROMISE with two halves, and both must hold: the gate is
9528
+ * consulted with the numeric device id, AND every line the channel admits
9529
+ * carries `tags: { deviceId }` with that same numeric id. The second half is
9530
+ * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
9531
+ * keeps `deviceId` out of the stream labels for cardinality, so the tag in
9532
+ * the body is the only way to filter.
9533
+ *
9534
+ * A channel whose lines carry the device only in `meta` (or not at all) is
9535
+ * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
9536
+ * the operator narrows to one camera, sees nothing, and concludes the code
9537
+ * path was never taken.
9538
+ */
9539
+ perDevice: boolean()
9540
+ });
9541
+ /**
9542
+ * An armed window over one channel, as the document hands it to a mirror.
9543
+ *
9544
+ * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
9545
+ * expires by itself, which is the one failure a boolean cannot avoid.
9546
+ */
9547
+ var LogChannelWindowSchema = object({
9548
+ channel: string().min(1),
9549
+ /** Epoch ms the window closes at. */
9550
+ armedUntilMs: number(),
9551
+ /** `null` = every camera. A non-empty list narrows to those numeric ids. */
9552
+ deviceIds: array(number().int()).readonly().nullable()
9553
+ });
9550
9554
  function pickPreferredRtspEntry(entries, pref, deviceId, options = {}) {
9551
9555
  if (entries.length === 0) return null;
9552
9556
  const prefix = `${deviceId}/`;
@@ -22346,7 +22350,7 @@ method(object({
22346
22350
  downloadId: string(),
22347
22351
  offset: number(),
22348
22352
  length: number()
22349
- }), _instanceof(Uint8Array)), method(object({ downloadId: string() }), _void(), { kind: "mutation" }), method(object({ type: StorageLocationTypeSchema.optional() }), array(StorageLocationSchema).readonly()), method(object({ type: StorageLocationTypeSchema }), StorageLocationSchema.nullable()), method(_void(), array(StorageLocationDeclarationSchema).readonly()), method(StorageLocationSchema.omit({
22353
+ }), _instanceof(Uint8Array)), method(object({ downloadId: string() }), _void(), { kind: "mutation" }), method(object({ type: StorageLocationTypeSchema.optional() }), array(StorageLocationSchema).readonly()), method(_void(), array(StorageLocationDeclarationSchema).readonly()), method(StorageLocationSchema.omit({
22350
22354
  createdAt: true,
22351
22355
  updatedAt: true
22352
22356
  }), StorageLocationSchema, {
@@ -35268,12 +35272,6 @@ Object.freeze({
35268
35272
  addonId: null,
35269
35273
  access: "view"
35270
35274
  },
35271
- "storage.getDefaultLocation": {
35272
- capName: "storage",
35273
- capScope: "system",
35274
- addonId: null,
35275
- access: "view"
35276
- },
35277
35275
  "storage.list": {
35278
35276
  capName: "storage",
35279
35277
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-export-hap",
3
- "version": "1.2.89",
3
+ "version": "1.2.90",
4
4
  "description": "HomeKit (HAP) exporter for CamStack devices. Publishes each exposed device as its own HomeKit accessory: cameras and doorbells with SRTP streaming, HomeKit Secure Video, motion, two-way audio, PTZ and battery; switches, lights, locks and sensors through a capability→service table.",
5
5
  "keywords": [
6
6
  "camstack",