Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
917434a
feat(cli): publish dedicated proxy binary
kwakayama Aug 2, 2026
59fbf93
fix(cli): harden proxy release contract
kwakayama Aug 2, 2026
dcf242a
Keep proxy release checks robust under extension mutation
kojiwakayama Aug 3, 2026
e63d67c
Reconcile the dedicated proxy release with current main
kojiwakayama Aug 3, 2026
4ac1e19
Preserve fork safety in the proxy smoke gate
kojiwakayama Aug 3, 2026
4829857
Keep the proxy dependency lock aligned with current main
kojiwakayama Aug 3, 2026
c8b8761
Keep proxy smoke checks stable during asynchronous startup
kojiwakayama Aug 3, 2026
30254f7
Reconcile the proxy artifact with the released framework baseline
kojiwakayama Aug 3, 2026
e99381d
Keep proxy script tests frozen after baseline reconciliation
kojiwakayama Aug 3, 2026
0a98e7a
Clarify standalone proxy provider activation
kojiwakayama Aug 3, 2026
6811e51
fix(platform): close filesystem hardening review gaps (#3323)
kojiwakayama Aug 3, 2026
0c30c9c
Merge remote-tracking branch 'origin/main' into codex/proxy-specific-…
kojiwakayama Aug 3, 2026
07cd1f4
Diagnose invalid CACHE_TYPE values and mark the rollout shim for removal
kojiwakayama Aug 3, 2026
e6083e7
fix(security): bind hosted source and environment identity (#3290)
kojiwakayama Aug 3, 2026
3192cea
Protect proxy binary release from hidden regressions
kojiwakayama Aug 3, 2026
f56afcd
fix(security): harden rate limits with extension-owned Redis (#3304)
kojiwakayama Aug 3, 2026
e2a93fc
Reject ambiguous SBOM lock argument
kojiwakayama Aug 3, 2026
f63646c
fix(agent): close alias pollution, unverified project lookups, and fa…
kojiwakayama Aug 3, 2026
37e2680
fix(utils): repair hash, id, extension, sleep, namespace, and memoize…
kojiwakayama Aug 3, 2026
c196ffc
fix(cli): harden deploy polling and route verification (#3311)
kojiwakayama Aug 3, 2026
5d107f1
fix(platform): fail closed on GitHub filesystem config errors (#3333)
kojiwakayama Aug 3, 2026
0d20941
Stabilize stuck worker admission regression (#3336)
kojiwakayama Aug 3, 2026
8c2b6e9
docs(security): document hosted identity rollout order and close #329…
kwakayama Aug 3, 2026
c6d924e
Keep proxy packaging from inheriting renderer memory
kojiwakayama Aug 3, 2026
81d739e
Turn the proxy memory fix into verifiable slices
kojiwakayama Aug 3, 2026
64e4b02
Reuse the proven proxy graph instead of the monolith
kojiwakayama Aug 3, 2026
162b8ff
Keep proxy releases available on both Linux architectures
kojiwakayama Aug 3, 2026
52c6c6c
Keep proxy rollout guidance actionable
kojiwakayama Aug 3, 2026
671df97
Reject proxy artifacts that recreate the staging OOM
kojiwakayama Aug 3, 2026
4d9596a
Make the proxy profile safe without caller conventions
kojiwakayama Aug 3, 2026
0a4b950
Preserve corrected proxy rollout guidance before release
kojiwakayama Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,23 @@ jobs:
# Version comes directly from deno.json
# ============================================

tests-proxy-binary:
if: ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.event_name == 'pull_request' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/setup-deno
- run: deno task build:prepare
- name: Compile proxy binary
run: |
deno run -A scripts/build/compile-binary.ts \
--entrypoint cli/proxy-main.ts \
--profile proxy \
--target x86_64-unknown-linux-gnu \
--output veryfront-proxy-linux-x64
- name: Smoke test proxy binary
run: bash scripts/build/smoke-proxy-binary.sh ./veryfront-proxy-linux-x64

build-binaries:
if: ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.ref == 'refs/heads/main' }}
runs-on: ${{ matrix.os }}
Expand All @@ -417,18 +434,33 @@ jobs:
- os: macos-latest
target: aarch64-apple-darwin
name: veryfront-macos-arm64
entrypoint: cli/main.ts
profile: full
- os: macos-latest
target: x86_64-apple-darwin
name: veryfront-macos-x64
entrypoint: cli/main.ts
profile: full
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
name: veryfront-linux-x64
entrypoint: cli/main.ts
profile: full
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
name: veryfront-linux-arm64
entrypoint: cli/main.ts
profile: full
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
name: veryfront-proxy-linux-x64
entrypoint: cli/proxy-main.ts
profile: proxy
- os: windows-2022
target: x86_64-pc-windows-msvc
name: veryfront-windows-x64.exe
entrypoint: cli/main.ts
profile: full
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/setup-deno
Expand All @@ -439,9 +471,16 @@ jobs:
shell: bash
run: |
deno run -A scripts/build/compile-binary.ts \
--entrypoint ${{ matrix.entrypoint }} \
--profile ${{ matrix.profile }} \
--target ${{ matrix.target }} \
--output ${{ matrix.name }}

- name: Smoke test proxy binary
if: matrix.profile == 'proxy'
shell: bash
run: bash scripts/build/smoke-proxy-binary.sh ./${{ matrix.name }}

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ matrix.name }}
Expand Down
38 changes: 5 additions & 33 deletions cli/commands/serve/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { exitProcess, registerTerminationSignals, showHeader } from "#cli/utils"
import { generateDefaultProjectId } from "../../utils/project.ts";
import { startCliProductionServer } from "#cli/shared/server-startup";
import { ensureCliBundlerContracts } from "#cli/shared/default-contracts";
import { runStandaloneProxyRuntime } from "./proxy-runtime.ts";

const STARTUP_ERROR_FLUSH_TIMEOUT_MS = 2_000;

Expand Down Expand Up @@ -141,39 +142,10 @@ async function runSplit(options: ServeOptions): Promise<void> {
}

async function runProxy(options: ServeOptions): Promise<void> {
showHeader();
cliLogger.info(`Starting proxy server on ${options.bindAddress}:${options.port}`);

const { setEnv } = await import("veryfront/platform");
setEnv("PORT", String(options.port));
setEnv("HOST", options.bindAddress);

const {
activateStandaloneProxyCacheExtension,
registerStandaloneProxyCacheExtensionTeardown,
} = await import(
"./proxy-extension-composition.ts"
);
const extensionLoader = await activateStandaloneProxyCacheExtension();
const teardownCacheExtension = await registerStandaloneProxyCacheExtensionTeardown(
extensionLoader,
);

// DenoHttpServer.serve() blocks until the server stops,
// so this import keeps the process alive.
try {
await import("veryfront/proxy/main");
} catch (error) {
try {
await teardownCacheExtension();
} catch (cleanupError) {
cliLogger.error("Failed to clean up proxy extensions after startup failure", cleanupError);
}
throw error;
}

// Keep the process alive (Deno.serve returns immediately in compiled binaries)
await new Promise(() => {});
await runStandaloneProxyRuntime({
bindAddress: options.bindAddress,
port: options.port,
});
}

function createDeferredProductionStartupErrorReporter(): {
Expand Down
41 changes: 30 additions & 11 deletions cli/commands/serve/proxy-extension-composition.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import { assertEquals, assertRejects, assertStrictEquals } from "#veryfront/test
import { afterEach, describe, it } from "#veryfront/testing/bdd.ts";
import { type ExtensionLoader, tryResolve } from "veryfront/extensions";
import type { TokenCacheStore } from "#veryfront/extensions/cache/index.ts";
import { RedisRuntimeProviderName } from "#veryfront/extensions/distributed/index.ts";
import { createCacheFromEnv, TracingTokenCache } from "#veryfront/proxy/cache/index.ts";
import { acquireExtensionTokenCacheStoreFromEnv } from "#veryfront/proxy/cache/extension-store.ts";
import { createProxyShutdownHooks } from "#veryfront/proxy/shutdown-hooks.ts";
import {
activateStandaloneProxyCacheExtension,
registerStandaloneProxyCacheExtensionTeardown,
activateStandaloneProxyExtensions,
registerStandaloneProxyExtensionTeardown,
} from "./proxy-extension-composition.ts";

describe("standalone proxy extension composition", () => {
Expand All @@ -28,8 +29,9 @@ 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();
loader = await activateStandaloneProxyExtensions();

assertEquals(loader, null);
});
Expand All @@ -38,12 +40,13 @@ describe("standalone proxy extension composition", () => {
Deno.env.set("CACHE_TYPE", "extension");
Deno.env.set("REDIS_URL", "redis://127.0.0.1:6379");

loader = await activateStandaloneProxyCacheExtension();
loader = await activateStandaloneProxyExtensions();
const shutdownHooks = createProxyShutdownHooks();
await registerStandaloneProxyCacheExtensionTeardown(loader, shutdownHooks.register);
await registerStandaloneProxyExtensionTeardown(loader, shutdownHooks.register);
const acquisition = await acquireExtensionTokenCacheStoreFromEnv();

assertEquals(loader !== null, true);
assertEquals(tryResolve(RedisRuntimeProviderName) !== undefined, true);
assertEquals(acquisition.kind, "borrowed");
assertStrictEquals(
acquisition.store,
Expand All @@ -60,17 +63,33 @@ describe("standalone proxy extension composition", () => {

assertEquals(await shutdownHooks.settle(), []);
assertEquals(tryResolve<TokenCacheStore>("TokenCacheStore"), undefined);
assertEquals(tryResolve(RedisRuntimeProviderName), undefined);
loader = null;
});

it("activates the Redis runtime for routing invalidation in memory-cache mode", async () => {
Deno.env.set("CACHE_TYPE", "memory");
Deno.env.set("REDIS_URL", "redis://127.0.0.1:6379");

loader = await activateStandaloneProxyExtensions();

assertEquals(loader !== null, true);
assertEquals(tryResolve<TokenCacheStore>("TokenCacheStore"), undefined);
assertEquals(tryResolve(RedisRuntimeProviderName) !== undefined, true);

await loader?.teardownAll();
loader = null;
assertEquals(tryResolve(RedisRuntimeProviderName), undefined);
});

it("tears down the provider when shutdown registration fails", async () => {
Deno.env.set("CACHE_TYPE", "extension");
Deno.env.set("REDIS_URL", "redis://127.0.0.1:6379");
loader = await activateStandaloneProxyCacheExtension();
loader = await activateStandaloneProxyExtensions();

await assertRejects(
() =>
registerStandaloneProxyCacheExtensionTeardown(loader, () => {
registerStandaloneProxyExtensionTeardown(loader, () => {
throw new Error("shutdown registration failed");
}),
Error,
Expand All @@ -83,8 +102,8 @@ describe("standalone proxy extension composition", () => {
it("tears down the provider when shutdown-hook disposal fails", async () => {
Deno.env.set("CACHE_TYPE", "extension");
Deno.env.set("REDIS_URL", "redis://127.0.0.1:6379");
loader = await activateStandaloneProxyCacheExtension();
const teardown = await registerStandaloneProxyCacheExtensionTeardown(
loader = await activateStandaloneProxyExtensions();
const teardown = await registerStandaloneProxyExtensionTeardown(
loader,
() => () => {
throw new Error("shutdown-hook disposal failed");
Expand All @@ -103,7 +122,7 @@ describe("standalone proxy extension composition", () => {
it("uses Promise intrinsics captured before extension-owned mutation", async () => {
Deno.env.set("CACHE_TYPE", "extension");
Deno.env.set("REDIS_URL", "redis://127.0.0.1:6379");
loader = await activateStandaloneProxyCacheExtension();
loader = await activateStandaloneProxyExtensions();
const resolveDescriptor = Object.getOwnPropertyDescriptor(Promise, "resolve")!;
let registration: Promise<() => Promise<void>> | undefined;

Expand All @@ -114,7 +133,7 @@ describe("standalone proxy extension composition", () => {
throw new Error("poisoned Promise.resolve");
},
});
registration = registerStandaloneProxyCacheExtensionTeardown(
registration = registerStandaloneProxyExtensionTeardown(
loader,
() => () => undefined,
);
Expand Down
86 changes: 54 additions & 32 deletions cli/commands/serve/proxy-extension-composition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@
* `TokenCacheStore` contract published by the extension loader.
*/

import { cliLogger } from "#cli/utils";
import { type ExtensionFactory, ExtensionLoader } from "veryfront/extensions";
import { cliLogger } from "veryfront/utils/logger";
import { ExtensionLoader } from "veryfront/extensions/loader";
import type { ExtensionFactory } from "veryfront/extensions/types";
import { importFirstPartyExtensionModule } from "veryfront/extensions/first-party-import";
import { getEnv } from "veryfront/platform";
import { getEnv } from "veryfront/platform/env";
import {
createProxyShutdownAggregateError,
type RegisterProxyShutdownHook,
registerProxyShutdownHook,
} from "veryfront/proxy/shutdown-hooks";

type CacheExtensionModule = Readonly<{ default: ExtensionFactory }>;
type ProxyExtensionModule = 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
Expand All @@ -42,54 +43,75 @@ 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";

/**
* 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 cache and Redis runtime
* providers. The returned loader owns provider teardown.
*/
async function activateStandaloneProxyCacheExtensionInternal(): Promise<ExtensionLoader | null> {
async function activateStandaloneProxyExtensionsInternal(): Promise<ExtensionLoader | null> {
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<CacheExtensionModule>(
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 selected: Array<{
origin: string;
packageName: string;
sourceDirectory: string;
}> = [];
if (cacheType === "extension") {
selected.push({
origin: "standalone proxy cache selection",
packageName: CACHE_EXTENSION_PACKAGE_NAME,
sourceDirectory: CACHE_EXTENSION_SOURCE_DIRECTORY,
});
}
if (getEnv("REDIS_URL")) {
selected.push({
origin: "standalone proxy routing invalidation",
packageName: REDIS_EXTENSION_PACKAGE_NAME,
sourceDirectory: REDIS_EXTENSION_SOURCE_DIRECTORY,
});
}
if (selected.length === 0) return null;

const extensions = await NativePromise.all(selected.map(async (definition) => {
const module = await importFirstPartyExtensionModule<ProxyExtensionModule>(
definition.sourceDirectory,
definition.packageName,
);
if (typeof module.default !== "function") {
throw new NativeTypeError(`${definition.packageName} must export an ExtensionFactory`);
}
return {
extension: module.default(),
source: "config" as const,
origin: definition.origin,
};
}));

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 {
await loader.teardownAll();
} catch (cleanupError) {
cliLogger.error("Failed to clean up standalone proxy cache extension", cleanupError);
cliLogger.error("Failed to clean up standalone proxy extensions", cleanupError);
}
throw error;
}
}

/** Start explicit standalone cache composition with a pinned lifecycle promise. */
export function activateStandaloneProxyCacheExtension(): Promise<ExtensionLoader | null> {
return pinCompositionPromise(activateStandaloneProxyCacheExtensionInternal());
/** Start explicit standalone proxy extension composition. */
export function activateStandaloneProxyExtensions(): Promise<ExtensionLoader | null> {
return pinCompositionPromise(activateStandaloneProxyExtensionsInternal());
}

async function registerStandaloneProxyCacheExtensionTeardownInternal(
async function registerStandaloneProxyExtensionTeardownInternal(
loader: ExtensionLoader | null,
registerHook: RegisterProxyShutdownHook,
): Promise<() => Promise<void>> {
Expand All @@ -114,7 +136,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 extension teardown",
);
}
throw error;
Expand All @@ -137,7 +159,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 extensions",
);
}
throw teardownError;
Expand All @@ -147,11 +169,11 @@ async function registerStandaloneProxyCacheExtensionTeardownInternal(
}

/** Register exactly-once provider teardown with the proxy's shutdown owner. */
export function registerStandaloneProxyCacheExtensionTeardown(
export function registerStandaloneProxyExtensionTeardown(
loader: ExtensionLoader | null,
registerHook: RegisterProxyShutdownHook = registerProxyShutdownHook,
): Promise<() => Promise<void>> {
return pinCompositionPromise(
registerStandaloneProxyCacheExtensionTeardownInternal(loader, registerHook),
registerStandaloneProxyExtensionTeardownInternal(loader, registerHook),
);
}
Loading