---
type: Axis
title: validation (declared-invariant axis)
description: The validation/declared-invariant architecture axis — the scope at which data invariants are DECLARED and enforced (none → field → cross-field → cross-entity). A per-entity-with-default axis whose commitment surface is the persistent-data schema (the constraint set the store guarantees). DEEP (RFC #75 5A.4 / P1 #851 → promoted 4.5.0a6 #856); worked example is the plan_entries→milestone cross-entity FK invariant (whose violation false-failed milestone_coverage, #754) vs the field CHECK that validates one value. NOTE — field-level *checks* (the code that validates one value) stay code-design per P2; this axis is the DECLARED-INVARIANT ladder, not the per-check implementation.
tags: [axis, architecture, validation, invariant, constraint, integrity, rfc-75, deep]
timestamp: 2026-06-20T00:00:00Z
---

# Validation (declared-invariant axis)

> **DEEP axis (P1 #851 → 4.5.0a6 #856).** Full §6 depth with a worked example
> from the skill's own history — the plan_entries→milestone FOREIGN KEY is a
> declared cross-entity invariant, and the #754 episode (a poisoned plan_entries
> projection that violated it, false-failing milestone_coverage) is the scar that
> shows why a multi-entity invariant must be store-guaranteed, not hoped-for in
> app code. **Scope note (P2 boundary):**
> a field-level *check* — the code that validates one value (a regex, a range) —
> is **code-design**, not an architecture axis (P2 #852). This axis is the
> *declared-invariant* ladder: at what SCOPE the system guarantees data integrity
> as a structural commitment, independent of any one check's implementation.

## Concept

Validation is the **declared-invariant** axis (RFC #75 §3): the scope at which
data invariants are declared and enforced as a structural guarantee. The ladder
runs none (no declared invariant; any value is stored) → field (per-attribute
invariants: type, not-null, range — declared on the column) → cross-field (an
invariant spanning fields of one row: start ≤ end, this-xor-that) → cross-entity
(an invariant spanning rows/entities: referential integrity, a uniqueness across
a relationship, an aggregate constraint). Its **commitment surface is the
persistent-data schema** (§4): the higher the scope, the more of the integrity
the *store* (constraints, triggers, transactions) guarantees rather than hoping
the application does — and relaxing a declared invariant later means data that
violated it may already have been stored. Its **scope is per-entity-with-default**
(§5): a system-wide default ("field-level invariants are declared on columns")
with per-entity escalation declared in the entity record.

## Why it is an axis (discriminator test — §2)

1. **Ordered tiers** — each rung declares an invariant over a wider scope (none
   ⊂ field ⊂ cross-field ⊂ cross-entity).
2. **Cross-cutting bundle** — a rung dictates the constraint/trigger set, the
   transaction boundary an invariant needs, the write-path checks, the error
   contract, and the integrity tests.
3. **Asymmetric migration cost** — adding a cross-entity invariant to a populated
   store requires the existing data to already satisfy it (or a data-cleanup
   first); removing a declared invariant is easy but data stored under the old
   guarantee can no longer be trusted to hold it.
4. **Requirement-keyed selection** — falsifiable probes pick the rung.

## Schema view (the §6 entry — validated by `axis_catalog`)

```yaml
id: validation
kind: architecture-axis
scope: per-entity-with-default
default_tier: field
maintenance_discipline: >-
  field-level invariants (type, not-null, range) are DECLARED on columns by
  default. A per-entity escalation to a cross-field or cross-entity invariant is
  declared in the entity record's axis-materialization facet AND backed by a
  store-level constraint / transaction where feasible, not only by application
  code. The discipline distinguishes the DECLARED INVARIANT (this axis — a
  structural guarantee) from the field CHECK that implements one value's
  validation (code-design, P2): a new cross-entity invariant must name the
  transaction boundary it needs. Per-entity deviations live in the entity record.
selection:
  probes:
    - "Is any stored value acceptable (a raw blob, a free note)? → none."
    - "Are there per-attribute invariants (type, not-null, a range)? → field."
    - "Does an invariant span several fields of ONE row (start ≤ end)? → cross-field."
    - "Does an invariant span rows/entities (referential integrity, a relationship uniqueness, an aggregate cap)? → cross-entity."
    - "Is a multi-row invariant enforced only in app code with no transaction boundary? → it is not actually guaranteed."
composes_with:
  - "integrity guarantee = validation × consistency (a cross-entity invariant needs a transaction)"
applies_when: []
tiers:
  - tier: none
    commitment_delta:
      surface: persistent-data-schema
      delta: "no declared invariant; any value is stored. The store makes no integrity promise."
    admin_tool: "none."
    enforcement:
      - "writes are accepted unconditionally."
    tests:
      - "an arbitrary value round-trips (the no-invariant contract is honest)."
    forecloses: "any reliance on stored data satisfying a constraint."
    migration_cost_to_next: "low — declare per-field invariants (type, not-null, range) on columns."
  - tier: field
    commitment_delta:
      surface: persistent-data-schema
      delta: "per-attribute invariants declared on the column: type, not-null, range, enum. The store guarantees each value's well-formedness. (The CHECK code itself is code-design — P2.)"
    admin_tool: "schema/constraint tool."
    enforcement:
      - "a column constraint (or an equivalent declared check) rejects an ill-typed/out-of-range value at write."
    tests:
      - "an out-of-range / wrong-type value is rejected; a valid value is stored."
    forecloses: "storing a value that violates its own column invariant."
    migration_cost_to_next: "medium — declare an invariant spanning several fields of one row (cross-field)."
  - tier: cross-field
    commitment_delta:
      surface: persistent-data-schema
      delta: "an invariant over several fields of ONE row (start ≤ end; exactly-one-of-these set). Declared as a row-level CHECK/trigger, not just hoped-for in code."
    admin_tool: "constraint editor; a violation report."
    enforcement:
      - "a row-level constraint rejects a row whose fields jointly violate the invariant."
    tests:
      - "a row with start > end is rejected; a consistent row is stored."
    forecloses: "storing internally-inconsistent rows."
    migration_cost_to_next: "high — declare an invariant spanning multiple rows/entities (cross-entity), needing a transaction boundary."
  - tier: cross-entity
    commitment_delta:
      surface: persistent-data-schema
      delta: "an invariant spanning rows/entities: referential integrity, uniqueness across a relationship, an aggregate cap. Needs a transaction (the consistency axis) to hold under concurrency."
    admin_tool: "FK/constraint console; an integrity-audit job."
    enforcement:
      - "the invariant is enforced by a constraint + a transaction boundary; a write that would violate it across entities is rejected atomically."
    tests:
      - "a write breaking referential integrity / an aggregate cap is rejected; concurrent writes cannot jointly violate it."
    forecloses: "treating multi-entity integrity as an app-code afterthought."
    migration_cost_to_next: "n/a — top of the ladder."
examples:
  - "The skill declares invariants across the ladder, and the CROSS-ENTITY rung is where the lessons live. plan_entries carry a FOREIGN KEY to their milestone — 'every plan entry references a real milestone' is a declared cross-entity invariant, and the #754 episode is its load-bearing scar: a poisoned plan_entries projection that violated the FK caused milestone_coverage to false-fail at cut time, which is exactly the failure this axis predicts — when a multi-entity invariant is real, the data violating it poisons every downstream consumer that trusted it. The canonical store also declares FIELD invariants (typed columns, not-null, the SCHEMA-version integer that gates migrations) and CROSS-FIELD invariants (a per-facet bitemporal shadow whose valid-time/transaction-time pair must be internally consistent). The axis's central distinction-vs-P2 is best shown by contrast: 'created_at holds a parseable timestamp' is a field CHECK — code that validates one value, code-design per #852 — whereas 'every plan_entry references a real milestone' and 'every child issue's milestone FK resolves' are declared cross-entity INVARIANTS, structural commitments the *store* must guarantee under concurrency (the consistency axis supplies the transaction boundary). The cautionary rung this axis names is a cross-entity invariant enforced only in app code with no transaction boundary — which is *not actually guaranteed*; the #754 false-fail is what happens when the projection that was supposed to uphold the invariant is itself what breaks it, which is why the discipline insists the FK live in the schema, not in a regenerator's good intentions."
```

## Selection procedure (§8)

Bidirectional check per entity under the field default. The escalation pass asks
"does an invariant span fields or entities, and is it guaranteed by the store?" —
a multi-entity invariant enforced only in app code (no transaction boundary) is
not actually guaranteed and is a systems-analysis finding (5A.5). Recorded in the
entity record.

## Composition (§5)

Validation × consistency = **integrity guarantee** — a cross-entity invariant
only holds under concurrency if the consistency axis supplies a transaction
boundary. Rule authored in 5A.3.
