# Typed boundaries

Turn `unknown` into a typed value — `res.json()`, a Supabase `select`/RPC `data`, any payload you didn't build here — with a Zod `.parse()`, never an `as` cast or a hand-rolled `interface`. A cast asserts a shape nobody checked; `.parse()` checks it, and both ends share one schema, so a mismatch is a parse error at the boundary instead of a `TypeError` later. It's the [Types rule](../../../CLAUDE.md) at the JSON boundary; arbe's wire schemas live in [`@arbe/core/schemas/`](../../../packages/core/schemas).

```ts
const body = (await res.json()) as { id: string }                // ✗ unchecked
const { id } = CreateThreadResponseSchema.parse(await res.json()) // ✓ checked, shared
```

Where schemas go:

- Client envelopes (`CreateThreadResponse`, `VersionInfo`) → [`client-responses.ts`](../../../packages/core/schemas/client-responses.ts).
- Domain shapes → beside their domain ([`workflow.ts`](../../../packages/core/schemas/workflow.ts), `usage.ts`, `secret.ts`, `sandbox.ts`); a narrow `select` reuses `RowSchema.pick({ … })`.
- www request bodies → `readJsonBody(event, schema)` ([`validate.ts`](../../../apps/www/src/lib/server/validate.ts)), which 400s on bad input.

Move a local schema into core once a second surface needs it — don't copy it.

A cast is still fine where the code can't own a core schema:

- Core-free packages like [`@arbe/task`](../../../packages/task) and [`@arbe/streams`](../../../packages/streams) — they can't import a core schema, so whoever hands them data parses it. The backstage is no longer one of these: it depends on `@arbe/core` and parses arbe shapes itself (`DispatchRunResponseSchema.parse` in `packages/core/dispatch/reply-turn.ts`).
- Generic transport (`readStream<T>()`, `requestJson<T>()`) — the caller names the schema.
- Third-party payloads (GitHub `latest.json`, Giphy) — parsed where they enter, by whoever owns them.
- Error envelopes you only stringify.

So parse a core schema when your code depends on `@arbe/core` and reads one of arbe's own shapes — in practice www, core, and the backstage. Convert casts as you find them; a list in a doc just goes stale.
