aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoramankumarsingh77 <amankumarsingh7702@gmail.com>2026-07-28 20:40:58 +0530
committerAman Kumar <145339995+amankumarsingh77@users.noreply.github.com>2026-08-02 19:12:46 +0530
commit3c4e0b21ad76a734731725cf16cdfb772aa69bbf (patch)
tree75b48925706de9f01ab84576dcdad836f889592b
parenta9bdbd8590801cd9c2fa0cda6203948ac19a779c (diff)
chore: make the boundary scanner see every cast, not just the unsafe forms
The scanner banned as any, as {, as unknown as, non-null assertions and ts-ignore, but said nothing about `as SomeNamedType` — so a new cast over CDP or JSON data could be added and CI would stay green, which is the exact regression the refactor exists to prevent. It also could not see a cast whose target type sits on the following line. Adds named-cast, function-cast and trailing-cast. All six surviving assertions in src/ are now visible to the scanner and individually exempted with a stated reason, so the allowlist is the complete inventory. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-rw-r--r--scripts/boundary-allowlist.ts35
-rw-r--r--scripts/check-boundaries.ts3
-rw-r--r--test/util/check-boundaries-test.ts20
3 files changed, 56 insertions, 2 deletions
diff --git a/scripts/boundary-allowlist.ts b/scripts/boundary-allowlist.ts
index be6816f..bb34139 100644
--- a/scripts/boundary-allowlist.ts
+++ b/scripts/boundary-allowlist.ts
@@ -13,4 +13,39 @@ export const ALLOWLIST: ReadonlyArray<Exemption> = [
reason:
"sharp is an optional dependency resolved by a dynamic import, so its shape is unknown at compile time; the cast only claims an optional .default and every value read off it is re-checked with typeof before use",
},
+ {
+ path: "src/util/sharp-shim.ts",
+ rule: "named-cast",
+ count: 1,
+ reason:
+ "the same optional-dependency boundary: candidate is narrowed to a callable by a typeof check on the line above before it is claimed to be a SharpFactory",
+ },
+ {
+ path: "src/cdp/commands.ts",
+ rule: "named-cast",
+ count: 1,
+ reason:
+ "the sanctioned CDP result decoder; guarded by the validate.Check on the preceding line, and unavoidable because a generic key cannot correlate with its mapped value type",
+ },
+ {
+ path: "src/cdp/events.ts",
+ rule: "named-cast",
+ count: 1,
+ reason:
+ "the sanctioned CDP event decoder; same guarded Check and same generic-correlation limit as cdp/commands.ts",
+ },
+ {
+ path: "src/util/tool.ts",
+ rule: "function-cast",
+ count: 1,
+ reason:
+ "renderCall is stored under a per-tool Static<S> but invoked through the erased TSchema form; the generic-correlation limit makes this unavoidable and the value is the tool's own handler, created with the same S",
+ },
+ {
+ path: "src/domains/js.ts",
+ rule: "trailing-cast",
+ count: 1,
+ reason:
+ "the AsyncFunction constructor is reached through Object.getPrototypeOf, which returns any; the cast restores the constructor signature that a plain unknown would lose",
+ },
];
diff --git a/scripts/check-boundaries.ts b/scripts/check-boundaries.ts
index 5dff96d..c438b83 100644
--- a/scripts/check-boundaries.ts
+++ b/scripts/check-boundaries.ts
@@ -17,6 +17,9 @@ const RULES: ReadonlyArray<{ readonly rule: string; readonly re: RegExp }> = [
{ rule: "non-null-assertion", re: /[A-Za-z_$)\]]!\s*(?:[.[(;,)\]}]|$)/ },
{ rule: "inline-object-cast", re: /\bas\s+\{/ },
{ rule: "double-cast", re: /\bas\s+unknown\s+as\b/ },
+ { rule: "named-cast", re: /\bas\s+(?:new\s+)?[A-Z][A-Za-z0-9_]*/ },
+ { rule: "function-cast", re: /\bas\s+\(/ },
+ { rule: "trailing-cast", re: /\bas\s*$/ },
];
export type SourceFile = { readonly path: string; readonly text: string };
diff --git a/test/util/check-boundaries-test.ts b/test/util/check-boundaries-test.ts
index e3c2440..b42baa0 100644
--- a/test/util/check-boundaries-test.ts
+++ b/test/util/check-boundaries-test.ts
@@ -77,8 +77,24 @@ describe("boundary check", () => {
assert.equal(checkSource([{ path: "a.ts", text: "const x = [1] as const;" }], noExemptions).length, 0);
});
- test("allows a cast to a named type", () => {
- assert.equal(checkSource([{ path: "a.ts", text: "return ok(raw as ResultOf<M>);" }], noExemptions).length, 0);
+ test("flags a cast to a named type", () => {
+ const v = checkSource([{ path: "a.ts", text: "return ok(raw as ResultOf<M>);" }], noExemptions);
+ assert.equal(v.some((x) => x.rule === "named-cast"), true);
+ });
+
+ test("flags a cast to a function type", () => {
+ const v = checkSource([{ path: "a.ts", text: "(fn as (a: unknown) => void)(x);" }], noExemptions);
+ assert.equal(v.some((x) => x.rule === "function-cast"), true);
+ });
+
+ test("flags a cast whose target type sits on the next line", () => {
+ const v = checkSource([{ path: "a.ts", text: "const f = base.constructor as\n new () => void;" }], noExemptions);
+ assert.equal(v.some((x) => x.rule === "trailing-cast"), true);
+ });
+
+ test("does not flag the word as inside prose", () => {
+ const text = 'const d = "Capture the page as a JPEG. DO NOT use as a default tool.";';
+ assert.equal(checkSource([{ path: "a.ts", text }], noExemptions).length, 0);
});
test("reports the 1-indexed line number and the trimmed text", () => {