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
+2 -35
View File
@@ -1,3 +1,4 @@
import { toStructuredErrorObject } from "@openclaw/normalization-core/error-coercion";
import type { WebSocket } from "ws";
import type {
WorkerConnectParams,
@@ -6,14 +7,11 @@ import type {
WorkerProtocolCloseReason,
} from "../../packages/gateway-protocol/src/schema/worker-admission.js";
import type { BackoffPolicy } from "../infra/backoff.js";
import { toErrorObject } from "../infra/errors.js";
const FENCED_CLOSE_REASONS = new Set<WorkerProtocolCloseReason>([
"credential-replaced",
"owner-epoch-mismatch",
]);
const ERROR_OWNED_FIELDS = new Set(["cause", "message", "name", "stack"]);
const PROTOTYPE_MUTATING_FIELDS = new Set(["__proto__", "constructor", "prototype"]);
export type WorkerFencedReason = "credential-replaced" | "owner-epoch-mismatch";
@@ -98,36 +96,5 @@ export function resolvePositiveTimeout(value: number | undefined, fallback: numb
}
export function toWorkerConnectionError(error: unknown): Error {
if (error instanceof Error) {
return error;
}
const message = String(error);
if ((typeof error !== "object" || error === null) && typeof error !== "function") {
return toErrorObject(error, message);
}
const normalized = toErrorObject({}, message);
normalized.cause = error;
try {
const detailKeys = Reflect.ownKeys(error).filter(
(key) =>
(typeof key !== "string" ||
(!ERROR_OWNED_FIELDS.has(key) && !PROTOTYPE_MUTATING_FIELDS.has(key))) &&
Reflect.getOwnPropertyDescriptor(error, key)?.enumerable,
);
for (const key of detailKeys) {
try {
Object.defineProperty(normalized, key, {
value: Reflect.get(error, key),
writable: true,
enumerable: true,
configurable: true,
});
} catch {
// Skip fields whose getters or property definitions reject access.
}
}
} catch {
// Opaque proxies may reject enumeration; preserve the original failure as the cause.
}
return normalized;
return toStructuredErrorObject(error);
}
-120
View File
@@ -132,28 +132,6 @@ function installThrowingThenHealthyListeners(connection: ReturnType<typeof creat
}
describe("worker connection error coercion", () => {
it("preserves existing Error identity without invoking custom toString", () => {
class ThrowingToStringError extends Error {
override toString(): string {
throw new Error("unexpected stringification");
}
}
const cause = { code: "ECONNRESET" };
const original = new ThrowingToStringError("worker failed", { cause });
original.name = "WorkerFailure";
const originalStack = original.stack;
const error = toWorkerConnectionError(original);
expect(error).toBe(original);
expect(error).toMatchObject({
cause,
message: "worker failed",
name: "WorkerFailure",
stack: originalStack,
});
});
it("preserves structured non-Error causes", () => {
const cause = { code: "ECONNRESET", status: 503 };
@@ -163,104 +141,6 @@ describe("worker connection error coercion", () => {
expect(error.cause).toBe(cause);
expect(error).toMatchObject(cause);
});
it("skips structured fields whose getters throw", () => {
const cause = {
get details(): never {
throw new Error("unexpected structured field read");
},
code: "ECONNRESET",
};
let error: Error | undefined;
expect(() => {
error = toWorkerConnectionError(cause);
}).not.toThrow();
expect(error).toMatchObject({ code: "ECONNRESET" });
expect(error).not.toHaveProperty("details");
});
it("preserves the base Error when structured enumeration traps throw", () => {
const handlers: ProxyHandler<{ code: string; status: number }>[] = [
{
ownKeys() {
throw new Error("unexpected ownKeys call");
},
},
{
ownKeys() {
return ["code", "status"];
},
getOwnPropertyDescriptor(target, key) {
if (key === "status") {
throw new Error("unexpected descriptor read");
}
return Reflect.getOwnPropertyDescriptor(target, key);
},
},
];
for (const handler of handlers) {
const cause = new Proxy({ code: "ECONNRESET", status: 503 }, handler);
const error = toWorkerConnectionError(cause);
expect(error).toMatchObject({ name: "Error", message: "[object Object]" });
expect(error.cause).toBe(cause);
expect(error).not.toHaveProperty("code");
expect(error).not.toHaveProperty("status");
}
});
it("preserves adapter-owned Error fields when structured cause fields collide", () => {
const detailKey = Symbol("detail");
let reservedReads = 0;
const cause = {
get name() {
reservedReads += 1;
return "SpoofedError";
},
get message() {
reservedReads += 1;
return "spoofed message";
},
get cause() {
reservedReads += 1;
return "spoofed cause";
},
get stack() {
reservedReads += 1;
return "spoofed stack";
},
code: "ECONNRESET",
details: { retryable: true },
[detailKey]: "symbol detail",
};
const error = toWorkerConnectionError(cause);
expect(reservedReads).toBe(0);
expect(error.message).toBe("[object Object]");
expect(error.cause).toBe(cause);
expect(error.name).toBe("Error");
expect(error.stack).toContain("Error: [object Object]");
expect(error).toMatchObject({ code: "ECONNRESET", details: { retryable: true } });
expect(Reflect.get(error, detailKey)).toBe("symbol detail");
});
it("rejects prototype-mutating structured cause fields", () => {
const cause = { constructor: { polluted: true }, prototype: { polluted: true } };
Object.defineProperty(cause, "__proto__", {
value: { polluted: true },
enumerable: true,
});
const error = toWorkerConnectionError(cause);
expect(Object.getPrototypeOf(error)).toBe(Error.prototype);
expect(Object.hasOwn(error, "__proto__")).toBe(false);
expect(Object.hasOwn(error, "constructor")).toBe(false);
expect(Object.hasOwn(error, "prototype")).toBe(false);
});
});
describe("WorkerConnection state listener isolation", () => {