Skip to content

Commit cc8e281

Browse files
committed
more improvements
1 parent f0925b4 commit cc8e281

61 files changed

Lines changed: 824 additions & 174 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/cleanup/SKILL.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,8 @@ Run each of these skills in order on the specified scope, passing through the sc
2323
6. `/emcn-design-review $ARGUMENTS`
2424

2525
After all skills have run, output a summary of what was found and fixed (or proposed) across all six passes.
26+
27+
## Boundary Audit Guidance
28+
29+
- When removing route-local Zod schemas, replacing raw `fetch(` calls in hooks, or removing `as unknown as X` casts, do not introduce `// boundary-raw-fetch: <reason>` or `// double-cast-allowed: <reason>` annotations to silence the audit. Fix the underlying call instead — adopt a contract from `@/lib/api/contracts/**` and use `requestJson(contract, ...)` from `@/lib/api/client/request`, or refine the type so the double cast is unnecessary.
30+
- Annotations are reserved for legitimate exceptions only: streaming responses, binary downloads, multipart uploads, signed-URL flows, OAuth redirects, external-origin requests, and double casts where no narrower type is available. Each annotation requires a non-empty reason; empty reasons fail `bun run check:api-validation:strict`.

.cursor/rules/sim-architecture.mdc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,5 @@ Boundary HTTP request and response shapes for all routes under `apps/sim/app/api
6565
- Clients call `requestJson(contract, ...)` from `apps/sim/lib/api/client/request.ts`; hooks import named type aliases from contracts, never `z.input/z.output`.
6666
- Routes under `apps/sim/app/api/v1/**` use `apps/sim/app/api/v1/middleware.ts` for shared auth, rate-limit, and workspace access. Compose contract validation inside that middleware.
6767
- `bun run check:api-validation` enforces this policy and must pass on PRs.
68+
69+
`bun run check:api-validation:strict` is the strict CI gate and additionally fails on annotations with empty reasons. Two per-line opt-out forms are recognized: `// boundary-raw-fetch: <reason>` (placed immediately above a legitimate raw `fetch(` call in `apps/sim/hooks/queries/**` or `apps/sim/hooks/selectors/**` for stream/binary/multipart/signed-URL/OAuth-redirect/external-origin cases) and `// double-cast-allowed: <reason>` (placed immediately above an `as unknown as X` cast outside test files). The reason must be non-empty. Whole-file allowlists for routes that legitimately import Zod for non-boundary reasons go through `INDIRECT_ZOD_ROUTES` in `scripts/check-api-validation-contracts.ts`, not per-line annotations.

.cursor/rules/sim-queries.mdc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ const handler = useCallback(() => {
128128

129129
- Hooks must import named type aliases from `@/lib/api/contracts/**` (e.g., `import { listEntitiesContract, type EntityList } from '@/lib/api/contracts/entities'`). Never write `z.input<...>` or `z.output<...>` in hooks.
130130
- Hooks must not `import { z } from 'zod'`. Boundary types come from contract aliases; non-boundary helpers can stay in plain TypeScript.
131-
- For non-contract endpoints (multipart uploads, binary downloads, streaming responses, signed-URL flows, OAuth redirects, external origins), it is OK to keep raw `fetch`. Mark each raw `fetch` with a TSDoc comment explaining which exception applies.
131+
- For non-contract endpoints (multipart uploads, binary downloads, streaming responses, signed-URL flows, OAuth redirects, external origins), it is OK to keep raw `fetch`. Each legitimate raw `fetch(` call inside `apps/sim/hooks/queries/**` or `apps/sim/hooks/selectors/**` must be preceded by a `// boundary-raw-fetch: <reason>` annotation on the immediately preceding line (up to three non-empty preceding comment lines are tolerated). The reason must be non-empty — empty reasons fail strict mode. The audit script `scripts/check-api-validation-contracts.ts` (`bun run check:api-validation` / `bun run check:api-validation:strict`) enforces this.
132132

133133
## Naming
134134

AGENTS.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,33 @@ Boundary HTTP request and response shapes for all routes under `apps/sim/app/api
124124
- Contracts export named schemas (e.g., `createFolderBodySchema`) AND named TypeScript type aliases (e.g., `export type CreateFolderBody = z.input<typeof createFolderBodySchema>`)
125125
- Clients (hooks, utilities, components) import the named type aliases from the contract file. They must never write `z.input<...>` / `z.output<...>` themselves
126126
- Shared identifier schemas live in `apps/sim/lib/api/contracts/primitives.ts` (e.g., `workspaceIdSchema`, `workflowIdSchema`). Reuse these instead of redefining string-based ID schemas
127-
- Audit script: `bun run check:api-validation` enforces boundary policy and prints ratchet metrics for route Zod imports, route-local schema constructors, route `ZodError` references, client hook Zod imports, and related counters. It must pass on PRs
127+
- Audit script: `bun run check:api-validation` enforces boundary policy and prints ratchet metrics for route Zod imports, route-local schema constructors, route `ZodError` references, client hook Zod imports, and related counters. It must pass on PRs. `bun run check:api-validation:strict` is the strict CI gate and additionally fails on annotations with empty reasons
128128

129129
Domain validators that are not HTTP boundaries — tools, blocks, triggers, connectors, realtime handlers, and internal helpers — may still use Zod directly. The contract rule is boundary-only.
130130

131+
### Boundary annotations
132+
133+
A small number of legitimate exceptions to the boundary rules are tolerated when annotated. The audit script recognizes two annotation forms:
134+
135+
- `// boundary-raw-fetch: <reason>` — placed on the line directly above a raw `fetch(` call inside `apps/sim/hooks/queries/**` or `apps/sim/hooks/selectors/**`. Use only for documented exceptions: streaming responses, binary downloads, multipart uploads, signed-URL flows, OAuth redirects, and external-origin requests
136+
- `// double-cast-allowed: <reason>` — placed on the line directly above an `as unknown as X` cast outside test files
137+
138+
Placement rule: the annotation must immediately precede the call or cast. Up to three non-empty preceding comment lines are tolerated, so additional context comments above the annotation are fine. The reason must be non-empty after trimming — annotations with empty reasons fail strict mode (`annotationsMissingReason`).
139+
140+
Whole-file allowlists for routes (legitimate non-boundary or auth-handled routes that legitimately import Zod for non-boundary reasons) go through `INDIRECT_ZOD_ROUTES` in `scripts/check-api-validation-contracts.ts`, not per-line annotations.
141+
142+
Examples:
143+
144+
```ts
145+
// boundary-raw-fetch: streaming SSE chunks must be processed as they arrive
146+
const response = await fetch(`/api/copilot/chat/stream?chatId=${chatId}`, { signal })
147+
```
148+
149+
```ts
150+
// double-cast-allowed: legacy provider type lacks the discriminator field we need
151+
const provider = config as unknown as LegacyProvider
152+
```
153+
131154
## API Route Pattern
132155

133156
Routes never `import { z } from 'zod'` and never define route-local boundary schemas. They consume the contract from `@/lib/api/contracts/**` and validate with canonical helpers from `@/lib/api/server`:

CLAUDE.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,33 @@ Boundary HTTP request and response shapes for all routes under `apps/sim/app/api
103103
- Contracts export named schemas (e.g., `createFolderBodySchema`) AND named TypeScript type aliases (e.g., `export type CreateFolderBody = z.input<typeof createFolderBodySchema>`)
104104
- Clients (hooks, utilities, components) import the named type aliases from the contract file. They must never write `z.input<...>` / `z.output<...>` themselves
105105
- Shared identifier schemas live in `apps/sim/lib/api/contracts/primitives.ts` (e.g., `workspaceIdSchema`, `workflowIdSchema`). Reuse these instead of redefining string-based ID schemas
106-
- Audit script: `bun run check:api-validation` enforces boundary policy and prints ratchet metrics for route Zod imports, route-local schema constructors, route `ZodError` references, client hook Zod imports, and related counters. It must pass on PRs
106+
- Audit script: `bun run check:api-validation` enforces boundary policy and prints ratchet metrics for route Zod imports, route-local schema constructors, route `ZodError` references, client hook Zod imports, and related counters. It must pass on PRs. `bun run check:api-validation:strict` is the strict CI gate and additionally fails on annotations with empty reasons
107107

108108
Domain validators that are not HTTP boundaries — tools, blocks, triggers, connectors, realtime handlers, and internal helpers — may still use Zod directly. The contract rule is boundary-only.
109109

110+
### Boundary annotations
111+
112+
A small number of legitimate exceptions to the boundary rules are tolerated when annotated. The audit script recognizes two annotation forms:
113+
114+
- `// boundary-raw-fetch: <reason>` — placed on the line directly above a raw `fetch(` call inside `apps/sim/hooks/queries/**` or `apps/sim/hooks/selectors/**`. Use only for documented exceptions: streaming responses, binary downloads, multipart uploads, signed-URL flows, OAuth redirects, and external-origin requests
115+
- `// double-cast-allowed: <reason>` — placed on the line directly above an `as unknown as X` cast outside test files
116+
117+
Placement rule: the annotation must immediately precede the call or cast. Up to three non-empty preceding comment lines are tolerated, so additional context comments above the annotation are fine. The reason must be non-empty after trimming — annotations with empty reasons fail strict mode (`annotationsMissingReason`).
118+
119+
Whole-file allowlists for routes (legitimate non-boundary or auth-handled routes that legitimately import Zod for non-boundary reasons) go through `INDIRECT_ZOD_ROUTES` in `scripts/check-api-validation-contracts.ts`, not per-line annotations.
120+
121+
Examples:
122+
123+
```ts
124+
// boundary-raw-fetch: streaming SSE chunks must be processed as they arrive
125+
const response = await fetch(`/api/copilot/chat/stream?chatId=${chatId}`, { signal })
126+
```
127+
128+
```ts
129+
// double-cast-allowed: legacy provider type lacks the discriminator field we need
130+
const provider = config as unknown as LegacyProvider
131+
```
132+
110133
## API Route Pattern
111134

112135
Every API route handler must be wrapped with `withRouteHandler`. This sets up `AsyncLocalStorage`-based request context so all loggers in the request lifecycle automatically include the request ID.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ See the [environment variables reference](https://docs.sim.ai/self-hosting/envir
141141
- **Runtime**: [Bun](https://bun.sh/)
142142
- **Database**: PostgreSQL with [Drizzle ORM](https://orm.drizzle.team)
143143
- **Authentication**: [Better Auth](https://better-auth.com)
144-
- **Schema Validation**: [Zod](https://zod.dev) (v4) — shared API contracts, request/response validation, and end-to-end client-server type safety
144+
- **Schema Validation**: [Zod](https://zod.dev)
145145
- **UI**: [Shadcn](https://ui.shadcn.com/), [Tailwind CSS](https://tailwindcss.com)
146146
- **Streaming Markdown**: [Streamdown](https://github.com/vercel/streamdown)
147147
- **State Management**: [Zustand](https://zustand-demo.pmnd.rs/), [TanStack Query](https://tanstack.com/query)

apps/sim/AGENTS.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,32 @@ Boundary HTTP request and response shapes for all routes under `apps/sim/app/api
7676
- Each contract is built with `defineRouteContract({ method, path, params?, query?, body?, headers?, response: { mode: 'json', schema } })` from `@/lib/api/contracts`.
7777
- Contracts export named schemas AND named TypeScript type aliases (e.g., `export type CreateFolderBody = z.input<typeof createFolderBodySchema>`). Clients import the named aliases — never `z.input<...>` / `z.output<...>` in hooks.
7878
- Shared identifier schemas live in `apps/sim/lib/api/contracts/primitives.ts` (e.g., `workspaceIdSchema`, `workflowIdSchema`).
79-
- Audit script: `bun run check:api-validation` enforces boundary policy and prints ratchet metrics for route Zod imports, route-local schema constructors, route `ZodError` references, client hook Zod imports, and related counters. It must pass on PRs.
79+
- Audit script: `bun run check:api-validation` enforces boundary policy and prints ratchet metrics for route Zod imports, route-local schema constructors, route `ZodError` references, client hook Zod imports, and related counters. It must pass on PRs. `bun run check:api-validation:strict` is the strict CI gate and additionally fails on annotations with empty reasons.
8080
- Domain validators that are not HTTP boundaries — tools, blocks, triggers, connectors, realtime handlers, and internal helpers — may still use Zod directly. The contract rule is boundary-only.
8181

82+
### Boundary annotations
83+
84+
A small number of legitimate exceptions to the boundary rules are tolerated when annotated. The audit script recognizes two annotation forms:
85+
86+
- `// boundary-raw-fetch: <reason>` — placed on the line directly above a raw `fetch(` call inside `apps/sim/hooks/queries/**` or `apps/sim/hooks/selectors/**`. Use only for documented exceptions: streaming responses, binary downloads, multipart uploads, signed-URL flows, OAuth redirects, and external-origin requests.
87+
- `// double-cast-allowed: <reason>` — placed on the line directly above an `as unknown as X` cast outside test files.
88+
89+
Placement rule: the annotation must immediately precede the call or cast. Up to three non-empty preceding comment lines are tolerated, so additional context comments above the annotation are fine. The reason must be non-empty after trimming — annotations with empty reasons fail strict mode (`annotationsMissingReason`).
90+
91+
Whole-file allowlists for routes (legitimate non-boundary or auth-handled routes that legitimately import Zod for non-boundary reasons) go through `INDIRECT_ZOD_ROUTES` in `scripts/check-api-validation-contracts.ts`, not per-line annotations.
92+
93+
Examples:
94+
95+
```ts
96+
// boundary-raw-fetch: streaming SSE chunks must be processed as they arrive
97+
const response = await fetch(`/api/copilot/chat/stream?chatId=${chatId}`, { signal })
98+
```
99+
100+
```ts
101+
// double-cast-allowed: legacy provider type lacks the discriminator field we need
102+
const provider = config as unknown as LegacyProvider
103+
```
104+
82105
## API Route Pattern
83106

84107
Routes never `import { z } from 'zod'` and never define route-local boundary schemas. They consume the contract from `@/lib/api/contracts/**` and validate with canonical helpers from `@/lib/api/server`:

apps/sim/app/api/creators/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
178178
name: data.name,
179179
profileImageUrl: data.profileImageUrl || null,
180180
details: Object.keys(details).length > 0 ? details : null,
181+
verified: false,
181182
createdBy: session.user.id,
182183
createdAt: now,
183184
updatedAt: now,

apps/sim/app/api/knowledge/[id]/documents/[documentId]/tag-definitions/route.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,11 @@ export const POST = withRouteHandler(
117117
const validatedData = validation.data
118118

119119
for (const def of validatedData.definitions) {
120-
// Defense-in-depth runtime check: contract leaves `fieldType` as `z.string()`
121-
// because tightening to the enum cascades into UI form state types. Cast here
122-
// to allow `includes` to accept the wider input.
120+
/**
121+
* Defense-in-depth runtime check: the contract types `fieldType` as a plain
122+
* string because tightening to the field-type enum cascades into UI form
123+
* state types. Cast here to allow `includes` to accept the wider input.
124+
*/
123125
if (!(SUPPORTED_FIELD_TYPES as readonly string[]).includes(def.fieldType)) {
124126
return NextResponse.json(
125127
{ error: 'Invalid request data', details: `Unsupported field type: ${def.fieldType}` },
Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import type { NextRequest, NextResponse } from 'next/server'
2-
import { z } from 'zod'
2+
import { mcpOauthAuthorizationServerMetadataContract } from '@/lib/api/contracts/mcp-oauth'
3+
import { parseRequest } from '@/lib/api/server'
4+
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
35
import { createMcpAuthorizationServerMetadataResponse } from '@/lib/mcp/oauth-discovery'
46

5-
const metadataQuerySchema = z.record(z.string(), z.string())
6-
7-
export async function GET(request: NextRequest): Promise<NextResponse> {
8-
metadataQuerySchema.parse(Object.fromEntries(request.nextUrl.searchParams))
7+
export const GET = withRouteHandler(async (request: NextRequest): Promise<NextResponse> => {
8+
const parsed = await parseRequest(mcpOauthAuthorizationServerMetadataContract, request, {})
9+
if (!parsed.success) return parsed.response as NextResponse
910

1011
return createMcpAuthorizationServerMetadataResponse()
11-
}
12+
})

0 commit comments

Comments
 (0)