From 3efa5ef2ea81d4d138ecfa1417795f164622cd94 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 23:33:58 +0000 Subject: [PATCH 1/4] perf(server): cap SQLite WAL size after checkpoints The WAL file grows to its largest-ever size and never shrinks. Set journal_size_limit so SQLite truncates it back to 16 MiB whenever a checkpoint resets the log. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01Y1jWssjvT14xf2uw1daK7Z --- .../src/persistence/Layers/Sqlite.test.ts | 27 +++++++++++++++++++ apps/server/src/persistence/Layers/Sqlite.ts | 2 ++ 2 files changed, 29 insertions(+) diff --git a/apps/server/src/persistence/Layers/Sqlite.test.ts b/apps/server/src/persistence/Layers/Sqlite.test.ts index 0b64e4f7fdcb..a7b97fc0c6b5 100644 --- a/apps/server/src/persistence/Layers/Sqlite.test.ts +++ b/apps/server/src/persistence/Layers/Sqlite.test.ts @@ -64,3 +64,30 @@ it.effect("applies busy_timeout in the shared persistence setup", () => assert.equal(rows[0]?.timeout, 5000); }).pipe(Effect.provide(SqlitePersistenceMemory)), ); + +it.effect("truncates the WAL back to the size limit after a checkpoint", () => { + const tempDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-sqlite-wal-")); + const dbPath = NodePath.join(tempDir, "state.sqlite"); + const walSize = () => NodeFS.statSync(`${dbPath}-wal`).size; + const limit = 16 * 1024 * 1024; + + return Effect.gen(function* () { + const sql = yield* SqlClient.SqlClient; + yield* sql`CREATE TABLE wal_probe(data BLOB)`; + yield* sql.withTransaction( + Effect.forEach( + Array.from({ length: 24 }), + () => sql`INSERT INTO wal_probe(data) VALUES (${new Uint8Array(1024 * 1024)})`, + ), + ); + assert.isAbove(walSize(), limit); + + // The next writes cross the autocheckpoint threshold, reset the WAL, and truncate it. + yield* sql`DELETE FROM wal_probe`; + yield* sql`INSERT INTO wal_probe(data) VALUES (${new Uint8Array(1)})`; + assert.isAtMost(walSize(), limit); + }).pipe( + Effect.provide(makeSqlitePersistenceLive(dbPath).pipe(Layer.provide(NodeServices.layer))), + Effect.ensuring(Effect.sync(() => NodeFS.rmSync(tempDir, { recursive: true, force: true }))), + ); +}); diff --git a/apps/server/src/persistence/Layers/Sqlite.ts b/apps/server/src/persistence/Layers/Sqlite.ts index 88342cbf1fad..9823c43993a7 100644 --- a/apps/server/src/persistence/Layers/Sqlite.ts +++ b/apps/server/src/persistence/Layers/Sqlite.ts @@ -15,6 +15,8 @@ const setup = Layer.effectDiscard( yield* sql`PRAGMA busy_timeout = 5000;`; yield* sql`PRAGMA foreign_keys = ON;`; yield* sql`PRAGMA journal_mode = WAL;`; + // WAL files never shrink on their own; truncate back to 16 MiB after each checkpoint reset. + yield* sql`PRAGMA journal_size_limit = 16777216;`; yield* runMigrations(); }), ); From 252fd532d9f996da0a1ed51c2a0d818afc260585 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 23:36:21 +0000 Subject: [PATCH 2/4] perf(server): prune expired DPoP and cloud replay-guard files Every DPoP-authenticated request and cloud health/mint call writes a single-use marker to the secrets directory, and nothing ever removed them. Proofs are only accepted for a few minutes, so sweep markers older than an hour on startup and every ten minutes. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01Y1jWssjvT14xf2uw1daK7Z --- .../server/src/auth/ServerSecretStore.test.ts | 35 ++++++++++++++++ apps/server/src/auth/ServerSecretStore.ts | 40 +++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/apps/server/src/auth/ServerSecretStore.test.ts b/apps/server/src/auth/ServerSecretStore.test.ts index 6b7850aa363b..39cde0208552 100644 --- a/apps/server/src/auth/ServerSecretStore.test.ts +++ b/apps/server/src/auth/ServerSecretStore.test.ts @@ -6,8 +6,10 @@ import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; +import * as Path from "effect/Path"; import * as Ref from "effect/Ref"; import * as PlatformError from "effect/PlatformError"; +import * as TestClock from "effect/testing/TestClock"; import * as ServerConfig from "../config.ts"; import * as ServerSecretStore from "./ServerSecretStore.ts"; @@ -265,4 +267,37 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => { assert.equal((error.cause as PlatformError.PlatformError).reason._tag, "PermissionDenied"); }).pipe(Effect.provide(makeRemoveFailureSecretStoreLayer())), ); + + it.effect("prunes replay-guard files older than the retention window", () => + Effect.gen(function* () { + const secretStore = yield* ServerSecretStore.ServerSecretStore; + const { secretsDir } = yield* ServerConfig.ServerConfig; + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const bytes = Uint8Array.from([1]); + + const names = [ + "dpop-proof-old", + "cloud-mint-nonce-old", + "cloud-health-jti-old", + "dpop-proof-fresh", + "session-signing-key", + ]; + yield* Effect.forEach(names, (name) => secretStore.create(name, bytes), { discard: true }); + yield* Effect.forEach( + ["dpop-proof-old", "cloud-mint-nonce-old", "cloud-health-jti-old", "session-signing-key"], + (name) => fileSystem.utimes(path.join(secretsDir, `${name}.bin`), 0, 0), + { discard: true }, + ); + const fresh = yield* fileSystem.stat(path.join(secretsDir, "dpop-proof-fresh.bin")); + yield* TestClock.setTime(Option.getOrThrow(fresh.mtime).getTime()); + + yield* ServerSecretStore.pruneReplayGuards(secretsDir); + + const remaining = yield* Effect.forEach(names, (name) => + secretStore.get(name).pipe(Effect.map(Option.isSome)), + ); + assert.deepEqual(remaining, [false, false, false, true, true]); + }).pipe(Effect.provide(Layer.provideMerge(ServerSecretStore.layer, makeServerConfigLayer()))), + ); }); diff --git a/apps/server/src/auth/ServerSecretStore.ts b/apps/server/src/auth/ServerSecretStore.ts index c386f7e51d3c..621b020c824b 100644 --- a/apps/server/src/auth/ServerSecretStore.ts +++ b/apps/server/src/auth/ServerSecretStore.ts @@ -1,5 +1,7 @@ +import * as Clock from "effect/Clock"; import * as Context from "effect/Context"; import * as Crypto from "effect/Crypto"; +import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; @@ -7,6 +9,7 @@ import * as Option from "effect/Option"; import * as Path from "effect/Path"; import * as Predicate from "effect/Predicate"; import * as PlatformError from "effect/PlatformError"; +import * as Schedule from "effect/Schedule"; import * as Schema from "effect/Schema"; import * as ServerConfig from "../config.ts"; @@ -149,6 +152,37 @@ export class ServerSecretStore extends Context.Service< } >()("t3/auth/ServerSecretStore") {} +// Single-use DPoP and cloud-proof replay markers. Each proof is only accepted for a few +// minutes after it is issued, so a marker older than the retention window guards nothing. +const REPLAY_GUARD_FILE = /^(?:dpop-proof-|cloud-(?:mint|health)-(?:nonce|jti)-).*\.bin$/; +const REPLAY_GUARD_RETENTION = Duration.hours(1); +const REPLAY_GUARD_SWEEP_INTERVAL = Duration.minutes(10); + +/** Removes replay-guard files in `secretsDir` last written before the retention window. */ +export const pruneReplayGuards = Effect.fn("ServerSecretStore.pruneReplayGuards")(function* ( + secretsDir: string, +) { + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const cutoff = (yield* Clock.currentTimeMillis) - Duration.toMillis(REPLAY_GUARD_RETENTION); + const names = yield* fileSystem.readDirectory(secretsDir); + yield* Effect.forEach( + names.filter((name) => REPLAY_GUARD_FILE.test(name)), + (name) => { + const filePath = path.join(secretsDir, name); + return fileSystem.stat(filePath).pipe( + Effect.flatMap((info) => + Option.exists(info.mtime, (mtime) => mtime.getTime() < cutoff) + ? fileSystem.remove(filePath) + : Effect.void, + ), + Effect.ignore, + ); + }, + { concurrency: 16, discard: true }, + ); +}); + /** @public Service construction is part of the canonical Effect module API. */ export const make = Effect.gen(function* () { const crypto = yield* Crypto.Crypto; @@ -167,6 +201,12 @@ export const make = Effect.gen(function* () { ), ); + yield* pruneReplayGuards(serverConfig.secretsDir).pipe( + Effect.catch((error) => Effect.logWarning("secret-store.replay-guard-prune-failed", { error })), + Effect.repeat(Schedule.spaced(REPLAY_GUARD_SWEEP_INTERVAL)), + Effect.forkScoped, + ); + const resolveSecretPath = (name: string) => path.join(serverConfig.secretsDir, `${name}.bin`); const get: ServerSecretStore["Service"]["get"] = (name) => From a929298ed38c9cd44445112c43c7140726409b26 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 23:41:19 +0000 Subject: [PATCH 3/4] fix(server): log a bounded category when replay-guard pruning fails Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01Y1jWssjvT14xf2uw1daK7Z --- apps/server/src/auth/ServerSecretStore.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/server/src/auth/ServerSecretStore.ts b/apps/server/src/auth/ServerSecretStore.ts index 621b020c824b..7366019fc011 100644 --- a/apps/server/src/auth/ServerSecretStore.ts +++ b/apps/server/src/auth/ServerSecretStore.ts @@ -202,7 +202,11 @@ export const make = Effect.gen(function* () { ); yield* pruneReplayGuards(serverConfig.secretsDir).pipe( - Effect.catch((error) => Effect.logWarning("secret-store.replay-guard-prune-failed", { error })), + Effect.catch((error) => + Effect.logWarning("secret-store.replay-guard-prune-failed", { + category: error.reason._tag, + }), + ), Effect.repeat(Schedule.spaced(REPLAY_GUARD_SWEEP_INTERVAL)), Effect.forkScoped, ); From d9d6ab551b7d47783dd397b1f02fbd884152be14 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 23:53:01 +0000 Subject: [PATCH 4/4] revert(server): drop replay-guard sweep in favor of #13695 #13695 already adds the same sweep, so this PR keeps only the SQLite WAL cap. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01Y1jWssjvT14xf2uw1daK7Z --- .../server/src/auth/ServerSecretStore.test.ts | 35 --------------- apps/server/src/auth/ServerSecretStore.ts | 44 ------------------- 2 files changed, 79 deletions(-) diff --git a/apps/server/src/auth/ServerSecretStore.test.ts b/apps/server/src/auth/ServerSecretStore.test.ts index 39cde0208552..6b7850aa363b 100644 --- a/apps/server/src/auth/ServerSecretStore.test.ts +++ b/apps/server/src/auth/ServerSecretStore.test.ts @@ -6,10 +6,8 @@ import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; -import * as Path from "effect/Path"; import * as Ref from "effect/Ref"; import * as PlatformError from "effect/PlatformError"; -import * as TestClock from "effect/testing/TestClock"; import * as ServerConfig from "../config.ts"; import * as ServerSecretStore from "./ServerSecretStore.ts"; @@ -267,37 +265,4 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => { assert.equal((error.cause as PlatformError.PlatformError).reason._tag, "PermissionDenied"); }).pipe(Effect.provide(makeRemoveFailureSecretStoreLayer())), ); - - it.effect("prunes replay-guard files older than the retention window", () => - Effect.gen(function* () { - const secretStore = yield* ServerSecretStore.ServerSecretStore; - const { secretsDir } = yield* ServerConfig.ServerConfig; - const fileSystem = yield* FileSystem.FileSystem; - const path = yield* Path.Path; - const bytes = Uint8Array.from([1]); - - const names = [ - "dpop-proof-old", - "cloud-mint-nonce-old", - "cloud-health-jti-old", - "dpop-proof-fresh", - "session-signing-key", - ]; - yield* Effect.forEach(names, (name) => secretStore.create(name, bytes), { discard: true }); - yield* Effect.forEach( - ["dpop-proof-old", "cloud-mint-nonce-old", "cloud-health-jti-old", "session-signing-key"], - (name) => fileSystem.utimes(path.join(secretsDir, `${name}.bin`), 0, 0), - { discard: true }, - ); - const fresh = yield* fileSystem.stat(path.join(secretsDir, "dpop-proof-fresh.bin")); - yield* TestClock.setTime(Option.getOrThrow(fresh.mtime).getTime()); - - yield* ServerSecretStore.pruneReplayGuards(secretsDir); - - const remaining = yield* Effect.forEach(names, (name) => - secretStore.get(name).pipe(Effect.map(Option.isSome)), - ); - assert.deepEqual(remaining, [false, false, false, true, true]); - }).pipe(Effect.provide(Layer.provideMerge(ServerSecretStore.layer, makeServerConfigLayer()))), - ); }); diff --git a/apps/server/src/auth/ServerSecretStore.ts b/apps/server/src/auth/ServerSecretStore.ts index 7366019fc011..c386f7e51d3c 100644 --- a/apps/server/src/auth/ServerSecretStore.ts +++ b/apps/server/src/auth/ServerSecretStore.ts @@ -1,7 +1,5 @@ -import * as Clock from "effect/Clock"; import * as Context from "effect/Context"; import * as Crypto from "effect/Crypto"; -import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; @@ -9,7 +7,6 @@ import * as Option from "effect/Option"; import * as Path from "effect/Path"; import * as Predicate from "effect/Predicate"; import * as PlatformError from "effect/PlatformError"; -import * as Schedule from "effect/Schedule"; import * as Schema from "effect/Schema"; import * as ServerConfig from "../config.ts"; @@ -152,37 +149,6 @@ export class ServerSecretStore extends Context.Service< } >()("t3/auth/ServerSecretStore") {} -// Single-use DPoP and cloud-proof replay markers. Each proof is only accepted for a few -// minutes after it is issued, so a marker older than the retention window guards nothing. -const REPLAY_GUARD_FILE = /^(?:dpop-proof-|cloud-(?:mint|health)-(?:nonce|jti)-).*\.bin$/; -const REPLAY_GUARD_RETENTION = Duration.hours(1); -const REPLAY_GUARD_SWEEP_INTERVAL = Duration.minutes(10); - -/** Removes replay-guard files in `secretsDir` last written before the retention window. */ -export const pruneReplayGuards = Effect.fn("ServerSecretStore.pruneReplayGuards")(function* ( - secretsDir: string, -) { - const fileSystem = yield* FileSystem.FileSystem; - const path = yield* Path.Path; - const cutoff = (yield* Clock.currentTimeMillis) - Duration.toMillis(REPLAY_GUARD_RETENTION); - const names = yield* fileSystem.readDirectory(secretsDir); - yield* Effect.forEach( - names.filter((name) => REPLAY_GUARD_FILE.test(name)), - (name) => { - const filePath = path.join(secretsDir, name); - return fileSystem.stat(filePath).pipe( - Effect.flatMap((info) => - Option.exists(info.mtime, (mtime) => mtime.getTime() < cutoff) - ? fileSystem.remove(filePath) - : Effect.void, - ), - Effect.ignore, - ); - }, - { concurrency: 16, discard: true }, - ); -}); - /** @public Service construction is part of the canonical Effect module API. */ export const make = Effect.gen(function* () { const crypto = yield* Crypto.Crypto; @@ -201,16 +167,6 @@ export const make = Effect.gen(function* () { ), ); - yield* pruneReplayGuards(serverConfig.secretsDir).pipe( - Effect.catch((error) => - Effect.logWarning("secret-store.replay-guard-prune-failed", { - category: error.reason._tag, - }), - ), - Effect.repeat(Schedule.spaced(REPLAY_GUARD_SWEEP_INTERVAL)), - Effect.forkScoped, - ); - const resolveSecretPath = (name: string) => path.join(serverConfig.secretsDir, `${name}.bin`); const get: ServerSecretStore["Service"]["get"] = (name) =>