Lesson 99 of 170

Design a safe migration

Martinez AI Studios Academy

Plan schema-direction checks, field transformations, validation, preservation, rejection, recovery, and transaction-safe output for a save migration.

1437. Lesson identity

Module
3.17 — Save migrations
Lesson
2 — Design a safe migration
Academic type
Guided Build
Lesson type
Practical
Order
2 in the module sequence
Estimated time
50–70 minutes, including practice

This lesson turns a compatibility concern into an explicit migration design. You will produce a migration matrix and validation plan before implementation.

1438. Learning objective

After this lesson, you can produce a migration matrix and validation plan that define supported schema paths, field and cross-field behavior, failure boundaries, and transaction-safe output.

1439. Why this matters

A save migration is not merely a conversion function. It is a boundary between data written under one contract and code expecting another. A design that handles only a normal legacy save can silently discard progress, accept values that violate the new rules, attempt an unsupported downgrade, or damage the source during an interrupted write. Writing these decisions first gives implementation and testing a precise contract.

1440. Prior knowledge

You should be able to:

  • explain why a save-format change creates a compatibility obligation;
  • distinguish schema identity from build identity;
  • distinguish an old representation from a new representation;
  • identify whether a change can be interpreted safely without migration code.

These capabilities come from 3.17 L1 — A save change is a compatibility decision. You also need basic familiarity with validation, defaults, and error handling.

1441. Core concept

A safe migration is an explicit decision table for every changed value and every failure boundary. It begins with a schema-direction gate, before any field transformation:

  1. Read the source schema identity without treating the save as if it already matched the target.
  2. Confirm that the source-to-target path is supported.
  3. If the source is older, follow the documented migration chain in order.
  4. If the source is newer than the loader, reject it unless a separately specified downgrade path exists.
  5. Only after the path is accepted may field and cross-field transformations run.

This distinction is essential: an unknown field inside a supported source schema may have a documented preserve-or-reject policy. An entire save from an unsupported newer schema is not merely a collection of unknown fields and must not be passed through an ordinary upgrade migration.

For each supported input, decide whether to transform it, validate the result, preserve information, reject the save, or recover through a defined fallback. These decisions are separate. A value can be transformable but fail validation, or valid in isolation but unsafe alongside related data.

A migration should answer six questions:

  1. Version gate: Is this exact source-to-target path supported?
  2. Transform: What old representation becomes the new representation?
  3. Validate: What constraints must hold after transformation?
  4. Preserve: Which information must remain unchanged or survive through an explicit mapping?
  5. Reject: Which conditions make the save unsafe to use?
  6. Recover: What bounded, user-visible action is available after rejection or transaction failure?

1442. Mental model

Use separate rows for schema-version rules, changed fields, cross-field rules, and transaction boundaries.

Rule scope Required decisions Evidence to record
Schema version Supported path, migration chain, newer-schema rejection, separately defined downgrade if any Source version, target version, selected path, gate outcome
Field Transform, validate, preserve, reject, recover where applicable Before/after examples and valid/invalid cases
Cross-field Relationship constraint and ownership of rejection or recovery Valid and impossible combinations
Transaction Output location, written-result validation, commit, backup or rollback, interruption behavior Source hash or identity, temporary-output result, read-back result, commit outcome

A cell may say “Not applicable — handled by rule X” when that decision properly belongs to a named schema-version, cross-field, or transaction rule. Do not invent a field-level recovery action when recovery is owned by a wider boundary.

Test the design across these input classes:

  • Expected legacy data: a valid save from a supported older schema.
  • Boundary data: minimum, maximum, empty, missing, and newly introduced values.
  • Malformed data: wrong types, invalid enumerations, impossible combinations, or truncated content.
  • Unknown data in a supported schema: fields or values not recognized by the migration but covered by its compatibility policy.
  • Unsupported schema direction: especially a source newer than the loader.
  • Transaction failure: interruption or failure while producing, validating, or committing output.

The matrix is complete only when every relevant cell contains a decision or points to the named rule that owns it.

1443. Concrete example

Suppose version 2 stores credits as an integer and version 3 introduces wallet:

v2: { "schemaVersion": 2, "credits": 120, "inventory": ["medkit"] }
v3: { "schemaVersion": 3, "wallet": { "credits": 120 }, "inventory": ["medkit"] }

A partial plan might be:

Input case Decision Result
Source v2, target v3 Accept the documented upgrade path Continue to transformation
Source v4, loader supports through v3 Reject at the version gate Do not run the v2-to-v3 field migration or overwrite the source
Integer credits from v2 Transform Move it to wallet.credits
Missing credits Recover or reject by contract Use a documented default only if zero preserves meaning; otherwise reject
Negative credits Reject Do not create a wallet with an invalid balance
Non-integer credits Reject Do not round silently
Unknown inventory item in supported v2 input Preserve or reject according to the inventory contract Never silently delete it
Truncated input Reject before output Keep the source untouched and report the recovery path
Interrupted temporary-output write Abort transaction Discard incomplete temporary output; retain the source and any required backup
Written output fails read-back validation Do not commit Preserve the source and record the validation failure
Validated output cannot be committed Apply the documented backup or rollback policy Do not report migration success unless the committed save is valid and identifiable

The example is intentionally incomplete. The actual contract must define the default, inventory, backup, rollback, and user-facing policies. The important point is that moving a field is only one part of the design.

1444. Transaction-safe output

Transformation and in-memory validation do not make replacement safe. Define the output transaction without assuming a particular storage API:

  1. Keep the source save unchanged while migrating.
  2. Write the candidate to a temporary location or a distinct new file.
  3. Read the written candidate back and validate its schema identity, structure, constraints, and preservation assertions.
  4. Commit or replace only after written-result validation succeeds.
  5. State when a backup is created and how rollback works if commit or replacement fails.
  6. Define behavior for interruption during writing, validation, or commit.
  7. Record success only when the committed output is distinguishable from a merely parsed or partially written candidate.

The policy must be testable: for every failure point, identify which artifact remains authoritative, which incomplete artifact is discarded or retained for diagnosis, and what the user is told.

1445. Common mistakes

  • Treating successful parsing as successful migration.
  • Treating a save from a newer schema as ordinary unknown field data.
  • Skipping required intermediate migrations in a documented chain.
  • Supplying a default that silently changes game meaning.
  • Requiring every field row to invent its own recovery action instead of referring to the cross-field or transaction rule that owns recovery.
  • Overwriting the source before the written candidate has passed read-back validation.
  • Reporting success after in-memory validation even though output commit failed.

1446. Guided practice

Create a migration matrix and validation plan for this hypothetical schema change:

Save version 4
- `credits`: integer, must be 0 or greater
- `rank`: one of "runner", "broker", "fixer"
- `inventory`: list of item identifiers
- `lastSafehouse`: identifier that may be absent in old saves

Save version 5
- `wallet.balance`: integer, must be 0 or greater
- `rank`: one of "runner", "broker", "fixer", "handler"
- `inventory`: list of item records with an identifier and quantity >= 1
- `safehouse.id`: required for a loaded save
- `safehouse.visited`: boolean

Work through these steps:

  1. Add a schema-version row before all field rows. Define behavior for source v4 to target v5, any documented older-version chain, source v5, and a source newer than v5. Do not define a downgrade unless the contract explicitly provides one.
  2. List every field that changes location, type, allowed values, requiredness, or meaning.
  3. Add at least one row for each changed field and each cross-field dependency.
  4. Add a transaction row covering temporary or new-file output, read-back validation, commit or replacement, backup or rollback policy, and interruption during writing.
  5. For each row, record the applicable transform, validation, preservation, rejection, and recovery decisions. If one does not apply, write “Not applicable — handled by rule X” and name the owning rule.
  6. Include cases for missing lastSafehouse, unknown rank, empty inventory, quantity zero, negative balance, unknown item identifier, truncated input, an unsupported newer source schema, and interrupted output writing.
  7. Mark decisions that require a product or systems owner to choose a policy rather than allowing the migration to infer one.
  8. Write a validation plan containing at least one expected legacy case, two boundary cases, two malformed cases, one unsupported-version case, one recovery case, and two transaction cases: interrupted writing and failed commit or replacement.
  9. Add a schema-versioned compatibility checkpoint. Record source and target versions, gate outcome, selected migration chain, validation and preservation results, transaction state, rejection or recovery outcome, and whether the original remained authoritative.

Your plan must distinguish a validated in-memory candidate, a validated written candidate, and a successfully committed save.

1447. Validation and evidence

Your work is complete when it includes:

  • a version gate that permits only documented source-to-target paths;
  • explicit rejection of a newer source schema unless a separate downgrade is specified;
  • a matrix covering changed fields and cross-field dependencies;
  • named ownership for decisions marked not applicable;
  • a policy for unknown fields and item identifiers inside supported input;
  • normal, boundary, malformed, truncated, unsupported-version, recovery, and transaction-failure cases;
  • a transaction rule covering separate output, read-back validation, commit, backup or rollback, and interruption;
  • a checkpoint showing which artifact remains authoritative after each failure;
  • at least one decision that requires human policy rather than silent inference.

A peer or implementation agent should be able to derive the behavior without inventing missing version, field, or transaction rules.

1448. Key takeaways

  • Check schema direction before transforming fields.
  • Unknown data in a supported schema is different from an unsupported newer schema.
  • Transformation and validation are separate obligations.
  • Cross-field and transaction rules may own rejection or recovery decisions.
  • A candidate is not safely migrated until its written form is validated and committed under a defined rollback policy.
  • The source must remain authoritative whenever migration, written-result validation, or commit fails.

1449. Next lesson

Continue to 3.18 — Localization architecture.

1450. Knowledge check

Answer these items for yourself before reading the answers.

What should a migration matrix specify for each affected field?

  • A. Only the code expression that copies the old value.
  • B. The applicable transform, validation, preservation, rejection, and recovery decisions, with references to named wider rules where needed.
  • C. Only the expected data from a valid legacy save.
  • D. A list of every possible future schema.
Show answer and feedback

Answer: The applicable transform, validation, preservation, rejection, and recovery decisions, with references to named wider rules where needed.

Why: The matrix is a behavioral contract. A field row must record each applicable decision and may point to a named cross-field or transaction rule when that wider rule owns rejection or recovery.

Why should an original save remain untouched until the written migrated result passes validation and is committed safely?

  • A. To make the migration run faster.
  • B. To avoid documenting the rejection reason.
  • C. To preserve an authoritative recovery source if transformation, writing, read-back validation, or commit fails.
  • D. To permit invalid values in the new schema.
Show answer and feedback

Answer: To preserve an authoritative recovery source if transformation, writing, read-back validation, or commit fails.

Why: In-memory validation is not the final safety boundary. The source must remain authoritative until the written candidate is validated and the transaction commits successfully.

What is the safest default for a legacy field whose absence may change the meaning of the save?

  • A. Infer a value silently from the player's other data.
  • B. Use a default only when its safety is documented; otherwise reject or use an explicit recovery path.
  • C. Delete the rest of the save and continue.
  • D. Accept any value because the field was optional before.
Show answer and feedback

Answer: Use a default only when its safety is documented; otherwise reject or use an explicit recovery path.

Why: An absent field may represent several historical states. A default is safe only when the contract establishes that it preserves the intended meaning.

Which validation set best demonstrates coverage of a migration plan?

  • A. One typical legacy save loaded once.
  • B. Only a malformed file that must be rejected.
  • C. Only maximum-value and minimum-value cases.
  • D. Expected legacy data, boundaries, malformed input, unknown data in supported schemas, unsupported versions, recovery, and transaction failures.
Show answer and feedback

Answer: Expected legacy data, boundaries, malformed input, unknown data in supported schemas, unsupported versions, recovery, and transaction failures.

Why: Migration risk spans data rules, schema direction, recovery, and output transactions. A happy-path test cannot establish safety.

A loader supports schemas through version 5, but it receives a version 6 save. No downgrade is specified. What should happen first?

  • A. Run the version 4-to-5 field transformations and preserve whatever remains.
  • B. Reject at the schema-version gate without transforming or replacing the source.
  • C. Delete every field the loader does not recognize.
  • D. Change the schema number to 5 and load normally.
Show answer and feedback

Answer: Reject at the schema-version gate without transforming or replacing the source.

Why: A newer unsupported schema is not ordinary unknown field data. Without a separately defined downgrade, the loader must reject it before field transformation.

Support