A House Divided A House DividedDocumentation
Changelog
Engineering/Architecture

Type and schema contracts

Last updated 2026-08-21
Source files

This document describes how compile-time types and runtime validation work together in A House Divided, and where each is required.

Philosophy#

  1. Static types document invariants inside the codebase and catch refactors at build time. They do not prove anything about data that crosses a trust boundary (HTTP, MongoDB, JWT claims, cron payloads, third-party hooks).

  2. Runtime validation (primarily Zod) is required at every boundary where data is produced by an untrusted or external source. Parsing fails closed: invalid input becomes a 4xx response or a rejected/null token, not a partially trusted object.

  3. Avoid decorative type complexity — extra generics, branded types, or deep conditional types are only justified when they remove real bugs or encode a stable domain rule. Prefer a small Zod schema and a shared z.infer type over duplicating shapes by hand.

  4. MongoDB documents are trusted only after the code that reads them has enforced expected shapes (queries, projections, application-level checks). TypeScript generics on collection<T>() describe intent; they do not validate at runtime. Treat documents that can be corrupted by legacy seeds or partial writes as unknown and parse when the risk matters.

Trust boundaries (what to validate)#

Boundary Typical mechanism Notes
HTTP request bodies parseJsonBody(request, zodSchema) in @/lib/api/validate Always validate JSON before use.
URL / search params parseBoundedIntParam, schemas.objectId, route-specific Zod ObjectIds must match hex length/pattern before new ObjectId.
Environment getValidatedEnv() in @/lib/env Lazy validation on first runtime auth/DB use; skipped in tests so next build can compile without secrets.
JWT cookie payload userPayloadSchema in @/lib/auth Cryptographic verification is not enough; claims must match expected shape.
Public API responses Strip server secrets before JSON (e.g. toPublicGameConfig) Never return Discord webhook URLs or other automation secrets to browsers.
Database reads Typed collections + domain logic Use precise TS types for maintainability; add parsing when accepting arbitrary/aggregated data.

When TypeScript alone is enough#

When runtime validation is required#

Project conventions#

Concern Location
JSON body parsing (parseJsonBody)
Env validation
JWT claim shape (userPayloadSchema, verifyAuth)
Public game config (PublicGameConfig), (toPublicGameConfig)
Leadership election DB filters ,
ObjectId hex vs ObjectId ,

Deferred / known gaps#

Connected pages

← Referenced by
None