← The ADLC library
Definition of done · 7

From PRD to ERD to execution plan, mechanically

Requirements, data model and work breakdown are usually three documents that drift apart. Here is what has to be true for the chain to hold without retyping.

A pattern you have lived through. A product manager writes a requirements document. It is good: it explains the problem, the user, the shape of the solution. An engineer reads it and draws a data model on a whiteboard, which encodes about a dozen decisions the document did not contain, because the document never had to say whether a subscription belongs to a user or to an organisation. Someone else breaks that into tickets, which encodes another dozen decisions, because the data model never had to say what happens when the migration runs against a row that violates the new constraint.

Three artefacts. Each one derived from the last by a person, in their head, over a few hours, with no record of the derivation. Three weeks later the requirements change slightly, the document gets updated, and neither of the downstream artefacts moves. From then on, the three documents describe three different products, and nobody can tell you which one the code implements.

This is the oldest problem in software delivery, and I want to be careful about what I claim: agents do not solve it. What they change is the economics, and the economics were the reason we tolerated the drift.

Why the chain broke in the first place

The chain from requirement to model to plan was always lossy for a structural reason: each derivation is expensive to do and nearly free to skip.

Re-deriving the data model after a requirements change takes an afternoon. Re-deriving the ticket breakdown takes another. Nobody has two afternoons, and the code already exists, so what actually happens is a targeted patch: change the two things that obviously need changing, ship it, and let the documents rot. The rot is rational at every individual step and catastrophic in aggregate.

Formal methods people have been pointing at this for forty years, and their answer, model-driven development, mostly failed in the market. It failed because the round trip was brittle: generated code that humans then edited could not be regenerated, so the model became a liability the moment reality diverged. Any modern attempt at this chain has to internalise that lesson or it will fail the same way.

What is different now is that derivation is cheap. Producing a candidate data model from a requirements document, or a candidate work breakdown from a data model, is a task models do well, in seconds, repeatedly. That does not make the derivation correct. It makes it affordable to redo, which is a different and more useful property.

The chain was always lossy for a structural reason: each derivation is expensive to do and nearly free to skip. Re-deriving the data model after a requirements change takes an afternoon, and nobody has two afternoons.

Model-driven development tried to fix this forty years ago and failed on brittleness, not on principle. What changed is not the idea. It is the cost of running it again.

The two things that make a chain hold

For requirements, model and plan to stay consistent, two properties have to hold, and everything else is detail.

Every downstream artefact must be regenerable from the upstream one. Not editable-and-then-synced. Regenerable. The moment a human hand-edits the generated plan and that edit cannot survive regeneration, the chain is broken and you are back to three drifting documents. This is exactly the mistake model-driven development made.

Every derivation step must be reviewable as a diff. The output of regeneration should be a proposal you can compare against what exists, not a wholesale replacement you have to re-read. If a requirements change produces a three-line diff in the plan, you can approve that in thirty seconds. If it produces a new plan document, nobody will read it and everyone will keep the old one.

Those two together give you a chain that survives change, which is the only thing that matters, because the initial pass was never the hard part.

Regenerable, not editable-and-syncedThe moment a hand edit cannot survive regeneration, the chain is severed and the artefacts diverge from there on.
Reviewable as a diffRegeneration should propose a comparison, not a wholesale replacement you have to re-read from scratch.
Two properties, and everything else is detail. Together they give you a chain that survives change, which is the only thing that matters, because the initial pass was never the hard part.

Making the PRD say the things the model needs

The reason a requirements document cannot mechanically produce a data model is not that models are hard. It is that the PRD is systematically silent about the exact facts the model requires. Cardinality. Ownership. Lifecycle. Uniqueness. Time.

You can fix most of that with a short structured section. Not a template rewrite, a section, and it is the highest-leverage change in this entire article.

## Domain facts

Entities: Organisation, User, Subscription, Seat, Invitation

- A Subscription belongs to exactly one Organisation. Not to a User.
- A User belongs to one or more Organisations (many-to-many via Membership).
- A Seat belongs to one Subscription and is either empty or occupied by
  exactly one Membership.
- Seat count on a Subscription can change mid-period; occupied seats can
  never exceed seat count.
- An Invitation targets an email address, not a User; the User may not
  exist yet.
- An Invitation expires 7 days after creation and can be resent, which
  extends expiry and invalidates the prior token.
- Deleting a Membership frees its Seat immediately; billing effects are
  deferred to period end.
- An Organisation cannot be deleted while a Subscription is active.

Ten lines. That is a data model. Every one of those sentences maps to a table, a foreign key, a uniqueness constraint or a state transition, and any of them being wrong is a bug you would otherwise find in week three.

More to the point, these are the sentences a product person can actually argue about, which is what you want. “Does a subscription belong to the org or the user” is a product question dressed as a schema question, and it has enormous consequences: it determines what happens when someone leaves, whether two orgs can share a payer, and how your sales team can package things. Deciding it on a whiteboard by an engineer at 4pm is how companies acquire billing architectures they regret.

Write the domain facts in the PRD and the derivation to a model stops being a leap of interpretation and becomes close to transcription.

The model as the pivot artefact

From that section, a schema falls out almost mechanically:

organisations(id, name, created_at, deleted_at NULL)
users(id, email UNIQUE, created_at)
memberships(id, org_id FK, user_id FK, role, created_at,
            UNIQUE(org_id, user_id))
subscriptions(id, org_id FK, plan, seat_count, period_start, period_end,
              status, UNIQUE(org_id) WHERE status = 'active')
seats(id, subscription_id FK, membership_id FK NULL,
      UNIQUE(subscription_id, membership_id) WHERE membership_id NOT NULL)
invitations(id, org_id FK, email, role, token_hash, expires_at,
            accepted_at NULL, superseded_at NULL)

Now look at what the schema exposes that the prose did not. The partial unique index on active subscriptions is a decision: can an organisation have two active subscriptions? The superseded_at on invitations came from “resending invalidates the prior token”, which was one clause in one sentence. The nullable membership_id on seats is what makes “empty or occupied” real. And occupied seats cannot exceed seat count has no representation at all in that schema, which tells you immediately that it needs to be an application-level invariant with a test, or a trigger, and that is a decision someone should make on purpose.

The value of generating the model mechanically is not the model. It is the questions the generation surfaces. Every ambiguity in the domain facts shows up as an unforced choice in the schema, and unforced choices are exactly what you want to see before code exists rather than after.

From model to plan, and the ordering constraint

The work breakdown is the step people most want to automate and where naive automation is worst, because a plan is not a list of things, it is a list of things in an order that is safe.

An expansion that respects that looks like this:

1. Migration: add `seats` table, backfill one seat per existing membership
   - [ ] Migration is reversible; down migration drops the table only if
         no rows have membership_id set
   - [ ] Backfill is idempotent: running twice produces the same row count
   - [ ] Runs against a copy of production data in under 60s
   - [ ] No application code reads `seats` yet

2. Write path: occupy a seat on membership creation (behind flag)
   - [ ] Creating a membership with the flag on occupies exactly one free
         seat, atomically with the membership insert
   - [ ] Creating a membership when no free seat exists returns 409
         `NO_SEATS_AVAILABLE` and creates neither membership nor seat
   - [ ] With the flag off, membership creation behaves as it does today

3. Read path: seat counts in the members UI
   - [ ] Members page shows occupied/total from `seats`, not from a count
         of memberships
   - [ ] Page issues no additional query per member row

4. Invariant enforcement: reducing seat_count below occupied
   - [ ] Reducing seat_count below the number of occupied seats returns 422
         with the list of seats that would be orphaned
   - [ ] No partial state: on rejection, seat_count is unchanged

5. Cleanup: remove the membership-count path, remove the flag
   - [ ] No remaining references to the old counting helper
   - [ ] Flag removed from config and from the deploy pipeline

The ordering is the intellectual content. Migration before write path, write path behind a flag before read path, invariant enforcement as its own step rather than smeared across the others, cleanup as a real ticket rather than a hope. A generated plan that hands you all five as parallel tickets is worse than useless, because it looks organised while being unshippable.

So the honest position on automating this step: expansion is mechanical, sequencing is judgement. A person should be reviewing the order, and the order is where they should spend their attention. The criteria underneath each step are the part that benefits most from drafting assistance, and the part where a reviewer can move quickly because errors are local.

What “mechanically” really means here

I have used the word throughout and I should define it, because it does not mean automatic.

Mechanical means: there is a defined transformation from artefact A to artefact B, the transformation can be rerun, and rerunning it produces a diff rather than a surprise.

Under that definition, a person doing the derivation by hand can still be mechanical, if they follow the same transformation each time and record the result. And a fully automated pipeline can be non-mechanical, if its output cannot be regenerated after human edits without losing them.

The practical implementation that works today is unglamorous. Requirements in a document with a domain facts section. Model in a schema file, in the repository, versioned. Plan as tickets, each linked back to the model change it depends on. When the requirement changes, you rerun the derivation, look at the diff, and apply the parts that are right. The chain holds because each link is cheap to re-traverse, not because anything is magic.

The link that most often rots is the last one, from plan to reality, because tickets get worked and the plan does not get updated. That link is a different problem and it has a different answer: read source control and reconcile the plan against what actually merged, rather than asking people to maintain it. That is the ground truth argument and it is elsewhere in this library.

Where this breaks down

Most product work does not start with a PRD, and pretending otherwise is where this whole framework becomes theatre. A large share of what teams build originates as a customer email, a support escalation, a conversation in a hallway, or a competitor shipping something. Building a formal chain from requirements to model to plan describes maybe the top ten percent of work by size and a much smaller fraction by count. If you install this as mandatory process, you get people writing retrospective PRDs to satisfy the pipeline, which is pure waste with a documentation smell.

The domain facts section is only as good as the person writing it, and it asks for something genuinely hard. “A Subscription belongs to exactly one Organisation” looks obvious once written and requires real domain understanding to get right. Product managers without technical depth will write facts that are wrong in ways that look confident, and now the wrongness is upstream of everything and carries authority. There is an argument that this section should be written jointly, in a room, and I think that argument is correct, which means the cost is a meeting rather than a document.

Regeneration promises more than it delivers once code exists. The clean story is that a requirements change flows down to a plan diff. In reality, by the time the requirement changes, three of the five steps have shipped, and the diff you need is not “here is the new plan” but “here is the migration from the world we built to the world we now want”. Nothing derives that from a PRD. It requires knowing what is in production, which is a different input entirely, and the chain as described has no idea. This is the biggest gap in the argument and I do not want to paper over it.

And there is a real risk of specification cascade. Each derivation adds detail, and detail added mechanically has a tendency to look authoritative regardless of provenance. A questionable fact in the PRD becomes a constraint in the schema becomes a criterion in a ticket becomes a test that enforces it forever. The chain propagates confidence as effectively as it propagates content, and a wrong decision travels faster and further than it did when three separate humans each had a chance to squint at it. The old lossy process had a hidden benefit: every handoff was a checkpoint where somebody could say “that seems off”. Automating the handoffs removes the checkpoints along with the drift.

The takeaway

The chain from requirement to model to plan breaks because each derivation is expensive to redo and free to skip. Cheap derivation changes that, but only if the downstream artefacts are regenerable and every regeneration shows up as a reviewable diff.

The concrete practice that delivers most of the value is much smaller than the theory: put a domain facts section in your requirements, ten lines of cardinality, ownership, lifecycle and uniqueness, written jointly by someone who knows the product and someone who knows the data. Everything downstream gets dramatically easier, and the arguments you have while writing it are the arguments you were going to have in week three anyway, at a tenth of the cost.

Then be honest that sequencing a plan is judgement, not expansion, and keep a person on it.

The next piece asks the uncomfortable follow-on question: when a machine drafts the acceptance criteria and a human approves them in one click, who actually owns the definition of done, and what does approval mean when the approver did not write a word of it.