diff --git a/scripts/lib/mcp-startup-probe.d.mts b/scripts/lib/mcp-startup-probe.d.mts index dda222485e..508de72638 100644 --- a/scripts/lib/mcp-startup-probe.d.mts +++ b/scripts/lib/mcp-startup-probe.d.mts @@ -3,13 +3,11 @@ import type { McpStartupBudgets, } from "../../src/config/mcpStartupBudgets.js"; -type McpStartupPolicy = McpStartupBudgets; - interface McpProcessOptions { readonly command: string; readonly args: readonly string[]; readonly environment: Readonly>; - readonly policy: McpStartupPolicy; + readonly policy: McpStartupBudgets; } interface McpModuleProfileOptions extends McpProcessOptions { diff --git a/scripts/verify-package-mcp.mjs b/scripts/verify-package-mcp.mjs index f4dcbccdd3..a8fc01499e 100644 --- a/scripts/verify-package-mcp.mjs +++ b/scripts/verify-package-mcp.mjs @@ -10,7 +10,7 @@ import { readFile, rm } from "node:fs/promises"; import { Client } from "@modelcontextprotocol/client"; import { StdioClientTransport } from "@modelcontextprotocol/client/stdio"; -import { resolveMcpStartupProbePolicy } from "../dist/mcpStartupPolicy.js"; +import { resolveMcpStartupBudgets } from "../dist/config/mcpStartupBudgets.js"; import { parseEvidence } from "../dist/domain/evidence.js"; import * as prompts from "./verify-package-prompts.mjs"; import { json, verifyCompleteToolCatalog } from "./lib/verify-package-core.mjs"; @@ -257,7 +257,7 @@ export async function verifyPackageMcp({ ( await execute(cli, ["mcp", "doctor", "--json"], { env: environment, - timeout: resolveMcpStartupProbePolicy(environment).doctor.milliseconds, + timeout: resolveMcpStartupBudgets(environment).doctor.milliseconds, }) ).stdout, ); diff --git a/scripts/verify-package.mjs b/scripts/verify-package.mjs index 6baa081f48..a20ecd487d 100644 --- a/scripts/verify-package.mjs +++ b/scripts/verify-package.mjs @@ -6,7 +6,7 @@ import { promisify } from "node:util"; import { TOOL_CONTRACTS } from "../dist/contracts/toolContracts.js"; import { PRODUCT_IDENTITY } from "../dist/identity.js"; -import { resolveMcpStartupProbePolicy } from "../dist/mcpStartupPolicy.js"; +import { resolveMcpStartupBudgets } from "../dist/config/mcpStartupBudgets.js"; import * as prompts from "./verify-package-prompts.mjs"; import { verifyPackageArtifactAndElectron } from "./verify-package-artifact.mjs"; import { verifyPackageCapabilitiesAndSearch } from "./verify-package-capabilities.mjs"; @@ -124,7 +124,7 @@ try { environment: { ...environmentData.environment, }, - policy: resolveMcpStartupProbePolicy(environmentData.environment), + policy: resolveMcpStartupBudgets(environmentData.environment), }), ); mcpModuleLoading = await runStage("MCP module loading", () => @@ -134,7 +134,7 @@ try { environment: { ...environmentData.environment, }, - policy: resolveMcpStartupProbePolicy(environmentData.environment), + policy: resolveMcpStartupBudgets(environmentData.environment), packageName: PRODUCT_IDENTITY.packageName, }), ); diff --git a/src/config/mcpStartupBudgets.ts b/src/config/mcpStartupBudgets.ts index 8b0cd9c79c..85a07b96be 100644 --- a/src/config/mcpStartupBudgets.ts +++ b/src/config/mcpStartupBudgets.ts @@ -66,19 +66,6 @@ const PHASES = [ }, ] as const satisfies readonly McpPhaseBudgetDefinition[]; -/** Initialize budget used when `REA_MCP_INITIALIZE_BUDGET_MS` is unset. */ -export const DEFAULT_MCP_INITIALIZE_BUDGET_MS = PHASES[0].defaultMilliseconds; - -/** tools/list budget used when `REA_MCP_TOOLS_LIST_BUDGET_MS` is unset. */ -export const DEFAULT_MCP_TOOLS_LIST_BUDGET_MS = PHASES[1].defaultMilliseconds; - -/** First-catalog budget used when `REA_MCP_FIRST_CATALOG_BUDGET_MS` is unset. */ -export const DEFAULT_MCP_FIRST_CATALOG_BUDGET_MS = - PHASES[2].defaultMilliseconds; - -/** Doctor deadline used when `REA_MCP_DOCTOR_DEADLINE_MS` is unset. */ -export const DEFAULT_MCP_DOCTOR_DEADLINE_MS = PHASES[3].defaultMilliseconds; - const phaseConstraint = (phase: McpPhaseBudgetDefinition): string => `${phase.setting} must be a positive decimal integer from ${String(MCP_PHASE_BUDGET_FLOOR_MS)} to ${String(MCP_PHASE_BUDGET_MAXIMUM_MS)} ms; the default is ${String(phase.defaultMilliseconds)} ms. Node schedules a delay above that maximum as 1 ms, so a larger value is refused.`; diff --git a/src/domain/apktool/apktoolResourceAnalysis.ts b/src/domain/apktool/apktoolResourceAnalysis.ts index 777750dab6..a10a6819d7 100644 --- a/src/domain/apktool/apktoolResourceAnalysis.ts +++ b/src/domain/apktool/apktoolResourceAnalysis.ts @@ -26,7 +26,8 @@ const ANDROID_NON_LOCALE_QUALIFIERS = new Set([ // and variant has 8 bytes (BinaryResourceParser / ResConfig). const MAX_ANDROID_LOCALE_SUFFIX = 2 + 3 + (1 + 4) + (1 + 3) + (1 + 8); -const localeQualifierSchema = z +/** Locale language tags admitted by Android resource table selection. */ +export const androidLocaleQualifierSchema = z .string() .min(2) .max(MAX_ANDROID_LOCALE_SUFFIX) @@ -40,9 +41,6 @@ const localeQualifierSchema = z "Android resource configuration qualifiers are not locales", ); -/** Locale language tags admitted by Android resource table selection. */ -export const androidLocaleQualifierSchema = localeQualifierSchema; - /** Normalize an admitted request alias at the locale-selection owner. */ export const androidLocaleDirectorySuffix = (locale: string): string => locale diff --git a/src/domain/python/pythonPyc.ts b/src/domain/python/pythonPyc.ts index 5dc0e97c3d..e5415a89cc 100644 --- a/src/domain/python/pythonPyc.ts +++ b/src/domain/python/pythonPyc.ts @@ -107,9 +107,9 @@ export const inspectPythonPycInputSchema = z.strictObject({ ), }); -const constPreviewLimit = 80; +export const PYTHON_PYC_CONST_PREVIEW_LIMIT = 80; /** Integers up to 256 bits fit the 80-character preview in decimal. */ -const intPreviewBits = 256; +export const PYTHON_PYC_INT_PREVIEW_BITS = 256; /** CPython float repr for values JSON numbers cannot carry. */ const pythonFloatSchema = z.union([z.number(), z.enum(["inf", "-inf", "nan"])]); @@ -120,14 +120,14 @@ const pythonConstSchema = z.discriminatedUnion("kind", [ z.strictObject({ kind: z.literal("int"), bits: uint, - value: z.string().min(1).max(constPreviewLimit).nullable(), + value: z.string().min(1).max(PYTHON_PYC_CONST_PREVIEW_LIMIT).nullable(), }), z.strictObject({ kind: z.literal("float"), value: pythonFloatSchema }), z.strictObject({ kind: z.literal("str"), bytes: uint, truncated: z.boolean(), - value: z.string().max(constPreviewLimit), + value: z.string().max(PYTHON_PYC_CONST_PREVIEW_LIMIT), }), z.strictObject({ kind: z.literal("bytes"), @@ -293,6 +293,3 @@ export const pythonPycSchema = z export type PythonPyc = z.infer; export type InspectPythonPycInput = z.infer; - -export const PYTHON_PYC_CONST_PREVIEW_LIMIT = constPreviewLimit; -export const PYTHON_PYC_INT_PREVIEW_BITS = intPreviewBits; diff --git a/src/mcpStartupPolicy.ts b/src/mcpStartupPolicy.ts index ef775c9d53..6ef4657f78 100644 --- a/src/mcpStartupPolicy.ts +++ b/src/mcpStartupPolicy.ts @@ -1,48 +1,21 @@ import type { AnalysisProviderSelector } from "./contracts/providerSelection.js"; import { - DEFAULT_MCP_DOCTOR_DEADLINE_MS, - DEFAULT_MCP_FIRST_CATALOG_BUDGET_MS, - DEFAULT_MCP_INITIALIZE_BUDGET_MS, - DEFAULT_MCP_TOOLS_LIST_BUDGET_MS, mcpPhaseBudgetRecovery, - resolveMcpStartupBudgets, type McpPhaseBudget, - type McpStartupBudgets, } from "./config/mcpStartupBudgets.js"; /** - * Supported MCP startup deadlines and operation headroom. - * - * Client timeouts are caller-facing configuration. The phase budgets below - * are the unset defaults of `REA_MCP_INITIALIZE_BUDGET_MS`, - * `REA_MCP_TOOLS_LIST_BUDGET_MS`, `REA_MCP_FIRST_CATALOG_BUDGET_MS`, and - * `REA_MCP_DOCTOR_DEADLINE_MS`. Callers that enforce a phase read - * `resolveMcpStartupProbePolicy` so a selected value is not ignored. - * `codexStartupTimeoutSeconds` stays client guidance and is not a setting. + * Client startup guidance, operation headroom, and doctor output retention. + * REA startup phase deadlines belong to config/mcpStartupBudgets; consumers + * resolve them from the selected environment with resolveMcpStartupBudgets. */ export const MCP_STARTUP_POLICY = { codexStartupTimeoutSeconds: 30, kimiStartupTimeoutMs: 30_000, toolOperationHeadroomMs: 120_000, - initializeBudgetMs: DEFAULT_MCP_INITIALIZE_BUDGET_MS, - toolsListBudgetMs: DEFAULT_MCP_TOOLS_LIST_BUDGET_MS, - firstCatalogBudgetMs: DEFAULT_MCP_FIRST_CATALOG_BUDGET_MS, - doctorDeadlineMs: DEFAULT_MCP_DOCTOR_DEADLINE_MS, doctorStderrBytes: 64 * 1024, } as const; -/** Phase budgets the packaged startup probe enforces. */ -export type McpStartupProbePolicy = McpStartupBudgets; - -/** - * Resolve the probe policy from the environment that starts the probe. - * An invalid phase setting is refused. Codex's 30 second startup figure - * is not part of this policy. - */ -export const resolveMcpStartupProbePolicy = ( - environment: Readonly>, -): McpStartupProbePolicy => resolveMcpStartupBudgets(environment); - /** Doctor startup recovery. The Codex figure is client guidance REA does not enforce. */ export const mcpDoctorDeadlineRecovery = (budget: McpPhaseBudget): string => `${mcpPhaseBudgetRecovery(budget)} Codex startup_timeout_sec of ${String(MCP_STARTUP_POLICY.codexStartupTimeoutSeconds)} seconds is client guidance; REA does not enforce that figure as this deadline.`; diff --git a/tests/boundary/mcp/mcpStartupPolicy.test.ts b/tests/boundary/mcp/mcpStartupPolicy.test.ts index 180e77a8c4..47007894fb 100644 --- a/tests/boundary/mcp/mcpStartupPolicy.test.ts +++ b/tests/boundary/mcp/mcpStartupPolicy.test.ts @@ -21,19 +21,17 @@ import { import { mcpDoctorDeadlineRecovery, MCP_STARTUP_POLICY, - resolveMcpStartupProbePolicy, } from "../../../src/mcpStartupPolicy.js"; describe("MCP startup policy", () => { it("names phase budget settings without turning Codex's 30s into a REA knob", () => { const budgets = resolveMcpStartupBudgets({}); expect(budgets).toEqual(DEFAULT_MCP_STARTUP_BUDGETS); - expect(resolveMcpStartupProbePolicy({})).toEqual(budgets); expect(MCP_STARTUP_POLICY.codexStartupTimeoutSeconds).toBe(30); - expect(MCP_STARTUP_POLICY.initializeBudgetMs).toBe(15_000); - expect(MCP_STARTUP_POLICY.toolsListBudgetMs).toBe(5_000); - expect(MCP_STARTUP_POLICY.firstCatalogBudgetMs).toBe(20_000); - expect(MCP_STARTUP_POLICY.doctorDeadlineMs).toBe(30_000); + expect(budgets.initialize.milliseconds).toBe(15_000); + expect(budgets.toolsList.milliseconds).toBe(5_000); + expect(budgets.firstCatalog.milliseconds).toBe(20_000); + expect(budgets.doctor.milliseconds).toBe(30_000); const initialize = mcpPhaseBudgetRecovery(budgets.initialize); expect(initialize).toBe( "The effective REA_MCP_INITIALIZE_BUDGET_MS value for the MCP initialize phase budget is 15000 ms (source default; default 15000 ms, floor 1 ms, maximum 2147483647 ms). Set REA_MCP_INITIALIZE_BUDGET_MS in the environment that starts REA to a positive decimal integer from 1 to 2147483647, then restart REA.", @@ -62,7 +60,7 @@ describe("MCP startup policy", () => { ); expect(doctor).toContain("does not enforce"); expect(doctor).not.toContain("REA_MCP_STARTUP"); - const selected = resolveMcpStartupProbePolicy({ + const selected = resolveMcpStartupBudgets({ REA_MCP_INITIALIZE_BUDGET_MS: "16000", }); expect(selected.initialize.milliseconds).toBe(16_000);