How the Graph Persistence Layer Handles Schema Migrations in Understand-Anything
The graph persistence layer performs schema migrations automatically during graph loading through a runtime validation pipeline that sanitizes, normalizes, and repairs legacy data structures without requiring manual migration scripts.
The Egonex-AI/Understand-Anything repository implements a zero-downtime approach to schema migrations for its knowledge graph format. Instead of versioning the database or requiring explicit upgrade scripts, the TypeScript-based graph persistence layer treats schema evolution as a validation concern, repairing older graph files on-the-fly when they are loaded from .understand-anything/knowledge-graph.json.
Asymmetric Migration Architecture
The persistence layer uses a deliberate asymmetry between saving and loading operations. When saving a graph, the system writes the already-sanitized KnowledgeGraph object directly to disk without embedding version-specific logic or migration metadata. This approach keeps the serialization logic simple and deterministic, as implemented in understand-anything-plugin/packages/core/src/persistence/index.ts lines 69-80.
When loading a graph, however, the system executes a comprehensive six-stage validation and auto-fix process that functions as an implicit schema migration engine. This ensures that graphs created by earlier versions of the tool conform to the current schema expectations before being returned to the application, according to the implementation in understand-anything-plugin/packages/core/src/persistence/index.ts lines 94-101.
The Six-Stage Migration Pipeline
During the load operation, the validateGraph function orchestrates a series of transformations that upgrade legacy graph structures to the current schema standard.
Stage 1: Sanitisation
The sanitizeGraph function removes structural inconsistencies that may exist in older files. It clears null collections, converts enum-like strings to lowercase, and strips optional fields that contain null values. This cleaning step prevents type mismatches during subsequent validation. The implementation resides in understand-anything-plugin/packages/core/src/schema.ts lines 48-66.
Stage 2: Alias Normalisation
Legacy graphs may use outdated naming conventions for node types and edge types. The normalizeGraph function rewrites these aliases to match current standards—for example, converting "func" to "function" and "extends" to "inherits". This normalization ensures that domain-specific terminology remains consistent across versions, as defined in understand-anything-plugin/packages/core/src/schema.ts lines 68-81.
Stage 3: Auto-Fix Defaults
The autoFixGraph function injects required fields that may be missing in older graph versions. For nodes, it ensures the presence of type, complexity, tags, and summary fields. For edges, it guarantees type, direction, and weight properties exist. The function also performs type coercion, converting string weights to numbers and clamping values to the valid range of [0, 1]. This comprehensive repair mechanism in understand-anything-plugin/packages/core/src/schema.ts lines 96-162 bridges structural gaps between schema versions.
Stage 4: Version Fallback
If the top-level version field is missing or not a string, the validator substitutes "1.0.0" as a safe default. This fallback mechanism, found in understand-anything-plugin/packages/core/src/schema.ts lines 654-655, ensures that unversioned legacy graphs receive appropriate default handling.
Stage 5: Referential Integrity
After repairing individual nodes and edges, the validator performs a referential integrity check. It drops any edge that references a non-existent node ID, preventing stale or broken references from corrupting the graph structure. This cleanup occurs in understand-anything-plugin/packages/core/src/schema.ts lines 727-758.
Stage 6: Fatal Error Detection
If the graph remains structurally unsound—such as having no valid nodes—the loader throws a fatal error early in the process. This fail-fast approach, implemented in understand-anything-plugin/packages/core/src/schema.ts lines 662-671, prevents the application from operating on corrupted data.
Practical Code Examples
The following examples demonstrate how the migration-aware loading behaves in practice.
Saving a Graph
When saving, no migration logic runs. The graph is serialized as-is:
import { saveGraph } from "./persistence/index.js";
import type { KnowledgeGraph } from "./types.js";
const graph: KnowledgeGraph = {
version: "1.0.0",
project: { /* …project metadata… */ },
nodes: [{ id: "node-1", type: "file", name: "index.ts", filePath: "/abs/path/src/index.ts", summary: "Entry", tags: [], complexity: "simple" }],
edges: [],
layers: [],
tour: [],
};
saveGraph("/my/project", graph); // writes .understand-anything/knowledge-graph.json
Loading with Auto-Migration
Loading triggers the full validation pipeline, automatically upgrading legacy data:
import { loadGraph } from "./persistence/index.js";
const graph = loadGraph("/my/project"); // validation runs
// `graph` now contains defaults for any missing fields, alias fixes, and a guaranteed version string.
Bypassing Validation
For debugging or inspection of raw on-disk data, validation can be disabled:
const raw = loadGraph("/my/project", { validate: false });
// `raw` is exactly the on‑disk JSON – useful for debugging malformed graphs.
Handling Load Errors
Implement error handling to catch fatal validation failures:
try {
const graph = loadGraph("/my/project");
} catch (e) {
console.error("Failed to load graph:", (e as Error).message);
}
Core Implementation Files
| File | Role |
|---|---|
understand-anything-plugin/packages/core/src/persistence/index.ts |
Saves/loads the graph and invokes validation. |
understand-anything-plugin/packages/core/src/schema.ts |
Defines Zod schemas, sanitises, normalises, and auto‑fixes graphs (migration logic). |
understand-anything-plugin/packages/core/src/types.ts |
TypeScript definitions for KnowledgeGraph and related structures. |
understand-anything-plugin/packages/core/src/__tests__/schema.test.ts |
Test suite that exercises migration scenarios. |
understand-anything-plugin/packages/core/src/__tests__/persistence.test.ts |
Tests the persistence round‑trip and validation behaviours. |
Summary
- Implicit migration: Schema upgrades occur during graph loading, not through explicit scripts.
- Six-stage pipeline: Sanitisation, alias normalisation, auto-fix defaults, version fallback, referential integrity checks, and fatal error detection ensure backward compatibility.
- Asymmetric design: Save operations are simple serializations while load operations perform complex validation and repair.
- Zero-downtime: Legacy graphs migrate automatically on first access without user intervention.
- Fail-fast protection: Structural corruption triggers immediate errors, preventing invalid state propagation.
Frequently Asked Questions
Does the graph persistence layer require manual migration scripts?
No. The system eliminates the need for manual migration scripts by embedding schema evolution logic directly into the validation pipeline. When loadGraph executes, it automatically repairs legacy structures through sanitisation, normalisation, and auto-fixing, ensuring older graphs conform to the current schema without explicit upgrade steps.
What happens if a loaded graph is missing required fields?
The autoFixGraph function injects sensible defaults for any missing required fields. For nodes, it adds type, complexity, tags, and summary. For edges, it ensures type, direction, and weight exist, coercing types and clamping numeric values to valid ranges. This allows partial or legacy graphs to function correctly under the current schema.
How does the system handle legacy naming conventions in node and edge types?
During the alias normalisation stage, the normalizeGraph function rewrites deprecated identifiers to their modern equivalents. For example, "func" becomes "function" and "extends" becomes "inherits". This mapping ensures that semantic changes in the domain model do not break existing graph files.
Can I load a graph without triggering the migration logic?
Yes. The loadGraph function accepts an options parameter that allows you to bypass validation. By passing { validate: false }, you can retrieve the raw JSON exactly as it exists on disk. This is useful for debugging corrupted files or inspecting legacy data structures before they undergo automatic repair.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →