From 991e5ba8a29ecebc42b47a8afcd46e29e2fdbe89 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Sat, 15 Aug 2026 02:16:10 +0200 Subject: [PATCH 1/4] fix(lab): align passive signal API limit --- src/server/management/lab-routes.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/server/management/lab-routes.ts b/src/server/management/lab-routes.ts index 5c0929e47f..efd80ff43a 100644 --- a/src/server/management/lab-routes.ts +++ b/src/server/management/lab-routes.ts @@ -30,6 +30,7 @@ import { LabProjectionIncompatibleError, LabProjectionUnavailableError, LAB_QUERY_MAX_PAGE_SIZE, + PASSIVE_PRODUCTION_MAX_LIMIT, queryLabArtifactByDigest, queryLabArtifacts, queryLabCatalogEntries, @@ -75,20 +76,24 @@ function projectionErrorResponse(err: unknown, ctx: ManagementContext): Response return null; } -function parseLimit(raw: string | null, ctx: ManagementContext): number | undefined | Response { +function parseLimit( + raw: string | null, + ctx: ManagementContext, + max = LAB_QUERY_MAX_PAGE_SIZE, +): number | undefined | Response { const parsed = raw === null ? undefined : parseQueryInt(raw); if (parsed === "invalid") { return errorResponse( "invalid_limit", - `limit must be an integer from 1 to ${LAB_QUERY_MAX_PAGE_SIZE}`, + `limit must be an integer from 1 to ${max}`, 400, ctx, ); } - if (parsed !== undefined && (parsed < 1 || parsed > LAB_QUERY_MAX_PAGE_SIZE)) { + if (parsed !== undefined && (parsed < 1 || parsed > max)) { return errorResponse( "invalid_limit", - `limit must be an integer from 1 to ${LAB_QUERY_MAX_PAGE_SIZE}`, + `limit must be an integer from 1 to ${max}`, 400, ctx, ); @@ -198,7 +203,7 @@ export async function handleLabRoutes(ctx: ManagementContext): Promise Date: Sat, 15 Aug 2026 02:16:51 +0200 Subject: [PATCH 2/4] fix(lab): validate passive subject IDs in CLI --- src/cli/lab.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cli/lab.ts b/src/cli/lab.ts index 013b96327a..5071404eb9 100644 --- a/src/cli/lab.ts +++ b/src/cli/lab.ts @@ -36,6 +36,7 @@ import { queryPassiveProductionSignals, type PassiveProductionQueryResultV1, } from "../lab/query"; +import { isLabRouteSubjectId } from "../usage/log"; import { CliUsageError, RuntimeApiError, @@ -243,6 +244,9 @@ export async function handleLabCommand(argv: string[], deps: LabCliDeps = {}): P const limit = takeIntegerOption(rest, "--limit", { min: 1 }); rejectArgs(rest, USAGE); if (!subjectId) throw new CliUsageError("--subject is required", USAGE); + if (!isLabRouteSubjectId(subjectId)) { + throw new CliUsageError("--subject must be an exact Lab route subject id", USAGE); + } const result = queryPassiveProductionSignals(subjectId, limit, configDir); printData(result, wantsJson, passiveProductionLines(result)); return; From 04d6b0890b57070aca4641b61dc691ba415c0689 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Sat, 15 Aug 2026 02:17:09 +0200 Subject: [PATCH 3/4] test(lab): cover passive read surface validation --- tests/lab-passive-production-surfaces.test.ts | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 tests/lab-passive-production-surfaces.test.ts diff --git a/tests/lab-passive-production-surfaces.test.ts b/tests/lab-passive-production-surfaces.test.ts new file mode 100644 index 0000000000..bcd5b11694 --- /dev/null +++ b/tests/lab-passive-production-surfaces.test.ts @@ -0,0 +1,93 @@ +import { afterEach, describe, expect, test } from "bun:test"; +import { mkdirSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { handleLabCommand } from "../src/cli/lab"; +import { + LAB_QUERY_MAX_PAGE_SIZE, + PASSIVE_PRODUCTION_MAX_LIMIT, +} from "../src/lab/query"; +import { handleManagementAPI } from "../src/server/management-api"; +import type { OcxConfig } from "../src/types"; +import { ManagementRequest } from "./helpers/management-auth"; + +const HOMES: string[] = []; + +function tempHome(): string { + const dir = join(tmpdir(), `ocx-lab-passive-surfaces-${process.pid}-${Math.random().toString(16).slice(2)}`); + mkdirSync(dir, { recursive: true, mode: 0o700 }); + HOMES.push(dir); + return dir; +} + +afterEach(() => { + for (const dir of HOMES.splice(0)) { + rmSync(dir, { recursive: true, force: true }); + } + delete process.env.OPENCODEX_HOME; +}); + +function config(): OcxConfig { + return { providers: {} } as OcxConfig; +} + +async function apiGet(home: string, path: string): Promise { + process.env.OPENCODEX_HOME = home; + const req = new ManagementRequest(`http://127.0.0.1${path}`, { method: "GET" }); + const response = await handleManagementAPI(req, new URL(req.url), config(), { + refreshCodexCatalog: async () => {}, + }); + expect(response).not.toBeNull(); + return response!; +} + +describe("CL-09 passive production read surfaces", () => { + test("management API uses the passive query limit without widening generic Lab pages", async () => { + const home = tempHome(); + const subjectId = "a".repeat(64); + + const accepted = await apiGet( + home, + `/api/lab/production-signals?subjectId=${subjectId}&limit=${PASSIVE_PRODUCTION_MAX_LIMIT}`, + ); + expect(accepted.status).toBe(200); + const acceptedBody = await accepted.json() as { + signals: unknown[]; + summary: { recentProductionAttempts: number }; + }; + expect(acceptedBody.signals).toEqual([]); + expect(acceptedBody.summary.recentProductionAttempts).toBe(0); + + const tooHigh = await apiGet( + home, + `/api/lab/production-signals?subjectId=${subjectId}&limit=${PASSIVE_PRODUCTION_MAX_LIMIT + 1}`, + ); + expect(tooHigh.status).toBe(400); + const tooHighBody = await tooHigh.json() as { error: { code: string; message: string } }; + expect(tooHighBody.error.code).toBe("invalid_limit"); + expect(tooHighBody.error.message).toContain(`1 to ${PASSIVE_PRODUCTION_MAX_LIMIT}`); + + const generic = await apiGet(home, `/api/lab/verdicts?limit=${LAB_QUERY_MAX_PAGE_SIZE + 1}`); + expect(generic.status).toBe(400); + const genericBody = await generic.json() as { error: { code: string; message: string } }; + expect(genericBody.error.code).toBe("invalid_limit"); + expect(genericBody.error.message).toContain(`1 to ${LAB_QUERY_MAX_PAGE_SIZE}`); + }); + + test("CLI reports malformed passive subject ids as the actual usage error", async () => { + const home = tempHome(); + const errors: string[] = []; + const originalError = console.error; + console.error = (...args: unknown[]) => { errors.push(args.join(" ")); }; + try { + expect(await handleLabCommand( + ["production-signals", "--subject", "not-a-subject-id"], + { configDir: home }, + )).toBe(2); + expect(errors.join("\n")).toContain("--subject must be an exact Lab route subject id"); + expect(errors.join("\n")).not.toContain("lab read failed"); + } finally { + console.error = originalError; + } + }); +}); From 19af68951d7c398c4ef2504726fe74ca4a7b0dcf Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Sat, 15 Aug 2026 02:24:29 +0200 Subject: [PATCH 4/4] test(lab): restore passive test environment --- tests/lab-passive-production-surfaces.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/lab-passive-production-surfaces.test.ts b/tests/lab-passive-production-surfaces.test.ts index bcd5b11694..66ae81fc53 100644 --- a/tests/lab-passive-production-surfaces.test.ts +++ b/tests/lab-passive-production-surfaces.test.ts @@ -12,6 +12,7 @@ import type { OcxConfig } from "../src/types"; import { ManagementRequest } from "./helpers/management-auth"; const HOMES: string[] = []; +const originalOpenCodexHome = process.env.OPENCODEX_HOME; function tempHome(): string { const dir = join(tmpdir(), `ocx-lab-passive-surfaces-${process.pid}-${Math.random().toString(16).slice(2)}`); @@ -24,7 +25,8 @@ afterEach(() => { for (const dir of HOMES.splice(0)) { rmSync(dir, { recursive: true, force: true }); } - delete process.env.OPENCODEX_HOME; + if (originalOpenCodexHome === undefined) delete process.env.OPENCODEX_HOME; + else process.env.OPENCODEX_HOME = originalOpenCodexHome; }); function config(): OcxConfig {