I started working in technology around 1993. One of my first jobs was in quality assurance, partly because there was no security profession to join yet.
There were people doing the work, but few companies were hiring for it. That changed within a decade. Until it did, people with the instincts that would later define security engineering landed in adjacent disciplines. Test was one of them.
That was true for me. I became a test manager fairly quickly, later worked as a test architect, then went on to software development, security, and a bunch of other things.
More than thirty years later, I find myself watching something funny happen.
AI is turning software developers back into testers.
Not the kind of testers we were in 1993. What it means to test software has changed several times since then. But at a more abstract level, the work is surprisingly familiar.
The person is no longer primarily producing the thing. They are increasingly trying to determine whether the thing that was produced is any good.
And we have been around this loop before.
We develop a specialty because a problem is hard. We get good enough at it to encode pieces of the expertise into process, tools, and automation. Eventually the machinery works well enough that the underlying expertise starts to look unnecessary. We distribute the responsibility, automate more of it, and convince ourselves that the problem has largely been solved.
Then the system grows, the environment changes, or a new technology arrives and exposes all of the judgment that never made it into the machinery.
The responsibility never left. It just changed costume.

When testing was a profession
In the 1990s, software development and software testing were much more clearly separated. Developers wrote software. Test organizations tried to figure out where it broke.
A lot of the work was manual. People installed builds, exercised features, constructed strange states, tested boundaries, wrote bug reports, and tried to reproduce failures. By modern standards, much of it would look labor-intensive. Some of it really was scut work.
But the separation had an important organizational property. The person who built the thing and the person whose job was to find out why it was wrong were different people.
Their incentives were different too. Developers were trying to make the product work and get it shipped. Testers were rewarded for finding the circumstances under which it did not work.
Those are complementary responsibilities, but they are not the same responsibility.
Over time, testing acquired a status problem. It was increasingly treated as work that did not require the same level of engineering skill as implementation. One response was to give more of it to junior developers.
Then we moved further. Instead of a separate organization owning quality, developers would test their own software.
Much of that change was good.
Unit testing was good. Test-driven development was good. Continuous integration was good. Automated regression testing was good. Testing closer to the point where software was written eliminated entire classes of expensive downstream failures.
The nature of testing changed too. It moved away from a model dominated by manually exercising a finished product and toward one where tests could become part of the way software itself was specified and constructed.
So the story is not that we eliminated QA and that was simply a mistake.
The mistake was gradually convincing ourselves that because we could automate more of the mechanics, we needed less of the judgment.
A test that passes is not evidence that the system is good
As automation improved, we became extraordinarily good at running tests.
A modern software project can execute tens of thousands of tests on every change. We can measure coverage, reject regressions, test configurations, fuzz interfaces, spin up entire environments, and tear them down again without anybody touching them.
The machinery works.
But there is a distinction that became easier to overlook.
A test can work perfectly and still tell you almost nothing useful about the quality of the system.
The hard question is not always whether the test passed. It is whether passing that test is evidence of the property you actually care about.
That gets harder as systems become more complicated. A system can have an enormous green test suite while failing in a way nobody thought to represent in the suite.
There is an important difference between mechanical verification and judgment about what deserves to be verified. One asks whether the checks we wrote passed. The other asks whether those were the right checks.
Automation became exceptionally good at answering the first question. It did much less to eliminate the difficulty of the second.
A test suite is an encoding of somebody’s model of how the system can fail. It captures the failures we anticipated, the properties we chose to represent, and the assumptions we knew enough to challenge.
It says much less about the failures nobody imagined.

Automation did not eliminate the test problem. It moved the test problem up a level.
The scarce skill became figuring out what to test, what failure looks like, which assumptions need to be challenged, and what evidence should actually make us confident in the result.
That is why what is happening with AI feels so familiar.
We thought AI would do the testing
One of the obvious expectations around generative AI was that it would automate still more testing.
If the AI can write the implementation, it can certainly write unit tests too. And it can.
But that misses the more important change.
AI is making implementation cheap.
A developer can already cause far more code to be produced than they could reasonably have written themselves. As agents improve, that multiplier gets larger. It is not difficult to imagine one engineer directing dozens, hundreds, or eventually thousands of concurrent software-producing processes.
At that point, traditional code review is not merely inefficient. It becomes physically impossible.
Nobody is going to carefully read every line produced by a thousand coding agents.
The generated output is also not quite like the output of an old deterministic compiler or code generator. These systems are probabilistic. Run them again and you may get a different implementation, a different decomposition, or a different mistake.
That makes the old assumption that we can inspect the artifact into correctness even less plausible.
So the human moves up a level.
Instead of spending most of the time constructing the implementation, we increasingly construct the conditions under which an implementation will be accepted. We write tests, evals, invariants, acceptance criteria, adversarial cases, and constraints. We decide what evidence is sufficient to tell us that the machine-produced result is actually good.
Software developers are becoming testers again.
It is not 1993-style manual QA. It is closer to specification, TDD, evaluation design, systems validation, and the construction of executable evidence.
But abstractly, it is the same work happening in a different way.
The form changes. The responsibility does not.
What the new testing actually looks like
The easy question is whether generated code passes the existing test suite.
The harder question is whether the suite represents the properties we actually care about.
Does the generated component behave correctly under inputs we did not anticipate? What happens when independently generated components interact and two locally correct decisions compose into a globally bad result? What happens after the system has been operating for days, accumulating state and acting on the consequences of its own earlier decisions?
What happens when the environment differs from the conditions represented in our evals? And what happens when an adversary deliberately searches for the assumptions we failed to encode?
Those questions are not answered by producing more tests mechanically.
They require somebody to form a theory of failure.
The scarce skill is increasingly understanding the mechanics of failure. That means knowing where the system boundaries are, which properties must remain true across those boundaries, what assumptions are hidden inside the architecture, and how reasonable local behavior can produce unreasonable global outcomes.
It also changes what a useful test looks like.
When implementations are relatively stable, testing specific examples can tell you a lot. When an AI can regenerate the implementation tomorrow, durable properties become more important. The question shifts from whether this implementation produces the expected result for this input toward which properties must remain true across whatever implementations the system produces.
Those properties might concern correctness, authority, state transitions, isolation, safety, or the relationship between components. The implementation can change while the invariant remains.
That is a different kind of leverage.
Verification also stops neatly ending at release. Some failures only emerge through interaction with real environments, long-running state, changing inputs, or behavior that was not represented during development. That pushes part of the evidence gathering into operation through telemetry, observability, runtime checks, and the behavior of the deployed system itself.
The new tester is therefore not just checking whether an implementation conforms to a specification somebody else already wrote.
Increasingly, they are responsible for deciding what the specification must say, which properties must survive implementation changes, which failures matter enough to detect, and what evidence is sufficient for the resulting system to deserve trust.
That is a substantially higher-order form of the same old responsibility.
High-quality scut work
There is a funny status inversion hiding in this.
Test used to be treated as scut work. Then we gave more of it to junior developers. Then we told every developer they were responsible for doing it themselves. Then we automated as much of it as we could.
Now we are automating the part we used to think was the prestige work, writing the software, and leaving the developer sitting above the machinery trying to determine whether any of what it produced is good.
We may have turned one of the industry’s prestige jobs into very high-quality scut work.
The funny part is that the scut work may now be where much of the value lives.
When implementation is expensive, the person who knows how to implement something is scarce. When implementation becomes cheap, the scarce person is the one who knows what should be built, what properties it needs to have, how it is likely to fail, what assumptions are hidden inside it, and what evidence would convince us that it works.
Generation gets cheaper. Judgment does not.
Security already did this
I have seen almost the same cycle happen with security.
In retrospect, it is probably not an accident that so many early security people came through test. Both disciplines train you to look at a system somebody else believes works and ask what they have failed to consider.
Testing asks how the behavior can violate what was intended. Security asks how trust, authority, or assumptions can be violated even when the system appears to be functioning normally.
Both reward a certain kind of skepticism.
When I started, security engineering barely existed as a normal software profession. By the late 1990s it was becoming one. By the early 2000s, it was clearly a distinct industry with dedicated teams and career paths.
That specialization happened because security was hard and ordinary development organizations were not consistently good at it.
Then we started saying something that was also fundamentally correct. Security should not be something another group bolts onto the product afterward. Developers should build secure systems themselves.
“Security is everyone’s responsibility.”
There is nothing wrong with that principle.
The problem is what happens when we confuse responsibility with expertise.
A product developer is trying to ship a product. They have schedules, features, performance requirements, compatibility issues, reliability problems, customer demands, and dozens of other things competing for attention.
Security becomes one of many things they are supposed to get right.
When that proves insufficient, organizations build machinery around the problem. We add secure development lifecycle processes, scanners, policy gates, paved-road platforms, and controls designed to make the safe thing easier than the unsafe thing.
All of those things can help.
But notice what we are doing.
After deciding the specialist function should be distributed into the rest of engineering, we are encoding pieces of that specialist judgment back into systems and processes.
A scanner encodes somebody’s knowledge of what a vulnerability looks like. A secure-by-default platform encodes somebody’s judgment about which choices should be permitted. A policy gate encodes somebody’s model of what conditions need to hold before software should be released.
The specialist may become less visible as that expertise gets embedded into the platform, but the expertise did not cease to exist. It became infrastructure.
Eventually there is enough machinery that it again becomes tempting to ask whether we really need the specialists.
Then the abstraction leaks.
A threat appears outside the model encoded in the scanner. A platform assumption no longer holds. A new system does something the existing governance framework was never designed to reason about.
Then we discover that we automated the known answers, not the ability to recognize new questions.
AI security and the latest rediscovery
AI makes this pattern almost comical because we are currently rediscovering old classes of security problems with new names.
Prompt injection is obviously not literally SQL injection. The implementation is different, the interpreter is different, and the failure modes are different.
But someone who spent the 1990s and 2000s dealing with SQL injection, command injection, script injection, confused deputies, trust boundaries, privilege separation, and the consequences of letting untrusted input become control should find the family resemblance hard to miss.
We have spent decades learning that you should be very careful when information from an untrusted party can influence what a privileged system interprets as instructions.
Now we have built enormously capable interpreters whose primary interface is natural language. We mix instructions and data in the same context, connect them to tools, and act surprised when hostile input changes what they do.
The technology is new. The institutional failure mode is not.
The same thing is happening in conversations about containment and sandboxing. We are once again discovering that powerful systems need boundaries, that those boundaries need to be enforced rather than merely described, and that capabilities should be constrained by something stronger than an instruction asking the system to behave.
None of that makes AI security trivial. The new systems create genuinely new problems. But novelty at one layer does not erase the accumulated lessons at another.
We knew versions of these things thirty years ago. What keeps recurring is not the exact vulnerability. It is the belief that a new abstraction has somehow relieved us of the old responsibility.
That is where the amnesia comes in.
The amnesia cycle
You could describe the pattern as specialization, codification, automation, perceived redundancy, loss of judgment, scaling failure, and rediscovery.

That is the amnesia.
We rarely forget the artifacts of the previous generation. We keep the test frameworks, scanners, development processes, controls, and automation.
What we forget is why the people who created those things thought the problem was hard in the first place.
Eventually we confuse the existence of the machinery with possession of the expertise that created it.
A passing test suite becomes evidence of quality. A security scanner becomes evidence of security. An AI eval becomes evidence that the AI is doing the right thing.

Until the system moves outside the assumptions those mechanisms encode.
Then the old problem appears again, wearing different clothes.
The responsibility never leaves
That is the through line I see between testing, security, and what is now happening with AI-assisted development.
We are very good at abstracting away mechanics. That is what engineering does.
But when we successfully automate the mechanics, it is easy to convince ourselves that we automated the underlying responsibility too.
We did not.
Testing did not disappear when the dedicated QA organization disappeared. Security did not disappear when we made it everyone’s responsibility. Verification will not disappear because an AI can generate both an implementation and a test suite that says the implementation is fine.
Somebody still has to decide what “fine” means. Somebody has to recognize the assumptions the automation does not know it is making. Somebody has to decide which failures matter. Somebody has to determine what evidence would falsify the claim that the system is working.
Somebody has to distinguish a system that successfully passes its tests from one that deserves to be trusted.
The responsibility never leaves. It changes costume.
I started my career in a world where developers wrote the software and people like me tested it. We spent the next thirty years treating test first as work developers should not have to do, then as work developers should do themselves, and finally as work machines should increasingly do for them.
Now the machines are starting to write the software.
And the developers are increasingly responsible for figuring out whether any of it is good.
Apparently the tester won.
