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,10 +1,11 @@
// Normalization core tests cover shared error coercion and formatting behavior.
import { describe, expect, it } from "vitest";
import { describe, expect, it, vi } from "vitest";
import {
coerceErrorMessage,
formatErrorMessage,
stringifyNonErrorCause,
toErrorObject,
toStructuredErrorObject,
toStringifiedError,
} from "./error-coercion.js";
@@ -64,6 +65,188 @@ describe("toErrorObject", () => {
});
});
describe("toStructuredErrorObject", () => {
it("preserves Error identity without coercing it", () => {
class ThrowingToStringError extends Error {
override toString(): string {
throw new Error("unexpected stringification");
}
}
const original = new ThrowingToStringError("request failed", {
cause: { code: "EIO" },
});
expect(toStructuredErrorObject(original)).toBe(original);
});
it("preserves primitive message and cause semantics", () => {
const stringError = toStructuredErrorObject("request failed");
expect(stringError).toMatchObject({ message: "request failed" });
expect(stringError).not.toHaveProperty("cause");
for (const value of [undefined, null, 503, false, 503n, Symbol("failure")]) {
const error = toStructuredErrorObject(value);
expect(error.message).toBe(String(value));
expect(Object.hasOwn(error, "cause")).toBe(true);
expect(error.cause).toBe(value);
}
});
it("preserves hostile stringification failures", () => {
const failure = {
[Symbol.toPrimitive]() {
throw new Error("stringification failed");
},
};
expect(() => toStructuredErrorObject(failure)).toThrow("stringification failed");
});
it("copies enumerable string and symbol details while retaining the original cause", () => {
const detailKey = Symbol("detail");
const throwingDetailKey = Symbol("throwing detail");
const cause = {
code: "EIO",
details: { retryable: true },
[detailKey]: "symbol detail",
};
Object.defineProperty(cause, "hidden", { value: "secret", enumerable: false });
Object.defineProperty(cause, throwingDetailKey, {
enumerable: true,
get() {
throw new Error("unexpected symbol field read");
},
});
const error = toStructuredErrorObject(cause);
expect(error).not.toBe(cause);
expect(error.message).toBe("[object Object]");
expect(error.cause).toBe(cause);
expect(error).toMatchObject({ code: "EIO", details: { retryable: true } });
expect(Object.getOwnPropertyDescriptor(error, "code")).toEqual({
value: "EIO",
writable: true,
enumerable: true,
configurable: true,
});
expect(Reflect.get(error, detailKey)).toBe("symbol detail");
expect(Object.hasOwn(error, throwingDetailKey)).toBe(false);
expect(error).not.toHaveProperty("hidden");
const functionCause = Object.assign(function requestFailure() {}, {
code: "EFUNCTION",
[detailKey]: "function symbol detail",
});
const functionError = toStructuredErrorObject(functionCause);
expect(functionError.message).toBe(String(functionCause));
expect(functionError.cause).toBe(functionCause);
expect(functionError).toMatchObject({ code: "EFUNCTION" });
expect(Reflect.get(functionError, detailKey)).toBe("function symbol detail");
});
it("skips fields whose definition fails and continues copying later details", () => {
const originalDefineProperty = Object.defineProperty;
const defineProperty = vi
.spyOn(Object, "defineProperty")
.mockImplementation(
(target: unknown, key: PropertyKey, attributes: PropertyDescriptor): unknown => {
if (target instanceof Error && key === "blocked") {
throw new Error("definition rejected");
}
return originalDefineProperty(target as object, key, attributes);
},
);
try {
const error = toStructuredErrorObject({ before: 1, blocked: 2, after: 3 });
expect(error).toMatchObject({ before: 1, after: 3 });
expect(error).not.toHaveProperty("blocked");
} finally {
defineProperty.mockRestore();
}
});
it("skips throwing fields and preserves the base Error for enumeration failures", () => {
const throwingGetter = {
get details(): never {
throw new Error("unexpected structured field read");
},
code: "EIO",
};
const ownKeysFailure = new Proxy(
{ code: "EIO" },
{
ownKeys() {
throw new Error("unexpected ownKeys call");
},
},
);
const descriptorFailure = new Proxy(
{ code: "EIO", status: 503 },
{
ownKeys() {
return ["code", "status"];
},
getOwnPropertyDescriptor(target, key) {
if (key === "status") {
throw new Error("unexpected descriptor read");
}
return Reflect.getOwnPropertyDescriptor(target, key);
},
},
);
expect(toStructuredErrorObject(throwingGetter)).toMatchObject({ code: "EIO" });
for (const cause of [ownKeysFailure, descriptorFailure]) {
const error = toStructuredErrorObject(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("protects Error-owned and prototype-mutating fields without reading them", () => {
let protectedReads = 0;
const cause = {
get name() {
protectedReads += 1;
return "SpoofedError";
},
get message() {
protectedReads += 1;
return "spoofed message";
},
get cause() {
protectedReads += 1;
return "spoofed cause";
},
get stack() {
protectedReads += 1;
return "spoofed stack";
},
constructor: { polluted: true },
prototype: { polluted: true },
code: "EIO",
};
Object.defineProperty(cause, "__proto__", {
value: { polluted: true },
enumerable: true,
});
const error = toStructuredErrorObject(cause);
expect(protectedReads).toBe(0);
expect(error).toMatchObject({ name: "Error", message: "[object Object]", code: "EIO" });
expect(error.cause).toBe(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("toStringifiedError", () => {
it("preserves Error identity and stringifies every other value", () => {
const error = new Error("boom");
@@ -4,6 +4,9 @@ export type FormatErrorMessageOptions = {
redact: (text: string) => string;
};
const STRUCTURED_ERROR_OWNED_FIELDS = new Set(["cause", "message", "name", "stack"]);
const STRUCTURED_ERROR_PROTOTYPE_FIELDS = new Set(["__proto__", "constructor", "prototype"]);
function readProperty(value: object, key: "cause" | "code" | "status"): unknown {
try {
return (value as Record<string, unknown>)[key];
@@ -125,6 +128,42 @@ export function toErrorObject(value: unknown, fallbackMessage: string): Error {
return error;
}
/** Preserves structured details while isolating hostile object field access. */
export function toStructuredErrorObject(value: unknown): Error {
if (value instanceof Error) {
return value;
}
const message = String(value);
if ((typeof value !== "object" || value === null) && typeof value !== "function") {
return toErrorObject(value, message);
}
const error = new Error(message, { cause: value });
try {
const detailKeys = Reflect.ownKeys(value).filter(
(key) =>
(typeof key !== "string" ||
(!STRUCTURED_ERROR_OWNED_FIELDS.has(key) &&
!STRUCTURED_ERROR_PROTOTYPE_FIELDS.has(key))) &&
Reflect.getOwnPropertyDescriptor(value, key)?.enumerable,
);
for (const key of detailKeys) {
try {
Object.defineProperty(error, key, {
value: Reflect.get(value, 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 error;
}
/** Preserves Error values and stringifies every other value into a new Error. */
export function toStringifiedError(value: unknown): Error {
return value instanceof Error ? value : new Error(String(value));