refactor: consolidate coercion contracts (#122458)

* refactor: consolidate coercion contracts

Centralize exact string, record, numeric, date, Boolean, argument, and structured-error coercions while preserving call-site semantics.

Migrate canonical-name collisions and deprecated internal SDK bypasses, deleting 55 net production/tooling lines. Expand declaration ownership enforcement to 101 allowed helpers and add a narrow export-completeness audit.

* fix: preserve standalone script coercions

Keep copied Control UI tooling self-contained and retain the trusted release harness module-relative source seam when the harness runs against an old target cwd.
This commit is contained in:
Peter Steinberger
2026-08-11 23:26:37 -07:00
committed by GitHub
parent 66fe424590
commit b080dd1e76
276 changed files with 1685 additions and 1663 deletions
+3 -3
View File
@@ -2274,9 +2274,9 @@ function parsePosixProcessRows(stdout: string) {
) {
continue;
}
const processId = parseStrictPositiveInteger(pidRaw);
const processId = parsePositivePosixProcessToken(pidRaw);
const parentProcessId = parseStrictUnsignedInteger(ppidRaw);
const rssKb = parseStrictPositiveInteger(rssKbRaw);
const rssKb = parsePositivePosixProcessToken(rssKbRaw);
const cpuPercent = parseStrictNonNegativeDecimal(cpuRaw);
if (
!Number.isInteger(processId) ||
@@ -2320,7 +2320,7 @@ function parseStrictUnsignedInteger(raw: string | undefined) {
return Number.isSafeInteger(parsed) ? parsed : null;
}
function parseStrictPositiveInteger(raw: string | undefined) {
function parsePositivePosixProcessToken(raw: string | undefined) {
const parsed = parseStrictUnsignedInteger(raw);
return parsed && parsed > 0 ? parsed : null;
}
+2 -2
View File
@@ -11,7 +11,7 @@ function parseExpectedStatus(raw) {
return Number(raw);
}
function resolveTimerTimeoutMs(valueMs, fallbackMs) {
function resolveOpenWebUiHttpProbeTimeoutMs(valueMs, fallbackMs) {
const value = Number.isFinite(valueMs) ? valueMs : fallbackMs;
return Math.min(Math.max(Math.floor(value), 1), MAX_TIMER_TIMEOUT_MS);
}
@@ -27,7 +27,7 @@ export async function probeHttpStatus({
throw new Error("usage: http-probe.mjs <url> [status|lt500]");
}
const expectedStatus = expectedRaw === "lt500" ? undefined : parseExpectedStatus(expectedRaw);
const resolvedTimeoutMs = resolveTimerTimeoutMs(timeoutMs, 30_000);
const resolvedTimeoutMs = resolveOpenWebUiHttpProbeTimeoutMs(timeoutMs, 30_000);
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), resolvedTimeoutMs);
let res;
@@ -50,17 +50,17 @@ function readPositiveNumberEnv(name, fallback) {
const MAX_TIMER_TIMEOUT_MS = 2_147_000_000;
function clampTimerTimeoutMs(valueMs) {
function clampPluginLifecycleTimerMs(valueMs) {
return Math.min(Math.max(Math.floor(valueMs), 1), MAX_TIMER_TIMEOUT_MS);
}
const pollMs = clampTimerTimeoutMs(
const pollMs = clampPluginLifecycleTimerMs(
readPositiveIntEnv("OPENCLAW_PLUGIN_LIFECYCLE_METRIC_POLL_MS", 100),
);
const timeoutMs = clampTimerTimeoutMs(
const timeoutMs = clampPluginLifecycleTimerMs(
readPositiveIntEnv("OPENCLAW_PLUGIN_LIFECYCLE_PHASE_TIMEOUT_MS", 300000),
);
const timeoutKillGraceMs = clampTimerTimeoutMs(
const timeoutKillGraceMs = clampPluginLifecycleTimerMs(
readPositiveIntEnv("OPENCLAW_PLUGIN_LIFECYCLE_TIMEOUT_KILL_GRACE_MS", 2000),
);
const maxRssKbThreshold = readPositiveIntEnv(
+3 -3
View File
@@ -9,7 +9,7 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import type { QaProviderMode } from "../../extensions/qa-lab/src/run-config.ts";
import type { QaSuiteRoundTripProbe } from "../../extensions/qa-lab/src/suite-round-trip.ts";
function parseBoolean(value: string | undefined) {
function isTruthyNpmTelegramEnvValue(value: string | undefined) {
const normalized = value?.trim().toLowerCase();
return normalized === "1" || normalized === "true" || normalized === "yes";
}
@@ -147,7 +147,7 @@ async function shouldFailPackageTelegramRun(
result: { summaryPath: string },
env: NodeJS.ProcessEnv = process.env,
) {
if (parseBoolean(env.OPENCLAW_NPM_TELEGRAM_ALLOW_FAILURES)) {
if (isTruthyNpmTelegramEnvValue(env.OPENCLAW_NPM_TELEGRAM_ALLOW_FAILURES)) {
return false;
}
const { readQaSuiteFailedOrSkippedScenarioCountFromFile } =
@@ -224,7 +224,7 @@ async function main() {
providerMode,
primaryModel,
alternateModel: process.env.OPENCLAW_NPM_TELEGRAM_ALT_MODEL,
fastMode: parseBoolean(process.env.OPENCLAW_NPM_TELEGRAM_FAST),
fastMode: isTruthyNpmTelegramEnvValue(process.env.OPENCLAW_NPM_TELEGRAM_FAST),
scenarioIds,
resolvedScenarioIds: prioritizeRoundTripProbeScenario(resolvedScenarioIds, rttOptions),
roundTripProbe: createRoundTripProbe(rttOptions),
+5 -5
View File
@@ -70,18 +70,18 @@ function readNonNegativeInt(name, fallback) {
return parsed;
}
function clampTimerTimeoutMs(valueMs, minMs = 1) {
function clampOpenWebUiTimerTimeoutMs(valueMs, minMs = 1) {
const min = Math.max(0, Math.floor(minMs));
const value = Number.isFinite(valueMs) ? valueMs : min;
return Math.min(Math.max(Math.floor(value), min), MAX_TIMER_TIMEOUT_MS);
}
function readPositiveTimerMs(name, fallback) {
return clampTimerTimeoutMs(readPositiveInt(name, fallback));
return clampOpenWebUiTimerTimeoutMs(readPositiveInt(name, fallback));
}
function readNonNegativeTimerMs(name, fallback) {
return clampTimerTimeoutMs(readNonNegativeInt(name, fallback), 0);
return clampOpenWebUiTimerTimeoutMs(readNonNegativeInt(name, fallback), 0);
}
function createTimeoutError(label, timeoutMs) {
@@ -91,7 +91,7 @@ function createTimeoutError(label, timeoutMs) {
}
async function withRequestTimeout(label, timeoutMs, run) {
const resolvedTimeoutMs = clampTimerTimeoutMs(timeoutMs);
const resolvedTimeoutMs = clampOpenWebUiTimerTimeoutMs(timeoutMs);
const controller = new AbortController();
const timeoutError = createTimeoutError(label, resolvedTimeoutMs);
let timer;
@@ -156,7 +156,7 @@ function buildAuthHeaders(token, cookie) {
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, clampTimerTimeoutMs(ms, 0));
setTimeout(resolve, clampOpenWebUiTimerTimeoutMs(ms, 0));
});
}
+4 -14
View File
@@ -14,6 +14,8 @@ import path from "node:path";
import { fileURLToPath } from "node:url";
import { clampTimerTimeoutMs } from "@openclaw/normalization-core/number-coercion";
import { sliceUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
import { parseStrictBooleanArg } from "../lib/arg-utils.mts";
import { coerceErrorMessage } from "../lib/error-format.mts";
import { sleep } from "../lib/sleep.mjs";
import { resolveWindowsTaskkillPath } from "../lib/windows-taskkill.mjs";
import { createPnpmRunnerSpawnSpec } from "../pnpm-runner.mts";
@@ -303,16 +305,6 @@ function parseTcpPort(value: string, label: string) {
return parsed;
}
function parseBoolean(value: string, label: string) {
if (value === "true") {
return true;
}
if (value === "false") {
return false;
}
throw new Error(`${label} must be true or false.`);
}
function createTelegramProofRunId() {
return `${new Date().toISOString().replace(/[:.]/gu, "-")}-${randomUUID().slice(0, 8)}`;
}
@@ -417,7 +409,7 @@ export function parseArgs(argvInput: string[]): Options {
} else if (arg === "--keep-box") {
opts.keepBox = true;
} else if (arg === "--link-preview") {
opts.linkPreview = parseBoolean(readValue(), "--link-preview");
opts.linkPreview = parseStrictBooleanArg(readValue(), "--link-preview");
} else if (arg === "--mock-port") {
opts.mockPort = parseTcpPort(readValue(), "--mock-port");
} else if (arg === "--mock-response-file") {
@@ -1656,9 +1648,7 @@ function destroyLocalSutRuntime(sut: { containerName?: string; tempRoot?: string
}
function cleanupFailureMessage(message: string, cleanupErrors: unknown[]) {
const details = cleanupErrors.map((error) =>
error instanceof Error ? error.message : String(error),
);
const details = cleanupErrors.map(coerceErrorMessage);
return [message, ...details.map((detail) => `Cleanup failure: ${detail}`)].join("\n");
}