mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
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:
committed by
GitHub
parent
66fe424590
commit
b080dd1e76
@@ -8,7 +8,7 @@ import {
|
||||
} from "@openclaw/crabline";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { parseBooleanValue, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
buildQaAgenticParityComparison,
|
||||
buildQaRuntimeParityReport,
|
||||
@@ -238,20 +238,11 @@ function parseQaModelThinkingOverrides(entries: readonly string[] | undefined) {
|
||||
}
|
||||
|
||||
function parseQaBooleanModelOption(label: string, value: string) {
|
||||
switch (value.trim().toLowerCase()) {
|
||||
case "1":
|
||||
case "on":
|
||||
case "true":
|
||||
case "yes":
|
||||
return true;
|
||||
case "0":
|
||||
case "false":
|
||||
case "no":
|
||||
case "off":
|
||||
return false;
|
||||
default:
|
||||
throw new Error(`${label} fast must be one of true, false, on, off, yes, no, 1, 0`);
|
||||
const parsed = parseBooleanValue(value);
|
||||
if (parsed === undefined) {
|
||||
throw new Error(`${label} fast must be one of true, false, on, off, yes, no, 1, 0`);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function parseQaPositiveIntegerOption(label: string, value: number | undefined) {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// Qa Lab plugin module implements coverage report behavior.
|
||||
import { normalizeStringEntriesLower } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
normalizeOptionalString as stringifyConfigValue,
|
||||
normalizeStringEntriesLower,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { QaSeedScenarioWithSource } from "./scenario-catalog.js";
|
||||
import {
|
||||
readQaScorecardTaxonomyReport,
|
||||
@@ -134,10 +137,6 @@ function scenarioSearchText(scenario: QaSeedScenarioWithSource) {
|
||||
);
|
||||
}
|
||||
|
||||
function stringifyConfigValue(value: unknown) {
|
||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
function summarizeScenarioSearchMatch(scenario: QaSeedScenarioWithSource): QaScenarioSearchMatch {
|
||||
const config = scenario.execution.config ?? {};
|
||||
return {
|
||||
|
||||
@@ -287,7 +287,7 @@ function resolveMatrixQaStreamingMode(
|
||||
function isMatrixQaStreamingConfig(
|
||||
value: MatrixQaConfigOverrides["streaming"],
|
||||
): value is MatrixQaStreamingConfig {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
return isRecord(value);
|
||||
}
|
||||
|
||||
function resolveMatrixQaStreamingPreviewToolProgress(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// QA Lab Slack credentials, instrumentation, and channel config.
|
||||
import type { WebClient } from "@slack/web-api";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { asNonArrayRecord, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
type SlackQaRuntimeEnv,
|
||||
type SlackQaConfigOverrides,
|
||||
@@ -52,9 +52,7 @@ export function parseSlackQaCredentialPayload(payload: unknown): SlackQaRuntimeE
|
||||
}
|
||||
|
||||
export function asPlainRecord(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: {};
|
||||
return asNonArrayRecord(value);
|
||||
}
|
||||
|
||||
type SlackQaPostMessageAttempt = {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import path from "node:path";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createQaBusState } from "./bus-state.js";
|
||||
import { runLoadedScenarioFlow } from "./scenario-flow-runner.test-support.js";
|
||||
@@ -168,8 +169,7 @@ async function runSessionMemoryRankingFlow(params: {
|
||||
seedQaSessionTranscript: async () => undefined,
|
||||
forceMemoryIndex,
|
||||
runAgentPrompt,
|
||||
normalizeLowercaseStringOrEmpty: (value: unknown) =>
|
||||
typeof value === "string" ? value.trim().toLowerCase() : "",
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
fetchJson,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import path from "node:path";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createQaBusState } from "./bus-state.js";
|
||||
import { runLoadedScenarioFlow } from "./scenario-flow-runner.test-support.js";
|
||||
@@ -19,8 +20,7 @@ async function runMemoryRecallScenario(recallReply?: string) {
|
||||
fs: { rm: async () => undefined },
|
||||
path,
|
||||
formatMemoryDreamingDay: () => "2026-08-05",
|
||||
normalizeLowercaseStringOrEmpty: (value: unknown) =>
|
||||
typeof value === "string" ? value.trim().toLowerCase() : "",
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
runAgentPrompt: async (_env: unknown, params: { message: string }) => {
|
||||
turnCount += 1;
|
||||
state.addInboundMessage({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createQaBusState } from "./bus-state.js";
|
||||
import { runLoadedScenarioFlow } from "./scenario-flow-runner.test-support.js";
|
||||
@@ -118,8 +119,7 @@ async function runFollowUp(params?: {
|
||||
runAgentPrompt,
|
||||
splitModelRef,
|
||||
normalizeModelRef,
|
||||
normalizeLowercaseStringOrEmpty: (value: unknown) =>
|
||||
typeof value === "string" ? value.trim().toLowerCase() : "",
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
resolveQaLiveTurnTimeoutMs: (_env: unknown, timeoutMs: number) => timeoutMs,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createQaBusState } from "./bus-state.js";
|
||||
import { hasModelSwitchContinuitySignal } from "./model-switch-eval.js";
|
||||
@@ -103,8 +104,7 @@ async function runToolContinuity(
|
||||
},
|
||||
splitModelRef,
|
||||
normalizeModelRef,
|
||||
normalizeLowercaseStringOrEmpty: (value: unknown) =>
|
||||
typeof value === "string" ? value.trim().toLowerCase() : "",
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
resolveQaLiveTurnTimeoutMs: (_env: unknown, timeoutMs: number) => timeoutMs,
|
||||
hasModelSwitchContinuitySignal,
|
||||
runAgentPrompt,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { join } from "node:path";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createQaBusState } from "./bus-state.js";
|
||||
import { readQaScenarioById } from "./scenario-catalog.js";
|
||||
@@ -44,8 +45,7 @@ function createCharacterScenarioApi(
|
||||
writeFile: async () => undefined,
|
||||
},
|
||||
path: { join },
|
||||
normalizeLowercaseStringOrEmpty: (value: unknown) =>
|
||||
typeof value === "string" ? value.trim().toLowerCase() : "",
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
resolveQaLiveTurnTimeoutMs: () => 10,
|
||||
waitForOutboundMessage: async (
|
||||
state: ReturnType<typeof createQaBusState>,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// Qa Lab tests cover scenario flow runner plugin behavior.
|
||||
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createQaBusState } from "./bus-state.js";
|
||||
import { QaSuiteScenarioSkipError } from "./errors.js";
|
||||
@@ -60,10 +62,8 @@ async function runWebchatTranscriptWait(
|
||||
}
|
||||
throw new Error("test condition was not met");
|
||||
},
|
||||
normalizeLowercaseStringOrEmpty: (value: unknown) =>
|
||||
typeof value === "string" ? value.trim().toLowerCase() : "",
|
||||
formatErrorMessage: (error: unknown) =>
|
||||
error instanceof Error ? error.message : String(error),
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
formatErrorMessage: coerceErrorMessage,
|
||||
liveTurnTimeoutMs: (_env: unknown, timeoutMs: number) => timeoutMs,
|
||||
},
|
||||
});
|
||||
@@ -284,8 +284,7 @@ function runPlanningEvidenceFixture(
|
||||
return summary;
|
||||
},
|
||||
resolveQaLiveTurnTimeoutMs: (_env: unknown, timeoutMs: number) => timeoutMs,
|
||||
normalizeLowercaseStringOrEmpty: (value: unknown) =>
|
||||
typeof value === "string" ? value.trim().toLowerCase() : "",
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
runAgentPrompt: async () => ({ started: { runId: "current-run" }, waited: { status: "ok" } }),
|
||||
},
|
||||
});
|
||||
@@ -397,8 +396,7 @@ describe("scenario-flow-runner", () => {
|
||||
throw new Error("goal artifact has not been written");
|
||||
},
|
||||
},
|
||||
normalizeLowercaseStringOrEmpty: (value: unknown) =>
|
||||
typeof value === "string" ? value.trim().toLowerCase() : "",
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
},
|
||||
onWaitForOutboundMessage: ({ waitCount, state: currentState }) => {
|
||||
const currentInbound = currentState
|
||||
@@ -562,8 +560,7 @@ describe("scenario-flow-runner", () => {
|
||||
runLoadedScenarioFlow(id, {
|
||||
state,
|
||||
api: {
|
||||
normalizeLowercaseStringOrEmpty: (value: unknown) =>
|
||||
typeof value === "string" ? value.trim().toLowerCase() : "",
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
runAgentPrompt: async () => {
|
||||
turnCount += 1;
|
||||
state.addOutboundMessage({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Qa Lab plugin module implements suite summary behavior.
|
||||
import fs from "node:fs/promises";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { asSafeIntegerInRange, isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { QaSuiteArtifactError } from "./errors.js";
|
||||
import type { QaEvidenceSummaryJson, QaEvidenceTiming } from "./evidence-summary.js";
|
||||
import type { QaProviderMode } from "./model-selection.js";
|
||||
@@ -110,7 +110,7 @@ async function readQaSuiteSummaryFile(summaryPath: string): Promise<unknown> {
|
||||
}
|
||||
|
||||
function readNonNegativeCount(value: unknown): number | null {
|
||||
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
||||
return asSafeIntegerInRange(value, { min: 0 }) ?? null;
|
||||
}
|
||||
|
||||
function assertQaSuiteSummaryHasExecutedScenarios(
|
||||
|
||||
Reference in New Issue
Block a user