@camstack/addon-provider-homematic 1.2.78 → 1.2.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/addon.js +158 -141
  2. package/dist/addon.mjs +158 -141
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -7624,111 +7624,6 @@ var CameraSwitchGroupSchema = object({
7624
7624
  fetchedAt: number()
7625
7625
  });
7626
7626
  /**
7627
- * Per-component log CHANNELS — the gate a hot path consults, and the registry
7628
- * an addon declares its channels in.
7629
- *
7630
- * ## Two axes, deliberately separated
7631
- *
7632
- * - **DECLARATION** — which channels exist. Only the addon knows:
7633
- * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
7634
- * baichuan/handshake. A hand-wired central list rots at the first addition,
7635
- * and rots silently. So a channel is declared where it is consulted, and the
7636
- * `log-channels` capability enumerates the declarations.
7637
- * - **VALUE** — at which level, for which scope, until when. That stays ONE
7638
- * thing: the logging settings document on the `system` cap. Two authorities
7639
- * over the values is the exact defect
7640
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
7641
- * remove; re-introducing it from the cure side would be grotesque.
7642
- *
7643
- * Nothing in this file reads a clock, an env var or a store. The registry is
7644
- * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
7645
- * the hot path with a value somebody actually read, and by
7646
- * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
7647
- * never reaches here, so it can neither disarm an armed channel nor arm a
7648
- * disarmed one (D49).
7649
- *
7650
- * ## The canonical call shape
7651
- *
7652
- * ```ts
7653
- * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
7654
- * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
7655
- * }
7656
- * ```
7657
- *
7658
- * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
7659
- * read. Disarmed, a call site costs one load and one branch, and the `extras`
7660
- * object literal is never constructed because it lives inside the branch. It
7661
- * is the same shape already proven in production at `stream-broker.ts:1650`,
7662
- * and the same discipline `LoggingGate.allowsDestination` uses for the
7663
- * destination floor (measured at 1.93 ns/call when off).
7664
- *
7665
- * ## Why a channel emits at `info`
7666
- *
7667
- * `loki-logging.addon.ts` pins the destination default at `info` and
7668
- * `loki-destination.ts` drops everything below it, so a line emitted at
7669
- * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
7670
- * minutes. A diagnostic that cannot be read an hour later is worse than no
7671
- * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
7672
- * emits at the channel's declared level, whose schema floor is `info`.
7673
- */
7674
- /**
7675
- * The level a channel writes at once armed.
7676
- *
7677
- * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
7678
- * not leave the process for Loki, and the whole point of arming a channel is
7679
- * to read it later.
7680
- */
7681
- var LogChannelLevelSchema = _enum([
7682
- "info",
7683
- "warn",
7684
- "error"
7685
- ]);
7686
- /**
7687
- * What an addon declares about one channel. No value, no state — a
7688
- * declaration is inert.
7689
- */
7690
- var LogChannelDescriptorSchema = object({
7691
- /**
7692
- * Dotted `area.thing`, unique across the workspace. `area` is conventionally
7693
- * the addon's short name so an operator reading a channel list can tell who
7694
- * owns it without a second lookup.
7695
- */
7696
- name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
7697
- /** One sentence: what the operator will SEE after arming it. */
7698
- description: string().min(1),
7699
- /** The level its lines are emitted at. Never below `info`. */
7700
- defaultLevel: LogChannelLevelSchema,
7701
- /**
7702
- * Whether this channel can be narrowed to a camera.
7703
- *
7704
- * `true` is a PROMISE with two halves, and both must hold: the gate is
7705
- * consulted with the numeric device id, AND every line the channel admits
7706
- * carries `tags: { deviceId }` with that same numeric id. The second half is
7707
- * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
7708
- * keeps `deviceId` out of the stream labels for cardinality, so the tag in
7709
- * the body is the only way to filter.
7710
- *
7711
- * A channel whose lines carry the device only in `meta` (or not at all) is
7712
- * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
7713
- * the operator narrows to one camera, sees nothing, and concludes the code
7714
- * path was never taken.
7715
- */
7716
- perDevice: boolean()
7717
- });
7718
- /**
7719
- * An armed window over one channel, as the document hands it to a mirror.
7720
- *
7721
- * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
7722
- * expires by itself, which is the one failure a boolean cannot avoid.
7723
- */
7724
- var LogChannelWindowSchema = object({
7725
- channel: string().min(1),
7726
- /** Epoch ms the window closes at. */
7727
- armedUntilMs: number(),
7728
- /** `null` = every camera. A non-empty list narrows to those numeric ids. */
7729
- deviceIds: array(number().int()).readonly().nullable()
7730
- });
7731
- /**
7732
7627
  * Ops-log — the durable, append-only operations audit shared by the
7733
7628
  * recordings and events management surfaces.
7734
7629
  *
@@ -8674,8 +8569,11 @@ var StorageLocationTypeSchema = string().regex(/^[a-z][a-zA-Z0-9-]*$/);
8674
8569
  * `STORAGE_LOCATION_CARDINALITY` map has been removed.
8675
8570
  *
8676
8571
  * `id` is a stable namespaced string of the form `<type>:<slug>`.
8677
- * The default location for a type uses `id === <type>:default` by
8678
- * convention (the bare type ref like `'backups'` resolves to it).
8572
+ * The seed names its first instance `<type>:default` — a NAME, not a flag.
8573
+ * There is no default location any more (D383): `enabled` is the whole write
8574
+ * model, and a bare type ref resolves to the sole location of the type, or —
8575
+ * transitionally, only while legacy NULL-stamped rows exist — to the row whose
8576
+ * slug is `default`.
8679
8577
  *
8680
8578
  * `isSystem` is a legacy persisted flag. Seed still creates the initial
8681
8579
  * `<type>:default` locations; the flag is no longer a lock, a badge, or a
@@ -8696,21 +8594,20 @@ var StorageLocationSchema = object({
8696
8594
  * flag at upsert time, not here (the schema is provider-agnostic).
8697
8595
  */
8698
8596
  nodeId: string().optional(),
8699
- isDefault: boolean().default(false),
8700
8597
  isSystem: boolean().default(false),
8701
8598
  /**
8702
- * Operator opt-in: whether consumers that BALANCE across several locations
8703
- * of a type may write here. Recordings reads it today; event media and
8704
- * backups are the next consumers, which is why the flag lives on the
8705
- * location rather than in any one addon's store — nothing has to be
8706
- * extended to add the next consumer.
8599
+ * THE write switch, and the only one (D383). `enabled: true` means every
8600
+ * consumer that chooses a write target for this type may write here, and all
8601
+ * enabled locations of a type are used TOGETHER; `false` means read-only —
8602
+ * still read, still played back, still age-swept, still drained, never
8603
+ * written.
8707
8604
  *
8708
- * OPTIONAL, and ABSENT MEANS ACTIVE. Every location persisted before the
8709
- * flag existed reads back with no flag and keeps working exactly as before;
8710
- * that is the whole compat story, and it is why no migration ships with it.
8711
- * A newly CREATED sibling is stamped `false` by the orchestrator (creating a
8712
- * disk must not silently start writing to it); the default of a type is
8713
- * always stamped `true`.
8605
+ * OPTIONAL only for the wire: an upsert that omits it means "leave what is
8606
+ * stored" on an update and "born inert unless it is the first location of its
8607
+ * type" on a create. On a PERSISTED row absence is legacy and it means
8608
+ * enabled — {@link isLocationEnabled} is the one place that says so, and the
8609
+ * orchestrator stamps every flagless row `true` once at hydrate so absence
8610
+ * stops existing rather than being re-derived on every read.
8714
8611
  */
8715
8612
  enabled: boolean().optional(),
8716
8613
  /** COMPUTED at read time by the orchestrator (statfs of the backing volume
@@ -8723,10 +8620,12 @@ var StorageLocationSchema = object({
8723
8620
  createdAt: number(),
8724
8621
  updatedAt: number()
8725
8622
  });
8623
+ object({ isDefault: boolean().optional() });
8726
8624
  /**
8727
8625
  * Reference accepted by consumer-facing `api.storage.*` calls.
8728
8626
  * Either:
8729
- * - a `StorageLocationType` (e.g. `'backups'`) → orchestrator resolves to the default of that type
8627
+ * - a `StorageLocationType` (e.g. `'backups'`) → the sole location of that type
8628
+ * (transitionally, the `<type>:default`-slugged row when several exist)
8730
8629
  * - a fully-qualified id (e.g. `'backups:nas-01'`) → addresses a specific instance
8731
8630
  *
8732
8631
  * The orchestrator's `resolveRef(ref)` handles both cases.
@@ -8902,6 +8801,111 @@ var DecoderSessionConfigSchema = object({
8902
8801
  */
8903
8802
  debug: boolean().optional()
8904
8803
  });
8804
+ /**
8805
+ * Per-component log CHANNELS — the gate a hot path consults, and the registry
8806
+ * an addon declares its channels in.
8807
+ *
8808
+ * ## Two axes, deliberately separated
8809
+ *
8810
+ * - **DECLARATION** — which channels exist. Only the addon knows:
8811
+ * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
8812
+ * baichuan/handshake. A hand-wired central list rots at the first addition,
8813
+ * and rots silently. So a channel is declared where it is consulted, and the
8814
+ * `log-channels` capability enumerates the declarations.
8815
+ * - **VALUE** — at which level, for which scope, until when. That stays ONE
8816
+ * thing: the logging settings document on the `system` cap. Two authorities
8817
+ * over the values is the exact defect
8818
+ * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
8819
+ * remove; re-introducing it from the cure side would be grotesque.
8820
+ *
8821
+ * Nothing in this file reads a clock, an env var or a store. The registry is
8822
+ * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
8823
+ * the hot path with a value somebody actually read, and by
8824
+ * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
8825
+ * never reaches here, so it can neither disarm an armed channel nor arm a
8826
+ * disarmed one (D49).
8827
+ *
8828
+ * ## The canonical call shape
8829
+ *
8830
+ * ```ts
8831
+ * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
8832
+ * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
8833
+ * }
8834
+ * ```
8835
+ *
8836
+ * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
8837
+ * read. Disarmed, a call site costs one load and one branch, and the `extras`
8838
+ * object literal is never constructed because it lives inside the branch. It
8839
+ * is the same shape already proven in production at `stream-broker.ts:1650`,
8840
+ * and the same discipline `LoggingGate.allowsDestination` uses for the
8841
+ * destination floor (measured at 1.93 ns/call when off).
8842
+ *
8843
+ * ## Why a channel emits at `info`
8844
+ *
8845
+ * `loki-logging.addon.ts` pins the destination default at `info` and
8846
+ * `loki-destination.ts` drops everything below it, so a line emitted at
8847
+ * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
8848
+ * minutes. A diagnostic that cannot be read an hour later is worse than no
8849
+ * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
8850
+ * emits at the channel's declared level, whose schema floor is `info`.
8851
+ */
8852
+ /**
8853
+ * The level a channel writes at once armed.
8854
+ *
8855
+ * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
8856
+ * not leave the process for Loki, and the whole point of arming a channel is
8857
+ * to read it later.
8858
+ */
8859
+ var LogChannelLevelSchema = _enum([
8860
+ "info",
8861
+ "warn",
8862
+ "error"
8863
+ ]);
8864
+ /**
8865
+ * What an addon declares about one channel. No value, no state — a
8866
+ * declaration is inert.
8867
+ */
8868
+ var LogChannelDescriptorSchema = object({
8869
+ /**
8870
+ * Dotted `area.thing`, unique across the workspace. `area` is conventionally
8871
+ * the addon's short name so an operator reading a channel list can tell who
8872
+ * owns it without a second lookup.
8873
+ */
8874
+ name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
8875
+ /** One sentence: what the operator will SEE after arming it. */
8876
+ description: string().min(1),
8877
+ /** The level its lines are emitted at. Never below `info`. */
8878
+ defaultLevel: LogChannelLevelSchema,
8879
+ /**
8880
+ * Whether this channel can be narrowed to a camera.
8881
+ *
8882
+ * `true` is a PROMISE with two halves, and both must hold: the gate is
8883
+ * consulted with the numeric device id, AND every line the channel admits
8884
+ * carries `tags: { deviceId }` with that same numeric id. The second half is
8885
+ * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
8886
+ * keeps `deviceId` out of the stream labels for cardinality, so the tag in
8887
+ * the body is the only way to filter.
8888
+ *
8889
+ * A channel whose lines carry the device only in `meta` (or not at all) is
8890
+ * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
8891
+ * the operator narrows to one camera, sees nothing, and concludes the code
8892
+ * path was never taken.
8893
+ */
8894
+ perDevice: boolean()
8895
+ });
8896
+ /**
8897
+ * An armed window over one channel, as the document hands it to a mirror.
8898
+ *
8899
+ * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
8900
+ * expires by itself, which is the one failure a boolean cannot avoid.
8901
+ */
8902
+ var LogChannelWindowSchema = object({
8903
+ channel: string().min(1),
8904
+ /** Epoch ms the window closes at. */
8905
+ armedUntilMs: number(),
8906
+ /** `null` = every camera. A non-empty list narrows to those numeric ids. */
8907
+ deviceIds: array(number().int()).readonly().nullable()
8908
+ });
8905
8909
  var MODEL_FORMATS = [
8906
8910
  "onnx",
8907
8911
  "coreml",
@@ -10558,12 +10562,30 @@ var BackupDestinationInfoSchema = object({
10558
10562
  lastSuccessAt: number().optional(),
10559
10563
  /** Newest-archive size from `manifests.json`, or undefined. */
10560
10564
  lastSuccessSizeBytes: number().optional(),
10561
- /** Per-destination cron expression. Empty = manual-only (no schedule). */
10565
+ /**
10566
+ * Cron cadence(s) of the ENABLED schedules that fan out to this
10567
+ * destination, comma-joined. Absent when no enabled schedule targets it
10568
+ * — a destination nothing is scheduled to write to must not advertise a
10569
+ * cadence (D384). This is never the `backup_destination_policies.cron`
10570
+ * column: that per-location cron has scheduled nothing since 2026-07-28
10571
+ * and reading it made a destination with a DISABLED schedule claim a
10572
+ * nightly run.
10573
+ */
10562
10574
  cron: string().optional(),
10563
- /** ms-epoch of next computed firing for this destination's cron, if any. */
10575
+ /** ms-epoch of the next firing across those schedules (earliest), if any. */
10564
10576
  nextRunAt: number().optional(),
10565
- /** ms-epoch of last successful scheduled run (mirrors policy.lastRunAt). */
10566
- lastRunAt: number().optional()
10577
+ /**
10578
+ * ms-epoch of the last time a run ATTEMPTED to write here — success or
10579
+ * failure. Never a success stamp: pair it with `lastSuccessAt` (the
10580
+ * newest archive that actually landed) and `lastError`.
10581
+ */
10582
+ lastAttemptAt: number().optional(),
10583
+ /**
10584
+ * Why the last attempt failed, verbatim. Absent when the last attempt
10585
+ * landed the archive. A destination that has never been written to has
10586
+ * neither this nor `lastAttemptAt`.
10587
+ */
10588
+ lastError: string().optional()
10567
10589
  });
10568
10590
  /**
10569
10591
  * Per-archive entry returned by `backup.listArchives({ destinationId })`.
@@ -10726,8 +10748,16 @@ var BackupScheduleSchema = object({
10726
10748
  retentionCount: number().int().min(1).max(1e3),
10727
10749
  /** Optional subset of source locations to include; omitted = all. */
10728
10750
  dataSources: array(string()).readonly().optional(),
10729
- /** ms-epoch of last successful run. */
10730
- lastRunAt: number().optional(),
10751
+ /**
10752
+ * ms-epoch of the last tick that FIRED this schedule. Stamped before the
10753
+ * archive runs (it is the dedupe anchor), so it says "attempted", never
10754
+ * "succeeded" — a run refused by every destination stamps it too.
10755
+ */
10756
+ lastAttemptAt: number().optional(),
10757
+ /** ms-epoch of the last run of this schedule that landed at ≥1 destination. */
10758
+ lastSuccessAt: number().optional(),
10759
+ /** Why the last fired run failed, verbatim. Absent when it succeeded. */
10760
+ lastError: string().optional(),
10731
10761
  /** ms-epoch of next computed firing (read-only, filled on list). */
10732
10762
  nextRunAt: number().optional()
10733
10763
  });
@@ -10776,14 +10806,7 @@ method(_void(), array(BackupDestinationInfoSchema).readonly(), { auth: "admin" }
10776
10806
  locationId: string(),
10777
10807
  enabled: boolean(),
10778
10808
  retentionCount: number().int().min(1).max(1e3),
10779
- label: string().optional(),
10780
- /**
10781
- * Per-destination cron expression. Empty string clears the
10782
- * schedule (manual-only). Validated server-side via croner;
10783
- * malformed expressions reject the upsert with an actionable
10784
- * message.
10785
- */
10786
- cron: string().optional()
10809
+ label: string().optional()
10787
10810
  }), _void(), {
10788
10811
  kind: "mutation",
10789
10812
  auth: "admin"
@@ -21981,7 +22004,7 @@ method(object({
21981
22004
  downloadId: string(),
21982
22005
  offset: number(),
21983
22006
  length: number()
21984
- }), _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({
22007
+ }), _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({
21985
22008
  createdAt: true,
21986
22009
  updatedAt: true
21987
22010
  }), StorageLocationSchema, {
@@ -38826,12 +38849,6 @@ Object.freeze({
38826
38849
  addonId: null,
38827
38850
  access: "view"
38828
38851
  },
38829
- "storage.getDefaultLocation": {
38830
- capName: "storage",
38831
- capScope: "system",
38832
- addonId: null,
38833
- access: "view"
38834
- },
38835
38852
  "storage.list": {
38836
38853
  capName: "storage",
38837
38854
  capScope: "system",
package/dist/addon.mjs CHANGED
@@ -7625,111 +7625,6 @@ var CameraSwitchGroupSchema = object({
7625
7625
  fetchedAt: number()
7626
7626
  });
7627
7627
  /**
7628
- * Per-component log CHANNELS — the gate a hot path consults, and the registry
7629
- * an addon declares its channels in.
7630
- *
7631
- * ## Two axes, deliberately separated
7632
- *
7633
- * - **DECLARATION** — which channels exist. Only the addon knows:
7634
- * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
7635
- * baichuan/handshake. A hand-wired central list rots at the first addition,
7636
- * and rots silently. So a channel is declared where it is consulted, and the
7637
- * `log-channels` capability enumerates the declarations.
7638
- * - **VALUE** — at which level, for which scope, until when. That stays ONE
7639
- * thing: the logging settings document on the `system` cap. Two authorities
7640
- * over the values is the exact defect
7641
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
7642
- * remove; re-introducing it from the cure side would be grotesque.
7643
- *
7644
- * Nothing in this file reads a clock, an env var or a store. The registry is
7645
- * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
7646
- * the hot path with a value somebody actually read, and by
7647
- * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
7648
- * never reaches here, so it can neither disarm an armed channel nor arm a
7649
- * disarmed one (D49).
7650
- *
7651
- * ## The canonical call shape
7652
- *
7653
- * ```ts
7654
- * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
7655
- * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
7656
- * }
7657
- * ```
7658
- *
7659
- * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
7660
- * read. Disarmed, a call site costs one load and one branch, and the `extras`
7661
- * object literal is never constructed because it lives inside the branch. It
7662
- * is the same shape already proven in production at `stream-broker.ts:1650`,
7663
- * and the same discipline `LoggingGate.allowsDestination` uses for the
7664
- * destination floor (measured at 1.93 ns/call when off).
7665
- *
7666
- * ## Why a channel emits at `info`
7667
- *
7668
- * `loki-logging.addon.ts` pins the destination default at `info` and
7669
- * `loki-destination.ts` drops everything below it, so a line emitted at
7670
- * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
7671
- * minutes. A diagnostic that cannot be read an hour later is worse than no
7672
- * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
7673
- * emits at the channel's declared level, whose schema floor is `info`.
7674
- */
7675
- /**
7676
- * The level a channel writes at once armed.
7677
- *
7678
- * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
7679
- * not leave the process for Loki, and the whole point of arming a channel is
7680
- * to read it later.
7681
- */
7682
- var LogChannelLevelSchema = _enum([
7683
- "info",
7684
- "warn",
7685
- "error"
7686
- ]);
7687
- /**
7688
- * What an addon declares about one channel. No value, no state — a
7689
- * declaration is inert.
7690
- */
7691
- var LogChannelDescriptorSchema = object({
7692
- /**
7693
- * Dotted `area.thing`, unique across the workspace. `area` is conventionally
7694
- * the addon's short name so an operator reading a channel list can tell who
7695
- * owns it without a second lookup.
7696
- */
7697
- name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
7698
- /** One sentence: what the operator will SEE after arming it. */
7699
- description: string().min(1),
7700
- /** The level its lines are emitted at. Never below `info`. */
7701
- defaultLevel: LogChannelLevelSchema,
7702
- /**
7703
- * Whether this channel can be narrowed to a camera.
7704
- *
7705
- * `true` is a PROMISE with two halves, and both must hold: the gate is
7706
- * consulted with the numeric device id, AND every line the channel admits
7707
- * carries `tags: { deviceId }` with that same numeric id. The second half is
7708
- * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
7709
- * keeps `deviceId` out of the stream labels for cardinality, so the tag in
7710
- * the body is the only way to filter.
7711
- *
7712
- * A channel whose lines carry the device only in `meta` (or not at all) is
7713
- * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
7714
- * the operator narrows to one camera, sees nothing, and concludes the code
7715
- * path was never taken.
7716
- */
7717
- perDevice: boolean()
7718
- });
7719
- /**
7720
- * An armed window over one channel, as the document hands it to a mirror.
7721
- *
7722
- * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
7723
- * expires by itself, which is the one failure a boolean cannot avoid.
7724
- */
7725
- var LogChannelWindowSchema = object({
7726
- channel: string().min(1),
7727
- /** Epoch ms the window closes at. */
7728
- armedUntilMs: number(),
7729
- /** `null` = every camera. A non-empty list narrows to those numeric ids. */
7730
- deviceIds: array(number().int()).readonly().nullable()
7731
- });
7732
- /**
7733
7628
  * Ops-log — the durable, append-only operations audit shared by the
7734
7629
  * recordings and events management surfaces.
7735
7630
  *
@@ -8675,8 +8570,11 @@ var StorageLocationTypeSchema = string().regex(/^[a-z][a-zA-Z0-9-]*$/);
8675
8570
  * `STORAGE_LOCATION_CARDINALITY` map has been removed.
8676
8571
  *
8677
8572
  * `id` is a stable namespaced string of the form `<type>:<slug>`.
8678
- * The default location for a type uses `id === <type>:default` by
8679
- * convention (the bare type ref like `'backups'` resolves to it).
8573
+ * The seed names its first instance `<type>:default` — a NAME, not a flag.
8574
+ * There is no default location any more (D383): `enabled` is the whole write
8575
+ * model, and a bare type ref resolves to the sole location of the type, or —
8576
+ * transitionally, only while legacy NULL-stamped rows exist — to the row whose
8577
+ * slug is `default`.
8680
8578
  *
8681
8579
  * `isSystem` is a legacy persisted flag. Seed still creates the initial
8682
8580
  * `<type>:default` locations; the flag is no longer a lock, a badge, or a
@@ -8697,21 +8595,20 @@ var StorageLocationSchema = object({
8697
8595
  * flag at upsert time, not here (the schema is provider-agnostic).
8698
8596
  */
8699
8597
  nodeId: string().optional(),
8700
- isDefault: boolean().default(false),
8701
8598
  isSystem: boolean().default(false),
8702
8599
  /**
8703
- * Operator opt-in: whether consumers that BALANCE across several locations
8704
- * of a type may write here. Recordings reads it today; event media and
8705
- * backups are the next consumers, which is why the flag lives on the
8706
- * location rather than in any one addon's store — nothing has to be
8707
- * extended to add the next consumer.
8600
+ * THE write switch, and the only one (D383). `enabled: true` means every
8601
+ * consumer that chooses a write target for this type may write here, and all
8602
+ * enabled locations of a type are used TOGETHER; `false` means read-only —
8603
+ * still read, still played back, still age-swept, still drained, never
8604
+ * written.
8708
8605
  *
8709
- * OPTIONAL, and ABSENT MEANS ACTIVE. Every location persisted before the
8710
- * flag existed reads back with no flag and keeps working exactly as before;
8711
- * that is the whole compat story, and it is why no migration ships with it.
8712
- * A newly CREATED sibling is stamped `false` by the orchestrator (creating a
8713
- * disk must not silently start writing to it); the default of a type is
8714
- * always stamped `true`.
8606
+ * OPTIONAL only for the wire: an upsert that omits it means "leave what is
8607
+ * stored" on an update and "born inert unless it is the first location of its
8608
+ * type" on a create. On a PERSISTED row absence is legacy and it means
8609
+ * enabled — {@link isLocationEnabled} is the one place that says so, and the
8610
+ * orchestrator stamps every flagless row `true` once at hydrate so absence
8611
+ * stops existing rather than being re-derived on every read.
8715
8612
  */
8716
8613
  enabled: boolean().optional(),
8717
8614
  /** COMPUTED at read time by the orchestrator (statfs of the backing volume
@@ -8724,10 +8621,12 @@ var StorageLocationSchema = object({
8724
8621
  createdAt: number(),
8725
8622
  updatedAt: number()
8726
8623
  });
8624
+ object({ isDefault: boolean().optional() });
8727
8625
  /**
8728
8626
  * Reference accepted by consumer-facing `api.storage.*` calls.
8729
8627
  * Either:
8730
- * - a `StorageLocationType` (e.g. `'backups'`) → orchestrator resolves to the default of that type
8628
+ * - a `StorageLocationType` (e.g. `'backups'`) → the sole location of that type
8629
+ * (transitionally, the `<type>:default`-slugged row when several exist)
8731
8630
  * - a fully-qualified id (e.g. `'backups:nas-01'`) → addresses a specific instance
8732
8631
  *
8733
8632
  * The orchestrator's `resolveRef(ref)` handles both cases.
@@ -8903,6 +8802,111 @@ var DecoderSessionConfigSchema = object({
8903
8802
  */
8904
8803
  debug: boolean().optional()
8905
8804
  });
8805
+ /**
8806
+ * Per-component log CHANNELS — the gate a hot path consults, and the registry
8807
+ * an addon declares its channels in.
8808
+ *
8809
+ * ## Two axes, deliberately separated
8810
+ *
8811
+ * - **DECLARATION** — which channels exist. Only the addon knows:
8812
+ * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
8813
+ * baichuan/handshake. A hand-wired central list rots at the first addition,
8814
+ * and rots silently. So a channel is declared where it is consulted, and the
8815
+ * `log-channels` capability enumerates the declarations.
8816
+ * - **VALUE** — at which level, for which scope, until when. That stays ONE
8817
+ * thing: the logging settings document on the `system` cap. Two authorities
8818
+ * over the values is the exact defect
8819
+ * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
8820
+ * remove; re-introducing it from the cure side would be grotesque.
8821
+ *
8822
+ * Nothing in this file reads a clock, an env var or a store. The registry is
8823
+ * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
8824
+ * the hot path with a value somebody actually read, and by
8825
+ * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
8826
+ * never reaches here, so it can neither disarm an armed channel nor arm a
8827
+ * disarmed one (D49).
8828
+ *
8829
+ * ## The canonical call shape
8830
+ *
8831
+ * ```ts
8832
+ * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
8833
+ * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
8834
+ * }
8835
+ * ```
8836
+ *
8837
+ * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
8838
+ * read. Disarmed, a call site costs one load and one branch, and the `extras`
8839
+ * object literal is never constructed because it lives inside the branch. It
8840
+ * is the same shape already proven in production at `stream-broker.ts:1650`,
8841
+ * and the same discipline `LoggingGate.allowsDestination` uses for the
8842
+ * destination floor (measured at 1.93 ns/call when off).
8843
+ *
8844
+ * ## Why a channel emits at `info`
8845
+ *
8846
+ * `loki-logging.addon.ts` pins the destination default at `info` and
8847
+ * `loki-destination.ts` drops everything below it, so a line emitted at
8848
+ * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
8849
+ * minutes. A diagnostic that cannot be read an hour later is worse than no
8850
+ * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
8851
+ * emits at the channel's declared level, whose schema floor is `info`.
8852
+ */
8853
+ /**
8854
+ * The level a channel writes at once armed.
8855
+ *
8856
+ * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
8857
+ * not leave the process for Loki, and the whole point of arming a channel is
8858
+ * to read it later.
8859
+ */
8860
+ var LogChannelLevelSchema = _enum([
8861
+ "info",
8862
+ "warn",
8863
+ "error"
8864
+ ]);
8865
+ /**
8866
+ * What an addon declares about one channel. No value, no state — a
8867
+ * declaration is inert.
8868
+ */
8869
+ var LogChannelDescriptorSchema = object({
8870
+ /**
8871
+ * Dotted `area.thing`, unique across the workspace. `area` is conventionally
8872
+ * the addon's short name so an operator reading a channel list can tell who
8873
+ * owns it without a second lookup.
8874
+ */
8875
+ name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
8876
+ /** One sentence: what the operator will SEE after arming it. */
8877
+ description: string().min(1),
8878
+ /** The level its lines are emitted at. Never below `info`. */
8879
+ defaultLevel: LogChannelLevelSchema,
8880
+ /**
8881
+ * Whether this channel can be narrowed to a camera.
8882
+ *
8883
+ * `true` is a PROMISE with two halves, and both must hold: the gate is
8884
+ * consulted with the numeric device id, AND every line the channel admits
8885
+ * carries `tags: { deviceId }` with that same numeric id. The second half is
8886
+ * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
8887
+ * keeps `deviceId` out of the stream labels for cardinality, so the tag in
8888
+ * the body is the only way to filter.
8889
+ *
8890
+ * A channel whose lines carry the device only in `meta` (or not at all) is
8891
+ * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
8892
+ * the operator narrows to one camera, sees nothing, and concludes the code
8893
+ * path was never taken.
8894
+ */
8895
+ perDevice: boolean()
8896
+ });
8897
+ /**
8898
+ * An armed window over one channel, as the document hands it to a mirror.
8899
+ *
8900
+ * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
8901
+ * expires by itself, which is the one failure a boolean cannot avoid.
8902
+ */
8903
+ var LogChannelWindowSchema = object({
8904
+ channel: string().min(1),
8905
+ /** Epoch ms the window closes at. */
8906
+ armedUntilMs: number(),
8907
+ /** `null` = every camera. A non-empty list narrows to those numeric ids. */
8908
+ deviceIds: array(number().int()).readonly().nullable()
8909
+ });
8906
8910
  var MODEL_FORMATS = [
8907
8911
  "onnx",
8908
8912
  "coreml",
@@ -10559,12 +10563,30 @@ var BackupDestinationInfoSchema = object({
10559
10563
  lastSuccessAt: number().optional(),
10560
10564
  /** Newest-archive size from `manifests.json`, or undefined. */
10561
10565
  lastSuccessSizeBytes: number().optional(),
10562
- /** Per-destination cron expression. Empty = manual-only (no schedule). */
10566
+ /**
10567
+ * Cron cadence(s) of the ENABLED schedules that fan out to this
10568
+ * destination, comma-joined. Absent when no enabled schedule targets it
10569
+ * — a destination nothing is scheduled to write to must not advertise a
10570
+ * cadence (D384). This is never the `backup_destination_policies.cron`
10571
+ * column: that per-location cron has scheduled nothing since 2026-07-28
10572
+ * and reading it made a destination with a DISABLED schedule claim a
10573
+ * nightly run.
10574
+ */
10563
10575
  cron: string().optional(),
10564
- /** ms-epoch of next computed firing for this destination's cron, if any. */
10576
+ /** ms-epoch of the next firing across those schedules (earliest), if any. */
10565
10577
  nextRunAt: number().optional(),
10566
- /** ms-epoch of last successful scheduled run (mirrors policy.lastRunAt). */
10567
- lastRunAt: number().optional()
10578
+ /**
10579
+ * ms-epoch of the last time a run ATTEMPTED to write here — success or
10580
+ * failure. Never a success stamp: pair it with `lastSuccessAt` (the
10581
+ * newest archive that actually landed) and `lastError`.
10582
+ */
10583
+ lastAttemptAt: number().optional(),
10584
+ /**
10585
+ * Why the last attempt failed, verbatim. Absent when the last attempt
10586
+ * landed the archive. A destination that has never been written to has
10587
+ * neither this nor `lastAttemptAt`.
10588
+ */
10589
+ lastError: string().optional()
10568
10590
  });
10569
10591
  /**
10570
10592
  * Per-archive entry returned by `backup.listArchives({ destinationId })`.
@@ -10727,8 +10749,16 @@ var BackupScheduleSchema = object({
10727
10749
  retentionCount: number().int().min(1).max(1e3),
10728
10750
  /** Optional subset of source locations to include; omitted = all. */
10729
10751
  dataSources: array(string()).readonly().optional(),
10730
- /** ms-epoch of last successful run. */
10731
- lastRunAt: number().optional(),
10752
+ /**
10753
+ * ms-epoch of the last tick that FIRED this schedule. Stamped before the
10754
+ * archive runs (it is the dedupe anchor), so it says "attempted", never
10755
+ * "succeeded" — a run refused by every destination stamps it too.
10756
+ */
10757
+ lastAttemptAt: number().optional(),
10758
+ /** ms-epoch of the last run of this schedule that landed at ≥1 destination. */
10759
+ lastSuccessAt: number().optional(),
10760
+ /** Why the last fired run failed, verbatim. Absent when it succeeded. */
10761
+ lastError: string().optional(),
10732
10762
  /** ms-epoch of next computed firing (read-only, filled on list). */
10733
10763
  nextRunAt: number().optional()
10734
10764
  });
@@ -10777,14 +10807,7 @@ method(_void(), array(BackupDestinationInfoSchema).readonly(), { auth: "admin" }
10777
10807
  locationId: string(),
10778
10808
  enabled: boolean(),
10779
10809
  retentionCount: number().int().min(1).max(1e3),
10780
- label: string().optional(),
10781
- /**
10782
- * Per-destination cron expression. Empty string clears the
10783
- * schedule (manual-only). Validated server-side via croner;
10784
- * malformed expressions reject the upsert with an actionable
10785
- * message.
10786
- */
10787
- cron: string().optional()
10810
+ label: string().optional()
10788
10811
  }), _void(), {
10789
10812
  kind: "mutation",
10790
10813
  auth: "admin"
@@ -21982,7 +22005,7 @@ method(object({
21982
22005
  downloadId: string(),
21983
22006
  offset: number(),
21984
22007
  length: number()
21985
- }), _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({
22008
+ }), _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({
21986
22009
  createdAt: true,
21987
22010
  updatedAt: true
21988
22011
  }), StorageLocationSchema, {
@@ -38827,12 +38850,6 @@ Object.freeze({
38827
38850
  addonId: null,
38828
38851
  access: "view"
38829
38852
  },
38830
- "storage.getDefaultLocation": {
38831
- capName: "storage",
38832
- capScope: "system",
38833
- addonId: null,
38834
- access: "view"
38835
- },
38836
38853
  "storage.list": {
38837
38854
  capName: "storage",
38838
38855
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-homematic",
3
- "version": "1.2.78",
3
+ "version": "1.2.80",
4
4
  "description": "Homematic / HomematicIP (CCU3 / RaspberryMatic) device-provider addon for CamStack — wraps the nodehomematic library",
5
5
  "keywords": [
6
6
  "camstack",