Essay
I Am Jack's Shit
A replacement product can discard the visible mess of a legacy system while also discarding the knowledge embedded inside it. The result is not modernization but redistributed complexity, imposed on users, partner teams, and the people now rebuilding what already worked.
- software
- organizations
- AI
- modernization
- the recurring enterprise belief that history is technical debt
I am Jack’s shit.
I began as modernization.
I was going to replace the old product.
The old product was difficult.
It had accumulated years of decisions, exceptions, workarounds, dependencies, user expectations, partner agreements, operating habits, and code nobody wanted to explain at a planning meeting.
It looked old because it was old.
This was treated as evidence against it.
A new product would be cleaner.
A new architecture would be easier to understand.
A new experience would be more consistent.
A new foundation would let the organization move faster.
The future would arrive without the stains of the past.
Then the future began routing people back to the past because the future could not do the work.
That is how I became Jack’s shit.
The clean break
The clean break is one of enterprise software’s most durable fantasies.
It begins with an understandable desire.
The existing system is frustrating.
Its boundaries are unclear.
The code contains compromises made by people who are gone.
Its user interface reflects several eras of product thinking at once.
Every change touches something unexpected.
Documentation describes the product that existed when the documentation was written.
The organization no longer knows whether a strange behavior is a defect, a contractual obligation, a user dependency, or the final surviving evidence of a decision made twelve years ago.
Eventually someone says the sentence that makes a rewrite sound inevitable:
It would be easier to start over.
Sometimes it would.
That possibility is what gives the fantasy credibility.
There are systems whose foundations are unsafe, whose technology is no longer supportable, whose design cannot meet the next requirement without becoming more dangerous than replacement. A rewrite is not automatically foolish. A monolith is not automatically virtuous. A monorepo is not a moral achievement. A distributed architecture is not a character defect.
The failure begins when “start over” quietly changes meaning.
It stops meaning:
Rebuild this capability with a deliberate migration plan because the existing design cannot carry the future safely.
It begins meaning:
Rebuild the visible product and assume the invisible knowledge will reappear when needed.
It will reappear.
It will reappear as a missing feature.
It will reappear as an authentication problem.
It will reappear as a partner escalation.
It will reappear as a user who cannot finish a task.
It will reappear as a temporary redirect to the old website that becomes permanent enough to receive a roadmap.
The clean break does not remove history.
It removes the organization’s ability to see which parts of history were holding the product together.
Legacy code is not only debt
The phrase “technical debt” is useful because it describes the future cost of expedient technical decisions.
It is also dangerous because organizations hear the word debt and imagine a balance that can be erased by deleting the account.
Legacy systems contain debt.
They also contain assets.
They contain proven behavior.
They contain compatibility with users who have built their work around the product.
They contain obscure validations added after obscure failures.
They contain sequencing rules nobody remembers because the rules have worked for so long that they disappeared into normality.
They contain the shape of the business as it was actually practiced, not merely as it was explained in a requirements document.
Some of that behavior is wrong.
Some is obsolete.
Some should be removed with enthusiasm and perhaps a small ceremony.
But the system does not label each line:
- accidental complexity;
- essential complexity;
- expired workaround;
- regulatory obligation;
- user expectation;
- partner dependency;
- ancient mistake now load-bearing.
The rewrite team receives all of it as one undifferentiated category called legacy.
Then it begins sorting after construction has already started.
This is not modernization.
It is archaeology performed by demolition crew.
The rewrite bargain
A rewrite makes a bargain with uncertainty.
The organization accepts a large amount of unknown work now in exchange for the belief that the finished system will contain less unknown work later.
The bargain can succeed.
But it depends on understanding what is being replaced.
That means understanding more than screens and endpoints.
It means understanding:
- why users perform tasks in a particular order;
- which partner teams rely on which contracts;
- where authorization is enforced and why;
- which failures the current product prevents silently;
- which workflows are rare but consequential;
- which inconsistencies are defects and which are accommodations;
- which responsibilities belong together even when the organization chart places them apart.
A rewrite is often approved precisely because the organization does not understand these things.
The absence of understanding becomes the justification for an approach that requires extraordinary understanding.
This is the first movement of Jack’s Shit:
We do not understand the old system, therefore we are qualified to replace it all at once.
The new product is built from the requirements the organization can still name.
The old product was built from every requirement the organization ever encountered, including the ones it forgot.
The rewrite enters production carrying the difference.
Users discover the difference for free.
Five repositories and a question
In the project that produced this essay, the replacement was divided across five repositories.
That fact is not, by itself, an indictment.
Multiple repositories can support real ownership boundaries, independent release schedules, smaller working sets, and clearer permissions. A monorepo can become an enormous administrative terrarium containing every dependency known to science. Repository strategy is a tradeoff, which is the phrase software engineers use when they would like to avoid being murdered by another software engineer.
But a repository boundary is not automatically a domain boundary.
A deployment boundary is not automatically a product boundary.
A team boundary is not automatically a boundary in the user’s task.
When the divisions are wrong, the code becomes locally tidy and globally incoherent.
A feature that once crossed function calls now crosses packages, applications, services, origins, cookies, tokens, pipelines, environments, and ownership assumptions.
Every separation creates a new place where the system must explain itself.
Who is calling?
What identity travels with the request?
Which application owns the session?
Can a browser forward the credential?
Should it?
Is the caller a user, a service, or a user acting through a service?
Which layer authenticates?
Which layer authorizes?
Which repository contains the contract?
Which team can change both sides?
The old product had complexity in code.
The replacement moved some of that complexity into topology.
Topology is still complexity.
It simply requires more meetings to debug.
Recently, my coworkers were reasoning through an authentication problem created at one of these seams.
They recognized that the original design was weak.
They used Copilot to explore several credible options.
Then someone asked:
I wonder if we need this endpoint to be auth gated at all?
That is an excellent question.
It may be the most important question in the entire design.
Then someone added:
But it’s still worth figuring out even if we don’t need it here.
Also correct.
The team may not need authentication for this endpoint. It still needs a coherent authentication model for the system.
This was rational architectural discussion.
It was also an accidental autopsy.
The team was finally separating two questions that should have been separated before the endpoint existed:
- Does this capability require protection?
- How does protection work across the architecture when it is required?
A good question can be evidence that people are thinking.
It can also be evidence that thinking began after construction.
The endpoint that inherited the architecture
Authentication is often described as a security concern.
In a distributed product, it is also an architectural truth serum.
It forces the system to reveal what it believes a user is, where identity lives, which component is trusted, how requests cross boundaries, and whether the architecture matches the actual task.
A weakly conceived endpoint can survive for months while returning harmless data.
Add authentication and the entire family history arrives.
Cookies reveal origins.
Tokens reveal audiences.
Service calls reveal trust boundaries.
Authorization reveals ownership.
Local development reveals assumptions that production politely concealed.
The question “How do we auth-gate this endpoint?” may therefore be too late and too narrow.
The better sequence is:
- What harm becomes possible if this capability is public?
- Does it expose sensitive data?
- Does it perform a privileged action?
- Is user identity actually needed?
- Is service identity enough?
- Can the capability be constrained by network placement, validation, rate limits, scope, or design?
- Why is this an endpoint rather than an operation inside an existing boundary?
Security is not the ritual application of authentication to every route.
A padlock on an unnecessary door is not architecture.
It is evidence that somebody built a door before deciding whether the room needed one.
The monorepo looks good from here
This is where the monorepo begins looking offensively attractive.
Not because a monorepo would solve authentication.
It would not.
A single repository cannot decide whether an endpoint should exist, define a threat model, establish trust, or prevent teams from creating bad boundaries with impressive consistency.
But a monorepo can make certain kinds of truth harder to avoid.
The client, service, shared renderer, contracts, tests, and infrastructure can be searched together.
A cross-cutting change can reveal its full blast radius before five pull requests and three release trains convert it into a scavenger hunt.
An engineer can change both sides of a contract atomically when they genuinely belong to one change.
Shared types can expose drift.
Integration tests can live near the system they integrate.
Repository-wide analysis can show that the “independent” applications are passing one user operation among themselves like a cursed family heirloom.
A monorepo does not create coherence.
It can, however, make incoherence visible in one place.
Multiple repositories work best when the architecture already has stable boundaries, explicit contracts, mature ownership, reliable versioning, independent deployability, and teams capable of operating the resulting distributed system.
Without those conditions, repository separation can become architecture cosplay.
The folders look autonomous.
The work is not.
Five repositories is not necessarily five bounded contexts.
Sometimes it is one product stored in five filing cabinets.
The progressive alternative
The original product did not need to be preserved in amber.
It needed to be treated as a living source of evidence.
A progressive refactor would not have meant polishing the old interface forever or refusing architectural change. It would have meant changing the product in slices small enough for reality to answer back.
Choose one workflow.
Characterize its current behavior.
Identify what users and partners depend on.
Create tests around the behavior that must survive.
Separate the capability behind a stable seam.
Improve or replace it.
Release it to a controlled group.
Observe failures.
Compare outcomes.
Listen to the people using it.
Correct the model.
Then continue.
This approach is slower only if time is measured until the first clean architecture diagram.
Measured until the organization has a functioning replacement, it may be much faster.
The progressive path produces value while migration continues.
It limits the number of simultaneous unknowns.
It allows users to absorb change instead of receiving a new mental model as an unrequested quarterly objective.
It allows partner teams to adapt one contract at a time.
It creates stopping points where evidence can alter the plan.
It lets the organization discover that a planned endpoint is unnecessary before constructing the security model around it.
Most importantly, it preserves comparison.
The old behavior and the new behavior coexist long enough to reveal the difference.
A full replacement often destroys that instrument.
Once the old system is declared obsolete, every discrepancy becomes a debate about intention.
Was the old behavior correct?
Was it accidental?
Did users rely on it?
Should the new product reproduce it?
Could anyone locate the person who knows?
Progressive refactoring asks these questions while the evidence is still running.
Users are not a cutover detail
Software teams often describe migration as movement between systems.
Users experience it as movement between realities.
A familiar task changes location.
A button becomes a workflow.
A workflow becomes a permission request.
A permission request becomes a ticket.
A ticket becomes a partner-team dependency.
The organization sees a new platform.
The user sees that something which worked on Tuesday requires instructions on Wednesday.
This cost is often dismissed as resistance to change.
Sometimes it is resistance.
People can prefer familiar dysfunction to unfamiliar improvement.
But users also possess knowledge the project does not.
Their habits expose which features matter.
Their confusion exposes where the new model contradicts the work.
Their complaints expose assumptions that architecture reviews did not.
Their workarounds are unapproved integration tests.
A progressive migration does not merely make change gentler.
It turns user reaction into design input before the entire replacement depends on being right.
In the system I know, scores of partner teams and thousands of users had to absorb a new experience while the replacement was still rediscovering the old product’s obligations.
That is not a user adoption problem.
It is a sequencing problem.
Feedback was positioned after commitment.
The organization built the answer, then asked reality to review the pull request.
Partner teams are part of the architecture
A product used by partner teams does not end at its repository, deployment, API gateway, or org chart.
Its real boundary includes everyone whose work changes when it changes.
A replacement architecture can be internally elegant while exporting disorder to every consumer.
The platform team celebrates a cleaner contract.
Eighty partner teams receive a migration.
The replacement team removes one compatibility layer.
Every consumer builds its own.
The new product simplifies one codebase by distributing effort across the organization.
This is another movement of Jack’s Shit:
Local simplification becomes shared inconvenience.
The project reports reduced technical debt.
The enterprise receives a larger total bill, itemized under other teams.
Progressive refactoring would not eliminate partner impact. It would make that impact observable and negotiable.
A contract could evolve with compatibility periods.
A workflow could migrate with real consumers before universal cutover.
A failed assumption could be corrected while it was still one failed assumption instead of a platform principle.
The partner teams could respond over time.
They could give feedback before their adaptations hardened into new technical debt.
Instead, the replacement created a second ecosystem and asked the first ecosystem to relocate.
When relocation failed, the product began referring work back to the old website.
The supposedly obsolete system became a dependency of its successor.
The corpse was placed on call.
The old website remains available
Nothing judges a replacement more clearly than its fallback.
A temporary fallback can be responsible engineering.
Migration needs escape hatches.
Rollback is not shameful.
Parallel operation can reduce risk.
But when major workflows return users to the old product because the replacement cannot yet support them, the fallback stops being merely operational.
It becomes an architectural statement.
The old system contained capabilities the rewrite did not understand, did not prioritize, or could not reproduce economically.
The replacement did not escape the legacy system.
It put a newer interface in front of continued dependence on it.
This creates the worst of both worlds:
- two products to support;
- two experiences to explain;
- two architectures to secure;
- two sets of defects;
- two operational models;
- migration logic that becomes product logic;
- users trained to navigate backward when the future reaches a dead end.
The organization pays for replacement and preservation simultaneously.
The old product remains, but now with the additional status of organizational embarrassment.
This embarrassment is dangerous because it encourages more haste.
The team wants to finish the replacement so the old system can finally disappear.
Every remaining old-system capability is treated as a defect in the migration schedule rather than evidence about the domain.
The project accelerates to escape proof that it moved too quickly.
AI arrived at the right historical moment
The traditional argument for a rewrite often depends on the cost of understanding the existing codebase.
The code is too large.
The history is too deep.
The dependencies are too tangled.
The experts have left.
The tests are incomplete.
The documentation cannot be trusted.
Understanding would take too long.
Then generative AI arrived with an unusual ability to assist precisely there.
It can explore a repository.
It can summarize unfamiliar code.
It can trace call paths.
It can compare implementations.
It can identify repeated patterns and suspicious divergence.
It can draft characterization tests.
It can help recover contracts from code, tests, logs, documentation, and version history.
It can propose seams for extraction.
It can explain why a change in one location affects behavior somewhere else.
It can maintain a working map while humans decide which parts of the map are true.
AI is not automatically reliable at any of this.
It can misunderstand behavior, invent rationale, miss runtime conditions, and produce confident nonsense with the serene posture of a consultant billing by the quarter.
Its output requires tests, review, source inspection, and judgment.
But those limitations do not erase the opportunity.
They define the method.
AI does not need to understand the entire legacy system perfectly to make progressive understanding dramatically cheaper.
It needs to help a capable engineer investigate one bounded question at a time.
What calls this function?
Which tests cover this behavior?
Where is this authorization decision repeated?
Which partner contract depends on this field?
What changed when this workaround appeared?
Can we preserve current behavior while moving this capability behind a seam?
What would fail if these two repositories changed independently?
This is not glamorous AI.
It does not produce a greenfield application in an afternoon so executives can experience a dangerous form of hope.
It produces comprehension.
Comprehension is less theatrical than generation.
It is also what a legacy modernization actually needs.
The rewrite cannon
Organizations prefer to point AI forward.
Generate the new service.
Generate the new interface.
Generate the migration script.
Generate the tests.
Generate the documentation explaining the generated system.
Velocity increases.
The number of artifacts increases.
The distance between an assumption and its implementation collapses.
This is useful when the assumption is correct.
When the assumption is wrong, AI allows the organization to become wrong at unprecedented scale.
The safer use of AI is not merely to produce more change.
It is to shorten the loop between change and understanding.
Use it to compare old and new behavior.
Use it to locate missing cases.
Use it to generate tests before generating replacements.
Use it to explain the system to the people changing it.
Use it to identify where a proposed service boundary cuts through one coherent operation.
Use it to ask whether the endpoint needs to exist before implementing three authentication strategies for it.
A progressive refactor plus AI could have changed the economics of the original product.
The work that once seemed too tedious could become tractable.
The undocumented could become partially documented.
The untested could acquire characterization tests.
The dangerous could be isolated.
The ancient could be replaced without requiring the entire organization to cross a bridge that was still being designed.
This is exactly where AI can matter most.
Not as a rewrite cannon.
As a flashlight in the existing machine.
If you let it
The final condition matters.
AI can help understand a complex codebase if the organization lets it.
That means allowing engineers to spend time understanding instead of treating comprehension as delay.
It means giving the tool enough context to see across the system.
It means preserving version history, tests, contracts, and operational evidence as inputs rather than dismissing them as old-world clutter.
It means accepting that an AI-assisted investigation may conclude that the planned work is unnecessary.
It means rewarding the deletion of a proposed endpoint as highly as its successful implementation.
It means permitting architecture to change after evidence appears.
It means using AI to challenge the roadmap, not merely accelerate it.
Most organizations want AI to increase output without increasing doubt.
They want answers faster.
They do not necessarily want better questions arriving early enough to invalidate funded decisions.
So AI is placed inside the same delivery machinery that produced the rewrite.
It generates code for approved stories.
It reviews pull requests inside predetermined boundaries.
It proposes options after the architecture has made the important options unavailable.
It becomes extremely effective at helping the organization continue.
Continuation is not correction.
The tool capable of exploring the old system is used to decorate the new one.
Then, late in the process, it helps a group of engineers ask whether an authenticated endpoint needs to be authenticated.
Or needs to exist.
The intelligence was available.
Permission arrived afterward.
Jack’s Shit Principle
This is Jack’s Shit Principle:
A replacement that treats the accumulated behavior of an existing system as waste does not eliminate that knowledge. It returns the knowledge to the organization as defects, exceptions, partner migrations, user confusion, architectural seams, and meetings.
The old product was not sacred.
It was evidence.
Its code contained mistakes.
Its experience contained compromises.
Its architecture deserved criticism.
But it also represented years of contact with reality.
The replacement treated accumulated contact with reality as contamination.
It wanted the domain without the history.
It wanted the users without their habits.
It wanted the partners without their dependencies.
It wanted distributed components without distributed-system costs.
It wanted security without first deciding what required protection.
It wanted modernization without migration.
It wanted AI as acceleration without AI as inquiry.
It wanted a clean break.
It got two products.
What should have happened
The original product should have been progressively refactored.
Not because progressive refactoring is always correct.
Because this product had users, partner teams, proven workflows, operational history, and a functioning system against which each change could have been tested.
The organization should have improved one capability at a time.
It should have preserved the user experience where change offered no value.
It should have changed the experience where evidence showed improvement.
It should have given partner teams small, reversible migrations with compatibility periods and feedback channels.
It should have discovered architecture through the domain rather than imposing architecture before rediscovering the domain.
It should have used AI to map behavior, recover intent, write characterization tests, compare contracts, trace dependencies, and simplify the code that people were afraid to touch.
It should have made the old product less old until the distinction between old and new stopped mattering.
Instead, the organization created a replacement dramatic enough to require adoption, fragmented enough to require coordination, incomplete enough to require fallback, and modern enough to make every old problem occur over HTTP.
The monorepo is not the hero of this story.
It is merely standing nearby with a very irritating expression.
I am Jack’s shit
I am the clean architecture after it meets the business.
I am the missing behavior that was dismissed as legacy.
I am the endpoint created by a boundary nobody tested.
I am the authentication design discovered after implementation.
I am the user sent backward to complete a task the future forgot.
I am the partner migration counted as somebody else’s work.
I am the second system built because the first system was difficult, now depending on the first system because the second system is incomplete.
I am complexity moved from code into coordination and reported as simplification.
I am the rewrite that escaped technical debt by refinancing it through the entire organization.
I am not proof that old software should live forever.
I am proof that replacement is not understanding.
The product did not need reverence.
It needed attention.
It needed to be changed in pieces small enough for users, partners, engineers, and reality to answer.
AI could have helped.
The original system could have taught its successor what it knew.
The organization could have modernized without demanding that everyone experience modernization as loss.
If it had let them.
It did not.
So here I am.
New.
Distributed.
Strategic.
And routing traffic back to v1.
Receipts
- Original Strangler Fig Application ↗ — Martin Fowler’s original description argues that incremental replacement can reduce risk, deliver value steadily, and allow closer observation than a cutover rewrite.
- Strangler Fig Pattern ↗ — Microsoft’s Azure Architecture Center describes gradually replacing specific legacy capabilities while the existing system remains available.
- Working in small batches ↗ — DORA explains how smaller changes shorten feedback loops, ease remediation, and provide an important safety mechanism when AI increases delivery velocity.
- Customer feedback ↗ — DORA describes customer feedback as part of a lean product approach that helps teams validate whether software is actually helping users.
- Monolith First ↗ — Fowler discusses the operational premium of microservices and the difficulty of choosing stable service boundaries before a domain is sufficiently understood.
- Microservices Architecture Style ↗ — Microsoft documents the additional complexity of distributed architectures, including service communication, testing, versioning, data consistency, observability, and security.
- Using GitHub Copilot to explore a codebase ↗ — GitHub documents Copilot’s use for understanding an unfamiliar repository and asking questions grounded in the codebase.
- Mainframe Modernization solutions from Google ↗ — Google describes generative AI uses in modernization including application-logic summaries, documentation, specifications, and suggested target architectures.