@camstack/system 1.2.83 → 1.2.85

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 (66) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  11. package/dist/builtins/console-logging/index.js +1 -1
  12. package/dist/builtins/console-logging/index.mjs +1 -1
  13. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  14. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  15. package/dist/builtins/device-manager/device-manager.addon.js +32 -2
  16. package/dist/builtins/device-manager/device-manager.addon.mjs +32 -2
  17. package/dist/builtins/device-manager/device-meta-actions.d.ts +12 -0
  18. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  19. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  20. package/dist/builtins/hub-forwarder/index.js +1 -1
  21. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  22. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  23. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  24. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  25. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  26. package/dist/builtins/local-network/local-network.addon.js +1 -1
  27. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  28. package/dist/builtins/loki-logging/index.js +1 -1
  29. package/dist/builtins/loki-logging/index.mjs +1 -1
  30. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  31. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  32. package/dist/builtins/platform-probe/index.js +1 -1
  33. package/dist/builtins/platform-probe/index.mjs +1 -1
  34. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  35. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  36. package/dist/builtins/snapshot/index.js +66 -11
  37. package/dist/builtins/snapshot/index.mjs +66 -11
  38. package/dist/builtins/snapshot/snapshot.addon.d.ts +39 -3
  39. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +30 -10
  40. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +31 -11
  41. package/dist/builtins/sqlite-storage/path-guard.d.ts +8 -1
  42. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
  43. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
  44. package/dist/builtins/storage-orchestrator/index.d.ts +5 -6
  45. package/dist/builtins/storage-orchestrator/index.mjs +1 -1
  46. package/dist/builtins/storage-orchestrator/provider-discovery.d.ts +1 -1
  47. package/dist/builtins/storage-orchestrator/storage-migration-coordinator.d.ts +1 -1
  48. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +1 -1
  49. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1252 -1191
  50. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1253 -1192
  51. package/dist/builtins/system-config/system-config.addon.js +1 -1
  52. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  53. package/dist/builtins/winston-logging/index.js +1 -1
  54. package/dist/builtins/winston-logging/index.mjs +1 -1
  55. package/dist/{dist-EAvNJD24.mjs → dist-DWuNENzg.mjs} +527 -11
  56. package/dist/{dist-CqiMPPs0.js → dist-DiScsb8j.js} +527 -11
  57. package/dist/index.js +83 -18
  58. package/dist/index.mjs +84 -19
  59. package/dist/kernel/addon-installer.d.ts +58 -6
  60. package/dist/kernel/moleculer/event-bus-core.d.ts +47 -1
  61. package/dist/kernel/transport/child-cap-protocol.d.ts +32 -0
  62. package/dist/kernel/transport/hub-cap-forward.d.ts +8 -0
  63. package/dist/kernel/transport/parent-unowned-call.d.ts +19 -0
  64. package/dist/{manifest-python-deps-DG_LLcZF.js → manifest-python-deps-C0zkl1Ae.js} +133 -53
  65. package/dist/{manifest-python-deps-C1DMs1J9.mjs → manifest-python-deps-fWzdEasI.mjs} +133 -53
  66. package/package.json +6 -1
@@ -1,4 +1,4 @@
1
- import { Ft as nodePin, Ht as EventCategory, St as DeviceType, bt as DeviceFeature, dt as streamQualityLabel, gt as BaseAddon, ht as errMsg, i as BatteryStatusSchema, st as snapshotCapability, w as bareAddonId } from "../../dist-EAvNJD24.mjs";
1
+ import { Ft as nodePin, Ht as EventCategory, St as DeviceType, bt as DeviceFeature, dt as streamQualityLabel, gt as BaseAddon, ht as errMsg, i as BatteryStatusSchema, st as snapshotCapability, w as bareAddonId } from "../../dist-DWuNENzg.mjs";
2
2
  import { z } from "zod";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { signExpiringUrl, verifyExpiringUrl } from "@camstack/types/node";
@@ -865,6 +865,11 @@ var SnapshotAddon = class SnapshotAddon extends BaseAddon {
865
865
  * currently holding, which is a fleet-wide blank grid after each restart. */
866
866
  linkSecret = null;
867
867
  /**
868
+ * In-flight `serveLinkDataPlane` attempt, so the retry below cannot start a
869
+ * second one while the first is still awaiting the settings store.
870
+ */
871
+ linkPlaneAttempt = null;
872
+ /**
868
873
  * How a full frame becomes a card-sized one. Defaults to the real ffmpeg
869
874
  * resize; injectable so the wrapper's own logic is testable without spawning a
870
875
  * process for every fixture. `JpegResizer` was declared for exactly this and
@@ -893,7 +898,7 @@ var SnapshotAddon = class SnapshotAddon extends BaseAddon {
893
898
  if (typeof deviceId === "number") this.evictRemovedDevice(deviceId, "device-unregistered");
894
899
  });
895
900
  await this.serveMediaDataPlane();
896
- await this.serveLinkDataPlane();
901
+ await this.ensureLinkDataPlane();
897
902
  return [{
898
903
  capability: snapshotCapability,
899
904
  provider
@@ -926,6 +931,33 @@ var SnapshotAddon = class SnapshotAddon extends BaseAddon {
926
931
  }
927
932
  }
928
933
  /**
934
+ * Serve the link plane if it is not already served — at init, and again on
935
+ * the first `getSnapshotLinks` after an attempt that could not finish.
936
+ *
937
+ * The retry is not defensive padding, it is the whole reason this wrapper
938
+ * exists. The plane needs a persisted signing secret, and the secret needs
939
+ * the settings store; this addon now boots in its OWN process, so "the store
940
+ * is not answering yet" is a state it can genuinely observe. A one-shot
941
+ * attempt turns that instant into a permanent condition: `linkDataPlane`
942
+ * stays null, every `getSnapshotLinks` row answers `url: null`, and every
943
+ * tile on every grid stays blank until someone restarts the runner. Nothing
944
+ * in the logs would say why, because nothing failed twice.
945
+ *
946
+ * Single-flighted: a burst of link requests joins one attempt.
947
+ */
948
+ async ensureLinkDataPlane() {
949
+ if (this.linkDataPlane !== null) return;
950
+ if (this.linkPlaneAttempt !== null) {
951
+ await this.linkPlaneAttempt;
952
+ return;
953
+ }
954
+ const attempt = this.serveLinkDataPlane().finally(() => {
955
+ this.linkPlaneAttempt = null;
956
+ });
957
+ this.linkPlaneAttempt = attempt;
958
+ await attempt;
959
+ }
960
+ /**
929
961
  * Serve the PUBLIC link plane: `GET /addon/snapshot/link/<deviceId>.jpg?w&v&exp&sig`.
930
962
  *
931
963
  * `access: 'public'` is deliberate and is the only interesting decision here.
@@ -994,20 +1026,42 @@ var SnapshotAddon = class SnapshotAddon extends BaseAddon {
994
1026
  linkRoutePrefix() {
995
1027
  return `/addon/${bareAddonId(this.ctx.id)}/link`;
996
1028
  }
997
- /** Mint-once-and-persist the signing secret. Null if it cannot be stored — a
998
- * secret held only in memory would break every outstanding link on restart,
999
- * so no plane is better than a plane whose links die unpredictably. */
1029
+ /**
1030
+ * Mint-once-and-persist the signing secret. Null when it cannot be
1031
+ * ESTABLISHED a secret held only in memory would break every outstanding
1032
+ * link on restart, so no plane is better than a plane whose links die
1033
+ * unpredictably.
1034
+ *
1035
+ * A null is never cached, so the next `getSnapshotLinks` tries again. That
1036
+ * matters now the wrapper runs in its own process: the settings store can be
1037
+ * a moment behind at boot, and this used to be a one-shot.
1038
+ *
1039
+ * The read-back is the gate. `state.get()` cannot distinguish "no secret
1040
+ * yet" from "the store could not be read" — `readAddonStoreWithRetry` gives
1041
+ * up and answers `{}` — so a store that is merely slow reads as empty and
1042
+ * this would mint a SECOND secret over a perfectly good first one, silently
1043
+ * invalidating every link a client is holding. Reading back what we just
1044
+ * wrote is the only evidence available at this layer that the store is
1045
+ * actually answering, and a mismatch means it is not.
1046
+ */
1000
1047
  async ensureLinkSecret() {
1001
1048
  if (this.linkSecret !== null) return this.linkSecret;
1002
1049
  try {
1003
1050
  const state = this.state("snapshotLinkSecret", z.string(), "");
1004
- let secret = await state.get();
1005
- if (secret === "") {
1006
- secret = randomUUID().replace(/-/g, "");
1007
- await state.set(secret);
1051
+ const stored = await state.get();
1052
+ if (stored !== "") {
1053
+ this.linkSecret = stored;
1054
+ return stored;
1055
+ }
1056
+ const minted = randomUUID().replace(/-/g, "");
1057
+ await state.set(minted);
1058
+ const confirmed = await state.get();
1059
+ if (confirmed !== minted) {
1060
+ this.ctx.logger.warn("snapshot: link secret did not persist — deferring the link plane rather than signing with a secret that will not survive", { meta: { confirmedEmpty: confirmed === "" } });
1061
+ return null;
1008
1062
  }
1009
- this.linkSecret = secret;
1010
- return secret;
1063
+ this.linkSecret = minted;
1064
+ return minted;
1011
1065
  } catch (err) {
1012
1066
  this.ctx.logger.warn("snapshot: could not establish link signing secret", { meta: { error: errMsg(err) } });
1013
1067
  return null;
@@ -1038,6 +1092,7 @@ var SnapshotAddon = class SnapshotAddon extends BaseAddon {
1038
1092
  * forward.
1039
1093
  */
1040
1094
  async getSnapshotLinks(input) {
1095
+ await this.ensureLinkDataPlane();
1041
1096
  const secret = await this.ensureLinkSecret();
1042
1097
  const expMs = snapshotLinkExpiry(Date.now(), SNAPSHOT_LINK_TTL_MS);
1043
1098
  const served = this.linkDataPlane !== null && secret !== null;
@@ -81,6 +81,11 @@ export declare class SnapshotAddon extends BaseAddon<SnapshotAddonConfig> {
81
81
  * secret that rolled on every boot would invalidate every link a client is
82
82
  * currently holding, which is a fleet-wide blank grid after each restart. */
83
83
  private linkSecret;
84
+ /**
85
+ * In-flight `serveLinkDataPlane` attempt, so the retry below cannot start a
86
+ * second one while the first is still awaiting the settings store.
87
+ */
88
+ private linkPlaneAttempt;
84
89
  /**
85
90
  * How a full frame becomes a card-sized one. Defaults to the real ffmpeg
86
91
  * resize; injectable so the wrapper's own logic is testable without spawning a
@@ -101,6 +106,22 @@ export declare class SnapshotAddon extends BaseAddon<SnapshotAddonConfig> {
101
106
  * base64 path.
102
107
  */
103
108
  private serveMediaDataPlane;
109
+ /**
110
+ * Serve the link plane if it is not already served — at init, and again on
111
+ * the first `getSnapshotLinks` after an attempt that could not finish.
112
+ *
113
+ * The retry is not defensive padding, it is the whole reason this wrapper
114
+ * exists. The plane needs a persisted signing secret, and the secret needs
115
+ * the settings store; this addon now boots in its OWN process, so "the store
116
+ * is not answering yet" is a state it can genuinely observe. A one-shot
117
+ * attempt turns that instant into a permanent condition: `linkDataPlane`
118
+ * stays null, every `getSnapshotLinks` row answers `url: null`, and every
119
+ * tile on every grid stays blank until someone restarts the runner. Nothing
120
+ * in the logs would say why, because nothing failed twice.
121
+ *
122
+ * Single-flighted: a burst of link requests joins one attempt.
123
+ */
124
+ private ensureLinkDataPlane;
104
125
  /**
105
126
  * Serve the PUBLIC link plane: `GET /addon/snapshot/link/<deviceId>.jpg?w&v&exp&sig`.
106
127
  *
@@ -128,9 +149,24 @@ export declare class SnapshotAddon extends BaseAddon<SnapshotAddonConfig> {
128
149
  */
129
150
  private handleLinkRequest;
130
151
  private linkRoutePrefix;
131
- /** Mint-once-and-persist the signing secret. Null if it cannot be stored — a
132
- * secret held only in memory would break every outstanding link on restart,
133
- * so no plane is better than a plane whose links die unpredictably. */
152
+ /**
153
+ * Mint-once-and-persist the signing secret. Null when it cannot be
154
+ * ESTABLISHED a secret held only in memory would break every outstanding
155
+ * link on restart, so no plane is better than a plane whose links die
156
+ * unpredictably.
157
+ *
158
+ * A null is never cached, so the next `getSnapshotLinks` tries again. That
159
+ * matters now the wrapper runs in its own process: the settings store can be
160
+ * a moment behind at boot, and this used to be a one-shot.
161
+ *
162
+ * The read-back is the gate. `state.get()` cannot distinguish "no secret
163
+ * yet" from "the store could not be read" — `readAddonStoreWithRetry` gives
164
+ * up and answers `{}` — so a store that is merely slow reads as empty and
165
+ * this would mint a SECOND secret over a perfectly good first one, silently
166
+ * invalidating every link a client is holding. Reading back what we just
167
+ * wrote is the only evidence available at this layer that the store is
168
+ * actually answering, and a mismatch means it is not.
169
+ */
134
170
  private ensureLinkSecret;
135
171
  /**
136
172
  * Mint signed links for the tiles a surface is rendering — and, in the same
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CqiMPPs0.js");
6
+ const require_dist = require("../../dist-DiScsb8j.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_fs_promises = require("node:fs/promises");
9
9
  let node_path = require("node:path");
@@ -13,9 +13,16 @@ let node_fs = require("node:fs");
13
13
  node_fs = require_chunk.__toESM(node_fs);
14
14
  //#region src/builtins/sqlite-storage/path-guard.ts
15
15
  /**
16
- * Allowed-root containment guard for the filesystem-browse cap. Pure +
17
- * synchronous so it is trivially unit-testable; the provider applies it AFTER
18
- * resolving the real path (so symlink escapes are also caught upstream).
16
+ * Guards for the filesystem-browse cap. Pure + synchronous so they are
17
+ * trivially unit-testable; the provider applies containment AFTER resolving the
18
+ * real path (so symlink escapes are also caught upstream).
19
+ *
20
+ * The guard is SPLIT (D115): `assertWithinAllowedRoots` contains WRITES
21
+ * (`createDir`) to the operator's allowed roots, while `assertBrowsablePath`
22
+ * lets read-only discovery (`browse`) see the whole filesystem. Confining
23
+ * discovery to the same roots meant the admin UI could not list `/`, so the
24
+ * path picker could not offer — or even reach — the mounts this node's own
25
+ * storage locations live on.
19
26
  */
20
27
  /** True iff `candidate` is one of, or nested under, an allowed root. */
21
28
  function isWithinAllowedRoots(candidate, allowedRoots) {
@@ -25,17 +32,30 @@ function isWithinAllowedRoots(candidate, allowedRoots) {
25
32
  return norm === r || norm.startsWith(r.endsWith(node_path.sep) ? r : r + node_path.sep);
26
33
  });
27
34
  }
28
- /** Throws unless `candidate` is within an allowed root. */
35
+ /** Throws unless `candidate` is within an allowed root. WRITES only. */
29
36
  function assertWithinAllowedRoots(candidate, allowedRoots) {
30
37
  if (!isWithinAllowedRoots(candidate, allowedRoots)) throw new Error(`path "${candidate}" is not within an allowed root`);
31
38
  }
39
+ /**
40
+ * Throws unless `candidate` is a browsable path. Read-only discovery is
41
+ * deliberately unconfined — the only requirement is an absolute path, so a
42
+ * relative one can never be resolved against whatever the runner's cwd
43
+ * happens to be.
44
+ */
45
+ function assertBrowsablePath(candidate) {
46
+ if (!(0, node_path.isAbsolute)(candidate)) throw new Error(`browse path "${candidate}" must be absolute`);
47
+ }
32
48
  //#endregion
33
49
  //#region src/builtins/sqlite-storage/filesystem-browse-provider.ts
34
50
  /**
35
- * Per-node filesystem-browse provider. Lists/creates directories under
36
- * operator-configured allowed roots only (sandboxed by `path-guard`). Backs the
37
- * `filesystem-browse` cap so the admin UI can pick a node + path for a
38
- * node-local storage location.
51
+ * Per-node filesystem-browse provider. Backs the `filesystem-browse` cap so the
52
+ * admin UI can pick a node + path for a node-local storage location.
53
+ *
54
+ * The two halves are guarded differently (D115): `browse` is read-only
55
+ * discovery and sees the whole filesystem from `/` down, while `createDir`
56
+ * stays contained to the operator's allowed roots. Containing discovery too
57
+ * meant the picker could not list `/`, and therefore could not show the mounts
58
+ * this node's own storage locations already live on.
39
59
  */
40
60
  var FilesystemBrowseProvider = class {
41
61
  allowedRoots;
@@ -47,8 +67,8 @@ var FilesystemBrowseProvider = class {
47
67
  return [...this.allowedRoots()];
48
68
  }
49
69
  async browse({ path }) {
70
+ assertBrowsablePath(path);
50
71
  const real = await (0, node_fs_promises.realpath)(path).catch(() => path);
51
- assertWithinAllowedRoots(real, await this.resolvedRoots());
52
72
  const entries = (await (0, node_fs_promises.readdir)(real, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => ({
53
73
  name: d.name,
54
74
  path: (0, node_path.join)(real, d.name)
@@ -1,15 +1,22 @@
1
- import { gt as BaseAddon, ut as storageProviderCapability, z as filesystemBrowseCapability } from "../../dist-EAvNJD24.mjs";
1
+ import { gt as BaseAddon, ut as storageProviderCapability, z as filesystemBrowseCapability } from "../../dist-DWuNENzg.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { mkdir, readdir, realpath, statfs } from "node:fs/promises";
4
4
  import * as path$1 from "node:path";
5
- import { basename, dirname, join, resolve, sep } from "node:path";
5
+ import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
6
6
  import { homedir } from "node:os";
7
7
  import * as fs from "node:fs";
8
8
  //#region src/builtins/sqlite-storage/path-guard.ts
9
9
  /**
10
- * Allowed-root containment guard for the filesystem-browse cap. Pure +
11
- * synchronous so it is trivially unit-testable; the provider applies it AFTER
12
- * resolving the real path (so symlink escapes are also caught upstream).
10
+ * Guards for the filesystem-browse cap. Pure + synchronous so they are
11
+ * trivially unit-testable; the provider applies containment AFTER resolving the
12
+ * real path (so symlink escapes are also caught upstream).
13
+ *
14
+ * The guard is SPLIT (D115): `assertWithinAllowedRoots` contains WRITES
15
+ * (`createDir`) to the operator's allowed roots, while `assertBrowsablePath`
16
+ * lets read-only discovery (`browse`) see the whole filesystem. Confining
17
+ * discovery to the same roots meant the admin UI could not list `/`, so the
18
+ * path picker could not offer — or even reach — the mounts this node's own
19
+ * storage locations live on.
13
20
  */
14
21
  /** True iff `candidate` is one of, or nested under, an allowed root. */
15
22
  function isWithinAllowedRoots(candidate, allowedRoots) {
@@ -19,17 +26,30 @@ function isWithinAllowedRoots(candidate, allowedRoots) {
19
26
  return norm === r || norm.startsWith(r.endsWith(sep) ? r : r + sep);
20
27
  });
21
28
  }
22
- /** Throws unless `candidate` is within an allowed root. */
29
+ /** Throws unless `candidate` is within an allowed root. WRITES only. */
23
30
  function assertWithinAllowedRoots(candidate, allowedRoots) {
24
31
  if (!isWithinAllowedRoots(candidate, allowedRoots)) throw new Error(`path "${candidate}" is not within an allowed root`);
25
32
  }
33
+ /**
34
+ * Throws unless `candidate` is a browsable path. Read-only discovery is
35
+ * deliberately unconfined — the only requirement is an absolute path, so a
36
+ * relative one can never be resolved against whatever the runner's cwd
37
+ * happens to be.
38
+ */
39
+ function assertBrowsablePath(candidate) {
40
+ if (!isAbsolute(candidate)) throw new Error(`browse path "${candidate}" must be absolute`);
41
+ }
26
42
  //#endregion
27
43
  //#region src/builtins/sqlite-storage/filesystem-browse-provider.ts
28
44
  /**
29
- * Per-node filesystem-browse provider. Lists/creates directories under
30
- * operator-configured allowed roots only (sandboxed by `path-guard`). Backs the
31
- * `filesystem-browse` cap so the admin UI can pick a node + path for a
32
- * node-local storage location.
45
+ * Per-node filesystem-browse provider. Backs the `filesystem-browse` cap so the
46
+ * admin UI can pick a node + path for a node-local storage location.
47
+ *
48
+ * The two halves are guarded differently (D115): `browse` is read-only
49
+ * discovery and sees the whole filesystem from `/` down, while `createDir`
50
+ * stays contained to the operator's allowed roots. Containing discovery too
51
+ * meant the picker could not list `/`, and therefore could not show the mounts
52
+ * this node's own storage locations already live on.
33
53
  */
34
54
  var FilesystemBrowseProvider = class {
35
55
  allowedRoots;
@@ -41,8 +61,8 @@ var FilesystemBrowseProvider = class {
41
61
  return [...this.allowedRoots()];
42
62
  }
43
63
  async browse({ path }) {
64
+ assertBrowsablePath(path);
44
65
  const real = await realpath(path).catch(() => path);
45
- assertWithinAllowedRoots(real, await this.resolvedRoots());
46
66
  const entries = (await readdir(real, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => ({
47
67
  name: d.name,
48
68
  path: join(real, d.name)
@@ -1,4 +1,11 @@
1
1
  /** True iff `candidate` is one of, or nested under, an allowed root. */
2
2
  export declare function isWithinAllowedRoots(candidate: string, allowedRoots: readonly string[]): boolean;
3
- /** Throws unless `candidate` is within an allowed root. */
3
+ /** Throws unless `candidate` is within an allowed root. WRITES only. */
4
4
  export declare function assertWithinAllowedRoots(candidate: string, allowedRoots: readonly string[]): void;
5
+ /**
6
+ * Throws unless `candidate` is a browsable path. Read-only discovery is
7
+ * deliberately unconfined — the only requirement is an absolute path, so a
8
+ * relative one can never be resolved against whatever the runner's cwd
9
+ * happens to be.
10
+ */
11
+ export declare function assertBrowsablePath(candidate: string): void;
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CqiMPPs0.js");
6
+ const require_dist = require("../../dist-DiScsb8j.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_fs = require("node:fs");
9
9
  let node_module = require("node:module");
@@ -1,4 +1,4 @@
1
- import { A as decodeVectorBase64, Et as asJsonObject, Lt as parseJsonUnknown, f as RUNTIME_DEFAULTS, gt as BaseAddon, ht as errMsg, k as dataStoreProviderCapability, mt as vectorStoreCapability, pt as vectorDimFromBase64, w as bareAddonId } from "../../dist-EAvNJD24.mjs";
1
+ import { A as decodeVectorBase64, Et as asJsonObject, Lt as parseJsonUnknown, f as RUNTIME_DEFAULTS, gt as BaseAddon, ht as errMsg, k as dataStoreProviderCapability, mt as vectorStoreCapability, pt as vectorDimFromBase64, w as bareAddonId } from "../../dist-DWuNENzg.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { statSync } from "node:fs";
@@ -1,8 +1,7 @@
1
- export { StorageOrchestratorAddon } from './storage-orchestrator.addon.js';
2
- export { StorageOrchestratorAddon as default } from './storage-orchestrator.addon.js';
3
- export { StorageOrchestratorService } from './storage-orchestrator.service.js';
4
- export { StorageMigrationCoordinator } from './storage-migration-coordinator.js';
1
+ export type { ILocationStore } from './location-store.js';
2
+ export { SqliteLocationStore } from './location-store.js';
5
3
  export type { FootageMigrationParticipant, LeaseParticipant, MediaMigrationParticipant, MigrationState, StorageMigrationCoordinatorDeps, StorageMigrationParticipants, } from './storage-migration-coordinator.js';
4
+ export { StorageMigrationCoordinator } from './storage-migration-coordinator.js';
5
+ export { StorageOrchestratorAddon, StorageOrchestratorAddon as default, } from './storage-orchestrator.addon.js';
6
6
  export type { ListLocationsFilter, StorageProviderLookup } from './storage-orchestrator.service.js';
7
- export { SqliteLocationStore } from './location-store.js';
8
- export type { ILocationStore } from './location-store.js';
7
+ export { StorageOrchestratorService } from './storage-orchestrator.service.js';
@@ -1,2 +1,2 @@
1
- import { StorageOrchestratorAddon, n as SqliteLocationStore, r as StorageOrchestratorService, t as StorageMigrationCoordinator } from "./storage-orchestrator.addon.mjs";
1
+ import { StorageOrchestratorAddon, n as StorageMigrationCoordinator, r as SqliteLocationStore, t as StorageOrchestratorService } from "./storage-orchestrator.addon.mjs";
2
2
  export { SqliteLocationStore, StorageMigrationCoordinator, StorageOrchestratorAddon, StorageOrchestratorAddon as default, StorageOrchestratorService };
@@ -1,4 +1,4 @@
1
- import { IStorageProviderImpl, IStorageCapProvider } from '@camstack/types';
1
+ import { IStorageCapProvider, IStorageProviderImpl } from '@camstack/types';
2
2
  /** One entry of the `storage.listProviders` output array. */
3
3
  export type StorageProviderListEntry = Awaited<ReturnType<IStorageCapProvider['listProviders']>>[number];
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { RelocateJob, StorageMigrationFootageMoveInput, StorageMigrationMediaMoveInput, StorageMigrationInput, StorageMigrationJob, StorageMigrationPlan } from '@camstack/types';
1
+ import { RelocateJob, StorageMigrationFootageMoveInput, StorageMigrationInput, StorageMigrationJob, StorageMigrationMediaMoveInput, StorageMigrationPlan } from '@camstack/types';
2
2
  import { StorageOrchestratorService } from './storage-orchestrator.service.js';
3
3
  export interface MigrationState {
4
4
  get(): Promise<StorageMigrationJob | null>;
@@ -1,4 +1,4 @@
1
- import { BaseAddon, ProviderRegistration } from '@camstack/types';
1
+ import { ProviderRegistration, BaseAddon } from '@camstack/types';
2
2
  interface StorageOrchestratorConfig extends Record<string, unknown> {
3
3
  }
4
4
  export declare class StorageOrchestratorAddon extends BaseAddon<StorageOrchestratorConfig> {