@camstack/addon-ai 0.4.79 → 0.4.81

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
@@ -7818,111 +7818,6 @@ var CameraSwitchGroupSchema = object({
7818
7818
  fetchedAt: number$1()
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$1(),
7922
- /** `null` = every camera. A non-empty list narrows to those numeric ids. */
7923
- deviceIds: array(number$1().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$1(),
8918
8815
  updatedAt: number$1()
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$1(),
9100
+ /** `null` = every camera. A non-empty list narrows to those numeric ids. */
9101
+ deviceIds: array(number$1().int()).readonly().nullable()
9102
+ });
9099
9103
  var MODEL_FORMATS = [
9100
9104
  "onnx",
9101
9105
  "coreml",
@@ -10752,12 +10756,30 @@ var BackupDestinationInfoSchema = object({
10752
10756
  lastSuccessAt: number$1().optional(),
10753
10757
  /** Newest-archive size from `manifests.json`, or undefined. */
10754
10758
  lastSuccessSizeBytes: number$1().optional(),
10755
- /** Per-destination cron expression. Empty = manual-only (no schedule). */
10759
+ /**
10760
+ * Cron cadence(s) of the ENABLED schedules that fan out to this
10761
+ * destination, comma-joined. Absent when no enabled schedule targets it
10762
+ * — a destination nothing is scheduled to write to must not advertise a
10763
+ * cadence (D384). This is never the `backup_destination_policies.cron`
10764
+ * column: that per-location cron has scheduled nothing since 2026-07-28
10765
+ * and reading it made a destination with a DISABLED schedule claim a
10766
+ * nightly run.
10767
+ */
10756
10768
  cron: string().optional(),
10757
- /** ms-epoch of next computed firing for this destination's cron, if any. */
10769
+ /** ms-epoch of the next firing across those schedules (earliest), if any. */
10758
10770
  nextRunAt: number$1().optional(),
10759
- /** ms-epoch of last successful scheduled run (mirrors policy.lastRunAt). */
10760
- lastRunAt: number$1().optional()
10771
+ /**
10772
+ * ms-epoch of the last time a run ATTEMPTED to write here — success or
10773
+ * failure. Never a success stamp: pair it with `lastSuccessAt` (the
10774
+ * newest archive that actually landed) and `lastError`.
10775
+ */
10776
+ lastAttemptAt: number$1().optional(),
10777
+ /**
10778
+ * Why the last attempt failed, verbatim. Absent when the last attempt
10779
+ * landed the archive. A destination that has never been written to has
10780
+ * neither this nor `lastAttemptAt`.
10781
+ */
10782
+ lastError: string().optional()
10761
10783
  });
10762
10784
  /**
10763
10785
  * Per-archive entry returned by `backup.listArchives({ destinationId })`.
@@ -10920,8 +10942,16 @@ var BackupScheduleSchema = object({
10920
10942
  retentionCount: number$1().int().min(1).max(1e3),
10921
10943
  /** Optional subset of source locations to include; omitted = all. */
10922
10944
  dataSources: array(string()).readonly().optional(),
10923
- /** ms-epoch of last successful run. */
10924
- lastRunAt: number$1().optional(),
10945
+ /**
10946
+ * ms-epoch of the last tick that FIRED this schedule. Stamped before the
10947
+ * archive runs (it is the dedupe anchor), so it says "attempted", never
10948
+ * "succeeded" — a run refused by every destination stamps it too.
10949
+ */
10950
+ lastAttemptAt: number$1().optional(),
10951
+ /** ms-epoch of the last run of this schedule that landed at ≥1 destination. */
10952
+ lastSuccessAt: number$1().optional(),
10953
+ /** Why the last fired run failed, verbatim. Absent when it succeeded. */
10954
+ lastError: string().optional(),
10925
10955
  /** ms-epoch of next computed firing (read-only, filled on list). */
10926
10956
  nextRunAt: number$1().optional()
10927
10957
  });
@@ -10970,14 +11000,7 @@ method(_void(), array(BackupDestinationInfoSchema).readonly(), { auth: "admin" }
10970
11000
  locationId: string(),
10971
11001
  enabled: boolean(),
10972
11002
  retentionCount: number$1().int().min(1).max(1e3),
10973
- label: string().optional(),
10974
- /**
10975
- * Per-destination cron expression. Empty string clears the
10976
- * schedule (manual-only). Validated server-side via croner;
10977
- * malformed expressions reject the upsert with an actionable
10978
- * message.
10979
- */
10980
- cron: string().optional()
11003
+ label: string().optional()
10981
11004
  }), _void(), {
10982
11005
  kind: "mutation",
10983
11006
  auth: "admin"
@@ -21835,7 +21858,7 @@ method(object({
21835
21858
  downloadId: string(),
21836
21859
  offset: number$1(),
21837
21860
  length: number$1()
21838
- }), _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({
21861
+ }), _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({
21839
21862
  createdAt: true,
21840
21863
  updatedAt: true
21841
21864
  }), 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/dist/addon.mjs CHANGED
@@ -7845,111 +7845,6 @@ var CameraSwitchGroupSchema = object({
7845
7845
  fetchedAt: number$1()
7846
7846
  });
7847
7847
  /**
7848
- * Per-component log CHANNELS — the gate a hot path consults, and the registry
7849
- * an addon declares its channels in.
7850
- *
7851
- * ## Two axes, deliberately separated
7852
- *
7853
- * - **DECLARATION** — which channels exist. Only the addon knows:
7854
- * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
7855
- * baichuan/handshake. A hand-wired central list rots at the first addition,
7856
- * and rots silently. So a channel is declared where it is consulted, and the
7857
- * `log-channels` capability enumerates the declarations.
7858
- * - **VALUE** — at which level, for which scope, until when. That stays ONE
7859
- * thing: the logging settings document on the `system` cap. Two authorities
7860
- * over the values is the exact defect
7861
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
7862
- * remove; re-introducing it from the cure side would be grotesque.
7863
- *
7864
- * Nothing in this file reads a clock, an env var or a store. The registry is
7865
- * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
7866
- * the hot path with a value somebody actually read, and by
7867
- * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
7868
- * never reaches here, so it can neither disarm an armed channel nor arm a
7869
- * disarmed one (D49).
7870
- *
7871
- * ## The canonical call shape
7872
- *
7873
- * ```ts
7874
- * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
7875
- * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
7876
- * }
7877
- * ```
7878
- *
7879
- * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
7880
- * read. Disarmed, a call site costs one load and one branch, and the `extras`
7881
- * object literal is never constructed because it lives inside the branch. It
7882
- * is the same shape already proven in production at `stream-broker.ts:1650`,
7883
- * and the same discipline `LoggingGate.allowsDestination` uses for the
7884
- * destination floor (measured at 1.93 ns/call when off).
7885
- *
7886
- * ## Why a channel emits at `info`
7887
- *
7888
- * `loki-logging.addon.ts` pins the destination default at `info` and
7889
- * `loki-destination.ts` drops everything below it, so a line emitted at
7890
- * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
7891
- * minutes. A diagnostic that cannot be read an hour later is worse than no
7892
- * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
7893
- * emits at the channel's declared level, whose schema floor is `info`.
7894
- */
7895
- /**
7896
- * The level a channel writes at once armed.
7897
- *
7898
- * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
7899
- * not leave the process for Loki, and the whole point of arming a channel is
7900
- * to read it later.
7901
- */
7902
- var LogChannelLevelSchema = _enum([
7903
- "info",
7904
- "warn",
7905
- "error"
7906
- ]);
7907
- /**
7908
- * What an addon declares about one channel. No value, no state — a
7909
- * declaration is inert.
7910
- */
7911
- var LogChannelDescriptorSchema = object({
7912
- /**
7913
- * Dotted `area.thing`, unique across the workspace. `area` is conventionally
7914
- * the addon's short name so an operator reading a channel list can tell who
7915
- * owns it without a second lookup.
7916
- */
7917
- name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
7918
- /** One sentence: what the operator will SEE after arming it. */
7919
- description: string().min(1),
7920
- /** The level its lines are emitted at. Never below `info`. */
7921
- defaultLevel: LogChannelLevelSchema,
7922
- /**
7923
- * Whether this channel can be narrowed to a camera.
7924
- *
7925
- * `true` is a PROMISE with two halves, and both must hold: the gate is
7926
- * consulted with the numeric device id, AND every line the channel admits
7927
- * carries `tags: { deviceId }` with that same numeric id. The second half is
7928
- * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
7929
- * keeps `deviceId` out of the stream labels for cardinality, so the tag in
7930
- * the body is the only way to filter.
7931
- *
7932
- * A channel whose lines carry the device only in `meta` (or not at all) is
7933
- * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
7934
- * the operator narrows to one camera, sees nothing, and concludes the code
7935
- * path was never taken.
7936
- */
7937
- perDevice: boolean()
7938
- });
7939
- /**
7940
- * An armed window over one channel, as the document hands it to a mirror.
7941
- *
7942
- * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
7943
- * expires by itself, which is the one failure a boolean cannot avoid.
7944
- */
7945
- var LogChannelWindowSchema = object({
7946
- channel: string().min(1),
7947
- /** Epoch ms the window closes at. */
7948
- armedUntilMs: number$1(),
7949
- /** `null` = every camera. A non-empty list narrows to those numeric ids. */
7950
- deviceIds: array(number$1().int()).readonly().nullable()
7951
- });
7952
- /**
7953
7848
  * Ops-log — the durable, append-only operations audit shared by the
7954
7849
  * recordings and events management surfaces.
7955
7850
  *
@@ -8895,8 +8790,11 @@ var StorageLocationTypeSchema = string().regex(/^[a-z][a-zA-Z0-9-]*$/);
8895
8790
  * `STORAGE_LOCATION_CARDINALITY` map has been removed.
8896
8791
  *
8897
8792
  * `id` is a stable namespaced string of the form `<type>:<slug>`.
8898
- * The default location for a type uses `id === <type>:default` by
8899
- * convention (the bare type ref like `'backups'` resolves to it).
8793
+ * The seed names its first instance `<type>:default` — a NAME, not a flag.
8794
+ * There is no default location any more (D383): `enabled` is the whole write
8795
+ * model, and a bare type ref resolves to the sole location of the type, or —
8796
+ * transitionally, only while legacy NULL-stamped rows exist — to the row whose
8797
+ * slug is `default`.
8900
8798
  *
8901
8799
  * `isSystem` is a legacy persisted flag. Seed still creates the initial
8902
8800
  * `<type>:default` locations; the flag is no longer a lock, a badge, or a
@@ -8917,21 +8815,20 @@ var StorageLocationSchema = object({
8917
8815
  * flag at upsert time, not here (the schema is provider-agnostic).
8918
8816
  */
8919
8817
  nodeId: string().optional(),
8920
- isDefault: boolean().default(false),
8921
8818
  isSystem: boolean().default(false),
8922
8819
  /**
8923
- * Operator opt-in: whether consumers that BALANCE across several locations
8924
- * of a type may write here. Recordings reads it today; event media and
8925
- * backups are the next consumers, which is why the flag lives on the
8926
- * location rather than in any one addon's store — nothing has to be
8927
- * extended to add the next consumer.
8820
+ * THE write switch, and the only one (D383). `enabled: true` means every
8821
+ * consumer that chooses a write target for this type may write here, and all
8822
+ * enabled locations of a type are used TOGETHER; `false` means read-only —
8823
+ * still read, still played back, still age-swept, still drained, never
8824
+ * written.
8928
8825
  *
8929
- * OPTIONAL, and ABSENT MEANS ACTIVE. Every location persisted before the
8930
- * flag existed reads back with no flag and keeps working exactly as before;
8931
- * that is the whole compat story, and it is why no migration ships with it.
8932
- * A newly CREATED sibling is stamped `false` by the orchestrator (creating a
8933
- * disk must not silently start writing to it); the default of a type is
8934
- * always stamped `true`.
8826
+ * OPTIONAL only for the wire: an upsert that omits it means "leave what is
8827
+ * stored" on an update and "born inert unless it is the first location of its
8828
+ * type" on a create. On a PERSISTED row absence is legacy and it means
8829
+ * enabled — {@link isLocationEnabled} is the one place that says so, and the
8830
+ * orchestrator stamps every flagless row `true` once at hydrate so absence
8831
+ * stops existing rather than being re-derived on every read.
8935
8832
  */
8936
8833
  enabled: boolean().optional(),
8937
8834
  /** COMPUTED at read time by the orchestrator (statfs of the backing volume
@@ -8944,10 +8841,12 @@ var StorageLocationSchema = object({
8944
8841
  createdAt: number$1(),
8945
8842
  updatedAt: number$1()
8946
8843
  });
8844
+ object({ isDefault: boolean().optional() });
8947
8845
  /**
8948
8846
  * Reference accepted by consumer-facing `api.storage.*` calls.
8949
8847
  * Either:
8950
- * - a `StorageLocationType` (e.g. `'backups'`) → orchestrator resolves to the default of that type
8848
+ * - a `StorageLocationType` (e.g. `'backups'`) → the sole location of that type
8849
+ * (transitionally, the `<type>:default`-slugged row when several exist)
8951
8850
  * - a fully-qualified id (e.g. `'backups:nas-01'`) → addresses a specific instance
8952
8851
  *
8953
8852
  * The orchestrator's `resolveRef(ref)` handles both cases.
@@ -9123,6 +9022,111 @@ var DecoderSessionConfigSchema = object({
9123
9022
  */
9124
9023
  debug: boolean().optional()
9125
9024
  });
9025
+ /**
9026
+ * Per-component log CHANNELS — the gate a hot path consults, and the registry
9027
+ * an addon declares its channels in.
9028
+ *
9029
+ * ## Two axes, deliberately separated
9030
+ *
9031
+ * - **DECLARATION** — which channels exist. Only the addon knows:
9032
+ * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
9033
+ * baichuan/handshake. A hand-wired central list rots at the first addition,
9034
+ * and rots silently. So a channel is declared where it is consulted, and the
9035
+ * `log-channels` capability enumerates the declarations.
9036
+ * - **VALUE** — at which level, for which scope, until when. That stays ONE
9037
+ * thing: the logging settings document on the `system` cap. Two authorities
9038
+ * over the values is the exact defect
9039
+ * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
9040
+ * remove; re-introducing it from the cure side would be grotesque.
9041
+ *
9042
+ * Nothing in this file reads a clock, an env var or a store. The registry is
9043
+ * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
9044
+ * the hot path with a value somebody actually read, and by
9045
+ * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
9046
+ * never reaches here, so it can neither disarm an armed channel nor arm a
9047
+ * disarmed one (D49).
9048
+ *
9049
+ * ## The canonical call shape
9050
+ *
9051
+ * ```ts
9052
+ * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
9053
+ * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
9054
+ * }
9055
+ * ```
9056
+ *
9057
+ * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
9058
+ * read. Disarmed, a call site costs one load and one branch, and the `extras`
9059
+ * object literal is never constructed because it lives inside the branch. It
9060
+ * is the same shape already proven in production at `stream-broker.ts:1650`,
9061
+ * and the same discipline `LoggingGate.allowsDestination` uses for the
9062
+ * destination floor (measured at 1.93 ns/call when off).
9063
+ *
9064
+ * ## Why a channel emits at `info`
9065
+ *
9066
+ * `loki-logging.addon.ts` pins the destination default at `info` and
9067
+ * `loki-destination.ts` drops everything below it, so a line emitted at
9068
+ * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
9069
+ * minutes. A diagnostic that cannot be read an hour later is worse than no
9070
+ * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
9071
+ * emits at the channel's declared level, whose schema floor is `info`.
9072
+ */
9073
+ /**
9074
+ * The level a channel writes at once armed.
9075
+ *
9076
+ * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
9077
+ * not leave the process for Loki, and the whole point of arming a channel is
9078
+ * to read it later.
9079
+ */
9080
+ var LogChannelLevelSchema = _enum([
9081
+ "info",
9082
+ "warn",
9083
+ "error"
9084
+ ]);
9085
+ /**
9086
+ * What an addon declares about one channel. No value, no state — a
9087
+ * declaration is inert.
9088
+ */
9089
+ var LogChannelDescriptorSchema = object({
9090
+ /**
9091
+ * Dotted `area.thing`, unique across the workspace. `area` is conventionally
9092
+ * the addon's short name so an operator reading a channel list can tell who
9093
+ * owns it without a second lookup.
9094
+ */
9095
+ name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
9096
+ /** One sentence: what the operator will SEE after arming it. */
9097
+ description: string().min(1),
9098
+ /** The level its lines are emitted at. Never below `info`. */
9099
+ defaultLevel: LogChannelLevelSchema,
9100
+ /**
9101
+ * Whether this channel can be narrowed to a camera.
9102
+ *
9103
+ * `true` is a PROMISE with two halves, and both must hold: the gate is
9104
+ * consulted with the numeric device id, AND every line the channel admits
9105
+ * carries `tags: { deviceId }` with that same numeric id. The second half is
9106
+ * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
9107
+ * keeps `deviceId` out of the stream labels for cardinality, so the tag in
9108
+ * the body is the only way to filter.
9109
+ *
9110
+ * A channel whose lines carry the device only in `meta` (or not at all) is
9111
+ * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
9112
+ * the operator narrows to one camera, sees nothing, and concludes the code
9113
+ * path was never taken.
9114
+ */
9115
+ perDevice: boolean()
9116
+ });
9117
+ /**
9118
+ * An armed window over one channel, as the document hands it to a mirror.
9119
+ *
9120
+ * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
9121
+ * expires by itself, which is the one failure a boolean cannot avoid.
9122
+ */
9123
+ var LogChannelWindowSchema = object({
9124
+ channel: string().min(1),
9125
+ /** Epoch ms the window closes at. */
9126
+ armedUntilMs: number$1(),
9127
+ /** `null` = every camera. A non-empty list narrows to those numeric ids. */
9128
+ deviceIds: array(number$1().int()).readonly().nullable()
9129
+ });
9126
9130
  var MODEL_FORMATS = [
9127
9131
  "onnx",
9128
9132
  "coreml",
@@ -10779,12 +10783,30 @@ var BackupDestinationInfoSchema = object({
10779
10783
  lastSuccessAt: number$1().optional(),
10780
10784
  /** Newest-archive size from `manifests.json`, or undefined. */
10781
10785
  lastSuccessSizeBytes: number$1().optional(),
10782
- /** Per-destination cron expression. Empty = manual-only (no schedule). */
10786
+ /**
10787
+ * Cron cadence(s) of the ENABLED schedules that fan out to this
10788
+ * destination, comma-joined. Absent when no enabled schedule targets it
10789
+ * — a destination nothing is scheduled to write to must not advertise a
10790
+ * cadence (D384). This is never the `backup_destination_policies.cron`
10791
+ * column: that per-location cron has scheduled nothing since 2026-07-28
10792
+ * and reading it made a destination with a DISABLED schedule claim a
10793
+ * nightly run.
10794
+ */
10783
10795
  cron: string().optional(),
10784
- /** ms-epoch of next computed firing for this destination's cron, if any. */
10796
+ /** ms-epoch of the next firing across those schedules (earliest), if any. */
10785
10797
  nextRunAt: number$1().optional(),
10786
- /** ms-epoch of last successful scheduled run (mirrors policy.lastRunAt). */
10787
- lastRunAt: number$1().optional()
10798
+ /**
10799
+ * ms-epoch of the last time a run ATTEMPTED to write here — success or
10800
+ * failure. Never a success stamp: pair it with `lastSuccessAt` (the
10801
+ * newest archive that actually landed) and `lastError`.
10802
+ */
10803
+ lastAttemptAt: number$1().optional(),
10804
+ /**
10805
+ * Why the last attempt failed, verbatim. Absent when the last attempt
10806
+ * landed the archive. A destination that has never been written to has
10807
+ * neither this nor `lastAttemptAt`.
10808
+ */
10809
+ lastError: string().optional()
10788
10810
  });
10789
10811
  /**
10790
10812
  * Per-archive entry returned by `backup.listArchives({ destinationId })`.
@@ -10947,8 +10969,16 @@ var BackupScheduleSchema = object({
10947
10969
  retentionCount: number$1().int().min(1).max(1e3),
10948
10970
  /** Optional subset of source locations to include; omitted = all. */
10949
10971
  dataSources: array(string()).readonly().optional(),
10950
- /** ms-epoch of last successful run. */
10951
- lastRunAt: number$1().optional(),
10972
+ /**
10973
+ * ms-epoch of the last tick that FIRED this schedule. Stamped before the
10974
+ * archive runs (it is the dedupe anchor), so it says "attempted", never
10975
+ * "succeeded" — a run refused by every destination stamps it too.
10976
+ */
10977
+ lastAttemptAt: number$1().optional(),
10978
+ /** ms-epoch of the last run of this schedule that landed at ≥1 destination. */
10979
+ lastSuccessAt: number$1().optional(),
10980
+ /** Why the last fired run failed, verbatim. Absent when it succeeded. */
10981
+ lastError: string().optional(),
10952
10982
  /** ms-epoch of next computed firing (read-only, filled on list). */
10953
10983
  nextRunAt: number$1().optional()
10954
10984
  });
@@ -10997,14 +11027,7 @@ method(_void(), array(BackupDestinationInfoSchema).readonly(), { auth: "admin" }
10997
11027
  locationId: string(),
10998
11028
  enabled: boolean(),
10999
11029
  retentionCount: number$1().int().min(1).max(1e3),
11000
- label: string().optional(),
11001
- /**
11002
- * Per-destination cron expression. Empty string clears the
11003
- * schedule (manual-only). Validated server-side via croner;
11004
- * malformed expressions reject the upsert with an actionable
11005
- * message.
11006
- */
11007
- cron: string().optional()
11030
+ label: string().optional()
11008
11031
  }), _void(), {
11009
11032
  kind: "mutation",
11010
11033
  auth: "admin"
@@ -21862,7 +21885,7 @@ method(object({
21862
21885
  downloadId: string(),
21863
21886
  offset: number$1(),
21864
21887
  length: number$1()
21865
- }), _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({
21888
+ }), _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({
21866
21889
  createdAt: true,
21867
21890
  updatedAt: true
21868
21891
  }), StorageLocationSchema, {
@@ -34711,12 +34734,6 @@ Object.freeze({
34711
34734
  addonId: null,
34712
34735
  access: "view"
34713
34736
  },
34714
- "storage.getDefaultLocation": {
34715
- capName: "storage",
34716
- capScope: "system",
34717
- addonId: null,
34718
- access: "view"
34719
- },
34720
34737
  "storage.list": {
34721
34738
  capName: "storage",
34722
34739
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-ai",
3
- "version": "0.4.79",
3
+ "version": "0.4.81",
4
4
  "description": "AI addon for CamStack — the `llm` collection provider (cloud, LAN, and camstack-managed local llama.cpp profiles) plus the per-node `llm-runtime` managed executor.",
5
5
  "keywords": [
6
6
  "camstack",