diff --git a/server/src/app-config/README.md b/server/src/app-config/README.md index c7e71ca4d..c24684035 100644 --- a/server/src/app-config/README.md +++ b/server/src/app-config/README.md @@ -78,13 +78,6 @@ is a *different* read of the same variable: `deriveSecurity()` reads it live via `readEnv()`/`RuntimeEnvService`, so a value set after boot is observed on the next call — it does not retroactively unlock the frozen cipher.) -**Tombstoned** (boot-aborting since the admin-config slice — configure storage -in the admin UI / `data/storage-config.json` instead): `TREK_S3_ENDPOINT`, -`TREK_S3_BUCKET`, `TREK_S3_ACCESS_KEY_ID`, `TREK_S3_SECRET_ACCESS_KEY`, -`TREK_S3_REGION`, `TREK_S3_KEY_PREFIX`, `TREK_S3_RETRIES`, `TREK_S3_TIMEOUT_MS`, -`TREK_UPLOADS_DIR` (see `storageTombstonePreconditions` in `env.ts`). -`TREK_PLACE_PHOTO_DIR` intentionally survives (documented, prod-supported) — do not tombstone it by association. - **Runtime-toggled** (read live on every access via `readEnv()` / `RuntimeEnvService`; tests mutate these mid-lifetime): TREK_MANAGED, PLACES_API_BASE, PLACES_API_KEY, MAPBOX_ACCESS_TOKEN, DEMO_MODE, NODE_ENV, APP_VERSION, APP_URL, TREK_API_DOCS_ENABLED, diff --git a/server/src/app-config/env.ts b/server/src/app-config/env.ts index 0b0179c3b..8e3d0f415 100644 --- a/server/src/app-config/env.ts +++ b/server/src/app-config/env.ts @@ -30,7 +30,7 @@ export function validateEnvAtBoot(raw: RawEnv = process.env as RawEnv): void { const key = String(issue.path[0]); return ` - ${key}=${JSON.stringify(raw[key])}: ${issue.message}`; }); - lines.push(...managedPreconditions(raw), ...storageTombstonePreconditions(raw)); + lines.push(...managedPreconditions(raw)); if (lines.length === 0) return; console.error(`Invalid environment configuration:\n${lines.join('\n')}`); throw new Error( @@ -68,29 +68,3 @@ function managedPreconditions(raw: RawEnv): string[] { return problems; } - -/** - * Storage configuration moved wholesale to the admin UI / data/storage-config.json - * (spec: docs/superpowers/specs/2026-08-19-storage-admin-config-design.md). - * These variables are tombstoned rather than ignored: an upgrading operator - * with them set would otherwise silently run local-only — the exact - * half-configured trap the old all-or-nothing rule existed to prevent. - * TREK_PLACE_PHOTO_DIR intentionally survives (documented, prod-supported). - */ -const REMOVED_STORAGE_VARS = [ - 'TREK_S3_ENDPOINT', - 'TREK_S3_BUCKET', - 'TREK_S3_ACCESS_KEY_ID', - 'TREK_S3_SECRET_ACCESS_KEY', - 'TREK_S3_REGION', - 'TREK_S3_KEY_PREFIX', - 'TREK_S3_RETRIES', - 'TREK_S3_TIMEOUT_MS', - 'TREK_UPLOADS_DIR', -] as const; - -function storageTombstonePreconditions(raw: RawEnv): string[] { - return REMOVED_STORAGE_VARS.filter((key) => Boolean(raw[key]?.trim())).map( - (key) => ` - ${key} was removed — configure storage in the admin UI or data/storage-config.json.`, - ); -} diff --git a/server/src/nest/storage/storage-registry.service.ts b/server/src/nest/storage/storage-registry.service.ts index c86ea57ee..d810b27ab 100644 --- a/server/src/nest/storage/storage-registry.service.ts +++ b/server/src/nest/storage/storage-registry.service.ts @@ -273,8 +273,8 @@ export class StorageRegistryService implements OnModuleInit { const placePhotoDir = this.env.env().paths.placePhotoDir; // 2. Built-in defaults; settings entries with the same name/category override. - // uploads-local's root is the computed default since TREK_UPLOADS_DIR was - // removed — relocation is a settings override row with the built-in's name. + // uploads-local's root is the computed default; relocation is a settings + // override row bearing the built-in's name. const backends = new Map(); backends.set('uploads-local', { name: 'uploads-local', type: 'local', options: { root: DEFAULT_UPLOADS_ROOT } }); backends.set('backups-local', { name: 'backups-local', type: 'local', options: { root: DEFAULT_BACKUPS_ROOT } }); diff --git a/server/tests/unit/app-config/validate.test.ts b/server/tests/unit/app-config/validate.test.ts index 372ed2439..d009f268a 100644 --- a/server/tests/unit/app-config/validate.test.ts +++ b/server/tests/unit/app-config/validate.test.ts @@ -156,38 +156,6 @@ describe('validateEnvAtBoot — centrally administered preconditions', () => { }); }); -describe('validateEnvAtBoot — removed storage variables (tombstones)', () => { - const removed = [ - 'TREK_S3_ENDPOINT', - 'TREK_S3_BUCKET', - 'TREK_S3_ACCESS_KEY_ID', - 'TREK_S3_SECRET_ACCESS_KEY', - 'TREK_S3_REGION', - 'TREK_S3_KEY_PREFIX', - 'TREK_S3_RETRIES', - 'TREK_S3_TIMEOUT_MS', - 'TREK_UPLOADS_DIR', - ] as const; - - it.each(removed)('TOMB-001 %s present at boot aborts, naming the replacement', (key) => { - const error = vi.spyOn(console, 'error').mockImplementation(() => {}); - expect(() => validateEnvAtBoot({ [key]: 'anything' })).toThrow( - /Invalid environment configuration \(1 problem\)/, - ); - const report = error.mock.calls.map((c) => c.join(' ')).join('\n'); - expect(report).toContain(`${key} was removed`); - expect(report).toContain('admin UI or data/storage-config.json'); - }); - - it('TOMB-002 TREK_PLACE_PHOTO_DIR survives (documented, prod-supported)', () => { - expect(() => validateEnvAtBoot({ TREK_PLACE_PHOTO_DIR: '/photos' })).not.toThrow(); - }); - - it('TOMB-003 blank values are unset-equivalent and do not trip the tombstone', () => { - expect(() => validateEnvAtBoot({ TREK_UPLOADS_DIR: ' ', TREK_S3_BUCKET: '' })).not.toThrow(); - }); -}); - describe('readEnv', () => { it('reads process.env live — a runtime mutation is visible on the next call', () => { const before = process.env.DEMO_MODE; diff --git a/server/tests/unit/nest/storage/storage-registry.service.test.ts b/server/tests/unit/nest/storage/storage-registry.service.test.ts index ab226c796..010a3803d 100644 --- a/server/tests/unit/nest/storage/storage-registry.service.test.ts +++ b/server/tests/unit/nest/storage/storage-registry.service.test.ts @@ -86,10 +86,10 @@ interface RegistryOpts { } /** - * TREK_UPLOADS_DIR is removed: the uploads-local root is the computed default, - * relocatable only via a settings override row bearing the built-in's name - * (first-class merge-by-name). The helper seeds that override so unit runs - * never write into the real server/uploads. + * The uploads-local root is the computed default, relocatable only via a + * settings override row bearing the built-in's name (first-class + * merge-by-name). The helper seeds that override so unit runs never write + * into the real server/uploads. */ function makeRegistry(opts: RegistryOpts = {}) { const uploadsRoot = opts.uploadsRoot ?? makeTmpDir();