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
+1 -4
View File
@@ -5,6 +5,7 @@ import path from "node:path";
import { setTimeout as delay } from "node:timers/promises";
import { pathToFileURL } from "node:url";
import { stripAnsiSequences } from "../../../../packages/terminal-core/src/ansi.js";
import { coerceErrorMessage as formatErrorMessage } from "../../../../scripts/lib/error-format.mts";
import { createQaScriptEvidenceWriter } from "../runtime/script-evidence.js";
const SCENARIO_ID = "cli-channel-picker";
@@ -18,10 +19,6 @@ type ProducerOptions = {
timeoutMs: number;
};
function formatErrorMessage(error: unknown) {
return error instanceof Error ? error.message : String(error);
}
function sanitizePickerTranscript(transcript: string) {
return stripAnsiSequences(transcript).replaceAll(
/123456(?:(?::|)[A-Za-z0-9_-]*)?/gu,
@@ -6,6 +6,7 @@ import {
QA_EVIDENCE_FILENAME,
type QaEvidenceSummaryJson,
} from "../../../../extensions/qa-lab/api.js";
import { coerceErrorMessage as formatErrorMessage } from "../../../../scripts/lib/error-format.mts";
import { spawnPnpmRunner as _spawnPnpmRunner } from "../../../../scripts/pnpm-runner.mts";
import {
createQaScriptBlockedStatusTracker,
@@ -170,10 +171,6 @@ function formatProviderList(providers: Iterable<string>): string {
return [...providers].toSorted().join(", ");
}
function formatErrorMessage(error: unknown) {
return error instanceof Error ? error.message : String(error);
}
function spawnLivePnpm(params: { pnpmArgs: string[]; env: NodeJS.ProcessEnv }) {
return _spawnPnpmRunner({
pnpmArgs: params.pnpmArgs,
@@ -8,6 +8,7 @@ import {
QA_EVIDENCE_FILENAME,
type QaEvidenceSummaryJson,
} from "../../../../extensions/qa-lab/api.js";
import { coerceErrorMessage as formatErrorMessage } from "../../../../scripts/lib/error-format.mts";
import { createBoundedChildOutput } from "../../../helpers/bounded-child-output.js";
import {
createQaScriptBlockedStatusTracker,
@@ -165,10 +166,6 @@ async function writeJson(filePath: string, value: unknown) {
await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
}
function formatErrorMessage(error: unknown) {
return error instanceof Error ? error.message : String(error);
}
async function resolveCandidateTarball(options: ProducerOptions) {
const explicitTarball = process.env[options.tarballEnv]?.trim();
if (explicitTarball) {
@@ -5,6 +5,7 @@ import {
QA_EVIDENCE_FILENAME,
type QaEvidenceSummaryJson,
} from "../../../../extensions/qa-lab/api.js";
import { coerceErrorMessage as formatError } from "../../../../scripts/lib/error-format.mts";
import { createQaScriptEvidenceWriter } from "./script-evidence.js";
const SCENARIO_ID = "browser-plugin-profiles-packaged";
@@ -20,10 +21,6 @@ type DockerOutcome = {
signal: NodeJS.Signals | null;
};
function formatError(error: unknown) {
return error instanceof Error ? error.message : String(error);
}
export function parseBrowserPluginProfilesOptions(args: string[]): ProducerOptions {
if (args.length !== 2 || args[0] !== "--artifact-base" || !args[1]) {
throw new Error("usage: --artifact-base <output-directory>");
@@ -7,6 +7,7 @@ import {
QA_EVIDENCE_FILENAME,
type QaEvidenceSummaryJson,
} from "../../../../extensions/qa-lab/api.js";
import { coerceErrorMessage as formatErrorMessage } from "../../../../scripts/lib/error-format.mts";
import { createQaScriptEvidenceWriter } from "./script-evidence.js";
const SOURCE_PATH = "test/e2e/qa-lab/runtime/docker-artifact-proof.ts";
@@ -42,10 +43,6 @@ type ArtifactIdentity = {
scenarioId: string;
};
function formatErrorMessage(error: unknown) {
return error instanceof Error ? error.message : String(error);
}
function isProofLane(value: string): value is DockerArtifactProofLane {
return Object.hasOwn(PROOFS, value);
}
@@ -11,6 +11,7 @@ import {
type QaEvidenceSummaryJson,
} from "../../../../extensions/qa-lab/api.js";
import { ProtocolSchemas } from "../../../../packages/gateway-protocol/src/schema/protocol-schemas.js";
import { coerceErrorMessage as formatErrorMessage } from "../../../../scripts/lib/error-format.mts";
import { listCoreGatewayMethodMetadata } from "../../../../src/gateway/methods/core-descriptors.js";
import { createQaScriptEvidenceWriter } from "./script-evidence.js";
@@ -187,10 +188,6 @@ export function buildPortableSwiftAnyCodableSource(source: string) {
return source.includes("import CoreFoundation") ? source : `import CoreFoundation\n${source}`;
}
function formatErrorMessage(error: unknown) {
return error instanceof Error ? error.message : String(error);
}
export function parseGatewayProtocolArtifactOptions(
args: readonly string[],
cwd = process.cwd(),
@@ -7,6 +7,7 @@ import {
type QaEvidenceSummaryJson,
validateQaEvidenceSummaryJson,
} from "../../../../extensions/qa-lab/api.js";
import { coerceErrorMessage as formatErrorMessage } from "../../../../scripts/lib/error-format.mts";
import { createQaScriptEvidenceWriter } from "./script-evidence.js";
const SCENARIO_ID = "managed-gateway-service-lifecycle";
@@ -91,10 +92,6 @@ if (process.platform === "darwin") {
});
}
function formatErrorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
function parseOptions(args: string[]): ProducerOptions {
if (args.length !== 2 || args[0] !== "--artifact-base" || !args[1]) {
throw new Error("usage: --artifact-base <output-directory>");
@@ -7,6 +7,7 @@ import {
QA_EVIDENCE_FILENAME,
type QaEvidenceSummaryJson,
} from "../../../../extensions/qa-lab/api.js";
import { coerceErrorMessage as formatErrorMessage } from "../../../../scripts/lib/error-format.mts";
import { createQaScriptEvidenceWriter } from "./script-evidence.js";
const SOURCE_PATH = "test/e2e/qa-lab/runtime/update-run-package-self-upgrade.ts";
@@ -44,10 +45,6 @@ type UpdateRunSelfUpgradeSummary = {
};
};
function formatErrorMessage(error: unknown) {
return error instanceof Error ? error.message : String(error);
}
export function parseUpdateRunSelfUpgradeOptions(args: string[]): ProducerOptions {
let artifactBase: string | undefined;
for (let index = 0; index < args.length; index += 1) {
@@ -11,6 +11,7 @@ import {
type QaEvidenceSummaryJson,
type QaSeedScenarioWithSource,
} from "../../../../extensions/qa-lab/api.js";
import { coerceErrorMessage as formatErrorMessage } from "../../../../scripts/lib/error-format.mts";
import { createQaScriptEvidenceWriter } from "../runtime/script-evidence.js";
const SOURCE_PATH = "test/e2e/qa-lab/tui/tui-pty-evidence-producer.ts";
@@ -89,10 +90,6 @@ type ProofMatrixCase = TuiPtyCase & {
matchedAssertions: string[];
};
function formatErrorMessage(error: unknown) {
return error instanceof Error ? error.message : String(error);
}
function readOptionValue(argv: readonly string[], index: number, option: string) {
const value = argv[index + 1];
if (!value || value.startsWith("--")) {
+2 -1
View File
@@ -1,4 +1,5 @@
import { createHash } from "node:crypto";
import { asFiniteNumber } from "@openclaw/normalization-core/number-coercion";
import { SessionManager } from "../../src/agents/sessions/session-manager.js";
import type { OpenClawConfig } from "../../src/config/config.js";
import { resolveAgentModelPrimaryValue } from "../../src/config/model-input.js";
@@ -672,7 +673,7 @@ type UsageRecord = {
};
function finite(value: unknown): number | null {
return typeof value === "number" && Number.isFinite(value) ? value : null;
return asFiniteNumber(value) ?? null;
}
type OpenAILongContextTurnMetric = {
+71
View File
@@ -2,13 +2,84 @@
import { describe, expect, it } from "vitest";
import {
booleanFlag,
classifyBoundedUnsignedDecimal,
intFlag,
parseFlagArgs,
parsePermissiveBooleanToken,
parseStrictBooleanArg,
readFlagValue,
stringFlag,
stringListFlag,
} from "../../scripts/lib/arg-utils.runtime.mjs";
describe("scripts/lib/arg-utils strict scalar grammars", () => {
it.each([
{ input: "true", expected: true },
{ input: "false", expected: false },
{ input: "", error: "--enabled must be true or false." },
{ input: " ", error: "--enabled must be true or false." },
{ input: " true", error: "--enabled must be true or false." },
{ input: "false ", error: "--enabled must be true or false." },
{ input: "TRUE", error: "--enabled must be true or false." },
{ input: "False", error: "--enabled must be true or false." },
{ input: "1", error: "--enabled must be true or false." },
{ input: "0", error: "--enabled must be true or false." },
{ input: "yes", error: "--enabled must be true or false." },
{ input: true, error: "--enabled must be true or false." },
{ input: 1, error: "--enabled must be true or false." },
])("parses strict Boolean token %#", ({ input, expected, error }) => {
if (error) {
expect(() => parseStrictBooleanArg(input, "--enabled")).toThrow(error);
return;
}
expect(parseStrictBooleanArg(input, "--enabled")).toBe(expected);
});
it.each([
{ input: "0", min: 0, max: 10, expected: { kind: "value", value: 0 } },
{ input: "001", min: 1, max: 10, expected: { kind: "value", value: 1 } },
{ input: "10", min: 0, max: 10, expected: { kind: "value", value: 10 } },
{ input: "0", min: 1, max: 10, expected: { kind: "below" } },
{ input: "11", min: 0, max: 10, expected: { kind: "above" } },
{ input: "9".repeat(400), min: 0, max: 10, expected: { kind: "above" } },
{ input: "", min: 0, max: 10, expected: { kind: "syntax" } },
{ input: " ", min: 0, max: 10, expected: { kind: "syntax" } },
{ input: " 1", min: 0, max: 10, expected: { kind: "syntax" } },
{ input: "1 ", min: 0, max: 10, expected: { kind: "syntax" } },
{ input: "+1", min: 0, max: 10, expected: { kind: "syntax" } },
{ input: "-1", min: 0, max: 10, expected: { kind: "syntax" } },
{ input: "1.0", min: 0, max: 10, expected: { kind: "syntax" } },
{ input: "1e1", min: 0, max: 10, expected: { kind: "syntax" } },
{ input: "0x10", min: 0, max: 10, expected: { kind: "syntax" } },
{ input: "0b10", min: 0, max: 10, expected: { kind: "syntax" } },
{ input: "1ms", min: 0, max: 10, expected: { kind: "syntax" } },
])("classifies bounded unsigned decimal %#", ({ input, min, max, expected }) => {
expect(classifyBoundedUnsignedDecimal(input, min, max)).toEqual(expected);
});
});
describe("scripts/lib/arg-utils permissive Boolean tokens", () => {
it.each([
{ input: "true", expected: true },
{ input: "1", expected: true },
{ input: "yes", expected: true },
{ input: "on", expected: true },
{ input: "false", expected: false },
{ input: "0", expected: false },
{ input: "no", expected: false },
{ input: "off", expected: false },
{ input: " TRUE ", expected: true },
{ input: " Off ", expected: false },
{ input: "", expected: undefined },
{ input: " ", expected: undefined },
{ input: "enabled", expected: undefined },
{ input: true, expected: undefined },
{ input: 1, expected: undefined },
])("parses $input as $expected", ({ input, expected }) => {
expect(parsePermissiveBooleanToken(input)).toBe(expected);
});
});
describe("scripts/lib/arg-utils parseFlagArgs", () => {
it("uses the last value when a flag is repeated", () => {
expect(readFlagValue(["-p", "first.json", "-p", "second.json"], "-p")).toBe("second.json");
@@ -3,8 +3,10 @@ import fs from "node:fs";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import {
auditCanonicalCoercionExports,
auditCoercionHelperDeclarations,
findBannedCoercionHelperDeclarations,
findExportedCallableNames,
isGovernedCoercionHelperPath,
runCoercionHelperDeclarationGuard,
type CoercionHelperCarveOut,
@@ -240,6 +242,68 @@ describe("coercion helper declaration AST guard", () => {
expect(isGovernedCoercionHelperPath(".github/actions/example/index.ts")).toBe(true);
});
it("finds directly exported callable declarations and export aliases", () => {
const source = [
"export function canonical() {}",
"function local() {}",
"export { local as alias };",
"export const VALUE = 1;",
].join("\n");
expect(findExportedCallableNames(source, "src/owner.ts")).toEqual(["alias", "canonical"]);
});
it.each([
["classified", ["kept"], [{ file: "src/owner.ts", name: "kept", status: "enforced" }]],
[
"deferred",
["format"],
[
{
file: "src/owner.ts",
name: "format",
status: "deferred",
reason: "Meaningful public collision.",
},
],
],
] as const)("accepts a %s canonical export", (_label, names, classifications) => {
expect(
auditCanonicalCoercionExports(new Map([["src/owner.ts", names]]), classifications),
).toEqual({
invalidClassifications: [],
staleClassifications: [],
unclassifiedExports: [],
});
});
it("reports unclassified exports and stale, duplicate, or blank deferred entries", () => {
const kept = { file: "src/owner.ts", name: "kept", status: "enforced" } as const;
const removed = {
file: "src/owner.ts",
name: "removed",
status: "deferred",
reason: "Removed owner.",
} as const;
const blank = {
file: "src/other.ts",
name: "unknown",
status: "deferred",
reason: "",
} as const;
const audit = auditCanonicalCoercionExports(
new Map([["src/owner.ts", ["kept", "newHelper"]]]),
[kept, kept, removed, blank],
);
expect(audit.invalidClassifications).toEqual([
"src/owner.ts [kept] is classified more than once",
"src/other.ts [unknown] needs a non-empty deferred reason",
]);
expect(audit.unclassifiedExports).toEqual([{ file: "src/owner.ts", name: "newHelper" }]);
expect(audit.staleClassifications).toEqual([removed, blank]);
});
it("scans a temporary repository and reports sorted, owner-specific diagnostics", () => {
const repoRoot = tempDirs.make("coercion-helper-guard-");
fs.mkdirSync(path.join(repoRoot, "src"), { recursive: true });
@@ -65,7 +65,7 @@ describe("scripts/check-deprecated-api-usage", () => {
}
});
it("bans internal imports of every deprecated reply facade", () => {
it("bans internal imports of every deprecated facade", () => {
const modulePaths = new Set(
BANNED_INTERNAL_PLUGIN_SDK_FACADE_MODULES.map((ban) => ban.modulePath),
);
@@ -74,6 +74,7 @@ describe("scripts/check-deprecated-api-usage", () => {
"src/plugin-sdk/channel-message",
"src/plugin-sdk/channel-reply-pipeline",
"src/plugin-sdk/inbound-reply-dispatch",
"src/plugin-sdk/text-runtime",
]) {
expect(modulePaths.has(facade), facade).toBe(true);
}
@@ -95,6 +96,7 @@ describe("scripts/check-deprecated-api-usage", () => {
'import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";',
'export { runChannelInboundEvent } from "../plugin-sdk/inbound-reply-dispatch.js";',
'const facade = await import ("../plugin-sdk/channel-message.js", { with: {} });',
'const text = require("@openclaw/plugin-sdk/text-runtime");',
].join("\n"),
});
@@ -106,6 +108,7 @@ describe("scripts/check-deprecated-api-usage", () => {
"src/channels/probe.ts:2: ../plugin-sdk/inbound-reply-dispatch.js",
);
expect(result.stderr).toContain("src/channels/probe.ts:3: ../plugin-sdk/channel-message.js");
expect(result.stderr).toContain("src/channels/probe.ts:4: @openclaw/plugin-sdk/text-runtime");
});
it("allows canonical compat re-exports and test files", () => {