---
type: Axis
title: schema-flexibility (shape-rigidity axis)
description: The schema-flexibility/shape architecture axis — how much an entity's persisted shape can vary (fixed → extension-columns → document/EAV → polymorphic). A per-entity-with-default axis whose commitment surface is the persistent-data schema. DEEP (RFC #75 5A.4 / P1 #851 → promoted 4.5.0a6 #856); worked example is the #604 extended-columns work (extension-columns over a fixed core) and why that same #604 episode is why the discipline forbids reaching for a blob to dodge a migration.
tags: [axis, architecture, schema-flexibility, shape, eav, document, polymorphic, rfc-75, deep]
timestamp: 2026-06-18T00:00:00Z
---

# Schema-flexibility (shape-rigidity axis)

> **DEEP axis (P1 #851 → 4.5.0a6 #856).** Full §6 depth with a worked example
> from the skill's own history — the #604 extended-columns work sat a canonical
> entity at extension-columns over an otherwise fixed core, and the same #604
> episode (a regenerator clobbering created_at) is exactly why this axis's
> discipline forbids reaching for a loosely-typed blob to dodge a real migration.
> Distinct from schema-evolution (how the shape *changes over time*) and
> schema-authority (where the shape's *truth lives*): schema-flexibility is how
> much an entity's shape *can vary at all*.

## Concept

Schema-flexibility is the **shape-rigidity** axis (RFC #75 §3): how much an
entity's persisted shape is allowed to vary. The ladder runs fixed (a closed set
of typed columns; every row has the same shape) → extension-columns (a fixed
core plus a few open/nullable extension columns for rare attributes) →
document/EAV (an open key-value or document blob; arbitrary attributes per row)
→ polymorphic (multiple distinct shapes share a table/hierarchy, discriminated by
a type column). Its **commitment surface is the persistent-data schema** (§4):
the rigidity is the schema. Its **scope is per-entity-with-default** (§5): a
system-wide default ("entities are fixed-schema") with per-entity deviations
(one entity may carry an extension blob).

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

1. **Ordered tiers** — each rung admits more shape variation per row.
2. **Cross-cutting bundle** — a rung dictates the column model, the
   query/index strategy (you cannot index an EAV blob like a column), validation,
   and the shape tests.
3. **Asymmetric migration cost** — promoting fixed → document is easy to add but
   hard to *walk back* (you must re-impose structure on heterogeneous rows);
   each rung trades queryability for flexibility.
4. **Requirement-keyed selection** — falsifiable probes pick the rung.

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

```yaml
id: schema-flexibility
kind: architecture-axis
scope: per-entity-with-default
default_tier: fixed
maintenance_discipline: >-
  entities are fixed-schema by default — a closed set of typed columns, each a
  deliberate migration to add. An extension column / document blob is a per-entity
  deviation that must be JUSTIFIED in the entity record (it trades away
  queryability + validation); the discipline forbids reaching for a blob to dodge
  a migration. Per-entity deviations live in the entity record.
selection:
  probes:
    - "Does every row have the same known, closed set of attributes? → fixed."
    - "Is the shape mostly fixed with a few RARE optional attributes? → extension-columns."
    - "Are per-row attributes open-ended / unknown at design time? → document/EAV."
    - "Do several genuinely DIFFERENT shapes share storage, discriminated by a type? → polymorphic."
    - "Is a blob being used to avoid a migration for a well-known attribute? → that attribute wants a fixed column, not flexibility."
composes_with:
  - "migration complexity = schema-flexibility × schema-evolution"
applies_when: []
tiers:
  - tier: fixed
    commitment_delta:
      surface: persistent-data-schema
      delta: "a closed set of typed columns; every row has the same shape. Maximum queryability + validation; every new attribute is a migration."
    admin_tool: "schema/migration tool."
    enforcement:
      - "no attribute exists without a typed column; unknown fields are rejected."
    tests:
      - "a row conforms to the fixed shape; an unexpected attribute is rejected."
    forecloses: "per-row attribute variation without a migration."
    migration_cost_to_next: "low — add a nullable/open extension column for rare attributes."
  - tier: extension-columns
    commitment_delta:
      surface: persistent-data-schema
      delta: "a fixed core plus a few open/nullable extension columns for rare or evolving attributes. Mostly queryable; the extensions are loosely typed."
    admin_tool: "extension-column registry; a usage report (which rows use which extension)."
    enforcement:
      - "core attributes stay typed columns; extensions are documented + bounded, not a dumping ground."
    tests:
      - "core fields validate strictly; an extension round-trips; a misused extension is flagged."
    forecloses: "strict whole-row typing; the extensions escape validation."
    migration_cost_to_next: "medium — adopt an open document/key-value model for fully open shapes."
  - tier: document-eav
    commitment_delta:
      surface: persistent-data-schema
      delta: "an open document blob or entity-attribute-value model; arbitrary per-row attributes. Maximum flexibility, minimum queryability + validation."
    admin_tool: "document/schema inspector; attribute-frequency report."
    enforcement:
      - "the open shape is validated by an application-level schema (not the DB); query paths index extracted keys explicitly."
    tests:
      - "an arbitrary-attribute row round-trips; an indexed extracted key is queryable; app-level validation holds."
    forecloses: "DB-enforced structure + cheap relational queries over attributes."
    migration_cost_to_next: "medium — discriminate distinct shapes by a type column (polymorphic)."
  - tier: polymorphic
    commitment_delta:
      surface: persistent-data-schema
      delta: "several distinct, known shapes share storage discriminated by a type column (single-table or class-table inheritance); each shape is structured but they coexist."
    admin_tool: "per-type schema view; type-discriminator audit."
    enforcement:
      - "each row's shape matches its type discriminator; a shape/type mismatch is rejected."
    tests:
      - "each type validates against its own shape; a mismatched row is caught."
    forecloses: "single-shape simplicity; readers must branch on type."
    migration_cost_to_next: "n/a — top of the ladder."
examples:
  - "Most canonical entities sit at FIXED — the issue, discussion, milestone, and session rows are closed sets of typed columns, each new attribute landing as a deliberate numbered migration (the schema-evolution axis's 'ship a migration, never auto-apply' policy, #778), which is exactly what keeps them maximally queryable and validatable and is why milestone_coverage can rely on a real `milestone` FK rather than parsing a blob. The one entity that climbed to EXTENSION-COLUMNS is the #604 extended-columns work, which added open/nullable columns to a canonical entity for evolving attributes — a justified deviation recorded against an otherwise fixed core. Crucially, the SAME #604 episode is the cautionary tale this axis's discipline is built on: #604/#698 is the temporality scar where a regenerator clobbered a genesis created_at, and the lesson generalized here is that loose, weakly-typed shape is dangerous to maintain — so the discipline forbids reaching for an extension blob to *dodge* a migration for a well-known attribute (a blob escapes the DB's validation and indexing, and the write paths that touch it are the ones most likely to clobber, as #604 showed). The canonical store has never needed DOCUMENT/EAV (no genuinely open-ended per-row attributes) or POLYMORPHIC (the entity kinds — issue, discussion, milestone — live in separate tables rather than a single discriminated table); the queryability those rungs trade away is worth more to this system than the flexibility, given how much of the gate machinery queries columns relationally."
```

## Selection procedure (§8)

Bidirectional check per entity under the fixed default. Recorded in the entity
record; an unjustified blob/extension for a well-known attribute is a
systems-analysis finding.

## Composition (§5)

Schema-flexibility × schema-evolution = **migration complexity** — a flexible
shape is harder to migrate safely. Rule authored in 5A.3.
