Gradual Typing Migrations — Retrofitting Types onto a Living Codebase

The great typing migrations — millions of dynamic-language lines brought under TypeScript, or type checkers built for Ruby and Python — are the canonical org-loop projects: multi-year, codebase-wide, impossible to pause the business for, and transformative when they land. They're worth studying even if you never run one, because they exercise every DevProd muscle at once: incremental adoption machinery, codemod fleets, ratchets that prevent backsliding, and the political craft of keeping a thousand engineers on board for years.

Post 2 built the tool; this post is the campaign — the worked example of changing a fundamental property of a codebase that refuses to stop moving (the same no-pause constraint that shaped zero-downtime migrations, transposed from data to code).

Why gradual is the only viable shape

The all-at-once version fails on arithmetic: a millions-of-lines conversion as a project means years of divergence from a codebase evolving underneath (zdm’s moving-table problem at repo scale), an unreviewable landing, and zero value until the end. Gradual typing inverts the value curve: the system is permanently in a mixed state — typed and untyped code interoperating — where every increment of typing delivers its benefit immediately and nothing waits for completion. The design decisions that define the campaign:

The machinery: infer, ratchet, enforce

The campaign runs on three mechanisms. Inference at scale — post 2’s type-informed codemods write the first draft: tooling that infers annotations from usage, call-graph analysis, and runtime trace collection (observed types harvested from production or test runs — an underused trick that converts dynamic behavior into draft signatures), landing ownership-sharded PR fleets of mostly-correct annotations that humans correct rather than compose. The ratchet — the campaign’s load-bearing invariant: typedness only increases. CI enforces per-module floors: a module at strict never regresses; files can’t add new suppressions without sign-off; the error count against the next strictness level only goes down (tscale’s merge gate carrying the campaign’s physics). Without the ratchet, migration and backsliding race forever; with it, every merge is campaign progress by default. The dashboard — percent-typed by module and team, escape-hatch counts trending, type-error-caught-bugs surfaced (the value evidence), because a multi-year campaign sustains itself on visible motion (devprod-01’s measurement discipline as campaign morale).

The political craft

The technical plan fails without the adoption campaign beside it: make typed strictly nicer — the editor experience (autocomplete, refactors, inline errors) is the daily bribe that recruits engineers better than any mandate; invest in IDE integration before pushing strictness; centralize the toil — the DevProd team types the hard shared modules, builds the wrappers for untyped dependencies, and runs the inference fleets; product teams mostly receive typedness and fix the interesting residue (post 4’s upgrades-as-a-service logic, previewed); never block the roadmap — strictness deadlines that collide with launches lose, publicly, and cost the campaign its mandate; the ratchet’s only-forward design means the campaign survives slow quarters without losing ground; and declare victory honestly — define done as “strict everywhere but the declared exception list, new code strict by birthright,” celebrate it, and hand the ratchet to maintenance. Campaigns that can’t define done become background noise that outlives their champions.

The takeaway: typing migrations work only as gradual campaigns — a strictness map turned module by module, boundaries explicit and escape hatches counted, trunk-first sequencing for compounding leverage — run on three mechanisms: inference-powered codemod fleets (usage analysis, runtime traces) for the first draft, a CI ratchet making typedness monotonic by construction, and a dashboard that keeps years-long motion visible. The human half decides it: editor experience as the daily recruiter, centralized toil, roadmap deference, and an honest definition of done. Every org-loop transformation looks like this one — which is why it’s the worked example.

Key takeaways

Further reading

Sources & References

the theory that makes the mixed state sound
what the campaign is retrofitting