Summary

Implements the schema migration system described in #519 — a pure, algebraic migration framework for structural schema evolution in ZIO Schema 2.

Note: This supersedes previous PRs #848 and #872, which are closed.

Architecture

  • DynamicMigration — Fully serializable untyped core that operates on DynamicValue. Contains no user functions, closures, or runtime code generation — only pure data.

  • MigrationAction — Sealed trait ADT with 14 action types, all path-based via DynamicOptic:

    • Record: AddField, DropField, Rename, TransformValue, Mandate, Optionalize, ChangeType
    • Join/Split: Join, Split — multi-field merge/split operations
    • Enum: RenameCase, TransformCase (with nested migration actions)
    • Collection/Map: TransformElements, TransformKeys, TransformValues
  • DynamicSchemaExpr — Pure data expression language (sealed trait of case objects/classes). No functions or closures. All evaluation logic lives in DynamicMigration.evalExpr(). Includes:

    • Literal, Identity, DefaultValue
    • 8 reversible primitive conversions: IntToString, StringToInt, LongToString, StringToLong, IntToLong, LongToInt, BooleanToString, StringToBoolean
  • Migration[A, B] — Typed wrapper with source/target schemas enabling migration(oldValue) to return Either[MigrationError, B]

  • MigrationBuilder[A, B] — Fluent DSL builder with string-based, typed optic, nested path, enum, and collection APIs

  • MigrationError — Path-aware error types for diagnostics

Key Design Decisions

  • Path-encoded field names: Field names are encoded as the terminal node in DynamicOptic paths (e.g., root.field("age")) rather than as separate string parameters
  • Atomic action sequences: Builder emits multiple atomic actions when operations involve both transformation and renaming (e.g., mandateField("src", "tgt", default) emits Mandate + Rename)
  • Pure data expressions: DynamicSchemaExpr contains zero logic — all evaluation lives in the migration engine, keeping expressions fully serializable

Laws Verified

  • Identity: Migration.identity[A].apply(a) == Right(a)
  • Associativity: (m1 ++ m2) ++ m3 produces the same result as m1 ++ (m2 ++ m3)
  • Structural reverse: m.reverse.reverse == m
  • Best-effort semantic inverse: m(a) == Right(b) => m.reverse(b) == Right(a) when sufficient information exists

Success Criteria Checklist

  • DynamicMigration fully serializable (pure data, no functions/closures)
  • DynamicSchemaExpr pure data expression language with reversibility
  • Migration[A, B] wraps schemas and actions
  • All actions path-based via DynamicOptic with path-encoded field names
  • User API uses selector functions via typed optics (reusing existing optic(_.field) macro)
  • Join/Split actions for multi-field operations
  • .build validates target fields are covered
  • .buildPartial supported
  • Structural reverse implemented for all actions and expressions
  • Identity and associativity laws hold
  • Enum rename / transform supported
  • Errors include path information
  • 136 comprehensive tests across 19 suites
  • Scala 2.13, Scala 3.3, and Scala 3.7 supported (JVM + JS)

Files

File Purpose
DynamicMigration.scala Untyped migration core with action application and expression evaluation
DynamicSchemaExpr.scala Pure data expression language (sealed trait, no functions)
MigrationAction.scala Sealed trait ADT with 14 action types
MigrationBuilder.scala Fluent DSL builder with string, optic, nested, enum, and collection APIs
Migration.scala Typed wrapper with schema integration
MigrationError.scala Path-aware error types
MigrationSpec.scala 136 tests across 19 suites

Test Plan

  • All 136 migration tests pass on Scala 2.13, 3.3, 3.7
  • Full test suite passes with no regressions
  • Scala.js compilation succeeds
  • scalafmtCheckAll passes
  • CI green on all 10 jobs

Closes #519

/claim #519

Claim

Total prize pool $8,000
Total paid $0
Status Pending
Submitted February 28, 2026
Last updated March 01, 2026

Contributors

SO

Solari Systems

@YourMarx13

100%

Sponsors

MA

marianaguzmanguerrero16-dev

@marianaguzmanguerrero16-dev

$4,000
ZI

ZIO

@ZIO

$4,000