Skip to content

ADR-0027: Migration Transformation Mechanism

Accepted

Date: 2026-06-25 Depends on: ADR-0026 accepted

Context

ADR-0026 establishes that migration of an existing Almathal-generated app is performed by updating its persisted Spec and re-running the standard pipeline. It establishes that when a new Adapter or Module version is not backward-compatible with the previous version, the new version’s Manifest declares migration steps in its compatibility block, which the Compatibility Validator applies during the pipeline run.

ADR-0026 explicitly defers to this ADR: the schema for expressing migration steps, the categories of change they can express, how the Compatibility Validator applies them across multiple versions, failure behavior, and idempotency requirements.

A migration is always run on a copy of the generated app’s artifacts, never on the working system itself. Migration steps are declarative constraints that the Compatibility Validator applies deterministically, with the Stitcher invoked only for steps that cannot be expressed declaratively. This is consistent with the harness engineering principle: deterministic rails handle what can be expressed precisely; the bounded LLM layer handles the genuinely novel remainder.

Decision

Step Schema

A migration step is declared in the Manifest’s compatibility.migration_steps array. Each step has four required fields and one optional field:

{
"from_version": ">=3.2.0,<3.3.0",
"to_version": "3.3.0",
"type": "config_rename",
"parameters": { ... },
"requires_stitcher": false
}
FieldTypeRequiredDescription
from_versionsemver rangeyesVersion range this step applies when migrating from
to_versionsemver stringyesThe exact version this step migrates to
typeenumyesThe category of change (see Step Scope below)
parametersobjectyesType-specific payload
requires_stitcherbooleannoWhen true, the Compatibility Validator hands this step to the Stitcher rather than applying it mechanically. Defaults to false.

The idempotency_check sub-field is required within parameters for all step types. It declares how the Validator detects that a step has already been applied, preventing re-application when a customer regenerates or extends a migrated app.

Step Scope

Seven step types are defined for v1. Each Manifest author selects the appropriate type when declaring a backward-incompatible change.

Typerequires_stitcherDescriptionExample
config_renamefalseRenames a configuration key in generated config filesspring.jpa.hibernate.ddl-autospring.jpa.ddl-auto
config_value_changefalseChanges a default or valid value for a configuration keyddl-auto: update deprecated; valid values are now validate and none
dependency_version_bumpfalseUpdates a dependency version reference in the generated build filespring-boot: 3.2.4spring-boot: 3.3.0
scaffolder_flag_changefalseUpdates a Scaffolder invocation parameter in the persisted SpecJHipster flag --auth jwt renamed to --auth-type jwt
schema_migrationfalseDeclares a Liquibase or Flyway changeset to be applied to the database schemaAdds created_by column to audit-enabled entity tables
api_breaking_changetrueAn Adapter’s public API changed in a way that is not source-compatibleMethod renamed, parameter order changed, error type changed
seam_contract_changetrueA Seam Contract’s concrete_pattern changed between versionsRepository method signature changed; all Seam call sites need updating

Schema migration note: The schema_migration type is idempotent by construction because Liquibase and Flyway use changeset checksums to prevent re-application. The Compatibility Validator must verify that the generated app’s stack includes a configured migration tool before applying this step type; if no migration tool is present, the step fails with a clear diagnostic.

Steps requiring the Stitcher (requires_stitcher: true): When a step declares requires_stitcher: true, the Compatibility Validator extracts the step’s parameters (including old and new concrete_pattern, affected Seam IDs, and any framework migration notes sourced at Manifest authoring time) and passes them to the Stitcher as bounded seam-filling tasks. The Stitcher’s output for these steps is subject to standard Build Verification before the migration is considered complete. This is the same mechanism as standard Seam filling — the migration step provides a structured contract, the Stitcher fills it.

Version Compatibility Source of Truth

The compatibility data declared in each Adapter’s Manifest is sourced from the upstream framework’s own release notes, migration guides, and official compatibility matrices. The platform team does not independently determine version compatibility — it faithfully transcribes what the framework itself declares, including known incompatibilities with peer dependencies.

When migrating an app with multiple Adapters (for example, Spring Boot 3.2 + Keycloak 24 + JWT 9.x), the Compatibility Validator walks each Adapter’s declared compatibility constraints collectively to determine whether the target version-set is valid as a whole. If any declared incompatibility is found among the resolved target set, the migration plan reports this before any step is executed.

Each framework and library follows its own versioning and migration conventions. The Compatibility Validator respects these conventions rather than imposing a platform-level migration model that conflicts with them.

Pre-Migration Feasibility Check

Before any migration step is applied, the Compatibility Validator performs a pre-migration feasibility check using the Manifest compatibility data for the target version-set:

  1. Resolves all target Adapter and Module versions
  2. Walks the compatibility matrix for the full resolved set
  3. Identifies declared incompatibilities, missing migration paths, and steps requiring the Stitcher
  4. Produces a migration plan presented to the user at a dedicated approval step before any generation runs

The migration plan includes:

  • Which steps will be applied and of what type
  • Which steps require Stitcher involvement and why
  • Declared incompatibilities in the target version-set
  • Relevant notes from framework release notes (sourced at Manifest authoring time)
  • Trade-offs and risks the customer should know before proceeding

The user reviews and approves the migration plan, or modifies their version targets, or abandons the migration, before any work begins. If migration is not feasible for the requested version combination, the platform states this plainly and shows what is possible — including alternative version targets or the option to generate a new version of the system from scratch rather than migrating the existing one. The user may engage conversationally to describe requirements and make architectural choices that shape how the Stitcher handles complex steps.

Execution Model

Migration runs against a copy of the generated app’s artifacts, not the working system. The working system remains untouched throughout the migration process:

  1. A copy is created from the app’s stored artifacts and persisted Spec
  2. The target Spec is produced by updating the copy’s version selections
  3. The standard pipeline runs against the copy (Compatibility Validation → Seam Validation → Scaffolding → Stitching → Build Verification)
  4. Migration steps are applied during the Compatibility Validation and Stitching stages
  5. On successful completion, the copy becomes the new delivered artifact, recorded as a new build in the audit trail with its own Spec
  6. On failure, the copy is discarded; the working system is unaffected and the prior Spec remains recoverable per ADR-0026

Rollback is always available: the previous artifact and its Spec are retained. The customer continues on the previous version until they choose to retry or migrate to a different target.

Failure Behavior

Pre-execution (infeasible migration): If the feasibility check finds a declared incompatibility or a missing migration path, the migration plan reports this at the approval step. No steps are executed, no copy is created. The customer sees a precise diagnostic identifying which version combination is incompatible, what the framework’s own documentation says about it, and what alternatives exist.

Mid-execution (step failure): If a migration step fails after execution has begun:

  • The in-progress copy is discarded
  • The working system is unaffected
  • The audit trail records the failed attempt, the specific step that failed, and the diagnostic
  • The customer is shown the precise failure point and, where possible, a suggested remedy

There is no partial migration state. Either the full migration completes and is delivered, or the working system remains at its prior version. This is enforced by the copy-based execution model — nothing is committed to a persistent location until Build Verification passes.

Idempotency

All migration steps must be idempotent. Manifest authors declare an idempotency_check in each step’s parameters specifying how the Validator detects prior application:

  • config_rename: check for existence of the new key name before renaming; skip if already renamed
  • config_value_change: check current value before applying; skip if already at target value
  • dependency_version_bump: check current declared version before bumping; skip if already at target
  • scaffolder_flag_change: check current flag value in persisted Spec; skip if already updated
  • schema_migration: Liquibase/Flyway changeset checksums handle this natively
  • api_breaking_change / seam_contract_change: the Stitcher checks for the new concrete_pattern before regenerating; skips if already updated

Idempotency is required because customers may regenerate or extend a previously-migrated app. A migration step that breaks on second application is incompatible with the reproducibility guarantee established in ADR-0026.

Customer Modification Interaction

The copy-based execution model means the working system is never modified. For seam code specifically:

The migration plan includes a section listing which Seams will be regenerated (all Seams affected by seam_contract_change or api_breaking_change steps). The customer is informed that modifications made to those Seam files since the last generation will not be automatically preserved. The customer may use the conversational interface during migration plan review to describe their custom behavior, and the Stitcher will incorporate this description into the regenerated Seam code as additional context.

The full problem of automatically detecting, diffing, and merging prior customer modifications with newly generated Seam code is deferred. See Future Considerations.

Rationale

Copy-based execution is the correct model for an enterprise migration facility. The working system must never be at risk during a migration attempt. Rollback complexity is eliminated: discarding the copy leaves the system in its prior state by construction. This matches standard practice: database migrations run against a copy before production; infrastructure changes use blue-green deployments.

Framework-native compatibility data prevents the platform from making independent compatibility claims that contradict upstream authorities. The Manifest records what the framework itself declares — no more, no less.

Pre-migration feasibility checks with a user-visible migration plan respect the customer’s time and trust. A migration attempted without upfront feasibility information wastes generation resources and produces confusing failures. The migration plan, grounded in Manifest compatibility data and framework release notes, gives customers genuine information before they commit.

requires_stitcher flag keeps the mechanism honest. Rather than attempting declarative application of structurally complex changes and failing unpredictably, the step type explicitly signals that LLM involvement is required. The Stitcher receives a bounded, structured task — the same model as standard Seam filling. This is harness engineering applied to migration: deterministic where possible, bounded LLM where necessary.

All seven step types in v1 because excluding known change categories would require Manifest authors to work around the schema rather than within it. The requires_stitcher flag handles complexity without narrowing the schema.

Idempotency is non-negotiable. A platform where regeneration is a first-class operation cannot tolerate migration steps that fail or produce corruption on second application.

Future Considerations

The following items were identified during the design of this ADR and are deferred to future decisions. Each is documented in detail at the referenced section of docs/design/migration-future-extensions.md.

[FE-MIGE-001] Customer modification detection and merge. The current model asks customers to describe their prior Seam customizations conversationally. A richer mechanism would detect modified files, produce a structured 3-way diff (old-generated | customer-modified | new-generated), and use the diff as Stitcher context to carry custom behavior forward automatically. See docs/design/migration-future-extensions.md#fe-mige-001 for the full design and implementation considerations.

[FE-MIGE-002] Tooling-aware schema migration execution. The schema_migration step type currently requires the Validator to detect a configured migration tool in the app’s stack. A richer implementation would make the tool configuration, changeset content, rollback SQL, and idempotency check first-class fields in the step’s parameters, enabling the Validator to invoke the tool directly. See docs/design/migration-future-extensions.md#fe-mige-002.

[FE-MIGE-003] Richer Stitcher context for breaking changes. For api_breaking_change and seam_contract_change steps, the Stitcher currently receives old and new concrete_pattern and affected Seam IDs. Future iterations would also provide: the prior Seam output, a framework migration guide excerpt sourced at Manifest authoring time, and structured acceptance tests. See docs/design/migration-future-extensions.md#fe-mige-003.

[FE-MIGE-004] LLM model version tracking in migration audit trail. ADR-0028 requires model version tracking for all Stitcher invocations. The migration-specific audit trail record needs additional fields: the step ID that triggered the invocation, the step type, the concrete_pattern delta, and whether this was a first attempt or retry. See docs/design/migration-future-extensions.md#fe-mige-004.

References

  • ADR-0026: Migration of Existing Apps — establishes the migration principle and explicitly defers the transformation mechanism to this ADR.
  • ADR-0010: Semver and Compatibility Matrix Versioning — establishes the compatibility block structure this ADR extends.
  • ADR-0012: Sanity Checks at Five Pipeline Stages — the Compatibility Validator executes deterministic migration steps; Build Verification gates Stitcher-handled steps.
  • ADR-0014: Save Approved Specs as Reusable Templates — prior Specs are retained per this ADR, enabling copy-based migration and rollback.
  • ADR-0020: Manifest Schema Versioning — governs the schema extension introducing migration_steps to the compatibility block.
  • ADR-0009: Fully-Specified Seam Contracts — seam_contract_change and api_breaking_change steps invoke the Stitcher against the seam-contract model established here.
  • Concepts → Manifest — the Manifest carries migration step declarations.
  • Runtime Pipeline — the pipeline stages within which migration steps execute.