A regulated enterprise has 600 legacy applications, 80 million lines of code, thousands of batch jobs and interfaces, and incomplete ownership records. Design a modernization portfolio factory that discovers the estate, extracts behavior, prioritizes waves, coordinates parallel teams and coding agents, and performs safe strangler cutovers.
Modernize the Map, Not the Myth
Design a factory that discovers, prioritizes, and incrementally replaces hundreds of legacy applications while dependencies change beneath parallel human and agent teams.
The answer map
Do not memorize every box. Remember this path and rebuild the answer from it.
Understand the problem
Restate the goal in plain English. Name the one thing the system must never get wrong.
I would not promise to “convert 80 million lines of code.” First we need a trustworthy map: which applications exist, what business jobs they perform, what data they use, what they call, who owns them, and how risky they are.
Every discovered rule or dependency must show its evidence and when that evidence was last checked. Tools can suggest what is true. The responsible owner approves it. We move one business capability at a time and compare behavior, not just code size.
Step 1 · Set the scope
Name the users, must-have features, speed and safety goals, and what is out of scope.
Scope the first year: actors, requirements, NFRs, and exclusions.
Actors are portfolio executives, domain owners, application owners, architects, security/compliance, modernization teams, agent operators, release managers, and auditors. Version one inventories code/repos, runtime/jobs, databases, interfaces, incidents, cost, usage, controls, and ownership. Builds a versioned dependency/capability graph. Extracts candidate business rules and characterization tests. Scores migration candidates. Creates governed waves/work orders. Tracks parity and cutovers. And reconciles actual retirement benefits.
Mock goals: initial 600-app inventory in 60 days. 95% of critical runtime flows mapped before wave approval. Graph updates within four hours of source change. Portfolio UI p95 under two seconds. No high-criticality cutover without dual-run and rollback. Seven-year decision/audit history. Out of scope: automatic deletion, big-bang rewrite, translating every dead line, and letting an agent deploy without approval.
Portfolio leaders, domain/app owners, architects, security, modernization teams, agent operators, release managers, auditors.
Multi-source discovery, evidence graph, behavior/rule extraction, scoring/scenarios, waves/work orders, parity, strangler cutover, retirement.
Every edge/rule cited and versioned. Conflicts visible. Weighted parity. Humans approve architecture and side effects.
600-app inventory in 60 days, 95% critical-flow coverage, update <4 h, UI p95 <2 s, reversible cutovers.
Read-only discovery, inherited ACLs, secret redaction, task-scoped agent tools, signed outputs, customer/VPC boundary.
Big-bang rewrite, automatic deletion/deploy, translating dead code, treating inventory or agent prose as truth.
Step 2 · Do the useful math
Estimate only what changes the design. After each number, say why it matters.
Use 600 apps, 80M LOC, four analysis passes, 120 workers at 20K LOC/minute, 30 migration teams, and four apps/team/year. Calculate and tell me what the arithmetic hides.
Raw scan work is 320M line-passes. At 2.4M LOC/minute aggregate, the ideal wall time is 133 minutes, about 2.2 hours. That proves scanning is not the 60-day bottleneck. Building, resolving, and validating cross-system behavior, opaque binaries, data semantics, owners, and controls dominate. We need hundreds of partitions for language isolation and skew, but more workers do not create knowledge.
Thirty teams at four apps/year retire 120 apps/year, so a naive portfolio takes five years. Critical shared dependencies and change traffic reduce that throughput. Reuse of extracted capabilities/contracts may improve it. The portfolio system must expose work-in-progress, blocked dependencies, and actual retirement—not reward teams for declaring a rewrite “done” while the mainframe remains.
Analysis throughput versus migration throughput
Steps 4–5 · APIs and data
Start with the few records the system cannot work without. Then add simple endpoints around them.
Give the core entities, keys, and APIs. How does your graph avoid becoming an opinionated wiki?
An Application has one permanent company ID even if its repository name changes. A DependencyAssertion says that one system may depend on another. It stores the direction, environment, exact evidence, when we observed it, how confident we are, and whether an owner approved it. Conflicting findings can both remain visible until a person resolves them.
Capabilities, owners, controls, costs, incidents, interfaces, data sets, rules, tests, and migration waves also have stable IDs. Each portfolio decision points to one saved graph version, so we can explain what information the team had at the time. APIs receive discoveries, show impact, approve connections, create migration waves, track behavior comparisons, and shift traffic safely.
Records and how we find them
| Record | Main key / retry key | Fields we never overwrite | How we find it |
|---|---|---|---|
Application | (enterprise_id, application_id) | names/aliases, owner, criticality, lifecycle, environments | inventory; capability/owner views |
ArtifactVersion | (artifact_type, content_digest) | source locator, ACL, captured_at, parser contract | incremental analysis by digest |
DependencyAssertion | (edge_id, assertion_version) | typed endpoints, environment, evidence, extractor, confidence, observed_at, approval | impact as-of graph version; conflict review |
BehaviorRule | (capability_id, rule_id, version) | Given/When/Then, evidence spans, SME state, effective time | characterization tests; policy diff |
MigrationWave | (portfolio_id, wave_id, version) | apps/capabilities, dependencies, capacity, gates, decision rationale | roadmap/scenario; readiness |
ParityResult | (release_id, fixture_id, comparator_version) | old/new outputs, side effects, value/risk slice, mismatch class | release gate; error buckets |
CutoverRoute | (contract_id, route_version) | cohort, old/new target, percentage, effective time, rollback pointer | runtime routing; audit |
Simple API
POST /v1/discovery/observations artifact digest + evidence
POST /v1/assertions/{id}:propose expected graph version
POST /v1/assertions/{id}:approve owner + decision reason
GET /v1/graph/impact?node=&as_of_graph= evidence-backed paths
POST /v1/portfolio/scenarios weights + hard constraints
POST /v1/waves pinned graph + capacity plan
POST /v1/work-orders/{id}:claim lease + agent identity
POST /v1/releases/{id}/parity-results signed harness result
POST /v1/contracts/{id}/traffic-shifts If-Match: route_version
POST /v1/applications/{id}:retire decommission evidence checklistSteps 3 and 6 · Draw, then follow one request
Explain the system in a few parts. Then follow one real request from the user to the final result.
Walk through the architecture from discovery to retirement.
I would draw four parts. Read-only collectors gather evidence from code repositories, configuration databases, job schedulers, database catalogs, network traffic, monitoring, tickets, and cost systems. Analysis workers inspect that evidence inside locked-down sandboxes. A dependency graph stores the findings and their sources. A portfolio service helps owners choose and run migration waves.
For one application, the collectors find its code, nightly jobs, database tables, and consumers. The owner reviews uncertain links. The portfolio service scores options using visible factors such as risk, cost, test coverage, and owner readiness. An approved wave creates plans, work, and tests. Agents help inside limited environments; people approve architecture and writes. We then compare old and new behavior, move a small traffic group, and keep a quick route back.
A dependency is an assertion with evidence
Static possibility, runtime observation, owner confirmation, and freshness remain distinct instead of collapsing into wiki truth.
Deep dive on the dependency graph and portfolio ranking. Static analysis and network traces disagree. One app looks low-value but is a hub.
I model provenance and observation conditions rather than force consensus. Static analysis may show a possible call. Runtime trace shows an observed call for a time/environment. A contract or owner approval adds authority. Edges carry confidence and last-seen time. We preserve negative evidence carefully—absence in sampled telemetry is not proof of no dependency.
Portfolio ranking is multi-criteria with hard constraints. A hub’s centrality and size of the impact may make it a prerequisite, a platform extraction candidate, or something to stabilize rather than migrate first. I build dependency-respecting waves with capacity, blackout windows, shared data ownership, and rollback constraints. Executives can run scenarios with weights, but every recommendation includes sensitivity: if a small weight change flips the rank, it is not a robust decision.
App A appears to call App B in source code, but no production trace has shown that call in 90 days. We save both facts: “possible in code” and “not recently seen.” We do not pretend either one is complete truth.
| Evidence | Strength | Caveat |
|---|---|---|
| Static call/data flow | Broad coverage | May be unreachable or environment-specific. |
| Runtime trace/network flow | Observed behavior | Absence may reflect sampling/window. |
| Scheduler/file catalog | Strong batch dependency | Often outside application source. |
| Owner/SME approval | Business meaning | Can be stale tribal knowledge. |
| Contract/characterization test | Executable expectation | Only covers represented cases. |
Migrate contracts and behavior in small steps
Keep one stable front door, compare old and new results, move a small user group, and preserve a clear rollback.
Deep dive on behavior extraction and strangler cutover. How do you know the replacement is equivalent?
We extract candidate rules from code, docs, tickets, production traces, and subject-matter interviews, each cited to evidence. We separate technical plumbing from policy and create Given/When/Then rules plus characterization fixtures from representative and edge-case production inputs. A golden-master harness compares outputs, side effects, latency, and accounting main rules using masked or synthetic data.
The stable front door that gradually moves work to the new system gives one stable contract. We begin shadow reads, then mirror traffic without effects, compare normalized outcomes, and investigate mismatches by value/risk slice. Where dual writes are necessary, one system remains source of truth and a database-backed event queue plus cross-checking detects divergence. We avoid uncontrolled bidirectional writes. Canary traffic increases by capability/cohort. Rollback shifts the route pointer and reconciles in-flight state. Retirement requires zero approved consumers, archived evidence, data retention disposition, credentials revoked, jobs stopped, and cost verified.
Move only address updates first. The old system still owns every other customer action. Route a small group through the new service, compare results, and switch them back with one route change if errors rise.
What if it breaks?
Protect the correct data first. Then contain the problem, recover, and explain what users see.
Two weeks before a wave, you discover a nightly mainframe file consumed by 41 applications. It never appeared in source code or network telemetry.
The wave readiness gate fails because critical-flow coverage is incomplete. I add scheduler and file-catalog evidence, quarantine the cutover, and use the file’s producer job, naming patterns, access audit, and downstream control totals to resolve consumers. The new assertion version triggers reverse-impact notifications and re-plans dependent waves.
Architecturally, discovery becomes continuous: code/CMDB alone was insufficient. I add batch scheduler, managed-file-transfer, filesystem audit, data history, and operator interview collectors. Graph completeness is measured per evidence family and critical capability, not one percentage. We may first wrap the file contract or extract a shared service instead of migrating the intended app. The schedule changes. The truth does not.
What if timing gets messy?
Retries, delayed messages, and two updates at once are normal. State the rule that keeps one clear result.
An agent-generated replacement matches 99.7% of transactions but differs on 0.3%—which represents 38% of transaction value. Product wants to proceed.
I block cutover. Count-weighted parity is the wrong objective. Value-, regulatory-, and risk-weighted slices are hard gates. The mismatch set becomes a first-class error bucket linked to rules, source lines, and fixtures. We determine whether the legacy behavior is required, a known bug, or undocumented policy. Only an authorized decision can intentionally diverge.
The agent’s summary is not evidence. We rerun the same tested build in the golden harness, add the high-value cases to a protected holdout, and require SME/legal approval for changed behavior. If business explicitly chooses a correction, we version the policy, migration rule, communication, and effective date. Otherwise the old route stays source of truth for that cohort while safe cohorts can canary separately.
Keep it safe and running
Cover overload, security, monitoring, and disaster recovery in that order.
Cover concurrency across teams/agents, consistency, overload control, security, monitoring, and DR.
Graph publication, approvals, wave membership, leases, and route changes use a database transaction that rejects stale updates. Analysis delivery is at least once and retry-safe on saved item digest plus analyzer version. Work orders have dependency graphs and exclusive leases for conflicting assets. Optimistic merge and code ownership handle parallel changes. Per-language queues, customer/domain quotas, retry budgets, poison-repo quarantine, and backfill pools prevent one COBOL monolith from starving change-driven analysis.
Connectors are read-only by default. Code and secrets stay in customer/VPC boundaries. Extracted data inherits source ACLs. Agent tools have task-scoped credentials, egress policy, signed outputs, and human approval for writes. Observe discovery freshness/completeness, conflicting/stale edges, analysis lag/cost, work-in-progress/block age, parity by value/risk slice, cutover error/latency/cross-checking, rollback time, retired cost, access anomalies, and audit gaps. Write-once evidence and event logs rebuild graph views. Cutover configs are replicated and restore/rollback is rehearsed.
Collector coverage, saved item freshness, unresolved identity, assertion conflict, graph critical-flow completeness.
Work in progress, blocked age, agent retries, review latency, dependency churn, reusable pattern yield.
Parity by value/risk, side-effect divergence, canary errors/latency, cross-checking lag, rollback time.
Compare-and-set safety check approvals/waves/routes. Retry-safe digest analyses. Leases on work. Database-backed event queue side effects. Versioned graph views.
Read-only scoped collectors, inherited ACLs, per-domain keys/indexes, secret scanning, sandboxed agents, egress allowlist.
Actual systems/jobs/contracts retired, credentials revoked, cost removed, incident/risk change, cycle time—not code generated.
Rollout and trade-offs
A credible production answer defines how it earns trust and how it retreats safely.
Give the rollout and key portfolio trade-off.
Start with read-only discovery in one domain. Validate identity and graph edges with owners. Publish portfolio scenarios without committing funds. Select a low-coupling pilot and one representative hard system. Build characterization harnesses before replacement. Shadow, dual-run, canary, and retire with explicit gates. Then scale reusable analyzers/contracts across waves. Compare forecast with actual cost, risk, lead time, and decommission completion quarterly.
The trade-off is local migration speed versus portfolio coherence. A team can ship a new app quickly while duplicating a shared capability and leaving dependencies behind. I accept slower wave approval to make interfaces, ownership, evidence, and retirement measurable—then use the factory to accelerate repeated, well-understood patterns.
- Begin read-only in one domain with named owners.
- Validate application/source identity and evidence semantics.
- Publish scenario/sensitivity views before funding decisions.
- Pilot one low-coupling and one representative difficult system.
- Build characterization and value-weighted parity before replacement.
- Shadow, dual-run, canary by cohort, prove rollback.
- Retire only after zero consumers and control/retention evidence.
- Scale reusable analyzers, contracts, and migration patterns. Compare forecast to outcome.
One-minute spoken recap
Practice this synthesis until it sounds conversational rather than memorized.
One-minute close.
I’ll summarize continuous discovery, evidence-backed decisions, parallel work controls, and reversible cutover.
Reference shelf
Definitions, traps, and the final checklist stay outside the timed mock.
- Assertion graph
- A graph whose edges are evidence-backed claims with confidence, time, source, and review state.
- Characterization test
- A test that captures what a legacy system currently does, including quirks.
- Cutover cohort
- A bounded customer, region, capability, or traffic slice routed to the replacement.
- Golden master
- Recorded legacy behavior used to compare a new implementation.
- Graph centrality
- A measure of structural importance; useful signal, not automatic migration priority.
- Strangler pattern
- Incrementally route capabilities around a legacy system until it can be retired.
- Traffic shadowing
- Run new logic on copied requests without creating business effects.
- Single authority
- During dual-run, one system remains canonical for a datum or side effect.
- Sensitivity analysis
- Testing whether reasonable scoring-weight changes alter a portfolio decision.
- Work-order lease
- Time-bounded exclusive claim on a task or conflicting asset.
- Retirement evidence
- Proof consumers, jobs, data duties, credentials, and cost were actually removed.
- Value-weighted parity
- Comparison weighted by monetary/regulatory risk rather than transaction count.
- Treating repository names as stable application identity.
- Building a graph with edges that lack source, confidence, or freshness.
- Assuming absence from sampled traces means no dependency.
- Ranking by a magic modernization score without hard constraints.
- Maximizing applications started instead of systems retired.
- Translating dead code because LOC is easy to count.
- Accepting 99.7% parity when the 0.3% carries most value.
- Allowing uncontrolled bidirectional dual writes.
- Letting agents deploy with broad persistent credentials.
- Testing a working tree instead of the signed built release.
- Retiring an app while batch/file consumers remain.
- Ignoring portfolio change during a multi-year migration.
- Clarified actors, authority, business harm, and out-of-scope.
- Did correct BOTEC and named the variable that changes the architecture.
- Defined stable IDs, versions, access paths, and retry keys.
- Separated source of truth state from derived indexes and model output.
- Explained consistency, retries, overload control, and unknown outcomes.
- Revised the design after both failure injections.
- Covered customer isolation, secrets, least privilege, deletion, and audit.
- Named golden signals, domain quality metrics, rollout gates, and rollback.