@camstack/addon-remote-storage 1.2.76 → 1.2.78

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/s3.addon.js CHANGED
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_shared = require("./shared-DlUg7bET.js");
5
+ const require_shared = require("./shared-D7aelvq0.js");
6
6
  let node_stream = require("node:stream");
7
7
  let _aws_sdk_client_s3 = require("@aws-sdk/client-s3");
8
8
  let _aws_sdk_lib_storage = require("@aws-sdk/lib-storage");
package/dist/s3.addon.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { c as BaseAddon, i as rearmIdleAbort, n as getOptionalBasePath, o as scheduleIdleAbort, s as storageProviderCapability, t as createSessionId } from "./shared-Ch1dJcLf.mjs";
1
+ import { c as BaseAddon, i as rearmIdleAbort, n as getOptionalBasePath, o as scheduleIdleAbort, s as storageProviderCapability, t as createSessionId } from "./shared-C5_1Zy4l.mjs";
2
2
  import { PassThrough } from "node:stream";
3
3
  import { DeleteObjectCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectsV2Command, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
4
4
  import { Upload } from "@aws-sdk/lib-storage";
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_shared = require("./shared-DlUg7bET.js");
5
+ const require_shared = require("./shared-D7aelvq0.js");
6
6
  let ssh2 = require("ssh2");
7
7
  let node_path = require("node:path");
8
8
  node_path = require_shared.__toESM(node_path);
@@ -1,4 +1,4 @@
1
- import { a as safeJoinRemotePath, c as BaseAddon, i as rearmIdleAbort, o as scheduleIdleAbort, r as getRequiredBasePath, s as storageProviderCapability, t as createSessionId } from "./shared-Ch1dJcLf.mjs";
1
+ import { a as safeJoinRemotePath, c as BaseAddon, i as rearmIdleAbort, o as scheduleIdleAbort, r as getRequiredBasePath, s as storageProviderCapability, t as createSessionId } from "./shared-C5_1Zy4l.mjs";
2
2
  import { Client } from "ssh2";
3
3
  import * as path from "node:path";
4
4
  //#region src/providers/sftp/sftp-config-schema.ts
@@ -7600,111 +7600,6 @@ var CameraSwitchGroupSchema = object({
7600
7600
  fetchedAt: number()
7601
7601
  });
7602
7602
  /**
7603
- * Per-component log CHANNELS — the gate a hot path consults, and the registry
7604
- * an addon declares its channels in.
7605
- *
7606
- * ## Two axes, deliberately separated
7607
- *
7608
- * - **DECLARATION** — which channels exist. Only the addon knows:
7609
- * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
7610
- * baichuan/handshake. A hand-wired central list rots at the first addition,
7611
- * and rots silently. So a channel is declared where it is consulted, and the
7612
- * `log-channels` capability enumerates the declarations.
7613
- * - **VALUE** — at which level, for which scope, until when. That stays ONE
7614
- * thing: the logging settings document on the `system` cap. Two authorities
7615
- * over the values is the exact defect
7616
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
7617
- * remove; re-introducing it from the cure side would be grotesque.
7618
- *
7619
- * Nothing in this file reads a clock, an env var or a store. The registry is
7620
- * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
7621
- * the hot path with a value somebody actually read, and by
7622
- * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
7623
- * never reaches here, so it can neither disarm an armed channel nor arm a
7624
- * disarmed one (D49).
7625
- *
7626
- * ## The canonical call shape
7627
- *
7628
- * ```ts
7629
- * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
7630
- * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
7631
- * }
7632
- * ```
7633
- *
7634
- * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
7635
- * read. Disarmed, a call site costs one load and one branch, and the `extras`
7636
- * object literal is never constructed because it lives inside the branch. It
7637
- * is the same shape already proven in production at `stream-broker.ts:1650`,
7638
- * and the same discipline `LoggingGate.allowsDestination` uses for the
7639
- * destination floor (measured at 1.93 ns/call when off).
7640
- *
7641
- * ## Why a channel emits at `info`
7642
- *
7643
- * `loki-logging.addon.ts` pins the destination default at `info` and
7644
- * `loki-destination.ts` drops everything below it, so a line emitted at
7645
- * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
7646
- * minutes. A diagnostic that cannot be read an hour later is worse than no
7647
- * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
7648
- * emits at the channel's declared level, whose schema floor is `info`.
7649
- */
7650
- /**
7651
- * The level a channel writes at once armed.
7652
- *
7653
- * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
7654
- * not leave the process for Loki, and the whole point of arming a channel is
7655
- * to read it later.
7656
- */
7657
- var LogChannelLevelSchema = _enum([
7658
- "info",
7659
- "warn",
7660
- "error"
7661
- ]);
7662
- /**
7663
- * What an addon declares about one channel. No value, no state — a
7664
- * declaration is inert.
7665
- */
7666
- var LogChannelDescriptorSchema = object({
7667
- /**
7668
- * Dotted `area.thing`, unique across the workspace. `area` is conventionally
7669
- * the addon's short name so an operator reading a channel list can tell who
7670
- * owns it without a second lookup.
7671
- */
7672
- name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
7673
- /** One sentence: what the operator will SEE after arming it. */
7674
- description: string().min(1),
7675
- /** The level its lines are emitted at. Never below `info`. */
7676
- defaultLevel: LogChannelLevelSchema,
7677
- /**
7678
- * Whether this channel can be narrowed to a camera.
7679
- *
7680
- * `true` is a PROMISE with two halves, and both must hold: the gate is
7681
- * consulted with the numeric device id, AND every line the channel admits
7682
- * carries `tags: { deviceId }` with that same numeric id. The second half is
7683
- * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
7684
- * keeps `deviceId` out of the stream labels for cardinality, so the tag in
7685
- * the body is the only way to filter.
7686
- *
7687
- * A channel whose lines carry the device only in `meta` (or not at all) is
7688
- * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
7689
- * the operator narrows to one camera, sees nothing, and concludes the code
7690
- * path was never taken.
7691
- */
7692
- perDevice: boolean()
7693
- });
7694
- /**
7695
- * An armed window over one channel, as the document hands it to a mirror.
7696
- *
7697
- * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
7698
- * expires by itself, which is the one failure a boolean cannot avoid.
7699
- */
7700
- var LogChannelWindowSchema = object({
7701
- channel: string().min(1),
7702
- /** Epoch ms the window closes at. */
7703
- armedUntilMs: number(),
7704
- /** `null` = every camera. A non-empty list narrows to those numeric ids. */
7705
- deviceIds: array(number().int()).readonly().nullable()
7706
- });
7707
- /**
7708
7603
  * Ops-log — the durable, append-only operations audit shared by the
7709
7604
  * recordings and events management surfaces.
7710
7605
  *
@@ -8650,8 +8545,11 @@ var StorageLocationTypeSchema = string().regex(/^[a-z][a-zA-Z0-9-]*$/);
8650
8545
  * `STORAGE_LOCATION_CARDINALITY` map has been removed.
8651
8546
  *
8652
8547
  * `id` is a stable namespaced string of the form `<type>:<slug>`.
8653
- * The default location for a type uses `id === <type>:default` by
8654
- * convention (the bare type ref like `'backups'` resolves to it).
8548
+ * The seed names its first instance `<type>:default` — a NAME, not a flag.
8549
+ * There is no default location any more (D383): `enabled` is the whole write
8550
+ * model, and a bare type ref resolves to the sole location of the type, or —
8551
+ * transitionally, only while legacy NULL-stamped rows exist — to the row whose
8552
+ * slug is `default`.
8655
8553
  *
8656
8554
  * `isSystem` is a legacy persisted flag. Seed still creates the initial
8657
8555
  * `<type>:default` locations; the flag is no longer a lock, a badge, or a
@@ -8672,21 +8570,20 @@ var StorageLocationSchema = object({
8672
8570
  * flag at upsert time, not here (the schema is provider-agnostic).
8673
8571
  */
8674
8572
  nodeId: string().optional(),
8675
- isDefault: boolean().default(false),
8676
8573
  isSystem: boolean().default(false),
8677
8574
  /**
8678
- * Operator opt-in: whether consumers that BALANCE across several locations
8679
- * of a type may write here. Recordings reads it today; event media and
8680
- * backups are the next consumers, which is why the flag lives on the
8681
- * location rather than in any one addon's store nothing has to be
8682
- * extended to add the next consumer.
8575
+ * THE write switch, and the only one (D383). `enabled: true` means every
8576
+ * consumer that chooses a write target for this type may write here, and all
8577
+ * enabled locations of a type are used TOGETHER; `false` means read-only
8578
+ * still read, still played back, still age-swept, still drained, never
8579
+ * written.
8683
8580
  *
8684
- * OPTIONAL, and ABSENT MEANS ACTIVE. Every location persisted before the
8685
- * flag existed reads back with no flag and keeps working exactly as before;
8686
- * that is the whole compat story, and it is why no migration ships with it.
8687
- * A newly CREATED sibling is stamped `false` by the orchestrator (creating a
8688
- * disk must not silently start writing to it); the default of a type is
8689
- * always stamped `true`.
8581
+ * OPTIONAL only for the wire: an upsert that omits it means "leave what is
8582
+ * stored" on an update and "born inert unless it is the first location of its
8583
+ * type" on a create. On a PERSISTED row absence is legacy and it means
8584
+ * enabled {@link isLocationEnabled} is the one place that says so, and the
8585
+ * orchestrator stamps every flagless row `true` once at hydrate so absence
8586
+ * stops existing rather than being re-derived on every read.
8690
8587
  */
8691
8588
  enabled: boolean().optional(),
8692
8589
  /** COMPUTED at read time by the orchestrator (statfs of the backing volume
@@ -8699,10 +8596,12 @@ var StorageLocationSchema = object({
8699
8596
  createdAt: number(),
8700
8597
  updatedAt: number()
8701
8598
  });
8599
+ object({ isDefault: boolean().optional() });
8702
8600
  /**
8703
8601
  * Reference accepted by consumer-facing `api.storage.*` calls.
8704
8602
  * Either:
8705
- * - a `StorageLocationType` (e.g. `'backups'`) → orchestrator resolves to the default of that type
8603
+ * - a `StorageLocationType` (e.g. `'backups'`) → the sole location of that type
8604
+ * (transitionally, the `<type>:default`-slugged row when several exist)
8706
8605
  * - a fully-qualified id (e.g. `'backups:nas-01'`) → addresses a specific instance
8707
8606
  *
8708
8607
  * The orchestrator's `resolveRef(ref)` handles both cases.
@@ -8878,6 +8777,111 @@ var DecoderSessionConfigSchema = object({
8878
8777
  */
8879
8778
  debug: boolean().optional()
8880
8779
  });
8780
+ /**
8781
+ * Per-component log CHANNELS — the gate a hot path consults, and the registry
8782
+ * an addon declares its channels in.
8783
+ *
8784
+ * ## Two axes, deliberately separated
8785
+ *
8786
+ * - **DECLARATION** — which channels exist. Only the addon knows:
8787
+ * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
8788
+ * baichuan/handshake. A hand-wired central list rots at the first addition,
8789
+ * and rots silently. So a channel is declared where it is consulted, and the
8790
+ * `log-channels` capability enumerates the declarations.
8791
+ * - **VALUE** — at which level, for which scope, until when. That stays ONE
8792
+ * thing: the logging settings document on the `system` cap. Two authorities
8793
+ * over the values is the exact defect
8794
+ * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
8795
+ * remove; re-introducing it from the cure side would be grotesque.
8796
+ *
8797
+ * Nothing in this file reads a clock, an env var or a store. The registry is
8798
+ * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
8799
+ * the hot path with a value somebody actually read, and by
8800
+ * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
8801
+ * never reaches here, so it can neither disarm an armed channel nor arm a
8802
+ * disarmed one (D49).
8803
+ *
8804
+ * ## The canonical call shape
8805
+ *
8806
+ * ```ts
8807
+ * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
8808
+ * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
8809
+ * }
8810
+ * ```
8811
+ *
8812
+ * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
8813
+ * read. Disarmed, a call site costs one load and one branch, and the `extras`
8814
+ * object literal is never constructed because it lives inside the branch. It
8815
+ * is the same shape already proven in production at `stream-broker.ts:1650`,
8816
+ * and the same discipline `LoggingGate.allowsDestination` uses for the
8817
+ * destination floor (measured at 1.93 ns/call when off).
8818
+ *
8819
+ * ## Why a channel emits at `info`
8820
+ *
8821
+ * `loki-logging.addon.ts` pins the destination default at `info` and
8822
+ * `loki-destination.ts` drops everything below it, so a line emitted at
8823
+ * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
8824
+ * minutes. A diagnostic that cannot be read an hour later is worse than no
8825
+ * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
8826
+ * emits at the channel's declared level, whose schema floor is `info`.
8827
+ */
8828
+ /**
8829
+ * The level a channel writes at once armed.
8830
+ *
8831
+ * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
8832
+ * not leave the process for Loki, and the whole point of arming a channel is
8833
+ * to read it later.
8834
+ */
8835
+ var LogChannelLevelSchema = _enum([
8836
+ "info",
8837
+ "warn",
8838
+ "error"
8839
+ ]);
8840
+ /**
8841
+ * What an addon declares about one channel. No value, no state — a
8842
+ * declaration is inert.
8843
+ */
8844
+ var LogChannelDescriptorSchema = object({
8845
+ /**
8846
+ * Dotted `area.thing`, unique across the workspace. `area` is conventionally
8847
+ * the addon's short name so an operator reading a channel list can tell who
8848
+ * owns it without a second lookup.
8849
+ */
8850
+ name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
8851
+ /** One sentence: what the operator will SEE after arming it. */
8852
+ description: string().min(1),
8853
+ /** The level its lines are emitted at. Never below `info`. */
8854
+ defaultLevel: LogChannelLevelSchema,
8855
+ /**
8856
+ * Whether this channel can be narrowed to a camera.
8857
+ *
8858
+ * `true` is a PROMISE with two halves, and both must hold: the gate is
8859
+ * consulted with the numeric device id, AND every line the channel admits
8860
+ * carries `tags: { deviceId }` with that same numeric id. The second half is
8861
+ * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
8862
+ * keeps `deviceId` out of the stream labels for cardinality, so the tag in
8863
+ * the body is the only way to filter.
8864
+ *
8865
+ * A channel whose lines carry the device only in `meta` (or not at all) is
8866
+ * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
8867
+ * the operator narrows to one camera, sees nothing, and concludes the code
8868
+ * path was never taken.
8869
+ */
8870
+ perDevice: boolean()
8871
+ });
8872
+ /**
8873
+ * An armed window over one channel, as the document hands it to a mirror.
8874
+ *
8875
+ * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
8876
+ * expires by itself, which is the one failure a boolean cannot avoid.
8877
+ */
8878
+ var LogChannelWindowSchema = object({
8879
+ channel: string().min(1),
8880
+ /** Epoch ms the window closes at. */
8881
+ armedUntilMs: number(),
8882
+ /** `null` = every camera. A non-empty list narrows to those numeric ids. */
8883
+ deviceIds: array(number().int()).readonly().nullable()
8884
+ });
8881
8885
  var MODEL_FORMATS = [
8882
8886
  "onnx",
8883
8887
  "coreml",
@@ -10534,12 +10538,30 @@ var BackupDestinationInfoSchema = object({
10534
10538
  lastSuccessAt: number().optional(),
10535
10539
  /** Newest-archive size from `manifests.json`, or undefined. */
10536
10540
  lastSuccessSizeBytes: number().optional(),
10537
- /** Per-destination cron expression. Empty = manual-only (no schedule). */
10541
+ /**
10542
+ * Cron cadence(s) of the ENABLED schedules that fan out to this
10543
+ * destination, comma-joined. Absent when no enabled schedule targets it
10544
+ * — a destination nothing is scheduled to write to must not advertise a
10545
+ * cadence (D384). This is never the `backup_destination_policies.cron`
10546
+ * column: that per-location cron has scheduled nothing since 2026-07-28
10547
+ * and reading it made a destination with a DISABLED schedule claim a
10548
+ * nightly run.
10549
+ */
10538
10550
  cron: string().optional(),
10539
- /** ms-epoch of next computed firing for this destination's cron, if any. */
10551
+ /** ms-epoch of the next firing across those schedules (earliest), if any. */
10540
10552
  nextRunAt: number().optional(),
10541
- /** ms-epoch of last successful scheduled run (mirrors policy.lastRunAt). */
10542
- lastRunAt: number().optional()
10553
+ /**
10554
+ * ms-epoch of the last time a run ATTEMPTED to write here — success or
10555
+ * failure. Never a success stamp: pair it with `lastSuccessAt` (the
10556
+ * newest archive that actually landed) and `lastError`.
10557
+ */
10558
+ lastAttemptAt: number().optional(),
10559
+ /**
10560
+ * Why the last attempt failed, verbatim. Absent when the last attempt
10561
+ * landed the archive. A destination that has never been written to has
10562
+ * neither this nor `lastAttemptAt`.
10563
+ */
10564
+ lastError: string().optional()
10543
10565
  });
10544
10566
  /**
10545
10567
  * Per-archive entry returned by `backup.listArchives({ destinationId })`.
@@ -10702,8 +10724,16 @@ var BackupScheduleSchema = object({
10702
10724
  retentionCount: number().int().min(1).max(1e3),
10703
10725
  /** Optional subset of source locations to include; omitted = all. */
10704
10726
  dataSources: array(string()).readonly().optional(),
10705
- /** ms-epoch of last successful run. */
10706
- lastRunAt: number().optional(),
10727
+ /**
10728
+ * ms-epoch of the last tick that FIRED this schedule. Stamped before the
10729
+ * archive runs (it is the dedupe anchor), so it says "attempted", never
10730
+ * "succeeded" — a run refused by every destination stamps it too.
10731
+ */
10732
+ lastAttemptAt: number().optional(),
10733
+ /** ms-epoch of the last run of this schedule that landed at ≥1 destination. */
10734
+ lastSuccessAt: number().optional(),
10735
+ /** Why the last fired run failed, verbatim. Absent when it succeeded. */
10736
+ lastError: string().optional(),
10707
10737
  /** ms-epoch of next computed firing (read-only, filled on list). */
10708
10738
  nextRunAt: number().optional()
10709
10739
  });
@@ -10752,14 +10782,7 @@ method(_void(), array(BackupDestinationInfoSchema).readonly(), { auth: "admin" }
10752
10782
  locationId: string(),
10753
10783
  enabled: boolean(),
10754
10784
  retentionCount: number().int().min(1).max(1e3),
10755
- label: string().optional(),
10756
- /**
10757
- * Per-destination cron expression. Empty string clears the
10758
- * schedule (manual-only). Validated server-side via croner;
10759
- * malformed expressions reject the upsert with an actionable
10760
- * message.
10761
- */
10762
- cron: string().optional()
10785
+ label: string().optional()
10763
10786
  }), _void(), {
10764
10787
  kind: "mutation",
10765
10788
  auth: "admin"
@@ -21523,7 +21546,7 @@ method(object({
21523
21546
  downloadId: string(),
21524
21547
  offset: number(),
21525
21548
  length: number()
21526
- }), _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({
21549
+ }), _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({
21527
21550
  createdAt: true,
21528
21551
  updatedAt: true
21529
21552
  }), StorageLocationSchema, {
@@ -34432,12 +34455,6 @@ Object.freeze({
34432
34455
  addonId: null,
34433
34456
  access: "view"
34434
34457
  },
34435
- "storage.getDefaultLocation": {
34436
- capName: "storage",
34437
- capScope: "system",
34438
- addonId: null,
34439
- access: "view"
34440
- },
34441
34458
  "storage.list": {
34442
34459
  capName: "storage",
34443
34460
  capScope: "system",
@@ -7623,111 +7623,6 @@ var CameraSwitchGroupSchema = object({
7623
7623
  fetchedAt: number()
7624
7624
  });
7625
7625
  /**
7626
- * Per-component log CHANNELS — the gate a hot path consults, and the registry
7627
- * an addon declares its channels in.
7628
- *
7629
- * ## Two axes, deliberately separated
7630
- *
7631
- * - **DECLARATION** — which channels exist. Only the addon knows:
7632
- * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
7633
- * baichuan/handshake. A hand-wired central list rots at the first addition,
7634
- * and rots silently. So a channel is declared where it is consulted, and the
7635
- * `log-channels` capability enumerates the declarations.
7636
- * - **VALUE** — at which level, for which scope, until when. That stays ONE
7637
- * thing: the logging settings document on the `system` cap. Two authorities
7638
- * over the values is the exact defect
7639
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
7640
- * remove; re-introducing it from the cure side would be grotesque.
7641
- *
7642
- * Nothing in this file reads a clock, an env var or a store. The registry is
7643
- * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
7644
- * the hot path with a value somebody actually read, and by
7645
- * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
7646
- * never reaches here, so it can neither disarm an armed channel nor arm a
7647
- * disarmed one (D49).
7648
- *
7649
- * ## The canonical call shape
7650
- *
7651
- * ```ts
7652
- * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
7653
- * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
7654
- * }
7655
- * ```
7656
- *
7657
- * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
7658
- * read. Disarmed, a call site costs one load and one branch, and the `extras`
7659
- * object literal is never constructed because it lives inside the branch. It
7660
- * is the same shape already proven in production at `stream-broker.ts:1650`,
7661
- * and the same discipline `LoggingGate.allowsDestination` uses for the
7662
- * destination floor (measured at 1.93 ns/call when off).
7663
- *
7664
- * ## Why a channel emits at `info`
7665
- *
7666
- * `loki-logging.addon.ts` pins the destination default at `info` and
7667
- * `loki-destination.ts` drops everything below it, so a line emitted at
7668
- * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
7669
- * minutes. A diagnostic that cannot be read an hour later is worse than no
7670
- * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
7671
- * emits at the channel's declared level, whose schema floor is `info`.
7672
- */
7673
- /**
7674
- * The level a channel writes at once armed.
7675
- *
7676
- * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
7677
- * not leave the process for Loki, and the whole point of arming a channel is
7678
- * to read it later.
7679
- */
7680
- var LogChannelLevelSchema = _enum([
7681
- "info",
7682
- "warn",
7683
- "error"
7684
- ]);
7685
- /**
7686
- * What an addon declares about one channel. No value, no state — a
7687
- * declaration is inert.
7688
- */
7689
- var LogChannelDescriptorSchema = object({
7690
- /**
7691
- * Dotted `area.thing`, unique across the workspace. `area` is conventionally
7692
- * the addon's short name so an operator reading a channel list can tell who
7693
- * owns it without a second lookup.
7694
- */
7695
- name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
7696
- /** One sentence: what the operator will SEE after arming it. */
7697
- description: string().min(1),
7698
- /** The level its lines are emitted at. Never below `info`. */
7699
- defaultLevel: LogChannelLevelSchema,
7700
- /**
7701
- * Whether this channel can be narrowed to a camera.
7702
- *
7703
- * `true` is a PROMISE with two halves, and both must hold: the gate is
7704
- * consulted with the numeric device id, AND every line the channel admits
7705
- * carries `tags: { deviceId }` with that same numeric id. The second half is
7706
- * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
7707
- * keeps `deviceId` out of the stream labels for cardinality, so the tag in
7708
- * the body is the only way to filter.
7709
- *
7710
- * A channel whose lines carry the device only in `meta` (or not at all) is
7711
- * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
7712
- * the operator narrows to one camera, sees nothing, and concludes the code
7713
- * path was never taken.
7714
- */
7715
- perDevice: boolean()
7716
- });
7717
- /**
7718
- * An armed window over one channel, as the document hands it to a mirror.
7719
- *
7720
- * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
7721
- * expires by itself, which is the one failure a boolean cannot avoid.
7722
- */
7723
- var LogChannelWindowSchema = object({
7724
- channel: string().min(1),
7725
- /** Epoch ms the window closes at. */
7726
- armedUntilMs: number(),
7727
- /** `null` = every camera. A non-empty list narrows to those numeric ids. */
7728
- deviceIds: array(number().int()).readonly().nullable()
7729
- });
7730
- /**
7731
7626
  * Ops-log — the durable, append-only operations audit shared by the
7732
7627
  * recordings and events management surfaces.
7733
7628
  *
@@ -8673,8 +8568,11 @@ var StorageLocationTypeSchema = string().regex(/^[a-z][a-zA-Z0-9-]*$/);
8673
8568
  * `STORAGE_LOCATION_CARDINALITY` map has been removed.
8674
8569
  *
8675
8570
  * `id` is a stable namespaced string of the form `<type>:<slug>`.
8676
- * The default location for a type uses `id === <type>:default` by
8677
- * convention (the bare type ref like `'backups'` resolves to it).
8571
+ * The seed names its first instance `<type>:default` — a NAME, not a flag.
8572
+ * There is no default location any more (D383): `enabled` is the whole write
8573
+ * model, and a bare type ref resolves to the sole location of the type, or —
8574
+ * transitionally, only while legacy NULL-stamped rows exist — to the row whose
8575
+ * slug is `default`.
8678
8576
  *
8679
8577
  * `isSystem` is a legacy persisted flag. Seed still creates the initial
8680
8578
  * `<type>:default` locations; the flag is no longer a lock, a badge, or a
@@ -8695,21 +8593,20 @@ var StorageLocationSchema = object({
8695
8593
  * flag at upsert time, not here (the schema is provider-agnostic).
8696
8594
  */
8697
8595
  nodeId: string().optional(),
8698
- isDefault: boolean().default(false),
8699
8596
  isSystem: boolean().default(false),
8700
8597
  /**
8701
- * Operator opt-in: whether consumers that BALANCE across several locations
8702
- * of a type may write here. Recordings reads it today; event media and
8703
- * backups are the next consumers, which is why the flag lives on the
8704
- * location rather than in any one addon's store nothing has to be
8705
- * extended to add the next consumer.
8598
+ * THE write switch, and the only one (D383). `enabled: true` means every
8599
+ * consumer that chooses a write target for this type may write here, and all
8600
+ * enabled locations of a type are used TOGETHER; `false` means read-only
8601
+ * still read, still played back, still age-swept, still drained, never
8602
+ * written.
8706
8603
  *
8707
- * OPTIONAL, and ABSENT MEANS ACTIVE. Every location persisted before the
8708
- * flag existed reads back with no flag and keeps working exactly as before;
8709
- * that is the whole compat story, and it is why no migration ships with it.
8710
- * A newly CREATED sibling is stamped `false` by the orchestrator (creating a
8711
- * disk must not silently start writing to it); the default of a type is
8712
- * always stamped `true`.
8604
+ * OPTIONAL only for the wire: an upsert that omits it means "leave what is
8605
+ * stored" on an update and "born inert unless it is the first location of its
8606
+ * type" on a create. On a PERSISTED row absence is legacy and it means
8607
+ * enabled {@link isLocationEnabled} is the one place that says so, and the
8608
+ * orchestrator stamps every flagless row `true` once at hydrate so absence
8609
+ * stops existing rather than being re-derived on every read.
8713
8610
  */
8714
8611
  enabled: boolean().optional(),
8715
8612
  /** COMPUTED at read time by the orchestrator (statfs of the backing volume
@@ -8722,10 +8619,12 @@ var StorageLocationSchema = object({
8722
8619
  createdAt: number(),
8723
8620
  updatedAt: number()
8724
8621
  });
8622
+ object({ isDefault: boolean().optional() });
8725
8623
  /**
8726
8624
  * Reference accepted by consumer-facing `api.storage.*` calls.
8727
8625
  * Either:
8728
- * - a `StorageLocationType` (e.g. `'backups'`) → orchestrator resolves to the default of that type
8626
+ * - a `StorageLocationType` (e.g. `'backups'`) → the sole location of that type
8627
+ * (transitionally, the `<type>:default`-slugged row when several exist)
8729
8628
  * - a fully-qualified id (e.g. `'backups:nas-01'`) → addresses a specific instance
8730
8629
  *
8731
8630
  * The orchestrator's `resolveRef(ref)` handles both cases.
@@ -8901,6 +8800,111 @@ var DecoderSessionConfigSchema = object({
8901
8800
  */
8902
8801
  debug: boolean().optional()
8903
8802
  });
8803
+ /**
8804
+ * Per-component log CHANNELS — the gate a hot path consults, and the registry
8805
+ * an addon declares its channels in.
8806
+ *
8807
+ * ## Two axes, deliberately separated
8808
+ *
8809
+ * - **DECLARATION** — which channels exist. Only the addon knows:
8810
+ * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
8811
+ * baichuan/handshake. A hand-wired central list rots at the first addition,
8812
+ * and rots silently. So a channel is declared where it is consulted, and the
8813
+ * `log-channels` capability enumerates the declarations.
8814
+ * - **VALUE** — at which level, for which scope, until when. That stays ONE
8815
+ * thing: the logging settings document on the `system` cap. Two authorities
8816
+ * over the values is the exact defect
8817
+ * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
8818
+ * remove; re-introducing it from the cure side would be grotesque.
8819
+ *
8820
+ * Nothing in this file reads a clock, an env var or a store. The registry is
8821
+ * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
8822
+ * the hot path with a value somebody actually read, and by
8823
+ * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
8824
+ * never reaches here, so it can neither disarm an armed channel nor arm a
8825
+ * disarmed one (D49).
8826
+ *
8827
+ * ## The canonical call shape
8828
+ *
8829
+ * ```ts
8830
+ * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
8831
+ * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
8832
+ * }
8833
+ * ```
8834
+ *
8835
+ * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
8836
+ * read. Disarmed, a call site costs one load and one branch, and the `extras`
8837
+ * object literal is never constructed because it lives inside the branch. It
8838
+ * is the same shape already proven in production at `stream-broker.ts:1650`,
8839
+ * and the same discipline `LoggingGate.allowsDestination` uses for the
8840
+ * destination floor (measured at 1.93 ns/call when off).
8841
+ *
8842
+ * ## Why a channel emits at `info`
8843
+ *
8844
+ * `loki-logging.addon.ts` pins the destination default at `info` and
8845
+ * `loki-destination.ts` drops everything below it, so a line emitted at
8846
+ * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
8847
+ * minutes. A diagnostic that cannot be read an hour later is worse than no
8848
+ * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
8849
+ * emits at the channel's declared level, whose schema floor is `info`.
8850
+ */
8851
+ /**
8852
+ * The level a channel writes at once armed.
8853
+ *
8854
+ * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
8855
+ * not leave the process for Loki, and the whole point of arming a channel is
8856
+ * to read it later.
8857
+ */
8858
+ var LogChannelLevelSchema = _enum([
8859
+ "info",
8860
+ "warn",
8861
+ "error"
8862
+ ]);
8863
+ /**
8864
+ * What an addon declares about one channel. No value, no state — a
8865
+ * declaration is inert.
8866
+ */
8867
+ var LogChannelDescriptorSchema = object({
8868
+ /**
8869
+ * Dotted `area.thing`, unique across the workspace. `area` is conventionally
8870
+ * the addon's short name so an operator reading a channel list can tell who
8871
+ * owns it without a second lookup.
8872
+ */
8873
+ name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
8874
+ /** One sentence: what the operator will SEE after arming it. */
8875
+ description: string().min(1),
8876
+ /** The level its lines are emitted at. Never below `info`. */
8877
+ defaultLevel: LogChannelLevelSchema,
8878
+ /**
8879
+ * Whether this channel can be narrowed to a camera.
8880
+ *
8881
+ * `true` is a PROMISE with two halves, and both must hold: the gate is
8882
+ * consulted with the numeric device id, AND every line the channel admits
8883
+ * carries `tags: { deviceId }` with that same numeric id. The second half is
8884
+ * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
8885
+ * keeps `deviceId` out of the stream labels for cardinality, so the tag in
8886
+ * the body is the only way to filter.
8887
+ *
8888
+ * A channel whose lines carry the device only in `meta` (or not at all) is
8889
+ * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
8890
+ * the operator narrows to one camera, sees nothing, and concludes the code
8891
+ * path was never taken.
8892
+ */
8893
+ perDevice: boolean()
8894
+ });
8895
+ /**
8896
+ * An armed window over one channel, as the document hands it to a mirror.
8897
+ *
8898
+ * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
8899
+ * expires by itself, which is the one failure a boolean cannot avoid.
8900
+ */
8901
+ var LogChannelWindowSchema = object({
8902
+ channel: string().min(1),
8903
+ /** Epoch ms the window closes at. */
8904
+ armedUntilMs: number(),
8905
+ /** `null` = every camera. A non-empty list narrows to those numeric ids. */
8906
+ deviceIds: array(number().int()).readonly().nullable()
8907
+ });
8904
8908
  var MODEL_FORMATS = [
8905
8909
  "onnx",
8906
8910
  "coreml",
@@ -10557,12 +10561,30 @@ var BackupDestinationInfoSchema = object({
10557
10561
  lastSuccessAt: number().optional(),
10558
10562
  /** Newest-archive size from `manifests.json`, or undefined. */
10559
10563
  lastSuccessSizeBytes: number().optional(),
10560
- /** Per-destination cron expression. Empty = manual-only (no schedule). */
10564
+ /**
10565
+ * Cron cadence(s) of the ENABLED schedules that fan out to this
10566
+ * destination, comma-joined. Absent when no enabled schedule targets it
10567
+ * — a destination nothing is scheduled to write to must not advertise a
10568
+ * cadence (D384). This is never the `backup_destination_policies.cron`
10569
+ * column: that per-location cron has scheduled nothing since 2026-07-28
10570
+ * and reading it made a destination with a DISABLED schedule claim a
10571
+ * nightly run.
10572
+ */
10561
10573
  cron: string().optional(),
10562
- /** ms-epoch of next computed firing for this destination's cron, if any. */
10574
+ /** ms-epoch of the next firing across those schedules (earliest), if any. */
10563
10575
  nextRunAt: number().optional(),
10564
- /** ms-epoch of last successful scheduled run (mirrors policy.lastRunAt). */
10565
- lastRunAt: number().optional()
10576
+ /**
10577
+ * ms-epoch of the last time a run ATTEMPTED to write here — success or
10578
+ * failure. Never a success stamp: pair it with `lastSuccessAt` (the
10579
+ * newest archive that actually landed) and `lastError`.
10580
+ */
10581
+ lastAttemptAt: number().optional(),
10582
+ /**
10583
+ * Why the last attempt failed, verbatim. Absent when the last attempt
10584
+ * landed the archive. A destination that has never been written to has
10585
+ * neither this nor `lastAttemptAt`.
10586
+ */
10587
+ lastError: string().optional()
10566
10588
  });
10567
10589
  /**
10568
10590
  * Per-archive entry returned by `backup.listArchives({ destinationId })`.
@@ -10725,8 +10747,16 @@ var BackupScheduleSchema = object({
10725
10747
  retentionCount: number().int().min(1).max(1e3),
10726
10748
  /** Optional subset of source locations to include; omitted = all. */
10727
10749
  dataSources: array(string()).readonly().optional(),
10728
- /** ms-epoch of last successful run. */
10729
- lastRunAt: number().optional(),
10750
+ /**
10751
+ * ms-epoch of the last tick that FIRED this schedule. Stamped before the
10752
+ * archive runs (it is the dedupe anchor), so it says "attempted", never
10753
+ * "succeeded" — a run refused by every destination stamps it too.
10754
+ */
10755
+ lastAttemptAt: number().optional(),
10756
+ /** ms-epoch of the last run of this schedule that landed at ≥1 destination. */
10757
+ lastSuccessAt: number().optional(),
10758
+ /** Why the last fired run failed, verbatim. Absent when it succeeded. */
10759
+ lastError: string().optional(),
10730
10760
  /** ms-epoch of next computed firing (read-only, filled on list). */
10731
10761
  nextRunAt: number().optional()
10732
10762
  });
@@ -10775,14 +10805,7 @@ method(_void(), array(BackupDestinationInfoSchema).readonly(), { auth: "admin" }
10775
10805
  locationId: string(),
10776
10806
  enabled: boolean(),
10777
10807
  retentionCount: number().int().min(1).max(1e3),
10778
- label: string().optional(),
10779
- /**
10780
- * Per-destination cron expression. Empty string clears the
10781
- * schedule (manual-only). Validated server-side via croner;
10782
- * malformed expressions reject the upsert with an actionable
10783
- * message.
10784
- */
10785
- cron: string().optional()
10808
+ label: string().optional()
10786
10809
  }), _void(), {
10787
10810
  kind: "mutation",
10788
10811
  auth: "admin"
@@ -21546,7 +21569,7 @@ method(object({
21546
21569
  downloadId: string(),
21547
21570
  offset: number(),
21548
21571
  length: number()
21549
- }), _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({
21572
+ }), _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({
21550
21573
  createdAt: true,
21551
21574
  updatedAt: true
21552
21575
  }), StorageLocationSchema, {
@@ -34455,12 +34478,6 @@ Object.freeze({
34455
34478
  addonId: null,
34456
34479
  access: "view"
34457
34480
  },
34458
- "storage.getDefaultLocation": {
34459
- capName: "storage",
34460
- capScope: "system",
34461
- addonId: null,
34462
- access: "view"
34463
- },
34464
34481
  "storage.list": {
34465
34482
  capName: "storage",
34466
34483
  capScope: "system",
package/dist/smb.addon.js CHANGED
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_shared = require("./shared-DlUg7bET.js");
5
+ const require_shared = require("./shared-D7aelvq0.js");
6
6
  let node_crypto = require("node:crypto");
7
7
  let node_path = require("node:path");
8
8
  node_path = require_shared.__toESM(node_path);
@@ -225,6 +225,28 @@ function shareIdentity(input) {
225
225
  function pathSegments(remotePath) {
226
226
  return remotePath.split("/").filter((s) => s.length > 0);
227
227
  }
228
+ /**
229
+ * Turn a directory path into the MASK `smbclient dir` needs to enumerate it.
230
+ *
231
+ * `samba-client`'s `list(x)` runs `smbclient -c 'dir x'`, and smbclient's
232
+ * `dir` argument is a wildcard mask evaluated against the current directory —
233
+ * it does NOT descend into a directory named by it. A bare directory path
234
+ * therefore returns that directory's own entry, and an empty string returns
235
+ * nothing at all, because an empty mask matches nothing.
236
+ *
237
+ * Measured on the hub, 2026-09-07: `backups:smb` held a 387 MB archive and a
238
+ * `manifests.json`; `storage.list` answered `[]` and `storage.exists`
239
+ * answered `false`, so every SMB upload was refused by its own post-transfer
240
+ * "did it land?" check even though the bytes were intact on the share. The
241
+ * unit suite was green because its fake client treated the argument as a
242
+ * directory — the fake supplied the semantics production assumed and
243
+ * smbclient does not have.
244
+ *
245
+ * @param dir `''` for the connection's basePath, else a relative directory.
246
+ */
247
+ function listMaskFor(dir) {
248
+ return dir === "" ? "*" : `${dir}/*`;
249
+ }
228
250
  //#endregion
229
251
  //#region src/providers/smb/smb-provider.ts
230
252
  /**
@@ -484,7 +506,7 @@ var SmbStorageProvider = class {
484
506
  const client = await this.clientForLocation(location);
485
507
  const dir = safeSmbRelativePath(prefix ?? "");
486
508
  try {
487
- return (await client.list(dir)).filter((e) => e.name !== "." && e.name !== "..").map((e) => dir === "" ? e.name : `${dir}/${e.name}`);
509
+ return (await client.list(listMaskFor(dir))).filter((e) => e.name !== "." && e.name !== "..").map((e) => dir === "" ? e.name : `${dir}/${e.name}`);
488
510
  } catch (err) {
489
511
  if (isNotFound(err)) return [];
490
512
  this.logger.warn("smb-storage: list failed", { meta: {
@@ -682,14 +704,19 @@ var SmbStorageProvider = class {
682
704
  ...cfg.basePath !== "" ? { directory: cfg.basePath } : {}
683
705
  });
684
706
  }
685
- /** One entry of a remote listing, or `null` when the path does not exist. */
707
+ /**
708
+ * One entry of a remote listing, or `null` when the path does not exist.
709
+ *
710
+ * Goes through {@link listMaskFor} — a bare directory path is NOT a
711
+ * listable argument for smbclient.
712
+ */
686
713
  async statRemote(location, relativePath) {
687
714
  const client = await this.clientForLocation(location);
688
715
  const target = safeSmbRelativePath(relativePath);
689
716
  const dir = target.includes("/") ? target.slice(0, target.lastIndexOf("/")) : "";
690
717
  const name = target.includes("/") ? target.slice(target.lastIndexOf("/") + 1) : target;
691
718
  try {
692
- return (await client.list(dir)).find((e) => e.name.toLowerCase() === name.toLowerCase()) ?? null;
719
+ return (await client.list(listMaskFor(dir))).find((e) => e.name.toLowerCase() === name.toLowerCase()) ?? null;
693
720
  } catch (err) {
694
721
  if (isNotFound(err)) return null;
695
722
  throw err;
@@ -1,4 +1,4 @@
1
- import { c as BaseAddon, i as rearmIdleAbort, o as scheduleIdleAbort, s as storageProviderCapability } from "./shared-Ch1dJcLf.mjs";
1
+ import { c as BaseAddon, i as rearmIdleAbort, o as scheduleIdleAbort, s as storageProviderCapability } from "./shared-C5_1Zy4l.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import * as path from "node:path";
4
4
  import * as fsp from "node:fs/promises";
@@ -218,6 +218,28 @@ function shareIdentity(input) {
218
218
  function pathSegments(remotePath) {
219
219
  return remotePath.split("/").filter((s) => s.length > 0);
220
220
  }
221
+ /**
222
+ * Turn a directory path into the MASK `smbclient dir` needs to enumerate it.
223
+ *
224
+ * `samba-client`'s `list(x)` runs `smbclient -c 'dir x'`, and smbclient's
225
+ * `dir` argument is a wildcard mask evaluated against the current directory —
226
+ * it does NOT descend into a directory named by it. A bare directory path
227
+ * therefore returns that directory's own entry, and an empty string returns
228
+ * nothing at all, because an empty mask matches nothing.
229
+ *
230
+ * Measured on the hub, 2026-09-07: `backups:smb` held a 387 MB archive and a
231
+ * `manifests.json`; `storage.list` answered `[]` and `storage.exists`
232
+ * answered `false`, so every SMB upload was refused by its own post-transfer
233
+ * "did it land?" check even though the bytes were intact on the share. The
234
+ * unit suite was green because its fake client treated the argument as a
235
+ * directory — the fake supplied the semantics production assumed and
236
+ * smbclient does not have.
237
+ *
238
+ * @param dir `''` for the connection's basePath, else a relative directory.
239
+ */
240
+ function listMaskFor(dir) {
241
+ return dir === "" ? "*" : `${dir}/*`;
242
+ }
221
243
  //#endregion
222
244
  //#region src/providers/smb/smb-provider.ts
223
245
  /**
@@ -477,7 +499,7 @@ var SmbStorageProvider = class {
477
499
  const client = await this.clientForLocation(location);
478
500
  const dir = safeSmbRelativePath(prefix ?? "");
479
501
  try {
480
- return (await client.list(dir)).filter((e) => e.name !== "." && e.name !== "..").map((e) => dir === "" ? e.name : `${dir}/${e.name}`);
502
+ return (await client.list(listMaskFor(dir))).filter((e) => e.name !== "." && e.name !== "..").map((e) => dir === "" ? e.name : `${dir}/${e.name}`);
481
503
  } catch (err) {
482
504
  if (isNotFound(err)) return [];
483
505
  this.logger.warn("smb-storage: list failed", { meta: {
@@ -675,14 +697,19 @@ var SmbStorageProvider = class {
675
697
  ...cfg.basePath !== "" ? { directory: cfg.basePath } : {}
676
698
  });
677
699
  }
678
- /** One entry of a remote listing, or `null` when the path does not exist. */
700
+ /**
701
+ * One entry of a remote listing, or `null` when the path does not exist.
702
+ *
703
+ * Goes through {@link listMaskFor} — a bare directory path is NOT a
704
+ * listable argument for smbclient.
705
+ */
679
706
  async statRemote(location, relativePath) {
680
707
  const client = await this.clientForLocation(location);
681
708
  const target = safeSmbRelativePath(relativePath);
682
709
  const dir = target.includes("/") ? target.slice(0, target.lastIndexOf("/")) : "";
683
710
  const name = target.includes("/") ? target.slice(target.lastIndexOf("/") + 1) : target;
684
711
  try {
685
- return (await client.list(dir)).find((e) => e.name.toLowerCase() === name.toLowerCase()) ?? null;
712
+ return (await client.list(listMaskFor(dir))).find((e) => e.name.toLowerCase() === name.toLowerCase()) ?? null;
686
713
  } catch (err) {
687
714
  if (isNotFound(err)) return null;
688
715
  throw err;
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_shared = require("./shared-DlUg7bET.js");
5
+ const require_shared = require("./shared-D7aelvq0.js");
6
6
  let node_stream = require("node:stream");
7
7
  let webdav = require("webdav");
8
8
  //#region src/providers/webdav/webdav-config-schema.ts
@@ -1,4 +1,4 @@
1
- import { a as safeJoinRemotePath, c as BaseAddon, i as rearmIdleAbort, o as scheduleIdleAbort, r as getRequiredBasePath, s as storageProviderCapability, t as createSessionId } from "./shared-Ch1dJcLf.mjs";
1
+ import { a as safeJoinRemotePath, c as BaseAddon, i as rearmIdleAbort, o as scheduleIdleAbort, r as getRequiredBasePath, s as storageProviderCapability, t as createSessionId } from "./shared-C5_1Zy4l.mjs";
2
2
  import { PassThrough } from "node:stream";
3
3
  import { AuthType, createClient } from "webdav";
4
4
  //#region src/providers/webdav/webdav-config-schema.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-remote-storage",
3
- "version": "1.2.76",
3
+ "version": "1.2.78",
4
4
  "description": "Remote storage providers (SFTP, S3, WebDAV, SMB) — unifies remote backends behind the storage-provider cap",
5
5
  "keywords": [
6
6
  "camstack",