Skip to main content
Dejan Demonjić brand logoDejan Demonjić
Decision log
Work
About
Contact

Decision log / Decision record

Pre-MVP: make it right, never comply

Before the first release there is nothing to stay compatible with. Code that adapts to older code, an older schema or stale development data is a defect, not a kindness. The rule, how it was found, where it has held and where it is shaping decisions not yet built, and the day it should end.

Filed
2026-10-11
Topic
Engineering systems
Reading
6 min
Tags
#architecture#quality#process

Part 1 of 3 Engineering systems

  1. Pre-MVP: make it right, never comply
  2. The quality gate that could not see async code
  3. Technical SEO for a Blazor site: what the crawler actually sees
Next The quality gate that could not see async code

The claim

Before the first release, nothing is backwards compatible: not code, not a schema, not data. Code that adapts to older code, a script that tolerates an older schema, a test that reads its expectation from stale development data: each is a defect, filed and fixed, never a kindness. It has been a binding rule across every product in Hexavera since 2026-10-03, and it should end at the first release: its own premise is that nothing is released yet.

Context

The rule was found the way most rules are found: by a check that passed for the wrong reason. The identity host seeds a demo catalogue of tenants and people with fixed keys, so that every product's development database agrees on who the administrator is. A development database that had been seeded before the catalogue existed held a catalogued person, the operator, under another key. A live check counted the operator's changes under the catalogue's key, which that database did not hold, so it passed on zero rows whatever happened.

The first patch made the check tolerant of the older database: it read the operator's key from that database by their address and took whatever key it held, so the check now asserted the database against itself. It was reversed the same day. A tolerant patch is a bet that the old shape will keep existing, and before a release it will not. The check now asserts that the operator holds the catalogue's key, the seed refuses a database that holds a catalogued row under another key, and the host does not start until that database is dropped; the next start creates and seeds it again.

The rule

It went into the repository's contributor instructions on 2026-10-03, in three parts:

  1. When something is not right, refactor it. Never write code, a script or a test that adapts to older code, an older schema or older data. Replace the old thing instead, in the same change or in one filed for it.
  2. Development databases are disposable. A development database that does not match what the current code creates (seeded before a seed changed, built by migrations since rebaselined) is dropped and created again. It is never accommodated: no lookup by a fallback key, no branch for "a database seeded before", no live check that reads its expectation from the stale data.
  3. Checks assert the design, not the database. A live check or a seed compares against the catalogue, the contract or the decision, and fails loudly when a database disagrees, saying to drop it.

The second part has a consequence the first does not spell out: every local database of every product can be dropped at any time, so whatever a flow needs after a drop must come back from migrations and seeds. A seed is not a convenience; it is the executable statement of what a fresh system contains.

The rule names development databases and is silent on remote and shared ones. Those stay outside it and are decided in writing: before the identity cut-over's rebaseline, its slicing record required confirmation that no cloud database was to be kept.

Where it has held, and where it is shaping work not yet built

The rule is cheap to write and expensive to keep, so here is where it has been kept, and where it is shaping decisions whose code does not exist yet.

  • The identity cut-over. The identity decision record, the first note of this log's identity series, keeps no token, route, table or account alive. The cloud went first: it was cut over in one step, the migrations of its shared schema were rebaselined into a single baseline, and development databases were dropped and reseeded; the assistant's cut-over is under way and the site's follows under the same decision. The alternative on the table was to accept both issuers for a few merges, and it was turned down because it contradicted that decision. It would probably have worked, and it would have left two code paths and a migration history nobody wanted to own.
  • The device contract. The decision for the device connectors rejects a versioned wire contract with the two-word reason "Pre-MVP rule". A contract fingerprint is to replace versioning: a mismatch is refused and alarmed, and the upgrade procedure stops the connector host with the other services and starts both from the same release. The connector host is not built yet; here the rule shapes the design, not running code.
  • Panel credentials. The credential redesign in the devices series deletes the plaintext storage rather than migrating it, because a migration of plaintext secrets is a second copy of them. That code is not built yet either.
  • Quality gates. The same instinct runs through the gates in this series: a baseline that only ratchets, an exclusion list that fails when an entry matches nothing, a floor on how many projects a scan must read. Each of them refuses to accommodate a state that is no longer true.

What it cost

  • Seeds have to be complete and somebody has to own them. Every flow that a contributor exercises locally must be reachable from an empty database.
  • A contributor who hits a refused start loses a few minutes dropping a database. That friction is the design; the alternative is a fallback that outlives the reason for it.
  • The rule's cost grows with every day of data a team accumulates, which is why it should end at the first release. On that day compatibility should become the rule, migrations should stop being rebaselined, and this rule should be retired in the same document that introduced it, dated.

What a team can take from this

Write the rule down where every contributor reads it, with the three parts and the end date. Treat compatibility code found before the release as a bug to file, not as caution to admire. Make seeds and migrations the only way a database comes into being, and make every check assert the design rather than the data in front of it. The moment you ship, switch the rule off deliberately; a team that forgets to is now breaking customers with the same confidence it used to break its own databases.

DD

Dejan Demonjić

Principal software engineer, Belgrade

Designs and builds secure, multi-tenant .NET platforms. Questions about this entry, or a different view on it, are welcome by email.

  • Email
  • LinkedIn
  • GitHub
Previous Technical SEO for a Blazor site: what the crawler actually seesA crawler sees bytes, not intent. Static server-side rendering, one content source for pages, sitemap and feeds, a real 404 and immutable fingerprinted assets get a Blazor site most of the way. The rest is reading your own output: an early export of this site showed a truncated title, modified dates hours before the published dates, an author link to nowhere and a configuration flag the renderer ignored. Each one with the rule it breaks and the fix.Next Talking to door controllers: what ISAPI teaches about typed outcomesA boolean cannot carry what an access-control panel answers. Three measured facts (a failed write with HTTP 200 and no status, an acknowledged write that changed nothing, a reused digest nonce the panel refused) turned a 27-member interface whose every write returned a boolean into a closed set of typed outcomes, a classification order, a read-back rule and a fresh challenge per request; asking which way a door fails added a deadline on anything that unlocks one.

In this entry

  1. The claim
  2. Context
  3. The rule
  4. Where it has held, and where it is shaping work not yet built
  5. What it cost
  6. What a team can take from this
Dejan Demonjić

Designing and building secure, multi-tenant .NET platforms, and writing down the decisions behind them.

Read

  • Decision log
  • Work
  • RSS feed

Contact

  • dejan@demonjic.com
  • Write to me
  • About

Elsewhere

© Dejan Demonjić. Written in Belgrade.