Skip to content

Commit a585174

Browse files
committed
test(tools): type the path-safety harnesses instead of erasing with any
These two harnesses are the template every service copied when the path hardening sweep began, and both open with `type AnyTool = ToolConfig<any, any>` plus an `as any` at the `url(...)` call. CLAUDE.md forbids `any` outright, and because they are the template the violation propagated into ten in-flight PRs before review flagged it. Fixing the source stops the next copy inheriting it. The obvious repair does not compile. `ToolConfig` takes its param type in the contravariant position of `request.url`, so no concrete member of the barrel's union is assignable to a widened `ToolConfig<Record<string, unknown>, ...>`, and `filter`'s type-predicate overload intersects rather than replaces -- filtering the union directly leaves the mismatch standing. Seeding the enumeration as `Object.values<unknown>(...)` makes the existing `isVercelTool` / `isDaytonaTool` predicate the single narrowing point, which is the `unknown`-plus-type-guard form the guidelines actually ask for, and removes the need for a cast at the call site. Note these files have no type coverage in CI from either direction: `apps/sim/tsconfig.json` excludes `**/*.test.ts`, and vitest does not typecheck. Verified with a temporary tsconfig lifting the exclusion, confirming via `--listFiles` that both files were genuinely in the program -- an empty program also reports zero errors. That config was not committed. Behaviour is unchanged: 1129 tests pass, identical to before.
1 parent 34276b9 commit a585174

2 files changed

Lines changed: 22 additions & 8 deletions

File tree

apps/sim/tools/daytona/sandbox_path_safety.test.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import { describe, expect, it } from 'vitest'
1717
import * as daytonaTools from '@/tools/daytona/index'
1818
import { daytonaToolboxUrl, encodeSandboxId } from '@/tools/daytona/utils'
19-
import type { ToolConfig } from '@/tools/types'
19+
import type { ToolConfig, ToolResponse } from '@/tools/types'
2020

2121
const TOOLBOX_PREFIX = '/toolbox/'
2222
const API_PREFIX = '/api/sandbox/'
@@ -48,7 +48,14 @@ const LEGITIMATE_IDS = [
4848

4949
const SAFE_ID = 'SAFEID'
5050

51-
type AnyTool = ToolConfig<any, any>
51+
/**
52+
* The slice of a tool this harness reads. `ToolConfig`'s param type sits in the
53+
* contravariant position of `request.url`, so no concrete member of the barrel's
54+
* union is assignable to a widened `ToolConfig<Record<string, unknown>, …>`. The
55+
* barrel is therefore seeded as `unknown` below and narrowed by {@link isDaytonaTool},
56+
* which is the single point where the type is established.
57+
*/
58+
type AnyTool = ToolConfig<Record<string, unknown>, ToolResponse>
5259

5360
function isDaytonaTool(value: unknown): value is AnyTool {
5461
return (
@@ -82,14 +89,14 @@ function buildPath(tool: AnyTool, sandboxId: string): string {
8289
if (typeof url !== 'function') {
8390
throw new Error(`${tool.id} does not build its URL from params`)
8491
}
85-
return new URL(url(buildParams(tool, sandboxId) as any)).pathname
92+
return new URL(url(buildParams(tool, sandboxId))).pathname
8693
}
8794

8895
function segmentsOf(pathname: string): string[] {
8996
return pathname.split('/')
9097
}
9198

92-
const SANDBOX_SCOPED_TOOLS = Object.values(daytonaTools)
99+
const SANDBOX_SCOPED_TOOLS = Object.values<unknown>(daytonaTools)
93100
.filter(isDaytonaTool)
94101
.filter((tool) => typeof tool.request?.url === 'function')
95102
.filter((tool) => {

apps/sim/tools/vercel/edge_config_path_safety.test.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
* output, because string matching is exactly what let this through.
2222
*/
2323
import { describe, expect, it } from 'vitest'
24-
import type { ToolConfig } from '@/tools/types'
24+
import type { ToolConfig, ToolResponse } from '@/tools/types'
2525
import { vercelDeleteEdgeConfigTool } from '@/tools/vercel/delete_edge_config'
2626
import { vercelGetEdgeConfigTool } from '@/tools/vercel/get_edge_config'
2727
import { vercelGetEdgeConfigItemsTool } from '@/tools/vercel/get_edge_config_items'
@@ -64,7 +64,14 @@ const LEGITIMATE_IDS = [
6464

6565
const SAFE_ID = 'SAFEID'
6666

67-
type AnyTool = ToolConfig<any, any>
67+
/**
68+
* The slice of a tool this harness reads. `ToolConfig`'s param type sits in the
69+
* contravariant position of `request.url`, so no concrete member of the barrel's
70+
* union is assignable to a widened `ToolConfig<Record<string, unknown>, …>`. The
71+
* barrel is therefore seeded as `unknown` below and narrowed by {@link isVercelTool},
72+
* which is the single point where the type is established.
73+
*/
74+
type AnyTool = ToolConfig<Record<string, unknown>, ToolResponse>
6875

6976
function isVercelTool(value: unknown): value is AnyTool {
7077
return (
@@ -102,14 +109,14 @@ function buildPath(tool: AnyTool, value: string): string {
102109
if (typeof url !== 'function') {
103110
throw new Error(`${tool.id} does not build its URL from params`)
104111
}
105-
return new URL(url(buildParams(tool, value) as any)).pathname
112+
return new URL(url(buildParams(tool, value))).pathname
106113
}
107114

108115
function segmentsOf(pathname: string): string[] {
109116
return pathname.split('/')
110117
}
111118

112-
const DYNAMIC_PATH_TOOLS = Object.values(vercelTools)
119+
const DYNAMIC_PATH_TOOLS = Object.values<unknown>(vercelTools)
113120
.filter(isVercelTool)
114121
.filter((tool) => typeof tool.request?.url === 'function')
115122
.filter((tool) => {

0 commit comments

Comments
 (0)