@camstack/addon-provider-homeassistant 1.2.93 → 1.2.95

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.
package/dist/addon.js CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_dist = require("./dist-14FaSOPJ.js");
2
+ const require_dist = require("./dist-CfzqMivH.js");
3
3
  let node_crypto = require("node:crypto");
4
4
  let node_zlib = require("node:zlib");
5
5
  //#region src/ha-device-source.ts
package/dist/addon.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as presenceCapability, A as controlCapability, At as EventCategory, B as humiditySensorCapability, C as buildAddonRouteProvider, Ct as number, D as colorCapability, Dt as union, E as climateControlCapability, Et as string, F as eventEmitterCapability, G as motionCapability, H as lawnMowerControlCapability, I as fanControlCapability, J as notifierCapability, K as normalizeUnit, L as floodCapability, M as deviceAdoptionCapability, O as connectivityCapability, Ot as unknown, P as enumSensorCapability, Q as prepareNotification, R as gasCapability, S as brokerCapability, St as literal, T as carbonMonoxideCapability, Tt as record, U as lockControlCapability, V as imageCapability, W as mediaPlayerCapability, Y as numericSensorCapability, Z as powerMeterCapability, _ as automationControlCapability, _t as createEvent, at as temperatureSensorCapability, b as binaryCapability, bt as array, ct as valveCapability, d as TargetSchema, dt as weatherCapability, et as pressureSensorCapability, f as accessoriesCapability, ft as errMsg, g as ambientLightSensorCapability, gt as DeviceType, h as alarmPanelCapability, ht as DeviceRole, it as tamperCapability, j as coverCapability, k as contactCapability, kt as url, lt as vibrationCapability, m as airQualitySensorCapability, mt as DeviceFeature, n as BaseDevice, nt as smokeCapability, ot as updateCapability, p as addonRoutesCapability, q as notificationOutputCapability, r as BaseDeviceProvider, rt as switchCapability, s as EnumSensorDateTimeFormatSchema, st as vacuumControlCapability, tt as scriptRunnerCapability, ut as waterHeaterCapability, v as batteryCapability, w as buttonCapability, wt as object, x as brightnessCapability, xt as discriminatedUnion, y as bestLocationMatch, yt as _enum, z as humidifierCapability } from "./dist-5-dwtcpP.mjs";
1
+ import { $ as presenceCapability, A as controlCapability, At as EventCategory, B as humiditySensorCapability, C as buildAddonRouteProvider, Ct as number, D as colorCapability, Dt as union, E as climateControlCapability, Et as string, F as eventEmitterCapability, G as motionCapability, H as lawnMowerControlCapability, I as fanControlCapability, J as notifierCapability, K as normalizeUnit, L as floodCapability, M as deviceAdoptionCapability, O as connectivityCapability, Ot as unknown, P as enumSensorCapability, Q as prepareNotification, R as gasCapability, S as brokerCapability, St as literal, T as carbonMonoxideCapability, Tt as record, U as lockControlCapability, V as imageCapability, W as mediaPlayerCapability, Y as numericSensorCapability, Z as powerMeterCapability, _ as automationControlCapability, _t as createEvent, at as temperatureSensorCapability, b as binaryCapability, bt as array, ct as valveCapability, d as TargetSchema, dt as weatherCapability, et as pressureSensorCapability, f as accessoriesCapability, ft as errMsg, g as ambientLightSensorCapability, gt as DeviceType, h as alarmPanelCapability, ht as DeviceRole, it as tamperCapability, j as coverCapability, k as contactCapability, kt as url, lt as vibrationCapability, m as airQualitySensorCapability, mt as DeviceFeature, n as BaseDevice, nt as smokeCapability, ot as updateCapability, p as addonRoutesCapability, q as notificationOutputCapability, r as BaseDeviceProvider, rt as switchCapability, s as EnumSensorDateTimeFormatSchema, st as vacuumControlCapability, tt as scriptRunnerCapability, ut as waterHeaterCapability, v as batteryCapability, w as buttonCapability, wt as object, x as brightnessCapability, xt as discriminatedUnion, y as bestLocationMatch, yt as _enum, z as humidifierCapability } from "./dist-_Tn2Geit.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { brotliCompressSync, deflateSync, gzipSync } from "node:zlib";
4
4
  //#region src/ha-device-source.ts
@@ -7818,111 +7818,6 @@ var CameraSwitchGroupSchema = object({
7818
7818
  fetchedAt: number()
7819
7819
  });
7820
7820
  /**
7821
- * Per-component log CHANNELS — the gate a hot path consults, and the registry
7822
- * an addon declares its channels in.
7823
- *
7824
- * ## Two axes, deliberately separated
7825
- *
7826
- * - **DECLARATION** — which channels exist. Only the addon knows:
7827
- * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
7828
- * baichuan/handshake. A hand-wired central list rots at the first addition,
7829
- * and rots silently. So a channel is declared where it is consulted, and the
7830
- * `log-channels` capability enumerates the declarations.
7831
- * - **VALUE** — at which level, for which scope, until when. That stays ONE
7832
- * thing: the logging settings document on the `system` cap. Two authorities
7833
- * over the values is the exact defect
7834
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
7835
- * remove; re-introducing it from the cure side would be grotesque.
7836
- *
7837
- * Nothing in this file reads a clock, an env var or a store. The registry is
7838
- * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
7839
- * the hot path with a value somebody actually read, and by
7840
- * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
7841
- * never reaches here, so it can neither disarm an armed channel nor arm a
7842
- * disarmed one (D49).
7843
- *
7844
- * ## The canonical call shape
7845
- *
7846
- * ```ts
7847
- * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
7848
- * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
7849
- * }
7850
- * ```
7851
- *
7852
- * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
7853
- * read. Disarmed, a call site costs one load and one branch, and the `extras`
7854
- * object literal is never constructed because it lives inside the branch. It
7855
- * is the same shape already proven in production at `stream-broker.ts:1650`,
7856
- * and the same discipline `LoggingGate.allowsDestination` uses for the
7857
- * destination floor (measured at 1.93 ns/call when off).
7858
- *
7859
- * ## Why a channel emits at `info`
7860
- *
7861
- * `loki-logging.addon.ts` pins the destination default at `info` and
7862
- * `loki-destination.ts` drops everything below it, so a line emitted at
7863
- * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
7864
- * minutes. A diagnostic that cannot be read an hour later is worse than no
7865
- * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
7866
- * emits at the channel's declared level, whose schema floor is `info`.
7867
- */
7868
- /**
7869
- * The level a channel writes at once armed.
7870
- *
7871
- * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
7872
- * not leave the process for Loki, and the whole point of arming a channel is
7873
- * to read it later.
7874
- */
7875
- var LogChannelLevelSchema = _enum([
7876
- "info",
7877
- "warn",
7878
- "error"
7879
- ]);
7880
- /**
7881
- * What an addon declares about one channel. No value, no state — a
7882
- * declaration is inert.
7883
- */
7884
- var LogChannelDescriptorSchema = object({
7885
- /**
7886
- * Dotted `area.thing`, unique across the workspace. `area` is conventionally
7887
- * the addon's short name so an operator reading a channel list can tell who
7888
- * owns it without a second lookup.
7889
- */
7890
- name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
7891
- /** One sentence: what the operator will SEE after arming it. */
7892
- description: string().min(1),
7893
- /** The level its lines are emitted at. Never below `info`. */
7894
- defaultLevel: LogChannelLevelSchema,
7895
- /**
7896
- * Whether this channel can be narrowed to a camera.
7897
- *
7898
- * `true` is a PROMISE with two halves, and both must hold: the gate is
7899
- * consulted with the numeric device id, AND every line the channel admits
7900
- * carries `tags: { deviceId }` with that same numeric id. The second half is
7901
- * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
7902
- * keeps `deviceId` out of the stream labels for cardinality, so the tag in
7903
- * the body is the only way to filter.
7904
- *
7905
- * A channel whose lines carry the device only in `meta` (or not at all) is
7906
- * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
7907
- * the operator narrows to one camera, sees nothing, and concludes the code
7908
- * path was never taken.
7909
- */
7910
- perDevice: boolean()
7911
- });
7912
- /**
7913
- * An armed window over one channel, as the document hands it to a mirror.
7914
- *
7915
- * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
7916
- * expires by itself, which is the one failure a boolean cannot avoid.
7917
- */
7918
- var LogChannelWindowSchema = object({
7919
- channel: string().min(1),
7920
- /** Epoch ms the window closes at. */
7921
- armedUntilMs: number(),
7922
- /** `null` = every camera. A non-empty list narrows to those numeric ids. */
7923
- deviceIds: array(number().int()).readonly().nullable()
7924
- });
7925
- /**
7926
7821
  * Ops-log — the durable, append-only operations audit shared by the
7927
7822
  * recordings and events management surfaces.
7928
7823
  *
@@ -8868,8 +8763,11 @@ var StorageLocationTypeSchema = string().regex(/^[a-z][a-zA-Z0-9-]*$/);
8868
8763
  * `STORAGE_LOCATION_CARDINALITY` map has been removed.
8869
8764
  *
8870
8765
  * `id` is a stable namespaced string of the form `<type>:<slug>`.
8871
- * The default location for a type uses `id === <type>:default` by
8872
- * convention (the bare type ref like `'backups'` resolves to it).
8766
+ * The seed names its first instance `<type>:default` — a NAME, not a flag.
8767
+ * There is no default location any more (D383): `enabled` is the whole write
8768
+ * model, and a bare type ref resolves to the sole location of the type, or —
8769
+ * transitionally, only while legacy NULL-stamped rows exist — to the row whose
8770
+ * slug is `default`.
8873
8771
  *
8874
8772
  * `isSystem` is a legacy persisted flag. Seed still creates the initial
8875
8773
  * `<type>:default` locations; the flag is no longer a lock, a badge, or a
@@ -8890,21 +8788,20 @@ var StorageLocationSchema = object({
8890
8788
  * flag at upsert time, not here (the schema is provider-agnostic).
8891
8789
  */
8892
8790
  nodeId: string().optional(),
8893
- isDefault: boolean().default(false),
8894
8791
  isSystem: boolean().default(false),
8895
8792
  /**
8896
- * Operator opt-in: whether consumers that BALANCE across several locations
8897
- * of a type may write here. Recordings reads it today; event media and
8898
- * backups are the next consumers, which is why the flag lives on the
8899
- * location rather than in any one addon's store — nothing has to be
8900
- * extended to add the next consumer.
8793
+ * THE write switch, and the only one (D383). `enabled: true` means every
8794
+ * consumer that chooses a write target for this type may write here, and all
8795
+ * enabled locations of a type are used TOGETHER; `false` means read-only —
8796
+ * still read, still played back, still age-swept, still drained, never
8797
+ * written.
8901
8798
  *
8902
- * OPTIONAL, and ABSENT MEANS ACTIVE. Every location persisted before the
8903
- * flag existed reads back with no flag and keeps working exactly as before;
8904
- * that is the whole compat story, and it is why no migration ships with it.
8905
- * A newly CREATED sibling is stamped `false` by the orchestrator (creating a
8906
- * disk must not silently start writing to it); the default of a type is
8907
- * always stamped `true`.
8799
+ * OPTIONAL only for the wire: an upsert that omits it means "leave what is
8800
+ * stored" on an update and "born inert unless it is the first location of its
8801
+ * type" on a create. On a PERSISTED row absence is legacy and it means
8802
+ * enabled — {@link isLocationEnabled} is the one place that says so, and the
8803
+ * orchestrator stamps every flagless row `true` once at hydrate so absence
8804
+ * stops existing rather than being re-derived on every read.
8908
8805
  */
8909
8806
  enabled: boolean().optional(),
8910
8807
  /** COMPUTED at read time by the orchestrator (statfs of the backing volume
@@ -8917,10 +8814,12 @@ var StorageLocationSchema = object({
8917
8814
  createdAt: number(),
8918
8815
  updatedAt: number()
8919
8816
  });
8817
+ object({ isDefault: boolean().optional() });
8920
8818
  /**
8921
8819
  * Reference accepted by consumer-facing `api.storage.*` calls.
8922
8820
  * Either:
8923
- * - a `StorageLocationType` (e.g. `'backups'`) → orchestrator resolves to the default of that type
8821
+ * - a `StorageLocationType` (e.g. `'backups'`) → the sole location of that type
8822
+ * (transitionally, the `<type>:default`-slugged row when several exist)
8924
8823
  * - a fully-qualified id (e.g. `'backups:nas-01'`) → addresses a specific instance
8925
8824
  *
8926
8825
  * The orchestrator's `resolveRef(ref)` handles both cases.
@@ -9096,6 +8995,111 @@ var DecoderSessionConfigSchema = object({
9096
8995
  */
9097
8996
  debug: boolean().optional()
9098
8997
  });
8998
+ /**
8999
+ * Per-component log CHANNELS — the gate a hot path consults, and the registry
9000
+ * an addon declares its channels in.
9001
+ *
9002
+ * ## Two axes, deliberately separated
9003
+ *
9004
+ * - **DECLARATION** — which channels exist. Only the addon knows:
9005
+ * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
9006
+ * baichuan/handshake. A hand-wired central list rots at the first addition,
9007
+ * and rots silently. So a channel is declared where it is consulted, and the
9008
+ * `log-channels` capability enumerates the declarations.
9009
+ * - **VALUE** — at which level, for which scope, until when. That stays ONE
9010
+ * thing: the logging settings document on the `system` cap. Two authorities
9011
+ * over the values is the exact defect
9012
+ * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
9013
+ * remove; re-introducing it from the cure side would be grotesque.
9014
+ *
9015
+ * Nothing in this file reads a clock, an env var or a store. The registry is
9016
+ * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
9017
+ * the hot path with a value somebody actually read, and by
9018
+ * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
9019
+ * never reaches here, so it can neither disarm an armed channel nor arm a
9020
+ * disarmed one (D49).
9021
+ *
9022
+ * ## The canonical call shape
9023
+ *
9024
+ * ```ts
9025
+ * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
9026
+ * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
9027
+ * }
9028
+ * ```
9029
+ *
9030
+ * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
9031
+ * read. Disarmed, a call site costs one load and one branch, and the `extras`
9032
+ * object literal is never constructed because it lives inside the branch. It
9033
+ * is the same shape already proven in production at `stream-broker.ts:1650`,
9034
+ * and the same discipline `LoggingGate.allowsDestination` uses for the
9035
+ * destination floor (measured at 1.93 ns/call when off).
9036
+ *
9037
+ * ## Why a channel emits at `info`
9038
+ *
9039
+ * `loki-logging.addon.ts` pins the destination default at `info` and
9040
+ * `loki-destination.ts` drops everything below it, so a line emitted at
9041
+ * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
9042
+ * minutes. A diagnostic that cannot be read an hour later is worse than no
9043
+ * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
9044
+ * emits at the channel's declared level, whose schema floor is `info`.
9045
+ */
9046
+ /**
9047
+ * The level a channel writes at once armed.
9048
+ *
9049
+ * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
9050
+ * not leave the process for Loki, and the whole point of arming a channel is
9051
+ * to read it later.
9052
+ */
9053
+ var LogChannelLevelSchema = _enum([
9054
+ "info",
9055
+ "warn",
9056
+ "error"
9057
+ ]);
9058
+ /**
9059
+ * What an addon declares about one channel. No value, no state — a
9060
+ * declaration is inert.
9061
+ */
9062
+ var LogChannelDescriptorSchema = object({
9063
+ /**
9064
+ * Dotted `area.thing`, unique across the workspace. `area` is conventionally
9065
+ * the addon's short name so an operator reading a channel list can tell who
9066
+ * owns it without a second lookup.
9067
+ */
9068
+ name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
9069
+ /** One sentence: what the operator will SEE after arming it. */
9070
+ description: string().min(1),
9071
+ /** The level its lines are emitted at. Never below `info`. */
9072
+ defaultLevel: LogChannelLevelSchema,
9073
+ /**
9074
+ * Whether this channel can be narrowed to a camera.
9075
+ *
9076
+ * `true` is a PROMISE with two halves, and both must hold: the gate is
9077
+ * consulted with the numeric device id, AND every line the channel admits
9078
+ * carries `tags: { deviceId }` with that same numeric id. The second half is
9079
+ * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
9080
+ * keeps `deviceId` out of the stream labels for cardinality, so the tag in
9081
+ * the body is the only way to filter.
9082
+ *
9083
+ * A channel whose lines carry the device only in `meta` (or not at all) is
9084
+ * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
9085
+ * the operator narrows to one camera, sees nothing, and concludes the code
9086
+ * path was never taken.
9087
+ */
9088
+ perDevice: boolean()
9089
+ });
9090
+ /**
9091
+ * An armed window over one channel, as the document hands it to a mirror.
9092
+ *
9093
+ * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
9094
+ * expires by itself, which is the one failure a boolean cannot avoid.
9095
+ */
9096
+ var LogChannelWindowSchema = object({
9097
+ channel: string().min(1),
9098
+ /** Epoch ms the window closes at. */
9099
+ armedUntilMs: number(),
9100
+ /** `null` = every camera. A non-empty list narrows to those numeric ids. */
9101
+ deviceIds: array(number().int()).readonly().nullable()
9102
+ });
9099
9103
  var MODEL_FORMATS = [
9100
9104
  "onnx",
9101
9105
  "coreml",
@@ -10771,12 +10775,30 @@ var BackupDestinationInfoSchema = object({
10771
10775
  lastSuccessAt: number().optional(),
10772
10776
  /** Newest-archive size from `manifests.json`, or undefined. */
10773
10777
  lastSuccessSizeBytes: number().optional(),
10774
- /** Per-destination cron expression. Empty = manual-only (no schedule). */
10778
+ /**
10779
+ * Cron cadence(s) of the ENABLED schedules that fan out to this
10780
+ * destination, comma-joined. Absent when no enabled schedule targets it
10781
+ * — a destination nothing is scheduled to write to must not advertise a
10782
+ * cadence (D384). This is never the `backup_destination_policies.cron`
10783
+ * column: that per-location cron has scheduled nothing since 2026-07-28
10784
+ * and reading it made a destination with a DISABLED schedule claim a
10785
+ * nightly run.
10786
+ */
10775
10787
  cron: string().optional(),
10776
- /** ms-epoch of next computed firing for this destination's cron, if any. */
10788
+ /** ms-epoch of the next firing across those schedules (earliest), if any. */
10777
10789
  nextRunAt: number().optional(),
10778
- /** ms-epoch of last successful scheduled run (mirrors policy.lastRunAt). */
10779
- lastRunAt: number().optional()
10790
+ /**
10791
+ * ms-epoch of the last time a run ATTEMPTED to write here — success or
10792
+ * failure. Never a success stamp: pair it with `lastSuccessAt` (the
10793
+ * newest archive that actually landed) and `lastError`.
10794
+ */
10795
+ lastAttemptAt: number().optional(),
10796
+ /**
10797
+ * Why the last attempt failed, verbatim. Absent when the last attempt
10798
+ * landed the archive. A destination that has never been written to has
10799
+ * neither this nor `lastAttemptAt`.
10800
+ */
10801
+ lastError: string().optional()
10780
10802
  });
10781
10803
  /**
10782
10804
  * Per-archive entry returned by `backup.listArchives({ destinationId })`.
@@ -10939,8 +10961,16 @@ var BackupScheduleSchema = object({
10939
10961
  retentionCount: number().int().min(1).max(1e3),
10940
10962
  /** Optional subset of source locations to include; omitted = all. */
10941
10963
  dataSources: array(string()).readonly().optional(),
10942
- /** ms-epoch of last successful run. */
10943
- lastRunAt: number().optional(),
10964
+ /**
10965
+ * ms-epoch of the last tick that FIRED this schedule. Stamped before the
10966
+ * archive runs (it is the dedupe anchor), so it says "attempted", never
10967
+ * "succeeded" — a run refused by every destination stamps it too.
10968
+ */
10969
+ lastAttemptAt: number().optional(),
10970
+ /** ms-epoch of the last run of this schedule that landed at ≥1 destination. */
10971
+ lastSuccessAt: number().optional(),
10972
+ /** Why the last fired run failed, verbatim. Absent when it succeeded. */
10973
+ lastError: string().optional(),
10944
10974
  /** ms-epoch of next computed firing (read-only, filled on list). */
10945
10975
  nextRunAt: number().optional()
10946
10976
  });
@@ -10989,14 +11019,7 @@ method(_void(), array(BackupDestinationInfoSchema).readonly(), { auth: "admin" }
10989
11019
  locationId: string(),
10990
11020
  enabled: boolean(),
10991
11021
  retentionCount: number().int().min(1).max(1e3),
10992
- label: string().optional(),
10993
- /**
10994
- * Per-destination cron expression. Empty string clears the
10995
- * schedule (manual-only). Validated server-side via croner;
10996
- * malformed expressions reject the upsert with an actionable
10997
- * message.
10998
- */
10999
- cron: string().optional()
11022
+ label: string().optional()
11000
11023
  }), _void(), {
11001
11024
  kind: "mutation",
11002
11025
  auth: "admin"
@@ -22294,7 +22317,7 @@ method(object({
22294
22317
  downloadId: string(),
22295
22318
  offset: number(),
22296
22319
  length: number()
22297
- }), _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({
22320
+ }), _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({
22298
22321
  createdAt: true,
22299
22322
  updatedAt: true
22300
22323
  }), StorageLocationSchema, {
@@ -39228,12 +39251,6 @@ Object.freeze({
39228
39251
  addonId: null,
39229
39252
  access: "view"
39230
39253
  },
39231
- "storage.getDefaultLocation": {
39232
- capName: "storage",
39233
- capScope: "system",
39234
- addonId: null,
39235
- access: "view"
39236
- },
39237
39254
  "storage.list": {
39238
39255
  capName: "storage",
39239
39256
  capScope: "system",
@@ -7818,111 +7818,6 @@ var CameraSwitchGroupSchema = object({
7818
7818
  fetchedAt: number()
7819
7819
  });
7820
7820
  /**
7821
- * Per-component log CHANNELS — the gate a hot path consults, and the registry
7822
- * an addon declares its channels in.
7823
- *
7824
- * ## Two axes, deliberately separated
7825
- *
7826
- * - **DECLARATION** — which channels exist. Only the addon knows:
7827
- * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
7828
- * baichuan/handshake. A hand-wired central list rots at the first addition,
7829
- * and rots silently. So a channel is declared where it is consulted, and the
7830
- * `log-channels` capability enumerates the declarations.
7831
- * - **VALUE** — at which level, for which scope, until when. That stays ONE
7832
- * thing: the logging settings document on the `system` cap. Two authorities
7833
- * over the values is the exact defect
7834
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
7835
- * remove; re-introducing it from the cure side would be grotesque.
7836
- *
7837
- * Nothing in this file reads a clock, an env var or a store. The registry is
7838
- * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
7839
- * the hot path with a value somebody actually read, and by
7840
- * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
7841
- * never reaches here, so it can neither disarm an armed channel nor arm a
7842
- * disarmed one (D49).
7843
- *
7844
- * ## The canonical call shape
7845
- *
7846
- * ```ts
7847
- * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
7848
- * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
7849
- * }
7850
- * ```
7851
- *
7852
- * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
7853
- * read. Disarmed, a call site costs one load and one branch, and the `extras`
7854
- * object literal is never constructed because it lives inside the branch. It
7855
- * is the same shape already proven in production at `stream-broker.ts:1650`,
7856
- * and the same discipline `LoggingGate.allowsDestination` uses for the
7857
- * destination floor (measured at 1.93 ns/call when off).
7858
- *
7859
- * ## Why a channel emits at `info`
7860
- *
7861
- * `loki-logging.addon.ts` pins the destination default at `info` and
7862
- * `loki-destination.ts` drops everything below it, so a line emitted at
7863
- * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
7864
- * minutes. A diagnostic that cannot be read an hour later is worse than no
7865
- * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
7866
- * emits at the channel's declared level, whose schema floor is `info`.
7867
- */
7868
- /**
7869
- * The level a channel writes at once armed.
7870
- *
7871
- * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
7872
- * not leave the process for Loki, and the whole point of arming a channel is
7873
- * to read it later.
7874
- */
7875
- var LogChannelLevelSchema = _enum([
7876
- "info",
7877
- "warn",
7878
- "error"
7879
- ]);
7880
- /**
7881
- * What an addon declares about one channel. No value, no state — a
7882
- * declaration is inert.
7883
- */
7884
- var LogChannelDescriptorSchema = object({
7885
- /**
7886
- * Dotted `area.thing`, unique across the workspace. `area` is conventionally
7887
- * the addon's short name so an operator reading a channel list can tell who
7888
- * owns it without a second lookup.
7889
- */
7890
- name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
7891
- /** One sentence: what the operator will SEE after arming it. */
7892
- description: string().min(1),
7893
- /** The level its lines are emitted at. Never below `info`. */
7894
- defaultLevel: LogChannelLevelSchema,
7895
- /**
7896
- * Whether this channel can be narrowed to a camera.
7897
- *
7898
- * `true` is a PROMISE with two halves, and both must hold: the gate is
7899
- * consulted with the numeric device id, AND every line the channel admits
7900
- * carries `tags: { deviceId }` with that same numeric id. The second half is
7901
- * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
7902
- * keeps `deviceId` out of the stream labels for cardinality, so the tag in
7903
- * the body is the only way to filter.
7904
- *
7905
- * A channel whose lines carry the device only in `meta` (or not at all) is
7906
- * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
7907
- * the operator narrows to one camera, sees nothing, and concludes the code
7908
- * path was never taken.
7909
- */
7910
- perDevice: boolean()
7911
- });
7912
- /**
7913
- * An armed window over one channel, as the document hands it to a mirror.
7914
- *
7915
- * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
7916
- * expires by itself, which is the one failure a boolean cannot avoid.
7917
- */
7918
- var LogChannelWindowSchema = object({
7919
- channel: string().min(1),
7920
- /** Epoch ms the window closes at. */
7921
- armedUntilMs: number(),
7922
- /** `null` = every camera. A non-empty list narrows to those numeric ids. */
7923
- deviceIds: array(number().int()).readonly().nullable()
7924
- });
7925
- /**
7926
7821
  * Ops-log — the durable, append-only operations audit shared by the
7927
7822
  * recordings and events management surfaces.
7928
7823
  *
@@ -8868,8 +8763,11 @@ var StorageLocationTypeSchema = string().regex(/^[a-z][a-zA-Z0-9-]*$/);
8868
8763
  * `STORAGE_LOCATION_CARDINALITY` map has been removed.
8869
8764
  *
8870
8765
  * `id` is a stable namespaced string of the form `<type>:<slug>`.
8871
- * The default location for a type uses `id === <type>:default` by
8872
- * convention (the bare type ref like `'backups'` resolves to it).
8766
+ * The seed names its first instance `<type>:default` — a NAME, not a flag.
8767
+ * There is no default location any more (D383): `enabled` is the whole write
8768
+ * model, and a bare type ref resolves to the sole location of the type, or —
8769
+ * transitionally, only while legacy NULL-stamped rows exist — to the row whose
8770
+ * slug is `default`.
8873
8771
  *
8874
8772
  * `isSystem` is a legacy persisted flag. Seed still creates the initial
8875
8773
  * `<type>:default` locations; the flag is no longer a lock, a badge, or a
@@ -8890,21 +8788,20 @@ var StorageLocationSchema = object({
8890
8788
  * flag at upsert time, not here (the schema is provider-agnostic).
8891
8789
  */
8892
8790
  nodeId: string().optional(),
8893
- isDefault: boolean().default(false),
8894
8791
  isSystem: boolean().default(false),
8895
8792
  /**
8896
- * Operator opt-in: whether consumers that BALANCE across several locations
8897
- * of a type may write here. Recordings reads it today; event media and
8898
- * backups are the next consumers, which is why the flag lives on the
8899
- * location rather than in any one addon's store — nothing has to be
8900
- * extended to add the next consumer.
8793
+ * THE write switch, and the only one (D383). `enabled: true` means every
8794
+ * consumer that chooses a write target for this type may write here, and all
8795
+ * enabled locations of a type are used TOGETHER; `false` means read-only —
8796
+ * still read, still played back, still age-swept, still drained, never
8797
+ * written.
8901
8798
  *
8902
- * OPTIONAL, and ABSENT MEANS ACTIVE. Every location persisted before the
8903
- * flag existed reads back with no flag and keeps working exactly as before;
8904
- * that is the whole compat story, and it is why no migration ships with it.
8905
- * A newly CREATED sibling is stamped `false` by the orchestrator (creating a
8906
- * disk must not silently start writing to it); the default of a type is
8907
- * always stamped `true`.
8799
+ * OPTIONAL only for the wire: an upsert that omits it means "leave what is
8800
+ * stored" on an update and "born inert unless it is the first location of its
8801
+ * type" on a create. On a PERSISTED row absence is legacy and it means
8802
+ * enabled — {@link isLocationEnabled} is the one place that says so, and the
8803
+ * orchestrator stamps every flagless row `true` once at hydrate so absence
8804
+ * stops existing rather than being re-derived on every read.
8908
8805
  */
8909
8806
  enabled: boolean().optional(),
8910
8807
  /** COMPUTED at read time by the orchestrator (statfs of the backing volume
@@ -8917,10 +8814,12 @@ var StorageLocationSchema = object({
8917
8814
  createdAt: number(),
8918
8815
  updatedAt: number()
8919
8816
  });
8817
+ object({ isDefault: boolean().optional() });
8920
8818
  /**
8921
8819
  * Reference accepted by consumer-facing `api.storage.*` calls.
8922
8820
  * Either:
8923
- * - a `StorageLocationType` (e.g. `'backups'`) → orchestrator resolves to the default of that type
8821
+ * - a `StorageLocationType` (e.g. `'backups'`) → the sole location of that type
8822
+ * (transitionally, the `<type>:default`-slugged row when several exist)
8924
8823
  * - a fully-qualified id (e.g. `'backups:nas-01'`) → addresses a specific instance
8925
8824
  *
8926
8825
  * The orchestrator's `resolveRef(ref)` handles both cases.
@@ -9096,6 +8995,111 @@ var DecoderSessionConfigSchema = object({
9096
8995
  */
9097
8996
  debug: boolean().optional()
9098
8997
  });
8998
+ /**
8999
+ * Per-component log CHANNELS — the gate a hot path consults, and the registry
9000
+ * an addon declares its channels in.
9001
+ *
9002
+ * ## Two axes, deliberately separated
9003
+ *
9004
+ * - **DECLARATION** — which channels exist. Only the addon knows:
9005
+ * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
9006
+ * baichuan/handshake. A hand-wired central list rots at the first addition,
9007
+ * and rots silently. So a channel is declared where it is consulted, and the
9008
+ * `log-channels` capability enumerates the declarations.
9009
+ * - **VALUE** — at which level, for which scope, until when. That stays ONE
9010
+ * thing: the logging settings document on the `system` cap. Two authorities
9011
+ * over the values is the exact defect
9012
+ * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
9013
+ * remove; re-introducing it from the cure side would be grotesque.
9014
+ *
9015
+ * Nothing in this file reads a clock, an env var or a store. The registry is
9016
+ * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
9017
+ * the hot path with a value somebody actually read, and by
9018
+ * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
9019
+ * never reaches here, so it can neither disarm an armed channel nor arm a
9020
+ * disarmed one (D49).
9021
+ *
9022
+ * ## The canonical call shape
9023
+ *
9024
+ * ```ts
9025
+ * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
9026
+ * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
9027
+ * }
9028
+ * ```
9029
+ *
9030
+ * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
9031
+ * read. Disarmed, a call site costs one load and one branch, and the `extras`
9032
+ * object literal is never constructed because it lives inside the branch. It
9033
+ * is the same shape already proven in production at `stream-broker.ts:1650`,
9034
+ * and the same discipline `LoggingGate.allowsDestination` uses for the
9035
+ * destination floor (measured at 1.93 ns/call when off).
9036
+ *
9037
+ * ## Why a channel emits at `info`
9038
+ *
9039
+ * `loki-logging.addon.ts` pins the destination default at `info` and
9040
+ * `loki-destination.ts` drops everything below it, so a line emitted at
9041
+ * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
9042
+ * minutes. A diagnostic that cannot be read an hour later is worse than no
9043
+ * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
9044
+ * emits at the channel's declared level, whose schema floor is `info`.
9045
+ */
9046
+ /**
9047
+ * The level a channel writes at once armed.
9048
+ *
9049
+ * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
9050
+ * not leave the process for Loki, and the whole point of arming a channel is
9051
+ * to read it later.
9052
+ */
9053
+ var LogChannelLevelSchema = _enum([
9054
+ "info",
9055
+ "warn",
9056
+ "error"
9057
+ ]);
9058
+ /**
9059
+ * What an addon declares about one channel. No value, no state — a
9060
+ * declaration is inert.
9061
+ */
9062
+ var LogChannelDescriptorSchema = object({
9063
+ /**
9064
+ * Dotted `area.thing`, unique across the workspace. `area` is conventionally
9065
+ * the addon's short name so an operator reading a channel list can tell who
9066
+ * owns it without a second lookup.
9067
+ */
9068
+ name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
9069
+ /** One sentence: what the operator will SEE after arming it. */
9070
+ description: string().min(1),
9071
+ /** The level its lines are emitted at. Never below `info`. */
9072
+ defaultLevel: LogChannelLevelSchema,
9073
+ /**
9074
+ * Whether this channel can be narrowed to a camera.
9075
+ *
9076
+ * `true` is a PROMISE with two halves, and both must hold: the gate is
9077
+ * consulted with the numeric device id, AND every line the channel admits
9078
+ * carries `tags: { deviceId }` with that same numeric id. The second half is
9079
+ * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
9080
+ * keeps `deviceId` out of the stream labels for cardinality, so the tag in
9081
+ * the body is the only way to filter.
9082
+ *
9083
+ * A channel whose lines carry the device only in `meta` (or not at all) is
9084
+ * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
9085
+ * the operator narrows to one camera, sees nothing, and concludes the code
9086
+ * path was never taken.
9087
+ */
9088
+ perDevice: boolean()
9089
+ });
9090
+ /**
9091
+ * An armed window over one channel, as the document hands it to a mirror.
9092
+ *
9093
+ * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
9094
+ * expires by itself, which is the one failure a boolean cannot avoid.
9095
+ */
9096
+ var LogChannelWindowSchema = object({
9097
+ channel: string().min(1),
9098
+ /** Epoch ms the window closes at. */
9099
+ armedUntilMs: number(),
9100
+ /** `null` = every camera. A non-empty list narrows to those numeric ids. */
9101
+ deviceIds: array(number().int()).readonly().nullable()
9102
+ });
9099
9103
  var MODEL_FORMATS = [
9100
9104
  "onnx",
9101
9105
  "coreml",
@@ -10771,12 +10775,30 @@ var BackupDestinationInfoSchema = object({
10771
10775
  lastSuccessAt: number().optional(),
10772
10776
  /** Newest-archive size from `manifests.json`, or undefined. */
10773
10777
  lastSuccessSizeBytes: number().optional(),
10774
- /** Per-destination cron expression. Empty = manual-only (no schedule). */
10778
+ /**
10779
+ * Cron cadence(s) of the ENABLED schedules that fan out to this
10780
+ * destination, comma-joined. Absent when no enabled schedule targets it
10781
+ * — a destination nothing is scheduled to write to must not advertise a
10782
+ * cadence (D384). This is never the `backup_destination_policies.cron`
10783
+ * column: that per-location cron has scheduled nothing since 2026-07-28
10784
+ * and reading it made a destination with a DISABLED schedule claim a
10785
+ * nightly run.
10786
+ */
10775
10787
  cron: string().optional(),
10776
- /** ms-epoch of next computed firing for this destination's cron, if any. */
10788
+ /** ms-epoch of the next firing across those schedules (earliest), if any. */
10777
10789
  nextRunAt: number().optional(),
10778
- /** ms-epoch of last successful scheduled run (mirrors policy.lastRunAt). */
10779
- lastRunAt: number().optional()
10790
+ /**
10791
+ * ms-epoch of the last time a run ATTEMPTED to write here — success or
10792
+ * failure. Never a success stamp: pair it with `lastSuccessAt` (the
10793
+ * newest archive that actually landed) and `lastError`.
10794
+ */
10795
+ lastAttemptAt: number().optional(),
10796
+ /**
10797
+ * Why the last attempt failed, verbatim. Absent when the last attempt
10798
+ * landed the archive. A destination that has never been written to has
10799
+ * neither this nor `lastAttemptAt`.
10800
+ */
10801
+ lastError: string().optional()
10780
10802
  });
10781
10803
  /**
10782
10804
  * Per-archive entry returned by `backup.listArchives({ destinationId })`.
@@ -10939,8 +10961,16 @@ var BackupScheduleSchema = object({
10939
10961
  retentionCount: number().int().min(1).max(1e3),
10940
10962
  /** Optional subset of source locations to include; omitted = all. */
10941
10963
  dataSources: array(string()).readonly().optional(),
10942
- /** ms-epoch of last successful run. */
10943
- lastRunAt: number().optional(),
10964
+ /**
10965
+ * ms-epoch of the last tick that FIRED this schedule. Stamped before the
10966
+ * archive runs (it is the dedupe anchor), so it says "attempted", never
10967
+ * "succeeded" — a run refused by every destination stamps it too.
10968
+ */
10969
+ lastAttemptAt: number().optional(),
10970
+ /** ms-epoch of the last run of this schedule that landed at ≥1 destination. */
10971
+ lastSuccessAt: number().optional(),
10972
+ /** Why the last fired run failed, verbatim. Absent when it succeeded. */
10973
+ lastError: string().optional(),
10944
10974
  /** ms-epoch of next computed firing (read-only, filled on list). */
10945
10975
  nextRunAt: number().optional()
10946
10976
  });
@@ -10989,14 +11019,7 @@ method(_void(), array(BackupDestinationInfoSchema).readonly(), { auth: "admin" }
10989
11019
  locationId: string(),
10990
11020
  enabled: boolean(),
10991
11021
  retentionCount: number().int().min(1).max(1e3),
10992
- label: string().optional(),
10993
- /**
10994
- * Per-destination cron expression. Empty string clears the
10995
- * schedule (manual-only). Validated server-side via croner;
10996
- * malformed expressions reject the upsert with an actionable
10997
- * message.
10998
- */
10999
- cron: string().optional()
11022
+ label: string().optional()
11000
11023
  }), _void(), {
11001
11024
  kind: "mutation",
11002
11025
  auth: "admin"
@@ -22294,7 +22317,7 @@ method(object({
22294
22317
  downloadId: string(),
22295
22318
  offset: number(),
22296
22319
  length: number()
22297
- }), _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({
22320
+ }), _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({
22298
22321
  createdAt: true,
22299
22322
  updatedAt: true
22300
22323
  }), StorageLocationSchema, {
@@ -39228,12 +39251,6 @@ Object.freeze({
39228
39251
  addonId: null,
39229
39252
  access: "view"
39230
39253
  },
39231
- "storage.getDefaultLocation": {
39232
- capName: "storage",
39233
- capScope: "system",
39234
- addonId: null,
39235
- access: "view"
39236
- },
39237
39254
  "storage.list": {
39238
39255
  capName: "storage",
39239
39256
  capScope: "system",
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  //#endregion
6
- const require_dist = require("../dist-14FaSOPJ.js");
6
+ const require_dist = require("../dist-CfzqMivH.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  //#region src/ha-export/topics.ts
9
9
  /**
@@ -1,4 +1,4 @@
1
- import { At as EventCategory, C as buildAddonRouteProvider, Et as string, N as deviceExportCapability, X as oauthIntegrationCapability, a as COCO_TO_MACRO, c as FanDirectionSchema, i as CAMERA_SWITCH_ORDER, l as HvacModeSchema, o as CameraSwitchIdSchema, p as addonRoutesCapability, pt as BaseAddon, t as AlarmArmModeSchema, u as MediaPlayerRepeatSchema, vt as nodePin } from "../dist-5-dwtcpP.mjs";
1
+ import { At as EventCategory, C as buildAddonRouteProvider, Et as string, N as deviceExportCapability, X as oauthIntegrationCapability, a as COCO_TO_MACRO, c as FanDirectionSchema, i as CAMERA_SWITCH_ORDER, l as HvacModeSchema, o as CameraSwitchIdSchema, p as addonRoutesCapability, pt as BaseAddon, t as AlarmArmModeSchema, u as MediaPlayerRepeatSchema, vt as nodePin } from "../dist-_Tn2Geit.mjs";
2
2
  import { createHmac, timingSafeEqual } from "node:crypto";
3
3
  //#region src/ha-export/topics.ts
4
4
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-homeassistant",
3
- "version": "1.2.93",
3
+ "version": "1.2.95",
4
4
  "description": "Home Assistant device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",