@camstack/addon-export-google 0.1.41 → 0.1.43

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.
@@ -7808,111 +7808,6 @@ var CameraSwitchGroupSchema = object({
7808
7808
  fetchedAt: number()
7809
7809
  });
7810
7810
  /**
7811
- * Per-component log CHANNELS — the gate a hot path consults, and the registry
7812
- * an addon declares its channels in.
7813
- *
7814
- * ## Two axes, deliberately separated
7815
- *
7816
- * - **DECLARATION** — which channels exist. Only the addon knows:
7817
- * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
7818
- * baichuan/handshake. A hand-wired central list rots at the first addition,
7819
- * and rots silently. So a channel is declared where it is consulted, and the
7820
- * `log-channels` capability enumerates the declarations.
7821
- * - **VALUE** — at which level, for which scope, until when. That stays ONE
7822
- * thing: the logging settings document on the `system` cap. Two authorities
7823
- * over the values is the exact defect
7824
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
7825
- * remove; re-introducing it from the cure side would be grotesque.
7826
- *
7827
- * Nothing in this file reads a clock, an env var or a store. The registry is
7828
- * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
7829
- * the hot path with a value somebody actually read, and by
7830
- * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
7831
- * never reaches here, so it can neither disarm an armed channel nor arm a
7832
- * disarmed one (D49).
7833
- *
7834
- * ## The canonical call shape
7835
- *
7836
- * ```ts
7837
- * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
7838
- * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
7839
- * }
7840
- * ```
7841
- *
7842
- * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
7843
- * read. Disarmed, a call site costs one load and one branch, and the `extras`
7844
- * object literal is never constructed because it lives inside the branch. It
7845
- * is the same shape already proven in production at `stream-broker.ts:1650`,
7846
- * and the same discipline `LoggingGate.allowsDestination` uses for the
7847
- * destination floor (measured at 1.93 ns/call when off).
7848
- *
7849
- * ## Why a channel emits at `info`
7850
- *
7851
- * `loki-logging.addon.ts` pins the destination default at `info` and
7852
- * `loki-destination.ts` drops everything below it, so a line emitted at
7853
- * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
7854
- * minutes. A diagnostic that cannot be read an hour later is worse than no
7855
- * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
7856
- * emits at the channel's declared level, whose schema floor is `info`.
7857
- */
7858
- /**
7859
- * The level a channel writes at once armed.
7860
- *
7861
- * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
7862
- * not leave the process for Loki, and the whole point of arming a channel is
7863
- * to read it later.
7864
- */
7865
- var LogChannelLevelSchema = _enum([
7866
- "info",
7867
- "warn",
7868
- "error"
7869
- ]);
7870
- /**
7871
- * What an addon declares about one channel. No value, no state — a
7872
- * declaration is inert.
7873
- */
7874
- var LogChannelDescriptorSchema = object({
7875
- /**
7876
- * Dotted `area.thing`, unique across the workspace. `area` is conventionally
7877
- * the addon's short name so an operator reading a channel list can tell who
7878
- * owns it without a second lookup.
7879
- */
7880
- name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
7881
- /** One sentence: what the operator will SEE after arming it. */
7882
- description: string().min(1),
7883
- /** The level its lines are emitted at. Never below `info`. */
7884
- defaultLevel: LogChannelLevelSchema,
7885
- /**
7886
- * Whether this channel can be narrowed to a camera.
7887
- *
7888
- * `true` is a PROMISE with two halves, and both must hold: the gate is
7889
- * consulted with the numeric device id, AND every line the channel admits
7890
- * carries `tags: { deviceId }` with that same numeric id. The second half is
7891
- * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
7892
- * keeps `deviceId` out of the stream labels for cardinality, so the tag in
7893
- * the body is the only way to filter.
7894
- *
7895
- * A channel whose lines carry the device only in `meta` (or not at all) is
7896
- * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
7897
- * the operator narrows to one camera, sees nothing, and concludes the code
7898
- * path was never taken.
7899
- */
7900
- perDevice: boolean()
7901
- });
7902
- /**
7903
- * An armed window over one channel, as the document hands it to a mirror.
7904
- *
7905
- * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
7906
- * expires by itself, which is the one failure a boolean cannot avoid.
7907
- */
7908
- var LogChannelWindowSchema = object({
7909
- channel: string().min(1),
7910
- /** Epoch ms the window closes at. */
7911
- armedUntilMs: number(),
7912
- /** `null` = every camera. A non-empty list narrows to those numeric ids. */
7913
- deviceIds: array(number().int()).readonly().nullable()
7914
- });
7915
- /**
7916
7811
  * Ops-log — the durable, append-only operations audit shared by the
7917
7812
  * recordings and events management surfaces.
7918
7813
  *
@@ -8858,8 +8753,11 @@ var StorageLocationTypeSchema = string().regex(/^[a-z][a-zA-Z0-9-]*$/);
8858
8753
  * `STORAGE_LOCATION_CARDINALITY` map has been removed.
8859
8754
  *
8860
8755
  * `id` is a stable namespaced string of the form `<type>:<slug>`.
8861
- * The default location for a type uses `id === <type>:default` by
8862
- * convention (the bare type ref like `'backups'` resolves to it).
8756
+ * The seed names its first instance `<type>:default` — a NAME, not a flag.
8757
+ * There is no default location any more (D383): `enabled` is the whole write
8758
+ * model, and a bare type ref resolves to the sole location of the type, or —
8759
+ * transitionally, only while legacy NULL-stamped rows exist — to the row whose
8760
+ * slug is `default`.
8863
8761
  *
8864
8762
  * `isSystem` is a legacy persisted flag. Seed still creates the initial
8865
8763
  * `<type>:default` locations; the flag is no longer a lock, a badge, or a
@@ -8880,21 +8778,20 @@ var StorageLocationSchema = object({
8880
8778
  * flag at upsert time, not here (the schema is provider-agnostic).
8881
8779
  */
8882
8780
  nodeId: string().optional(),
8883
- isDefault: boolean().default(false),
8884
8781
  isSystem: boolean().default(false),
8885
8782
  /**
8886
- * Operator opt-in: whether consumers that BALANCE across several locations
8887
- * of a type may write here. Recordings reads it today; event media and
8888
- * backups are the next consumers, which is why the flag lives on the
8889
- * location rather than in any one addon's store — nothing has to be
8890
- * extended to add the next consumer.
8783
+ * THE write switch, and the only one (D383). `enabled: true` means every
8784
+ * consumer that chooses a write target for this type may write here, and all
8785
+ * enabled locations of a type are used TOGETHER; `false` means read-only —
8786
+ * still read, still played back, still age-swept, still drained, never
8787
+ * written.
8891
8788
  *
8892
- * OPTIONAL, and ABSENT MEANS ACTIVE. Every location persisted before the
8893
- * flag existed reads back with no flag and keeps working exactly as before;
8894
- * that is the whole compat story, and it is why no migration ships with it.
8895
- * A newly CREATED sibling is stamped `false` by the orchestrator (creating a
8896
- * disk must not silently start writing to it); the default of a type is
8897
- * always stamped `true`.
8789
+ * OPTIONAL only for the wire: an upsert that omits it means "leave what is
8790
+ * stored" on an update and "born inert unless it is the first location of its
8791
+ * type" on a create. On a PERSISTED row absence is legacy and it means
8792
+ * enabled — {@link isLocationEnabled} is the one place that says so, and the
8793
+ * orchestrator stamps every flagless row `true` once at hydrate so absence
8794
+ * stops existing rather than being re-derived on every read.
8898
8795
  */
8899
8796
  enabled: boolean().optional(),
8900
8797
  /** COMPUTED at read time by the orchestrator (statfs of the backing volume
@@ -8907,10 +8804,12 @@ var StorageLocationSchema = object({
8907
8804
  createdAt: number(),
8908
8805
  updatedAt: number()
8909
8806
  });
8807
+ object({ isDefault: boolean().optional() });
8910
8808
  /**
8911
8809
  * Reference accepted by consumer-facing `api.storage.*` calls.
8912
8810
  * Either:
8913
- * - a `StorageLocationType` (e.g. `'backups'`) → orchestrator resolves to the default of that type
8811
+ * - a `StorageLocationType` (e.g. `'backups'`) → the sole location of that type
8812
+ * (transitionally, the `<type>:default`-slugged row when several exist)
8914
8813
  * - a fully-qualified id (e.g. `'backups:nas-01'`) → addresses a specific instance
8915
8814
  *
8916
8815
  * The orchestrator's `resolveRef(ref)` handles both cases.
@@ -9086,6 +8985,111 @@ var DecoderSessionConfigSchema = object({
9086
8985
  */
9087
8986
  debug: boolean().optional()
9088
8987
  });
8988
+ /**
8989
+ * Per-component log CHANNELS — the gate a hot path consults, and the registry
8990
+ * an addon declares its channels in.
8991
+ *
8992
+ * ## Two axes, deliberately separated
8993
+ *
8994
+ * - **DECLARATION** — which channels exist. Only the addon knows:
8995
+ * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
8996
+ * baichuan/handshake. A hand-wired central list rots at the first addition,
8997
+ * and rots silently. So a channel is declared where it is consulted, and the
8998
+ * `log-channels` capability enumerates the declarations.
8999
+ * - **VALUE** — at which level, for which scope, until when. That stays ONE
9000
+ * thing: the logging settings document on the `system` cap. Two authorities
9001
+ * over the values is the exact defect
9002
+ * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
9003
+ * remove; re-introducing it from the cure side would be grotesque.
9004
+ *
9005
+ * Nothing in this file reads a clock, an env var or a store. The registry is
9006
+ * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
9007
+ * the hot path with a value somebody actually read, and by
9008
+ * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
9009
+ * never reaches here, so it can neither disarm an armed channel nor arm a
9010
+ * disarmed one (D49).
9011
+ *
9012
+ * ## The canonical call shape
9013
+ *
9014
+ * ```ts
9015
+ * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
9016
+ * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
9017
+ * }
9018
+ * ```
9019
+ *
9020
+ * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
9021
+ * read. Disarmed, a call site costs one load and one branch, and the `extras`
9022
+ * object literal is never constructed because it lives inside the branch. It
9023
+ * is the same shape already proven in production at `stream-broker.ts:1650`,
9024
+ * and the same discipline `LoggingGate.allowsDestination` uses for the
9025
+ * destination floor (measured at 1.93 ns/call when off).
9026
+ *
9027
+ * ## Why a channel emits at `info`
9028
+ *
9029
+ * `loki-logging.addon.ts` pins the destination default at `info` and
9030
+ * `loki-destination.ts` drops everything below it, so a line emitted at
9031
+ * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
9032
+ * minutes. A diagnostic that cannot be read an hour later is worse than no
9033
+ * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
9034
+ * emits at the channel's declared level, whose schema floor is `info`.
9035
+ */
9036
+ /**
9037
+ * The level a channel writes at once armed.
9038
+ *
9039
+ * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
9040
+ * not leave the process for Loki, and the whole point of arming a channel is
9041
+ * to read it later.
9042
+ */
9043
+ var LogChannelLevelSchema = _enum([
9044
+ "info",
9045
+ "warn",
9046
+ "error"
9047
+ ]);
9048
+ /**
9049
+ * What an addon declares about one channel. No value, no state — a
9050
+ * declaration is inert.
9051
+ */
9052
+ var LogChannelDescriptorSchema = object({
9053
+ /**
9054
+ * Dotted `area.thing`, unique across the workspace. `area` is conventionally
9055
+ * the addon's short name so an operator reading a channel list can tell who
9056
+ * owns it without a second lookup.
9057
+ */
9058
+ name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
9059
+ /** One sentence: what the operator will SEE after arming it. */
9060
+ description: string().min(1),
9061
+ /** The level its lines are emitted at. Never below `info`. */
9062
+ defaultLevel: LogChannelLevelSchema,
9063
+ /**
9064
+ * Whether this channel can be narrowed to a camera.
9065
+ *
9066
+ * `true` is a PROMISE with two halves, and both must hold: the gate is
9067
+ * consulted with the numeric device id, AND every line the channel admits
9068
+ * carries `tags: { deviceId }` with that same numeric id. The second half is
9069
+ * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
9070
+ * keeps `deviceId` out of the stream labels for cardinality, so the tag in
9071
+ * the body is the only way to filter.
9072
+ *
9073
+ * A channel whose lines carry the device only in `meta` (or not at all) is
9074
+ * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
9075
+ * the operator narrows to one camera, sees nothing, and concludes the code
9076
+ * path was never taken.
9077
+ */
9078
+ perDevice: boolean()
9079
+ });
9080
+ /**
9081
+ * An armed window over one channel, as the document hands it to a mirror.
9082
+ *
9083
+ * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
9084
+ * expires by itself, which is the one failure a boolean cannot avoid.
9085
+ */
9086
+ var LogChannelWindowSchema = object({
9087
+ channel: string().min(1),
9088
+ /** Epoch ms the window closes at. */
9089
+ armedUntilMs: number(),
9090
+ /** `null` = every camera. A non-empty list narrows to those numeric ids. */
9091
+ deviceIds: array(number().int()).readonly().nullable()
9092
+ });
9089
9093
  var MODEL_FORMATS = [
9090
9094
  "onnx",
9091
9095
  "coreml",
@@ -10768,12 +10772,30 @@ var BackupDestinationInfoSchema = object({
10768
10772
  lastSuccessAt: number().optional(),
10769
10773
  /** Newest-archive size from `manifests.json`, or undefined. */
10770
10774
  lastSuccessSizeBytes: number().optional(),
10771
- /** Per-destination cron expression. Empty = manual-only (no schedule). */
10775
+ /**
10776
+ * Cron cadence(s) of the ENABLED schedules that fan out to this
10777
+ * destination, comma-joined. Absent when no enabled schedule targets it
10778
+ * — a destination nothing is scheduled to write to must not advertise a
10779
+ * cadence (D384). This is never the `backup_destination_policies.cron`
10780
+ * column: that per-location cron has scheduled nothing since 2026-07-28
10781
+ * and reading it made a destination with a DISABLED schedule claim a
10782
+ * nightly run.
10783
+ */
10772
10784
  cron: string().optional(),
10773
- /** ms-epoch of next computed firing for this destination's cron, if any. */
10785
+ /** ms-epoch of the next firing across those schedules (earliest), if any. */
10774
10786
  nextRunAt: number().optional(),
10775
- /** ms-epoch of last successful scheduled run (mirrors policy.lastRunAt). */
10776
- lastRunAt: number().optional()
10787
+ /**
10788
+ * ms-epoch of the last time a run ATTEMPTED to write here — success or
10789
+ * failure. Never a success stamp: pair it with `lastSuccessAt` (the
10790
+ * newest archive that actually landed) and `lastError`.
10791
+ */
10792
+ lastAttemptAt: number().optional(),
10793
+ /**
10794
+ * Why the last attempt failed, verbatim. Absent when the last attempt
10795
+ * landed the archive. A destination that has never been written to has
10796
+ * neither this nor `lastAttemptAt`.
10797
+ */
10798
+ lastError: string().optional()
10777
10799
  });
10778
10800
  /**
10779
10801
  * Per-archive entry returned by `backup.listArchives({ destinationId })`.
@@ -10936,8 +10958,16 @@ var BackupScheduleSchema = object({
10936
10958
  retentionCount: number().int().min(1).max(1e3),
10937
10959
  /** Optional subset of source locations to include; omitted = all. */
10938
10960
  dataSources: array(string()).readonly().optional(),
10939
- /** ms-epoch of last successful run. */
10940
- lastRunAt: number().optional(),
10961
+ /**
10962
+ * ms-epoch of the last tick that FIRED this schedule. Stamped before the
10963
+ * archive runs (it is the dedupe anchor), so it says "attempted", never
10964
+ * "succeeded" — a run refused by every destination stamps it too.
10965
+ */
10966
+ lastAttemptAt: number().optional(),
10967
+ /** ms-epoch of the last run of this schedule that landed at ≥1 destination. */
10968
+ lastSuccessAt: number().optional(),
10969
+ /** Why the last fired run failed, verbatim. Absent when it succeeded. */
10970
+ lastError: string().optional(),
10941
10971
  /** ms-epoch of next computed firing (read-only, filled on list). */
10942
10972
  nextRunAt: number().optional()
10943
10973
  });
@@ -10986,14 +11016,7 @@ method(_void(), array(BackupDestinationInfoSchema).readonly(), { auth: "admin" }
10986
11016
  locationId: string(),
10987
11017
  enabled: boolean(),
10988
11018
  retentionCount: number().int().min(1).max(1e3),
10989
- label: string().optional(),
10990
- /**
10991
- * Per-destination cron expression. Empty string clears the
10992
- * schedule (manual-only). Validated server-side via croner;
10993
- * malformed expressions reject the upsert with an actionable
10994
- * message.
10995
- */
10996
- cron: string().optional()
11019
+ label: string().optional()
10997
11020
  }), _void(), {
10998
11021
  kind: "mutation",
10999
11022
  auth: "admin"
@@ -21820,7 +21843,7 @@ method(object({
21820
21843
  downloadId: string(),
21821
21844
  offset: number(),
21822
21845
  length: number()
21823
- }), _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({
21846
+ }), _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({
21824
21847
  createdAt: true,
21825
21848
  updatedAt: true
21826
21849
  }), StorageLocationSchema, {
@@ -34688,12 +34711,6 @@ Object.freeze({
34688
34711
  addonId: null,
34689
34712
  access: "view"
34690
34713
  },
34691
- "storage.getDefaultLocation": {
34692
- capName: "storage",
34693
- capScope: "system",
34694
- addonId: null,
34695
- access: "view"
34696
- },
34697
34714
  "storage.list": {
34698
34715
  capName: "storage",
34699
34716
  capScope: "system",
@@ -7804,111 +7804,6 @@ var CameraSwitchGroupSchema = object({
7804
7804
  fetchedAt: number()
7805
7805
  });
7806
7806
  /**
7807
- * Per-component log CHANNELS — the gate a hot path consults, and the registry
7808
- * an addon declares its channels in.
7809
- *
7810
- * ## Two axes, deliberately separated
7811
- *
7812
- * - **DECLARATION** — which channels exist. Only the addon knows:
7813
- * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
7814
- * baichuan/handshake. A hand-wired central list rots at the first addition,
7815
- * and rots silently. So a channel is declared where it is consulted, and the
7816
- * `log-channels` capability enumerates the declarations.
7817
- * - **VALUE** — at which level, for which scope, until when. That stays ONE
7818
- * thing: the logging settings document on the `system` cap. Two authorities
7819
- * over the values is the exact defect
7820
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
7821
- * remove; re-introducing it from the cure side would be grotesque.
7822
- *
7823
- * Nothing in this file reads a clock, an env var or a store. The registry is
7824
- * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
7825
- * the hot path with a value somebody actually read, and by
7826
- * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
7827
- * never reaches here, so it can neither disarm an armed channel nor arm a
7828
- * disarmed one (D49).
7829
- *
7830
- * ## The canonical call shape
7831
- *
7832
- * ```ts
7833
- * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
7834
- * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
7835
- * }
7836
- * ```
7837
- *
7838
- * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
7839
- * read. Disarmed, a call site costs one load and one branch, and the `extras`
7840
- * object literal is never constructed because it lives inside the branch. It
7841
- * is the same shape already proven in production at `stream-broker.ts:1650`,
7842
- * and the same discipline `LoggingGate.allowsDestination` uses for the
7843
- * destination floor (measured at 1.93 ns/call when off).
7844
- *
7845
- * ## Why a channel emits at `info`
7846
- *
7847
- * `loki-logging.addon.ts` pins the destination default at `info` and
7848
- * `loki-destination.ts` drops everything below it, so a line emitted at
7849
- * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
7850
- * minutes. A diagnostic that cannot be read an hour later is worse than no
7851
- * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
7852
- * emits at the channel's declared level, whose schema floor is `info`.
7853
- */
7854
- /**
7855
- * The level a channel writes at once armed.
7856
- *
7857
- * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
7858
- * not leave the process for Loki, and the whole point of arming a channel is
7859
- * to read it later.
7860
- */
7861
- var LogChannelLevelSchema = _enum([
7862
- "info",
7863
- "warn",
7864
- "error"
7865
- ]);
7866
- /**
7867
- * What an addon declares about one channel. No value, no state — a
7868
- * declaration is inert.
7869
- */
7870
- var LogChannelDescriptorSchema = object({
7871
- /**
7872
- * Dotted `area.thing`, unique across the workspace. `area` is conventionally
7873
- * the addon's short name so an operator reading a channel list can tell who
7874
- * owns it without a second lookup.
7875
- */
7876
- name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
7877
- /** One sentence: what the operator will SEE after arming it. */
7878
- description: string().min(1),
7879
- /** The level its lines are emitted at. Never below `info`. */
7880
- defaultLevel: LogChannelLevelSchema,
7881
- /**
7882
- * Whether this channel can be narrowed to a camera.
7883
- *
7884
- * `true` is a PROMISE with two halves, and both must hold: the gate is
7885
- * consulted with the numeric device id, AND every line the channel admits
7886
- * carries `tags: { deviceId }` with that same numeric id. The second half is
7887
- * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
7888
- * keeps `deviceId` out of the stream labels for cardinality, so the tag in
7889
- * the body is the only way to filter.
7890
- *
7891
- * A channel whose lines carry the device only in `meta` (or not at all) is
7892
- * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
7893
- * the operator narrows to one camera, sees nothing, and concludes the code
7894
- * path was never taken.
7895
- */
7896
- perDevice: boolean()
7897
- });
7898
- /**
7899
- * An armed window over one channel, as the document hands it to a mirror.
7900
- *
7901
- * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
7902
- * expires by itself, which is the one failure a boolean cannot avoid.
7903
- */
7904
- var LogChannelWindowSchema = object({
7905
- channel: string().min(1),
7906
- /** Epoch ms the window closes at. */
7907
- armedUntilMs: number(),
7908
- /** `null` = every camera. A non-empty list narrows to those numeric ids. */
7909
- deviceIds: array(number().int()).readonly().nullable()
7910
- });
7911
- /**
7912
7807
  * Ops-log — the durable, append-only operations audit shared by the
7913
7808
  * recordings and events management surfaces.
7914
7809
  *
@@ -8854,8 +8749,11 @@ var StorageLocationTypeSchema = string().regex(/^[a-z][a-zA-Z0-9-]*$/);
8854
8749
  * `STORAGE_LOCATION_CARDINALITY` map has been removed.
8855
8750
  *
8856
8751
  * `id` is a stable namespaced string of the form `<type>:<slug>`.
8857
- * The default location for a type uses `id === <type>:default` by
8858
- * convention (the bare type ref like `'backups'` resolves to it).
8752
+ * The seed names its first instance `<type>:default` — a NAME, not a flag.
8753
+ * There is no default location any more (D383): `enabled` is the whole write
8754
+ * model, and a bare type ref resolves to the sole location of the type, or —
8755
+ * transitionally, only while legacy NULL-stamped rows exist — to the row whose
8756
+ * slug is `default`.
8859
8757
  *
8860
8758
  * `isSystem` is a legacy persisted flag. Seed still creates the initial
8861
8759
  * `<type>:default` locations; the flag is no longer a lock, a badge, or a
@@ -8876,21 +8774,20 @@ var StorageLocationSchema = object({
8876
8774
  * flag at upsert time, not here (the schema is provider-agnostic).
8877
8775
  */
8878
8776
  nodeId: string().optional(),
8879
- isDefault: boolean().default(false),
8880
8777
  isSystem: boolean().default(false),
8881
8778
  /**
8882
- * Operator opt-in: whether consumers that BALANCE across several locations
8883
- * of a type may write here. Recordings reads it today; event media and
8884
- * backups are the next consumers, which is why the flag lives on the
8885
- * location rather than in any one addon's store — nothing has to be
8886
- * extended to add the next consumer.
8779
+ * THE write switch, and the only one (D383). `enabled: true` means every
8780
+ * consumer that chooses a write target for this type may write here, and all
8781
+ * enabled locations of a type are used TOGETHER; `false` means read-only —
8782
+ * still read, still played back, still age-swept, still drained, never
8783
+ * written.
8887
8784
  *
8888
- * OPTIONAL, and ABSENT MEANS ACTIVE. Every location persisted before the
8889
- * flag existed reads back with no flag and keeps working exactly as before;
8890
- * that is the whole compat story, and it is why no migration ships with it.
8891
- * A newly CREATED sibling is stamped `false` by the orchestrator (creating a
8892
- * disk must not silently start writing to it); the default of a type is
8893
- * always stamped `true`.
8785
+ * OPTIONAL only for the wire: an upsert that omits it means "leave what is
8786
+ * stored" on an update and "born inert unless it is the first location of its
8787
+ * type" on a create. On a PERSISTED row absence is legacy and it means
8788
+ * enabled — {@link isLocationEnabled} is the one place that says so, and the
8789
+ * orchestrator stamps every flagless row `true` once at hydrate so absence
8790
+ * stops existing rather than being re-derived on every read.
8894
8791
  */
8895
8792
  enabled: boolean().optional(),
8896
8793
  /** COMPUTED at read time by the orchestrator (statfs of the backing volume
@@ -8903,10 +8800,12 @@ var StorageLocationSchema = object({
8903
8800
  createdAt: number(),
8904
8801
  updatedAt: number()
8905
8802
  });
8803
+ object({ isDefault: boolean().optional() });
8906
8804
  /**
8907
8805
  * Reference accepted by consumer-facing `api.storage.*` calls.
8908
8806
  * Either:
8909
- * - a `StorageLocationType` (e.g. `'backups'`) → orchestrator resolves to the default of that type
8807
+ * - a `StorageLocationType` (e.g. `'backups'`) → the sole location of that type
8808
+ * (transitionally, the `<type>:default`-slugged row when several exist)
8910
8809
  * - a fully-qualified id (e.g. `'backups:nas-01'`) → addresses a specific instance
8911
8810
  *
8912
8811
  * The orchestrator's `resolveRef(ref)` handles both cases.
@@ -9082,6 +8981,111 @@ var DecoderSessionConfigSchema = object({
9082
8981
  */
9083
8982
  debug: boolean().optional()
9084
8983
  });
8984
+ /**
8985
+ * Per-component log CHANNELS — the gate a hot path consults, and the registry
8986
+ * an addon declares its channels in.
8987
+ *
8988
+ * ## Two axes, deliberately separated
8989
+ *
8990
+ * - **DECLARATION** — which channels exist. Only the addon knows:
8991
+ * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
8992
+ * baichuan/handshake. A hand-wired central list rots at the first addition,
8993
+ * and rots silently. So a channel is declared where it is consulted, and the
8994
+ * `log-channels` capability enumerates the declarations.
8995
+ * - **VALUE** — at which level, for which scope, until when. That stays ONE
8996
+ * thing: the logging settings document on the `system` cap. Two authorities
8997
+ * over the values is the exact defect
8998
+ * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
8999
+ * remove; re-introducing it from the cure side would be grotesque.
9000
+ *
9001
+ * Nothing in this file reads a clock, an env var or a store. The registry is
9002
+ * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
9003
+ * the hot path with a value somebody actually read, and by
9004
+ * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
9005
+ * never reaches here, so it can neither disarm an armed channel nor arm a
9006
+ * disarmed one (D49).
9007
+ *
9008
+ * ## The canonical call shape
9009
+ *
9010
+ * ```ts
9011
+ * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
9012
+ * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
9013
+ * }
9014
+ * ```
9015
+ *
9016
+ * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
9017
+ * read. Disarmed, a call site costs one load and one branch, and the `extras`
9018
+ * object literal is never constructed because it lives inside the branch. It
9019
+ * is the same shape already proven in production at `stream-broker.ts:1650`,
9020
+ * and the same discipline `LoggingGate.allowsDestination` uses for the
9021
+ * destination floor (measured at 1.93 ns/call when off).
9022
+ *
9023
+ * ## Why a channel emits at `info`
9024
+ *
9025
+ * `loki-logging.addon.ts` pins the destination default at `info` and
9026
+ * `loki-destination.ts` drops everything below it, so a line emitted at
9027
+ * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
9028
+ * minutes. A diagnostic that cannot be read an hour later is worse than no
9029
+ * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
9030
+ * emits at the channel's declared level, whose schema floor is `info`.
9031
+ */
9032
+ /**
9033
+ * The level a channel writes at once armed.
9034
+ *
9035
+ * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
9036
+ * not leave the process for Loki, and the whole point of arming a channel is
9037
+ * to read it later.
9038
+ */
9039
+ var LogChannelLevelSchema = _enum([
9040
+ "info",
9041
+ "warn",
9042
+ "error"
9043
+ ]);
9044
+ /**
9045
+ * What an addon declares about one channel. No value, no state — a
9046
+ * declaration is inert.
9047
+ */
9048
+ var LogChannelDescriptorSchema = object({
9049
+ /**
9050
+ * Dotted `area.thing`, unique across the workspace. `area` is conventionally
9051
+ * the addon's short name so an operator reading a channel list can tell who
9052
+ * owns it without a second lookup.
9053
+ */
9054
+ name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
9055
+ /** One sentence: what the operator will SEE after arming it. */
9056
+ description: string().min(1),
9057
+ /** The level its lines are emitted at. Never below `info`. */
9058
+ defaultLevel: LogChannelLevelSchema,
9059
+ /**
9060
+ * Whether this channel can be narrowed to a camera.
9061
+ *
9062
+ * `true` is a PROMISE with two halves, and both must hold: the gate is
9063
+ * consulted with the numeric device id, AND every line the channel admits
9064
+ * carries `tags: { deviceId }` with that same numeric id. The second half is
9065
+ * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
9066
+ * keeps `deviceId` out of the stream labels for cardinality, so the tag in
9067
+ * the body is the only way to filter.
9068
+ *
9069
+ * A channel whose lines carry the device only in `meta` (or not at all) is
9070
+ * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
9071
+ * the operator narrows to one camera, sees nothing, and concludes the code
9072
+ * path was never taken.
9073
+ */
9074
+ perDevice: boolean()
9075
+ });
9076
+ /**
9077
+ * An armed window over one channel, as the document hands it to a mirror.
9078
+ *
9079
+ * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
9080
+ * expires by itself, which is the one failure a boolean cannot avoid.
9081
+ */
9082
+ var LogChannelWindowSchema = object({
9083
+ channel: string().min(1),
9084
+ /** Epoch ms the window closes at. */
9085
+ armedUntilMs: number(),
9086
+ /** `null` = every camera. A non-empty list narrows to those numeric ids. */
9087
+ deviceIds: array(number().int()).readonly().nullable()
9088
+ });
9085
9089
  var MODEL_FORMATS = [
9086
9090
  "onnx",
9087
9091
  "coreml",
@@ -10764,12 +10768,30 @@ var BackupDestinationInfoSchema = object({
10764
10768
  lastSuccessAt: number().optional(),
10765
10769
  /** Newest-archive size from `manifests.json`, or undefined. */
10766
10770
  lastSuccessSizeBytes: number().optional(),
10767
- /** Per-destination cron expression. Empty = manual-only (no schedule). */
10771
+ /**
10772
+ * Cron cadence(s) of the ENABLED schedules that fan out to this
10773
+ * destination, comma-joined. Absent when no enabled schedule targets it
10774
+ * — a destination nothing is scheduled to write to must not advertise a
10775
+ * cadence (D384). This is never the `backup_destination_policies.cron`
10776
+ * column: that per-location cron has scheduled nothing since 2026-07-28
10777
+ * and reading it made a destination with a DISABLED schedule claim a
10778
+ * nightly run.
10779
+ */
10768
10780
  cron: string().optional(),
10769
- /** ms-epoch of next computed firing for this destination's cron, if any. */
10781
+ /** ms-epoch of the next firing across those schedules (earliest), if any. */
10770
10782
  nextRunAt: number().optional(),
10771
- /** ms-epoch of last successful scheduled run (mirrors policy.lastRunAt). */
10772
- lastRunAt: number().optional()
10783
+ /**
10784
+ * ms-epoch of the last time a run ATTEMPTED to write here — success or
10785
+ * failure. Never a success stamp: pair it with `lastSuccessAt` (the
10786
+ * newest archive that actually landed) and `lastError`.
10787
+ */
10788
+ lastAttemptAt: number().optional(),
10789
+ /**
10790
+ * Why the last attempt failed, verbatim. Absent when the last attempt
10791
+ * landed the archive. A destination that has never been written to has
10792
+ * neither this nor `lastAttemptAt`.
10793
+ */
10794
+ lastError: string().optional()
10773
10795
  });
10774
10796
  /**
10775
10797
  * Per-archive entry returned by `backup.listArchives({ destinationId })`.
@@ -10932,8 +10954,16 @@ var BackupScheduleSchema = object({
10932
10954
  retentionCount: number().int().min(1).max(1e3),
10933
10955
  /** Optional subset of source locations to include; omitted = all. */
10934
10956
  dataSources: array(string()).readonly().optional(),
10935
- /** ms-epoch of last successful run. */
10936
- lastRunAt: number().optional(),
10957
+ /**
10958
+ * ms-epoch of the last tick that FIRED this schedule. Stamped before the
10959
+ * archive runs (it is the dedupe anchor), so it says "attempted", never
10960
+ * "succeeded" — a run refused by every destination stamps it too.
10961
+ */
10962
+ lastAttemptAt: number().optional(),
10963
+ /** ms-epoch of the last run of this schedule that landed at ≥1 destination. */
10964
+ lastSuccessAt: number().optional(),
10965
+ /** Why the last fired run failed, verbatim. Absent when it succeeded. */
10966
+ lastError: string().optional(),
10937
10967
  /** ms-epoch of next computed firing (read-only, filled on list). */
10938
10968
  nextRunAt: number().optional()
10939
10969
  });
@@ -10982,14 +11012,7 @@ method(_void(), array(BackupDestinationInfoSchema).readonly(), { auth: "admin" }
10982
11012
  locationId: string(),
10983
11013
  enabled: boolean(),
10984
11014
  retentionCount: number().int().min(1).max(1e3),
10985
- label: string().optional(),
10986
- /**
10987
- * Per-destination cron expression. Empty string clears the
10988
- * schedule (manual-only). Validated server-side via croner;
10989
- * malformed expressions reject the upsert with an actionable
10990
- * message.
10991
- */
10992
- cron: string().optional()
11015
+ label: string().optional()
10993
11016
  }), _void(), {
10994
11017
  kind: "mutation",
10995
11018
  auth: "admin"
@@ -21816,7 +21839,7 @@ method(object({
21816
21839
  downloadId: string(),
21817
21840
  offset: number(),
21818
21841
  length: number()
21819
- }), _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({
21842
+ }), _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({
21820
21843
  createdAt: true,
21821
21844
  updatedAt: true
21822
21845
  }), StorageLocationSchema, {
@@ -34684,12 +34707,6 @@ Object.freeze({
34684
34707
  addonId: null,
34685
34708
  access: "view"
34686
34709
  },
34687
- "storage.getDefaultLocation": {
34688
- capName: "storage",
34689
- capScope: "system",
34690
- addonId: null,
34691
- access: "view"
34692
- },
34693
34710
  "storage.list": {
34694
34711
  capName: "storage",
34695
34712
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-export-google",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
4
4
  "description": "Google Home export — hub-side smart-home fulfillment (SYNC / QUERY / EXECUTE / DISCONNECT) for the non-camera fleet, served over the hub's own OAuth account link. No Google credential is stored, sent or required.",
5
5
  "keywords": [
6
6
  "camstack",