From c13ed4b69138d2ef923ebb2a6e6d23e3b2f8a196 Mon Sep 17 00:00:00 2001 From: Kentaro Wakayama Date: Mon, 3 Aug 2026 18:45:22 +0200 Subject: [PATCH 1/6] fix(proxy): activate redis runtime provider --- .../serve/proxy-extension-composition.test.ts | 13 ++++ .../serve/proxy-extension-composition.ts | 67 +++++++++++++------ 2 files changed, 61 insertions(+), 19 deletions(-) diff --git a/cli/commands/serve/proxy-extension-composition.test.ts b/cli/commands/serve/proxy-extension-composition.test.ts index fa65cf7dce..befb698044 100644 --- a/cli/commands/serve/proxy-extension-composition.test.ts +++ b/cli/commands/serve/proxy-extension-composition.test.ts @@ -3,6 +3,7 @@ import "#veryfront/schemas/_test-setup.ts"; import { assertEquals, assertRejects, assertStrictEquals } from "#veryfront/testing/assert.ts"; import { afterEach, describe, it } from "#veryfront/testing/bdd.ts"; import { type ExtensionLoader, tryResolve } from "veryfront/extensions"; +import { RedisRuntimeProviderName } from "veryfront/extensions/distributed"; import type { TokenCacheStore } from "#veryfront/extensions/cache/index.ts"; import { createCacheFromEnv, TracingTokenCache } from "#veryfront/proxy/cache/index.ts"; import { acquireExtensionTokenCacheStoreFromEnv } from "#veryfront/proxy/cache/extension-store.ts"; @@ -28,12 +29,23 @@ describe("standalone proxy extension composition", () => { it("does not import or activate a cache provider for the memory backend", async () => { Deno.env.set("CACHE_TYPE", "memory"); + Deno.env.delete("REDIS_URL"); loader = await activateStandaloneProxyCacheExtension(); assertEquals(loader, null); }); + it("activates ext-redis when routing invalidation uses Redis", async () => { + Deno.env.set("CACHE_TYPE", "memory"); + Deno.env.set("REDIS_URL", "redis://127.0.0.1:6379"); + + loader = await activateStandaloneProxyCacheExtension(); + + assertEquals(loader !== null, true); + assertEquals(tryResolve(RedisRuntimeProviderName) !== undefined, true); + }); + it("activates ext-cache-redis before standalone cache acquisition", async () => { Deno.env.set("CACHE_TYPE", "extension"); Deno.env.set("REDIS_URL", "redis://127.0.0.1:6379"); @@ -44,6 +56,7 @@ describe("standalone proxy extension composition", () => { const acquisition = await acquireExtensionTokenCacheStoreFromEnv(); assertEquals(loader !== null, true); + assertEquals(tryResolve(RedisRuntimeProviderName) !== undefined, true); assertEquals(acquisition.kind, "borrowed"); assertStrictEquals( acquisition.store, diff --git a/cli/commands/serve/proxy-extension-composition.ts b/cli/commands/serve/proxy-extension-composition.ts index bab7a657a3..8ba235131f 100644 --- a/cli/commands/serve/proxy-extension-composition.ts +++ b/cli/commands/serve/proxy-extension-composition.ts @@ -16,7 +16,7 @@ import { registerProxyShutdownHook, } from "veryfront/proxy/shutdown-hooks"; -type CacheExtensionModule = Readonly<{ default: ExtensionFactory }>; +type FirstPartyExtensionModule = Readonly<{ default: ExtensionFactory }>; // This module is evaluated before extension activation. Pin promises created // by the later teardown path so extension code cannot replace Promise species @@ -42,37 +42,66 @@ const resolvedBeforeExtensionActivation = pinCompositionPromise( const CACHE_EXTENSION_SOURCE_DIRECTORY = "ext-cache-redis"; const CACHE_EXTENSION_PACKAGE_NAME = "@veryfront/ext-cache-redis"; +const REDIS_EXTENSION_SOURCE_DIRECTORY = "ext-redis"; +const REDIS_EXTENSION_PACKAGE_NAME = "@veryfront/ext-redis"; + +async function loadFirstPartyExtension( + sourceDirectory: string, + packageName: string, +): Promise { + const module = await importFirstPartyExtensionModule( + sourceDirectory, + packageName, + ); + if (typeof module.default !== "function") { + throw new NativeTypeError(`${packageName} must export an ExtensionFactory`); + } + return module.default; +} /** - * Activate the standalone proxy's explicitly selected cache extension. - * Memory mode performs no extension import. The returned loader owns provider - * teardown; the proxy borrows its registered `TokenCacheStore`. + * Activate the standalone proxy's explicitly selected infrastructure extensions. + * The returned loader owns provider teardown; the proxy borrows the registered + * Redis runtime and optional `TokenCacheStore` providers. */ async function activateStandaloneProxyCacheExtensionInternal(): Promise { const cacheType = getEnv("CACHE_TYPE") || "memory"; if (cacheType !== "memory" && cacheType !== "extension") { throw new NativeTypeError("CACHE_TYPE must be memory or extension"); } - if (cacheType === "memory") return null; - const module = await importFirstPartyExtensionModule( - CACHE_EXTENSION_SOURCE_DIRECTORY, - CACHE_EXTENSION_PACKAGE_NAME, - ); - if (typeof module.default !== "function") { - throw new NativeTypeError(`${CACHE_EXTENSION_PACKAGE_NAME} must export an ExtensionFactory`); + const extensions: Array<{ + extension: ReturnType; + origin: string; + source: "config"; + }> = []; + if (getEnv("REDIS_URL")) { + const redisExtension = await loadFirstPartyExtension( + REDIS_EXTENSION_SOURCE_DIRECTORY, + REDIS_EXTENSION_PACKAGE_NAME, + ); + extensions.push({ + extension: redisExtension(), + source: "config", + origin: "standalone proxy Redis runtime", + }); } + if (cacheType === "extension") { + const cacheExtension = await loadFirstPartyExtension( + CACHE_EXTENSION_SOURCE_DIRECTORY, + CACHE_EXTENSION_PACKAGE_NAME, + ); + extensions.push({ + extension: cacheExtension(), + source: "config", + origin: "standalone proxy cache selection", + }); + } + if (extensions.length === 0) return null; const loader = new ExtensionLoader(cliLogger); try { - await loader.setupAll( - [{ - extension: module.default(), - source: "config", - origin: "standalone proxy cache selection", - }], - {}, - ); + await loader.setupAll(extensions, {}); return loader; } catch (error) { try { From 0073b759bfd2b58192d83499f5c4c6ae24915585 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 18:58:14 +0200 Subject: [PATCH 2/6] Keep proxy infrastructure cleanup diagnostics accurate Name the full infrastructure extension set in cleanup failures because the activation path now owns both Redis runtime and optional token-cache providers. Constraint: The activation loader can contain multiple infrastructure extensions Rejected: Keep cache-only wording | misidentifies Redis runtime teardown failures Confidence: high Scope-risk: narrow Reversibility: clean Tested: proxy extension composition suite (6 steps), deno fmt, lint, check, git diff --check Not-tested: Full repository suite before commit --- cli/commands/serve/proxy-extension-composition.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cli/commands/serve/proxy-extension-composition.ts b/cli/commands/serve/proxy-extension-composition.ts index 8ba235131f..76066283b0 100644 --- a/cli/commands/serve/proxy-extension-composition.ts +++ b/cli/commands/serve/proxy-extension-composition.ts @@ -107,7 +107,10 @@ async function activateStandaloneProxyCacheExtensionInternal(): Promise Date: Mon, 3 Aug 2026 19:09:17 +0200 Subject: [PATCH 3/6] fix(proxy): align infrastructure teardown diagnostics --- cli/commands/serve/proxy-extension-composition.test.ts | 4 ++++ cli/commands/serve/proxy-extension-composition.ts | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cli/commands/serve/proxy-extension-composition.test.ts b/cli/commands/serve/proxy-extension-composition.test.ts index befb698044..54cf670d17 100644 --- a/cli/commands/serve/proxy-extension-composition.test.ts +++ b/cli/commands/serve/proxy-extension-composition.test.ts @@ -73,6 +73,7 @@ describe("standalone proxy extension composition", () => { assertEquals(await shutdownHooks.settle(), []); assertEquals(tryResolve("TokenCacheStore"), undefined); + assertEquals(tryResolve(RedisRuntimeProviderName), undefined); loader = null; }); @@ -90,6 +91,7 @@ describe("standalone proxy extension composition", () => { "shutdown registration failed", ); assertEquals(tryResolve("TokenCacheStore"), undefined); + assertEquals(tryResolve(RedisRuntimeProviderName), undefined); loader = null; }); @@ -110,6 +112,7 @@ describe("standalone proxy extension composition", () => { "shutdown-hook disposal failed", ); assertEquals(tryResolve("TokenCacheStore"), undefined); + assertEquals(tryResolve(RedisRuntimeProviderName), undefined); loader = null; }); @@ -150,6 +153,7 @@ describe("standalone proxy extension composition", () => { } await cleanup; assertEquals(tryResolve("TokenCacheStore"), undefined); + assertEquals(tryResolve(RedisRuntimeProviderName), undefined); loader = null; }); }); diff --git a/cli/commands/serve/proxy-extension-composition.ts b/cli/commands/serve/proxy-extension-composition.ts index 76066283b0..2d145b6c6a 100644 --- a/cli/commands/serve/proxy-extension-composition.ts +++ b/cli/commands/serve/proxy-extension-composition.ts @@ -146,7 +146,7 @@ async function registerStandaloneProxyCacheExtensionTeardownInternal( } catch (cleanupError) { throw createProxyShutdownAggregateError( [error, cleanupError], - "Failed to register and clean up standalone proxy cache extension teardown", + "Failed to register and clean up standalone proxy infrastructure extension teardown", ); } throw error; @@ -169,7 +169,7 @@ async function registerStandaloneProxyCacheExtensionTeardownInternal( if (disposalFailed) { throw createProxyShutdownAggregateError( [disposalError, teardownError], - "Failed to unregister and tear down standalone proxy cache extension", + "Failed to unregister and tear down standalone proxy infrastructure extensions", ); } throw teardownError; From 8d329a132c5c0ee1c9b114fe56215f3dec06dd3d Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 20:21:50 +0200 Subject: [PATCH 4/6] Keep proxy branch audit on patched brace-expansion The proxy Redis runtime branch now includes current main, whose audit path still resolves brace-expansion 5.0.8 from the sandbox shell extension import map. Pin the extension to the patch release so the branch can pass the same security audit gate before #3340 lands. Constraint: npm audit blocks high-severity advisories on PR branches. Constraint: Use patch-level dependency updates only. Rejected: Wait for the main audit PR to merge first | this PR needs independent green branch checks before queueing. Confidence: high Scope-risk: narrow Tested: npx --yes deno@2.7.7 fmt --check cli/commands/serve/proxy-extension-composition.ts cli/commands/serve/proxy-extension-composition.test.ts extensions/ext-sandbox-shell-tools/deno.json deno.lock Tested: git diff --check Tested: npx --yes deno@2.7.7 task audit Tested: npx --yes deno@2.7.7 lint cli/commands/serve/proxy-extension-composition.ts cli/commands/serve/proxy-extension-composition.test.ts extensions/ext-sandbox-shell-tools/deno.json Tested: npx --yes deno@2.7.7 check cli/commands/serve/proxy-extension-composition.ts cli/commands/serve/proxy-extension-composition.test.ts Tested: VF_DISABLE_LRU_INTERVAL=1 NODE_ENV=production LOG_FORMAT=text npx --yes deno@2.7.7 test --no-check --allow-all cli/commands/serve/proxy-extension-composition.test.ts cli/commands/serve/proxy-runtime.test.ts --- deno.lock | 8 ++++---- extensions/ext-sandbox-shell-tools/deno.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/deno.lock b/deno.lock index 3db661d016..9122020026 100644 --- a/deno.lock +++ b/deno.lock @@ -61,7 +61,7 @@ "npm:ajv@8.18.0": "8.18.0", "npm:bash-tool@1.3.18": "1.3.18_ai@7.0.41__zod@3.25.76_just-bash@3.0.1", "npm:better-sqlite3@9.6.0": "9.6.0", - "npm:brace-expansion@5.0.8": "5.0.8", + "npm:brace-expansion@5.0.9": "5.0.9", "npm:browserslist@4.28.7": "4.28.7", "npm:daisyui@5.5.14": "5.5.14", "npm:es-module-lexer@2.3.1": "2.3.1", @@ -2961,8 +2961,8 @@ "bowser@2.14.1": { "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==" }, - "brace-expansion@5.0.8": { - "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", + "brace-expansion@5.0.9": { + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dependencies": [ "balanced-match" ] @@ -6545,7 +6545,7 @@ "jsr:@std/testing@1.0.17", "npm:ai@7.0.41", "npm:bash-tool@1.3.18", - "npm:brace-expansion@5.0.8", + "npm:brace-expansion@5.0.9", "npm:just-bash@3.0.1" ] }, diff --git a/extensions/ext-sandbox-shell-tools/deno.json b/extensions/ext-sandbox-shell-tools/deno.json index a3e0f55a05..39abf1754d 100644 --- a/extensions/ext-sandbox-shell-tools/deno.json +++ b/extensions/ext-sandbox-shell-tools/deno.json @@ -14,7 +14,7 @@ "imports": { "ai": "npm:ai@7.0.41", "bash-tool": "npm:bash-tool@1.3.18", - "brace-expansion": "npm:brace-expansion@5.0.8", + "brace-expansion": "npm:brace-expansion@5.0.9", "just-bash": "npm:just-bash@3.0.1", "@std/assert": "jsr:@std/assert@1.0.19", "@std/testing/bdd": "jsr:@std/testing@1.0.17/bdd", From 8ff2ac245896b9526ad6aaa3660c7170ad36e110 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 20:24:05 +0200 Subject: [PATCH 5/6] Keep proxy dependency lock on patched brace-expansion The proxy binary CI job regenerates scripts/build/proxy-deno.lock before compiling. After the branch moved the sandbox shell extension to brace-expansion 5.0.9, that generated proxy lock still recorded 5.0.8, so the hosted lock-current check failed before compilation. Constraint: tests-proxy-binary requires the generated proxy lock to be byte-current. Constraint: Use patch-level dependency updates only. Rejected: Remove the lock-current check | it caught the real generated-artifact drift. Confidence: high Scope-risk: narrow Tested: npx --yes deno@2.7.7 task build:proxy-lock Tested: git diff --check --- scripts/build/proxy-deno.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build/proxy-deno.lock b/scripts/build/proxy-deno.lock index e9d40ce6b6..9adb8b0cfa 100644 --- a/scripts/build/proxy-deno.lock +++ b/scripts/build/proxy-deno.lock @@ -1690,7 +1690,7 @@ "jsr:@std/testing@1.0.17", "npm:ai@7.0.41", "npm:bash-tool@1.3.18", - "npm:brace-expansion@5.0.8", + "npm:brace-expansion@5.0.9", "npm:just-bash@3.0.1" ] }, From 3087c00327f902f7a7179a74696a985a67eac6c8 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 20:27:06 +0200 Subject: [PATCH 6/6] Align proxy branch package metadata with patched dependency The proxy branch already updates the sandbox shell extension and proxy lock to brace-expansion 5.0.9, but the npm metadata fixture still carried the previous direct dependency version. This keeps the metadata policy test aligned with the patched dependency so the branch does not clear the proxy lock gate only to fail the adjacent npm package metadata gate. Constraint: Main CI requires the proxy lock and metadata expectations to agree on the patched dependency version. Rejected: Leave the fixture stale because the dependency is filtered out | the fixture is the regression source for package metadata normalization and should reflect current manifests. Confidence: high Scope-risk: narrow Tested: deno task build:proxy-lock with clean proxy lock diff, scripts npm metadata and compile-binary tests, script fmt/lint/check, proxy composition adjacent suites, git diff --check --- scripts/build/npm-package-metadata.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build/npm-package-metadata.test.ts b/scripts/build/npm-package-metadata.test.ts index 614bda6aa8..2a2d9c20c1 100644 --- a/scripts/build/npm-package-metadata.test.ts +++ b/scripts/build/npm-package-metadata.test.ts @@ -404,7 +404,7 @@ describe("normalizeNpmPackageMetadata", () => { "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-node": "0.218.0", "@sentry/deno": "10.68.0", - "brace-expansion": "5.0.8", + "brace-expansion": "5.0.9", "gaxios": "7.2.0", "gcp-metadata": "8.1.2", "protobufjs": "7.6.5",