Ask for a change halfway through a fixed-scope build and you will be quoted hours. The hours are the part both sides can see, and they are usually not what the change costs.
What it costs is the guarantee you give up. Most designs hold some rule for free by having no way to break it. Change the design and the rule does not disappear. It moves, from something the system cannot violate into something a person has to remember. That transfer almost never appears on the quote, and unlike the hours it does not end when the change ships.
The clearest way to see it is a worked example, so this article uses one. It is illustrative rather than a client story: a composite of a request that comes up in almost every multi-tenant build, with no customer and no numbers attached.
The hours are the half you can see
The oldest careful writing on this is procurement law, and it is more honest about the shape of the problem than most software contracts are. Under the standard fixed-price changes clause the buyer can order a change in writing, and the adjustment that follows covers any increase in the cost of, or the time required for, performance of any part of the work, whether or not changed by the order.[1]
That last clause was drafted by people who already knew a change costs you somewhere it did not touch.
The regulations are equally blunt about why nobody prices that part: contractors’ accounting systems are seldom designed to segregate the costs of performing changed work.[2] The sentence is about government contractors’ ledgers, and it describes a software engagement just as well, because the second half of a change is a property you used to have, and properties do not appear in a ledger.
Timing makes it worse in a way that is measurable elsewhere. Ibbs collected 169 large projects across twelve countries, ranging from $3.2 million to $15 billion in installed cost, and found that projects taking most of their change late ran at lower productivity than projects whose change arrived early.[3] That is construction, and the curve is not offered here as if it transferred to software. The mechanism does: late change lands on decisions other things have already been built on top of.
Which is also the theoretical objection to fixed price. It gives strong effort incentives and gets expensive as design completeness falls, because every deviation has to be renegotiated as a change order.[4] We quote fixed scope anyway, and refuse it for genuinely exploratory work. The renegotiation is the price of that position, so it is worth being good at.
A worked example: one database per customer, then one for everyone
Suppose a B2B product is being built with a database per customer. Every tenant gets its own Postgres database, and the application picks the connection from the tenant on the request. In the vocabulary AWS uses for this, it is a silo model, and isolation there is generally much simpler to enforce.[5]
The reason is structural. A query running against customer A’s database has no
rows from customer B to return. There is no WHERE clause to forget, because
the other tenant’s data is not on the other end of the connection. Cross-tenant
leakage is not prevented by care. It is prevented by absence.
Two months in, the request arrives: move everyone into one shared database with
a tenant_id column. The reasons are real. A database per customer means a
migration run per customer, a backup schedule per customer and a connection pool
per customer, and AWS names the cost directly: with every tenant in its own
environment you give up much of the cost efficiency SaaS is supposed to
provide.[5]
Here is the half that gets quoted. Add tenant_id to every table, migrate the
data, collapse the connection logic, and rewrite every query that used to trust
the connection to scope itself. It is a bounded piece of work and when it merges
it is finished.
Here is the half that does not get quoted. Every one of those queries now has to
carry tenant_id correctly, and so does every query written after it, by
anyone, for the life of the product.
-- Database per customer: the connection is the boundary.
SELECT * FROM invoices WHERE id = $1;
-- One shared database: the boundary is now a clause somebody has to write.
SELECT * FROM invoices WHERE id = $1 AND tenant_id = $2;The first query cannot leak. The second cannot leak either, as long as nobody ever writes the first one again. AWS’s own framing of the shared model is that networking and IAM can no longer draw the line between tenants, and that sharing increases the chance of cross-tenant access.[6]
A guarantee by construction and a guarantee by habit
Industrial safety has ranked these for decades and the ranking is not close. NIOSH puts elimination, substitution and engineering controls above administrative controls and PPE, because the first three “control exposures without significant human interaction” while administrative controls and PPE “require significant and ongoing effort by workers and their supervisors”.[7]
The change in the example moved tenant isolation down that ladder. Before, it was an engineering control: the design removed the hazard. After, it is an administrative one: a rule every developer follows on every query, reviewed by another developer who is also busy.
That is the thing a change request should be priced against, and the reason the hours are misleading. The hours bought a new schema. The change spent a guarantee.
What you can buy back, and what you cannot
The response to giving up a structural guarantee is to reconstruct as much of it as the new design allows, and to charge for that reconstruction inside the change rather than discovering it later.
Postgres has a mechanism that fits this example closely. With row-level security enabled on a table, access has to be allowed by a policy, and a table with no policy at all defaults to deny: no rows are visible and none can be modified.[8]
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
ALTER TABLE invoices FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON invoices
USING (tenant_id = current_setting('app.tenant_id')::uuid);
-- The application sets this once per transaction:
SET LOCAL app.tenant_id = '6f1c...';Now a query that forgets its tenant_id returns nothing instead of returning
someone else’s invoice. That is a real recovery. The database, not the author of
each query, holds the line again.
It is still partial, and the gaps are the useful part of the quote. Superusers
and roles with BYPASSRLS always skip row security, and table owners skip it
too unless the table is forced, which is why the second line in that block
exists.[8] Many applications connect as the owner of their own
tables, so leaving FORCE out quietly returns the design to the remembered-rule
version while every policy still looks correct. Migrations, admin scripts and
reporting jobs frequently run as exactly the roles that bypass it.
So the result, stated precisely: isolation is structural again for the application’s ordinary path, and procedural for every role that bypasses it. Saying which part is missing is the difference between a change quote and a sales document.
Why a sensible team makes the change anyway
Because the cost argument is real, and at some number of customers the silo model stops scaling operationally. AWS lists cost, agility and onboarding automation as the silo model’s standing costs.[5] A team that refuses this change on principle is usually wrong.
The goal is to price it properly. When the justification is cost and the price is a permanent discipline, the only defence against talking yourself into it cheaply is having written down what it takes away before deciding.
How we price one
Three lines, and we will show you all three:
The hours. The part everyone expects. Usually the smallest number on the page.
The guarantee traded, named. What was true by construction before this change, what makes it true afterwards, and who holds it. If the answer is “a developer remembers”, that goes in the quote in those words. If a mechanism like row-level security can take some of it back, that reconstruction is a line item of its own.
The recurring cost, per unit, with the unit named. Not “some ongoing
maintenance”. In the example: one policy and one isolation test per new table,
and one check of every new database role against BYPASSRLS, for as long as the
product runs.
We also price the change before doing it, which sounds obvious and is the rule most often broken in practice by both sides. Federal procurement makes it explicit: modifications are to be priced before execution where that can be done without harming the buyer’s interest, and personnel other than the contracting officer are told not to direct or encourage the contractor to perform work that should be the subject of a modification.[9] Translated out of procurement language: the person who said yes in the design channel on Thursday may not have been the person who could say yes, and the work they encouraged is now unpriced and half done.
A change agreed in a chat thread, started the same afternoon, and invoiced three weeks later is the single most common way a fixed-scope engagement turns into an argument. It is also avoidable in about ten minutes.
What this costs you, as the buyer
Four things, none of them comfortable:
Changes get slower to agree than to build. A change whose diff is one afternoon can take longer to quote than to implement, because naming the guarantee means going and reading the thing that currently holds it. Some buyers experience this as a studio being difficult about a small request.
Some quotes look absurd next to the diff. When the recurring half is large, the number does not match the size of the change, and it is not supposed to. We will show the arithmetic. You may still think it is wrong.
You will be told no in a way that sounds like a yes. Our answer to a request like the one above is usually “yes, and here is what it permanently costs”, which is harder to act on than a flat refusal.
The method has a failure mode, and it is us. Naming a guarantee makes a change sound expensive, so there is an obvious incentive to find one in every request. Not every design holds a rule for free. When we cannot point at the specific line of code or clause that made the old rule unbreakable, there was no guarantee, and the change is just hours. Ask us to point at it.
Before you ask for a change, ask three questions
They take five minutes and they work on any vendor, including us:
- What does this design guarantee today that it will not guarantee afterwards? If the answer is “nothing”, good, it is a cheap change.
- What step does this add that has to run every time, who runs it, and how often?
- What happens if that step is skipped? “We would notice” is not an answer. “The database returns no rows” is.
Then ask the same three about the thing you are building in-house, because the questions are not really about vendors.
A change request is not a request for more work. It is a request to keep the same promise a different way, and the new way is the part still being paid for a year later.
Sources
- 52.243-1 Changes-Fixed-Price
Supports: Under the standard fixed-price changes clause the equitable adjustment covers an increase or decrease in the cost of, or the time required for, performance of any part of the work, whether or not changed by the order, and the contractor must assert its right to an adjustment within 30 days from receipt of the written order.
- FAR 43.203 Change order accounting procedures
Supports: Contractors' accounting systems are seldom designed to segregate the costs of performing changed work.
- Change and the Loss of Productivity in Construction: A Field Guide
Supports: Ibbs collected data from 169 large projects in twelve countries, with total installed costs from $3.2 million to $15 billion, and found that projects taking most of their change late in the schedule ran at lower productivity than projects whose change came earlier.
- Incentives versus Transaction Costs: A Theory of Procurement Contracts
Supports: Fixed-price contracts give strong effort incentives but become expensive as design completeness falls, because every deviation has to be renegotiated as a change order.
- Silo isolation - SaaS Tenant Isolation Strategies
Supports: In the silo model each tenant runs a fully siloed stack, isolation is generally much simpler to enforce, and the cost is that every tenant running in its own environment gives up much of the cost efficiency associated with SaaS.
- Pool isolation - SaaS Tenant Isolation Strategies
Supports: In the pool model tenants share infrastructure, networking and IAM constructs can no longer create the boundaries between tenants, and the shared model increases the chance of cross-tenant access.
- About Hierarchy of Controls
Supports: Elimination, substitution and engineering controls are ranked as more effective because they control exposures without significant human interaction, while administrative controls and PPE require significant and ongoing effort by workers and their supervisors.
- Row Security Policies
Supports: When row security is enabled and no policy exists, a default-deny policy applies and no rows are visible or modifiable; superusers and roles with BYPASSRLS always bypass row security, and table owners normally do too unless the table is set to FORCE ROW LEVEL SECURITY.
- FAR 43.102 Authorization
Supports: Contract modifications shall be priced before their execution where that can be done without adversely affecting the Government's interest, and personnel other than the contracting officer shall not direct or encourage the contractor to perform work that should be the subject of a contract modification.