Skip to content
Back to skills

Versioned Schema Evolution

ASecurity

Stamp every serialized artifact with a schema version and evolve the format over time without breaking data that was already persisted — detect the version on load, migrate old shapes forward, and keep readers for old versions working. Use this whenever you design or change a serialization format (JSON, SQLite rows, pickle, .npy), whenever you load data that an older version of the code may have written, whenever you add or remove a field from something already on disk, and whenever a "load" ...

  • 8 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 3, 2026
ai-agentsrustgosqlexpressdatabase

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned September 3, 2026

npx -y skills add annatchijova/SKILLS --skill versioned-schema-evolution --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Versioned Schema Evolution?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Versioned Schema Evolution
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/annatchijova-versioned-schema-evolution/badge)](https://www.skillsdirectory.com/skills/annatchijova-versioned-schema-evolution)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: versioned-schema-evolution
description: Stamp every serialized artifact with a schema version and evolve the format over time without breaking data that was already persisted — detect the version on load, migrate old shapes forward, and keep readers for old versions working. Use this whenever you design or change a serialization format (JSON, SQLite rows, pickle, .npy), whenever you load data that an older version of the code may have written, whenever you add or remove a field from something already on disk, and whenever a "load" path has to cope with more than one shape of stored data. Triggers — "schema migration", "backward compatible", "version mismatch", "old saved data", "from_dict", "deserialize", "we changed the format". Push to use this whenever serialized data outlives the exact code version that wrote it.
---

# Versioned Schema Evolution

Any format you persist will outlive the code that wrote it. The moment a saved file, a database row, or a serialized model can be loaded by a *different* version of your code than created it, you have a schema-evolution problem — and if the format carries no version, you have an unsolvable one: you cannot safely change it, because you cannot tell what you are looking at. The version stamp is not metadata; it is the contract that makes the format evolvable at all.

## Stamp the version — in the artifact, not just the code

Write a version field into every serialized artifact (`"engine_version": "pca_v2.4"`). This is distinct from a hashing version (see `deterministic-core`, which versions the *canonical form* so a seal stays stable): this version governs *deserialization* — it tells the loader which shape it is about to read. Increment it whenever the serialized shape changes in a way that affects how it must be read back.

## Detect the version on load, and decide deliberately

A loader must read the stamped version first and branch on it. There are three honest responses to a version it sees:

- **Known and current** — load directly.
- **Known but older** — migrate forward (below), or load with documented best-effort compatibility.
- **Unknown / newer than this code** — warn loudly and proceed best-effort, or refuse, depending on how load-bearing the data is. Never load a future version silently as if it were the current one; the fields you assume may not mean what you think.

A version mismatch is information the caller needs, surfaced as a warning, not swallowed.

## Migrate old shapes forward

When changes accumulate, a chain of small migrators beats a pile of conditionals. Register a migrator per version step (`v1 -> v2`, `v2 -> v3`) and, on load, apply them in sequence from the artifact's version up to the current one. Each migrator does one thing: add the field, rename the key, split the column. The loader never special-cases "what if this is v1 being read by v3 code" — it just walks the chain. This keeps every individual migration small and reviewable, and it means old data is always readable by new code.

## Backward compatibility: a missing field is not a zero

The subtle trap is filling a field that an old schema didn't have. If v1 saved no `mean_vector` and v2 needs one, defaulting it to zeros produces an object that *loads cleanly and is wrong* — the model is now centered on the wrong point and every result over it is quietly off. The honest move is to fill the safe default *and mark the artifact as degraded* (`requires_rebuild = True`) so a staleness check rebuilds it from real data instead of trusting the reconstruction. That hand-off to `honest-degradation` is the difference between "backward compatible" and "backward compatible and silently incorrect".

## Match the strictness to the source

Deserialization strictness depends on what the source can preserve. JSON cannot encode a dtype — every number comes back as a 64-bit float — so a JSON loader must coerce (with a warning), not reject, on dtype. A binary source (pickle, `.npy`) *does* preserve dtype, so there a mismatch is a real corruption and should raise. A single `strict` flag, defaulted correctly per source, expresses this: `strict=False` for text round-trips, `strict=True` for binary.

## Keep the old readers alive

When you bump the version, do not delete the ability to verify or read the previous one. Historical artifacts — sealed bundles, audit records, archived models — were written under the old schema and must still validate. "Increment the version and keep supporting verification for the prior version" is the rule; a migration that orphans everything written before it is a data-loss event wearing a refactor's clothes.

## Tooling

`scripts/schema_versioning.py` is a small, dependency-free framework: `dump()` stamps the current version, `load()` detects the stored version, walks a registered chain of migrators up to current, warns on unknown/newer versions, and supports per-source strictness. It includes a worked `v1 -> v2` migration. Run it to see an old-shape record loaded, migrated, and flagged.

Files in this skill

  • SKILL.md4.9 KB
  • scripts/schema_versioning.py4.9 KB
  • versioned-schema-evolution.trigger.json1.2 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.