Level Up

How to Build AWS Migration Waves from Application Dependencies

Level Up Team

A migration wave is not a date on a program plan. It is a cut set: the set of workloads you can move without orphaning a runtime, identity, or data dependency.

If you do not have a dependency graph, you do not have waves. You have a hope.

This is how Level Up turns graph + 7 Rs + constraints into the waves you see in the example assessment.

Four layers, not twenty workshops

Most estates collapse into:

  1. Foundation — identity, access, observability, connectivity
  2. Shared services — files, backup, integration gateways
  3. Line of business — apps whose dependencies now live in AWS or are retained
  4. Core systems — ERP, CRM, warehouses that everything else already talks to from the new side

In the Acme example that is AD / bastion / monitoring, then files / backup / EDI, then WMS / QMS / MES, then ERP / CRM / data warehouse.

A small algorithm

for each workload:
  if disposition is retain:
    park it (wave 0)
  else if only foundation depends on it:
    candidate for wave 1
  else:
    wave = 1 + max(wave of hard dependencies)
    raise wave when a human constraint says so
      (latency, license, freeze, event window)

Hard dependencies are runtime and identity edges with known or observed evidence. Inferred data edges create tasks, not silent wave promotions.

def assign_waves(workloads, edges, constraints):
    parked = {w.id for w in workloads if w.disposition in {"retain"}}
    remaining = [w for w in workloads if w.id not in parked]
    waves = {w.id: 1 for w in remaining}

    changed = True
    while changed:
        changed = False
        for edge in edges:
            if edge.basis == "inferred" or edge.type == "data":
                continue
            if edge.to in parked or edge.from_id in parked:
                continue
            nxt = waves[edge.to] + 1
            if waves[edge.from_id] < nxt:
                waves[edge.from_id] = nxt
                changed = True

    for constraint in constraints:
        waves[constraint.workload] = max(waves[constraint.workload], constraint.min_wave)
    return waves

This is deterministic analysis. It is not an AI planner.

Constraints that software should not invent

  • Plant MES must stay within a measured RTT of OT
  • Oracle license mobility is unconfirmed
  • A business freeze blocks intranet
  • A SaaS print replacement is not accepted yet

Those become risk-register items and wave floors. See the 7 Rs as a backlog.

Output a wave people can execute

Each wave should list:

  • Workloads in the cut
  • Prerequisites from earlier waves
  • Terraform candidates
  • Ansible candidates
  • Validation that must pass before the next wave starts

That is what the AWS migration assessment generates. You can run it yourself or run it with Level Up.