Validation Architecture
Layer Responsibilities
core/ → SSOT: constraints + schemas (ArkType, Standard Schema)
api/ → Gatekeeper: reject invalid requests (sValidator)
front/ → UX: phase-based feedback (withValidationErrorMessages)
Design Decisions
Why constraints separate from schemas?
- Constraints are plain values (reusable in messages, tests, etc.)
- Schemas are validation logic (may change libraries)
- Types derived from constraints (SSOT)
Why Standard Schema?
- Library-agnostic interface (
@standard-schema/spec)
- ArkType today, can swap to Zod/Valibot later
- Both api (sValidator) and front use same interface
Why phase-based validation in front?
onChange: lenient (no required checks) → no red during typing
onDraftSubmit: intermediate
onConfirmedSubmit: strict (all rules) → block invalid submit
Core schema is for "final correctness". Front needs UX timing control.
Why validate twice (front + api)?
- Front validation: UX improvement (optional, can fail)
- API validation: security boundary (required, never trust client)
Key Files
core/src/model/*/constraints.ts - constraint values
core/src/model/*/schema.ts - ArkType schemas
api/src/model/*/index.ts - sValidator usage
front/src/model/common/lib/validation.ts - phase-based utilities
front/src/model/*/detail/form/inputs/*/validation.ts - field validation