Half a Contract
Requirements kept arriving marked Done with basic acceptance criteria unmet and no smoke test. The pattern wasn't code quality — the project had a definition of ready and no definition of done.
The requirement was marked Done. The first acceptance criterion failed on the first click.
Not an edge case. Not a data problem. The headline behaviour the story existed to deliver, simply not happening.
The Shape of the Problem
One story like that is nothing. Developers are not testers, environments drift, a build gets promoted from the wrong branch. You raise it, it comes back, everyone moves on.
The problem was that it kept happening. Requirements were coming back from QA in volume, for the same reason each time: acceptance criteria not met, no smoke test performed before handover.
Same failure class, different stories, week after week. At that point it stops being a series of incidents and becomes a property of the process.
What the Returns Had in Common
The interesting thing about those defects was how shallow they were.
None of them required a clever scenario. None needed unusual data, a rare configuration or a second system. They were found by opening the story, reading the first criterion, and doing the most obvious thing the criterion described.
That is diagnostic. A defect found by the first obvious action was reachable by anyone who spent two minutes with the ticket before passing it on. Its survival to QA is not evidence that the code was hard. It is evidence that nobody looked.
So the work was not being returned because it was complex. It was being returned because the absolute basics had never been checked, and there was no point in the process where checking them was anybody's job.
The Missing Half
The project had a definition of ready. Informal — never written down, never printed on a board — but real and respected. People knew roughly what a story needed before development could sensibly start, and stories that arrived without it got pushed back.
There was no definition of done. Not a weak one. Not one people ignored. It did not exist, and when the subject came up, the honest answer was that this is a thing we do not have.
Which explains the pattern exactly. "Done" was left to individual judgement, and individual judgement is generous by default. A developer who has finished writing the code has genuinely finished their part. Moving the ticket is not dishonesty — it is a reasonable interpretation of a word that nobody in the project had ever defined.
Everyone had a definition. Nobody had the same one.
What It Was Not
It was not a code quality problem, and the distinction matters because it changes what you fix.
Code quality problems look like defects that require skill to find and skill to fix: deep logic errors, race conditions, integration edge cases. The response to those is review, pairing, better unit coverage.
These were not that. The same shallow failure class, arriving repeatedly, points at a missing handover gate, and no amount of developer skill closes a gate that has never been defined. A more experienced engineer with the same undefined finish line ships the same untested story.
It was also not a testing problem, though it presented as one. The defects were being caught. The cost was that the test window went on proving the basics worked — the cheapest possible use of the most expensive phase.
That is why it went to the project manager as a project risk rather than as a stack of bug reports. A bug report describes one story. The finding was about all of them.
The Rule Worth Taking
A definition of ready without a definition of done is half a contract. You have agreed when work may start and never agreed when it is finished, so the finish line lands wherever each person happens to draw it.
Done is not a status transition. A requirement is done when its acceptance criteria are demonstrably met — which means somebody has actually run the headline journey once, on the environment being handed over, before moving the ticket.
That is a smoke test, and it is minutes of work. It is also the single cheapest thing in the entire delivery chain, because every one of those minutes replaces a full return cycle: a QA execution, a defect write-up, a triage, a fix, a redeployment and a retest.
The absence of it is not visible on any board. There is no column called "nobody checked". It shows up only as a return rate that everybody explains individually and nobody explains as a whole.
On your project, can two people write down what Done means and produce the same sentence?