@ultimat3/storage 9.0.0 → 11.0.0

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/CLAUDE.md CHANGED
@@ -154,6 +154,16 @@ Gotchas:
154
154
  may be `NODE_ENV`'s, never a variable the process did not set. `usesDevStorageSecret()` is the
155
155
  `x doctor` predicate, mirroring core's `usesDevCursorSecret()`; it reads the env var, so a disk
156
156
  handed an explicit `signingSecret` is outside its question.
157
+ - **`localDriver({ env })` and `usesDevStorageSecret({ env })` are ONE question about ONE table**
158
+ (`As of 2026-08-23`). The predicate learned core's `env` slot first, so `dev-runtime.ts`'s guard
159
+ — `!isLocal({ env }) && usesDevStorageSecret({ env })` — asked about the BOOT while the
160
+ constructor it guards still read `process.env` for the secret, for `isLocal()` and for the
161
+ environment its refusal names. An embedding caller whose env is not the process's (`serveApp({ env })`,
162
+ a test fixture) got the verdict from one table and the behaviour from another, in the dangerous
163
+ direction: a production boot with no secret, launched from a development shell that has one,
164
+ signing every grant with the published literal. All three reads now come off `options.env ??
165
+ process.env`, so a bare `localDriver({ root })` is unchanged and additive. `driver-local.test.ts`
166
+ pins it by mutation — reverting any one read to `process.env` fails.
157
167
  - **The mounted read half is `@ultimat3/cli`'s `dev-storage.ts`, not this package.** `GET
158
168
  /_storage/:disk/*key` gates on `@ultimat3/policy`'s `evaluate()` (`storage:read`), which is tier
159
169
  2 and unreachable from here — so a "serve this object" helper in this package could only ever be
package/README.md CHANGED
@@ -113,6 +113,12 @@ any key with a `maxBytes` and `contentType` of their choosing, which `acceptSign
113
113
  trusts over the app's own `uploadPolicy`. Setting `STORAGE_SIGNING_SECRET=$DEV_SIGNING_SECRET`, or
114
114
  pasting the literal into `signingSecret`, is refused exactly as an unset variable is. `usesDevStorageSecret()` is the
115
115
  `x doctor` probe for it, the twin of core's `usesDevCursorSecret()`.
116
+
117
+ Both read **one table**, and `env` is how a caller says which. `localDriver({ root, env })` reads
118
+ the secret, the environment test and the environment the refusal names off that table;
119
+ `usesDevStorageSecret({ env })` reads it off the same one. Both default to `process.env`, so a bare
120
+ call is unchanged — pass `env` wherever the boot's environment is not the process's (`serveApp({ env })`,
121
+ a test fixture), or the guard answers about one process and the disk signs according to another.
116
122
  Verification is constant-time, checks the signature *before* the expiry (a forged URL never
117
123
  learns it was merely late), takes a `Clock` so tests freeze time, and returns
118
124
  `{ ok: false, reason }` rather than throwing — `malformed | unsafe-key | signature-mismatch |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/storage",
3
- "version": "9.0.0",
3
+ "version": "11.0.0",
4
4
  "description": "Named disks over Bun.file and Bun.s3: safe keys, signed URLs, sniffed uploads",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,6 +31,6 @@
31
31
  "test": "bun test"
32
32
  },
33
33
  "dependencies": {
34
- "@ultimat3/core": "9.0.0"
34
+ "@ultimat3/core": "11.0.0"
35
35
  }
36
36
  }
@@ -3,7 +3,14 @@
3
3
  // Content type, etag and user metadata live in a sidecar under `.meta/`: a POSIX file has
4
4
  // nowhere to keep them, and `get` must round-trip exactly what `put` was handed.
5
5
 
6
- import { type Clock, isLocal, resolveEnvironment, stringField, systemClock } from '@ultimat3/core';
6
+ import {
7
+ type Clock,
8
+ isLocal,
9
+ type ResolveEnvironmentOptions,
10
+ resolveEnvironment,
11
+ stringField,
12
+ systemClock,
13
+ } from '@ultimat3/core';
7
14
  import {
8
15
  DEFAULT_CONTENT_TYPE,
9
16
  etagOf,
@@ -52,9 +59,17 @@ export const STORAGE_SIGNING_SECRET_KEY = 'STORAGE_SIGNING_SECRET';
52
59
  * Reads the environment, not a driver instance: this is the same question `x doctor` asks about
53
60
  * the cursor secret, and a disk handed an explicit `signingSecret` in `app.config.ts` never
54
61
  * consults the variable at all.
62
+ *
63
+ * `env` is core's own slot, so this half of the guard reads the SAME table its other half does:
64
+ * `dev-runtime.ts` asks `!isLocal({ env }) && usesDevStorageSecret({ env })`, and an embedding
65
+ * caller (`serveApp({ env })`, a test fixture) whose `env` is not `process.env` used to get one
66
+ * answer about the boot and one about the process — for the decision of whether a disk may be
67
+ * signed with the published development key. Defaulted to `process.env`, so a bare call is
68
+ * unchanged.
55
69
  */
56
- export function usesDevStorageSecret(): boolean {
57
- const configured = process.env[STORAGE_SIGNING_SECRET_KEY];
70
+ export function usesDevStorageSecret(options?: Pick<ResolveEnvironmentOptions, 'env'>): boolean {
71
+ const source = options?.env ?? (process.env as Record<string, string | undefined>);
72
+ const configured = source[STORAGE_SIGNING_SECRET_KEY];
58
73
  return configured === undefined || configured === '' || configured === DEV_SIGNING_SECRET;
59
74
  }
60
75
 
@@ -66,6 +81,21 @@ export interface LocalDriverOptions {
66
81
  /** Route prefix the dev server serves signed URLs from. */
67
82
  readonly baseUrl?: string | undefined;
68
83
  readonly clock?: Clock | undefined;
84
+ /**
85
+ * The environment table this DISK belongs to — the boot's, which is not always the process's.
86
+ * Core's own slot (`ResolveEnvironmentOptions['env']`), narrowed to that one field because the
87
+ * `fallback` beside it is a question this constructor never asks.
88
+ *
89
+ * It exists because the guard and the thing it guards have to read one table. `x doctor` and
90
+ * `dev-runtime.ts` ask `!isLocal({ env }) && usesDevStorageSecret({ env })` about the boot; the
91
+ * constructor below is what actually decides whether this disk signs with the published
92
+ * development key, and while it read `process.env` an embedding caller (`serveApp({ env })`, a
93
+ * test fixture) got the verdict from one table and the behaviour from another — in the
94
+ * dangerous direction, a production boot signing with a key published in this repo.
95
+ *
96
+ * Defaults to `process.env`, so a bare `localDriver({ root })` is unchanged.
97
+ */
98
+ readonly env?: ResolveEnvironmentOptions['env'];
69
99
  /**
70
100
  * Ceiling on ONE server-side `put()`, because `put()` buffers the whole body. Defaults to the
71
101
  * upload policy's ceiling — the same number for the same fact. The dev disk enforces it for
@@ -143,12 +173,17 @@ export function localDriver(options: LocalDriverOptions): StorageDriver {
143
173
  // the first upload.
144
174
  // The published literal counts as no secret at all, whichever way it arrives: an env var or an
145
175
  // `app.config.ts` that pasted it in signs exactly as weakly as the fallback does.
146
- const supplied = options.signingSecret ?? process.env[STORAGE_SIGNING_SECRET_KEY];
176
+ // One table for all three reads — the secret, the environment test and the environment the
177
+ // refusal names. Splitting them is how the guard and the disk came to answer about two
178
+ // different processes.
179
+ const env = options.env ?? (process.env as Record<string, string | undefined>);
180
+ const supplied = options.signingSecret ?? env[STORAGE_SIGNING_SECRET_KEY];
147
181
  const configured =
148
182
  supplied === undefined || supplied === '' || supplied === DEV_SIGNING_SECRET
149
183
  ? undefined
150
184
  : supplied;
151
- if (configured === undefined && !isLocal()) throw signingSecretMissing(resolveEnvironment());
185
+ if (configured === undefined && !isLocal({ env }))
186
+ throw signingSecretMissing(resolveEnvironment({ env }));
152
187
  const secret = configured ?? DEV_SIGNING_SECRET;
153
188
 
154
189
  const filePath = (key: string): string => `${root}/${key}`;
package/src/errors.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // rejected upload must tell the caller which constraint fired and where that constraint is
3
3
  // configured, or the caller retries the same bytes forever.
4
4
 
5
- import { errorDocsUrl, registerErrorCodes, renderThrowable, UltimateError } from '@ultimat3/core';
5
+ import { registerErrorCodes, renderThrowable, UltimateError } from '@ultimat3/core';
6
6
 
7
7
  /** Codes this package declares and owns. */
8
8
  export const STORAGE_OWNED_ERROR_CODES = [
@@ -80,7 +80,6 @@ export class StorageError extends UltimateError {
80
80
  code: init.code,
81
81
  cause: init.cause,
82
82
  fix: init.fix,
83
- docs: errorDocsUrl(init.code),
84
83
  meta: init.meta,
85
84
  });
86
85
  }