
# Validation

Sovrium validates data at two layers: **config-time** (the schema is decoded with Effect Schema when the app boots, catching invalid definitions) and **runtime** (records are checked against field rules, constraints, and permissions on every write). This page summarizes the validation surface for tables.

## Field-Level Validation

Many field types carry built-in validation derived from their properties:

| Rule                  | Applies to                                       | Effect                                                  |
| --------------------- | ------------------------------------------------ | ------------------------------------------------------- |
| `required`            | All fields                                       | Value must be present on every record.                  |
| `unique`              | All fields                                       | No two records may share the value.                     |
| `min` / `max`         | `integer`, `decimal`, `currency`, `percentage`   | Value must fall within the inclusive range.             |
| `precision`           | `decimal` (1–10), `currency`/`percentage` (0–10) | Restricts decimal places.                               |
| `currency` (ISO 4217) | `currency`                                       | Must be a valid three-letter code.                      |
| `maxLength`           | `rich-text`                                      | Caps character length.                                  |
| `maxSelections`       | `multi-select`                                   | Caps choices; cannot exceed the number of `options`.    |
| `options`             | `single-select`, `multi-select`, `status`        | Values must come from the declared option list.         |
| format validation     | `email`, `url`                                   | Must match RFC 5322 / a valid absolute URL.             |
| `targetLanguage`      | `ai-translate`                                   | Must be an ISO 639-1 code (optionally region-suffixed). |
| `categories` / `tags` | `ai-categorize` / `ai-tag`                       | Minimum 2 entries, no duplicates.                       |

## Table-Level Validation (Config Time)

When the schema is decoded, Sovrium enforces structural rules and rejects invalid configurations with descriptive errors:

- **Field names and IDs** must be unique within a table.
- **Field & table names** must match `^[a-z][a-z0-9_]*` (after sanitization) and stay within 63 characters.
- **Index names** and **constraint names** must be unique within the table and match the identifier pattern.
- **Primary key** fields must reference real fields.
- **Indexes, permissions, views, webhooks** are checked so their field references name actual table fields.
- **`count` / `rollup` / `lookup`** fields must reference an existing `relationship` field in the same table.
- **Formula fields** are validated for correct field references.
- **Webhook names** must be unique, and `payload` field selectors must name real fields (the implicit `id` is always valid).

## CHECK Constraints

Use [CHECK constraints](/en/docs/table-indexes-constraints#check-constraints) for cross-field and conditional rules that field-level validation cannot express:

```yaml
constraints:
  - { name: chk_end_after_start, check: 'end_date > start_date' }
  - { name: chk_active_members_have_email, check: '(is_active = false) OR (email IS NOT NULL)' }
```

## Uniqueness & Integrity

- Single-field uniqueness: `field.unique: true` or a top-level `unique: [{ fields: [x] }]`.
- Composite uniqueness: top-level `unique: [{ fields: [a, b] }]` (becomes a unique index) or a unique [index](/en/docs/table-indexes-constraints#indexes).
- Partial uniqueness: a unique index with a `where` clause (e.g. unique slug only among non-deleted rows).
- Relational integrity: [`relationship`](/en/docs/relational-fields) fields create foreign keys with `onDelete`/`onUpdate` actions; composite references use `foreignKeys`.

:::callout
**Fail fast at boot.** Because configuration is decoded with Effect Schema, a malformed table definition (duplicate field name, dangling relationship reference, invalid currency code, out-of-range precision) fails when the app starts — not silently at runtime. Run `sovrium validate app.yaml` to check a config before deploying.
:::
