fix(engine,producer): apply software-GPU screenshot invariant at concrete-resolved point

Addresses Miguel's R1 blockers:

1. `browserGpuMode: "auto"` that runtime-probes to software slipped past the
   `resolveConfig` clamp — that clamp only sees the pre-resolve string. Add
   `shouldClampToScreenshotForConcreteGpu(resolvedGpuMode, currentForceScreenshot, env)`
   in `packages/engine/src/config.ts` and apply it at BOTH concrete-resolution
   sites:
   - `packages/engine/src/services/frameCapture.ts`: downgrades `preMode`
     from "beginframe" to "screenshot" when resolved GPU is software (respects
     `PRODUCER_FORCE_SCREENSHOT=false` env opt-out), fixing the routing.
   - `packages/producer/src/services/renderOrchestrator.ts`: updates
     `captureObservability.forceScreenshot` (and thus `captureMode`) at the
     same call site, fixing the observability truth on the auto → software
     case.

2. New unit tests in `config.test.ts`:
   - Documents the auto-branch gap (resolveConfig leaves auto as
     forceScreenshot=false — the runtime companion closes it).
   - 5 branch tests on `shouldClampToScreenshotForConcreteGpu` covering
     software / hardware / already-forced / env-opt-out / non-"false" env
     values.
   Full suite: 56/56 pass.

Scope narrowing on Blocker 2: the distributed rendering path at
`packages/producer/src/services/distributed/plan.ts:753-754` and
`renderChunk.ts:462-466` explicitly hardcodes `browserGpuMode:"software",
forceScreenshot:false` post-resolveConfig and stays outside this PR's
invariant boundary. `compileStage` may still flip it to true for alpha
formats, but generic MP4 distributed renders on SwiftShader hosts remain
BeginFrame. That's a separate architectural cleanup (needs its own
behavior-change trace); the PR body now scopes the invariant to the
in-process CLI/orchestrator path.
This commit is contained in:
Vance Ingalls
2026-07-13 23:13:30 +00:00
parent ba5168293f
commit 2e44602f99
5 changed files with 123 additions and 7 deletions
+59 -1
View File
@@ -1,7 +1,12 @@
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { resolveConfig, DEFAULT_CONFIG, scaleProtocolTimeoutForComposition } from "./config.js";
import {
resolveConfig,
DEFAULT_CONFIG,
scaleProtocolTimeoutForComposition,
shouldClampToScreenshotForConcreteGpu,
} from "./config.js";
import { isLowMemorySystem } from "./services/systemMemory.js";
describe("resolveConfig", () => {
@@ -337,6 +342,59 @@ describe("resolveConfig", () => {
const config = resolveConfig({ forceScreenshot: true });
expect(config.forceScreenshot).toBe(true);
});
it("documents the auto-branch gap: resolveConfig leaves auto→software as forceScreenshot=false", () => {
// resolveConfig's clamp keys on the string `browserGpuMode`; `"auto"`
// that runtime-probes to software is invisible to this layer. The
// runtime companion `shouldClampToScreenshotForConcreteGpu` (below)
// closes the gap at the frameCapture + renderOrchestrator sites.
setEnv("PRODUCER_BROWSER_GPU_MODE", "auto");
unsetEnv("PRODUCER_FORCE_SCREENSHOT");
const config = resolveConfig();
expect(config.browserGpuMode).toBe("auto");
expect(config.forceScreenshot).toBe(false);
});
});
describe("shouldClampToScreenshotForConcreteGpu (runtime companion for auto→software)", () => {
it("returns true when resolved GPU is software AND forceScreenshot is currently false", () => {
// Env explicitly cleared so PRODUCER_FORCE_SCREENSHOT="false" opt-out
// doesn't fire.
expect(
shouldClampToScreenshotForConcreteGpu("software", false, {} as NodeJS.ProcessEnv),
).toBe(true);
});
it("returns false when resolved GPU is hardware (no clamp needed)", () => {
expect(
shouldClampToScreenshotForConcreteGpu("hardware", false, {} as NodeJS.ProcessEnv),
).toBe(false);
});
it("returns false when forceScreenshot is already true (invariant already satisfied)", () => {
expect(shouldClampToScreenshotForConcreteGpu("software", true, {} as NodeJS.ProcessEnv)).toBe(
false,
);
});
it("honors PRODUCER_FORCE_SCREENSHOT=false env opt-out on software", () => {
// BeginFrame-on-software debugging escape hatch.
expect(
shouldClampToScreenshotForConcreteGpu("software", false, {
PRODUCER_FORCE_SCREENSHOT: "false",
} as NodeJS.ProcessEnv),
).toBe(false);
});
it("does NOT treat other PRODUCER_FORCE_SCREENSHOT values as opt-out", () => {
// Only literal "false" opts out; "true", "0", missing, anything else clamps.
for (const value of [undefined, "true", "1", "0", "no", ""]) {
const env = (
value === undefined ? {} : { PRODUCER_FORCE_SCREENSHOT: value }
) as NodeJS.ProcessEnv;
expect(shouldClampToScreenshotForConcreteGpu("software", false, env)).toBe(true);
}
});
});
describe("lowMemoryMode", () => {