Phasing Is the Architecture: Lay the Reliability Core at Phase One or Rebuild Four Times
A product that must grow from a seed either lays its reliability core at phase one, or rebuilds at every growth step and re-litigates the same reliability questions four times. The core is deliberately more than phase one needs and exactly what the phases after it require, because some properties can be added later at linear cost and some cannot be added later at all. This gives the filter for what belongs in the core, the mechanical test for whether a phase is additive, and what to do when a phase genuinely changes the core's contract.
Phasing Is the Architecture: Lay the Reliability Core at Phase One or Rebuild Four Times
The short answer. When a product is planned as a sequence of phases growing from a small seed, there are two available paths and only two. You lay the reliability core at phase one, in which case each later phase adds on top of something that still means what it meant. Or you scope phase one to phase one, in which case every growth step rebuilds part of the foundation and the team re-litigates the same reliability questions, four times, each time with more data at risk and less schedule to do it in. The core is deliberately more than phase one needs. It is exactly what the four phases after it require, and the reason is that some properties can be added later at linear cost while others cannot be added later at all, because the history they depend on was never recorded.
A five-phase roadmap arrives. Phase one is a single donation form taking one-off payments. Phase five is a multi-entity fundraising operation with recurring giving, reconciliation against the payment provider, finance reporting and several teams in it.
The sensible-sounding instruction is to build the smallest thing that satisfies phase one and not over-engineer. For the interface, that is correct. For the data, it is the decision that costs the most money in the whole programme, and it gets made in the first week by whoever writes the first migration.
Some things are additive and some things are not
This is the entire argument, so it is worth being precise.
Reporting is additive. You can add a dashboard in phase three over rows written in phase one, and the cost is roughly proportional to the number of reports. Nothing about the existing data has to change.
An audit trail is not additive. If phase one stored a payment as a mutable row with an amount that gets updated in place, then by phase three there is no history to reconcile, because every correction overwrote the thing you would reconcile against. You cannot add an audit trail retroactively over data that was overwritten. The option is gone, not expensive.
That asymmetry is the filter. For every property you are tempted to defer, ask what the retroactive version of it looks like. If the retroactive version is work, defer it. If the retroactive version is a guess dressed up as a backfill, it belongs in the core.
What belongs in the core
Five things, each one justified by failing that test.
An append-only record of money events. Every event the payment provider tells you about, stored as written, with the provider's own identifier under a unique constraint. Retroactively: impossible, because you do not have the events you did not keep. This single table is also where duplicate detection lives, since a redelivered event becomes a constraint violation rather than a second credit.
Idempotency on every externally triggered write. Retroactively: you can add the mechanism later, and you cannot repair the duplicates already created, and worse, you will not reliably be able to identify which rows were duplicates. The damage is not reversible even once the bug is fixed.
Explicit identity and ownership on every record. Who paid, which entity receives, which campaign or fund it belongs to, recorded at capture. Retroactively: attribution reconstructed later is an inference, and once a report is published off it, the inference becomes the record.
A time model with two timestamps. When the event occurred according to the source, and when your system recorded it, with time zone handled explicitly rather than by accident. Retroactively: once those are collapsed into one column you cannot recover the distinction, and every question about late-arriving data becomes unanswerable.
Change history for anything a human can edit. Who changed which field, when, from what value to what value. Retroactively: impossible, for the same reason as the audit trail.
Equally important is what does not belong. Reporting, the interface, roles beyond the minimum two or three, third-party integrations, recurring billing schedules, notification templates, multi-currency presentation. Every one of those is additive at a cost proportional to its own size, and putting them in phase one is over-engineering in the way the instruction meant.
The mechanical test for whether a phase is additive
One question, applied to each phase: does this phase require any already-written row to mean something different than it meant when it was written?
If no, the phase is a layer. If yes, the phase is a core change, whatever the roadmap calls it.
The sharper operational version of the same test: does this phase add a column that must be non-null on historical rows, whose historical value is not recoverable from data you already hold? If so, you are changing the core.
Worked through the usual phases:
A reporting dashboard reads existing rows and leaves their meaning intact. Additive.
Recurring giving creates new rows of a new kind without touching existing ones, provided the event record already carries a type so a one-off and a scheduled payment are distinguishable. If the core assumed every payment was a one-off and encoded that assumption by omission, this phase is a core change. That is precisely why the type field belongs in phase one, where it is one column, rather than in phase three, where it is a backfill over live money data.
A second receiving entity requires every existing row to have an entity, and the historical rows' entity is implied by the fact that there was only one. That is recoverable, so the migration is honest, but it is still a core change and should be scoped as one. If the entity column was non-nullable from phase one, the phase is additive instead.
Multi-currency requires every existing amount to have a currency and a minor-unit convention. If amounts were stored as integers in minor units with an explicit currency from the start, additive. If they were stored as floating point in an assumed currency, this is a core change and also a correctness incident waiting to be discovered, because binary floating point cannot represent most decimal money values exactly and the rounding errors accumulate in exactly the aggregates finance looks at.
When a phase genuinely changes the core's contract
Sometimes it does, and the right response is to scope it as a core change with its own plan rather than smuggle it in as part of a feature.
Smuggling it in looks like this: a nullable column, a comment explaining what null means, and a code path that treats null as a default. Six months later the default is load-bearing, nobody remembers it was a migration artefact, and a report quietly includes rows it should have excluded.
Scoping it properly looks like this. The record format gets a version. There is a written interpretation rule for pre-migration rows, stating what they mean by definition rather than by inference. The backfill is explicit and recorded as a backfill, with its own provenance, so a row whose entity was assigned by migration is distinguishable from one whose entity was captured. There is a reconciliation report over the migration showing totals before and after. And there is a written note of what was lost, because something always is.
Price it as its own phase. A core change needs the same scrutiny the original core got, and putting it inside a feature phase guarantees it gets the scrutiny the feature got instead.
Why it is four rebuilds and not one
The failure is not that one migration happens. It is that the same conversation happens at every step.
At phase two somebody asks what happens if the payment event arrives twice. At phase three somebody asks how finance reconciles against the provider's own report. At phase four somebody asks who is allowed to see which entity's data. At phase five somebody asks whether the historical totals can be trusted.
These are the same four questions, and if the answers were never recorded as properties of the core, each one gets reconstructed from scratch by whoever is available, under more schedule pressure than last time, over a larger dataset, with more consequences for getting it wrong. The cost of each round rises while the appetite to touch the foundation falls, which is the worst pair of curves to have crossing.
The core's real function is to answer those four questions once, in code and in a constraint, so that later phases inherit the answer rather than reopen it.
The honest cost of the core
It is more than phase one needs. That should be argued on its own terms rather than disguised as something phase one requires.
In code it is small: one append-only table, a handful of unique and non-null constraints, integer money with explicit currency, two timestamps, a type column, an entity column, and a change-history table. A competent team adds that in days, not weeks.
What it actually costs is agreement. Deciding which entity receives, deciding what the money event type enumeration is, deciding what capture-time attribution means when a payer arrives through an unattributed link. Those decisions are the expensive part and they are also the part that does not get cheaper later. They get harder, because by then there is data that assumed an answer.
The honest limit
This is a bet on the roadmap. If phases two through five never happen, you have paid an insurance premium on a product that did not grow, and the honest accounting is that the premium was real and so was the risk. The bet is favourable when the later phases are the reason the product exists, and unfavourable when phase one is a genuine experiment that may be deleted.
A core cannot anticipate everything, and one that tries becomes a framework nobody is willing to change, which is a worse outcome than the problem it prevents. The discipline is narrow on purpose. It is the "cannot be added retroactively" filter, not general extensibility, and anything that passes the filter easily should be left out.
And reconstruction is sometimes possible, which softens the argument honestly. A payment provider holds its own record of every transaction, and a surprising amount of lost history can be rebuilt from it. That escape hatch is real and it is time-limited: it works while the provider still retains the data, while the mapping between their identifiers and yours is still known, and while somebody still remembers which integration was live in which month. Relying on it is a decision, not a safety net, and it should be written down as one.
FAQ
Should phase one be the smallest thing that works? For the interface, yes. For the data, no. A product planned to grow either lays its reliability core at phase one or rebuilds at each growth step, re-litigating the same reliability questions four times with more data at risk each round. The core is deliberately more than phase one needs and exactly what the later phases require.
How do I decide what belongs in the core? Ask what the retroactive version of each property looks like. If adding it later is ordinary work, defer it. If adding it later means inventing history you never recorded, it belongs in the core. That filter selects an append-only event record, idempotency, capture-time attribution, a two-timestamp time model and change history, and it excludes reporting, roles and integrations.
What is the test for whether a phase is additive? Whether the phase requires any already-written row to mean something different from what it meant when written. Operationally, whether it adds a column that must be non-null on historical rows whose historical value is not recoverable from data you hold. If so, it is a core change regardless of what the roadmap calls it, and it needs its own plan.
What do I do when a phase has to change the core? Scope it as a core change rather than as part of a feature. Version the record format, write an explicit interpretation rule for pre-migration rows, mark backfilled values as backfilled so they are distinguishable from captured ones, produce a reconciliation report over the migration, and record what was lost. The failure mode to avoid is a nullable column and a comment.
Why does deferring reliability cost more than once? Because the questions recur at every growth step: duplicate delivery at phase two, reconciliation at phase three, access boundaries at phase four, trustworthy historical totals at phase five. If the answers were never recorded as properties of the core, each one is reconstructed under tighter deadlines over a larger dataset, while the team's willingness to touch the foundation keeps falling.
What if the later phases never happen? Then you overbuilt and paid an insurance premium on a product that did not grow. That is an honest cost and worth stating plainly. The bet is favourable when the later phases are the reason the product exists and unfavourable when phase one is a genuine experiment that may be discarded, so decide which situation you are in before applying any of this.
The four boring checks and the data-distribution slice, ending in a rebuild-or-repair verdict with the evidence for it. An afternoon of work, and it is designed to be carried into the meeting where somebody is proposing six months.
Tell us the system, the stakes, and the date that matters. You get a straight technical reply from the person who would lead the work, within 24 hours.
Bring us the program