Approach
Opinions, held for reasons.
We run a dozen of our own systems in production. Everything below is a lesson from one of them, usually an expensive one.
Delivery
How a build actually runs.
Understand the actual problem
The brief is rarely the problem. We spend the first conversations working out what breaks if nothing changes, who feels it, and what the cheapest correct fix looks like — including the possibility that it is not software.
Decide the architecture before the pixels
Tenancy model, data model, trust boundaries, and how the thing gets deployed. These are the decisions that are expensive to reverse, so they get made deliberately and written down while they are still cheap.
Deploy on day one
A real environment, on real infrastructure, with real certificates, before there is anything interesting in it. Deployment is not a phase at the end — it is the thing everything else is tested against.
Ship in increments you can use
Working software at the end of every cycle, on the deployed environment, in your hands. If you cannot click it, we have not finished it, and any estimate about it is fiction.
Harden, reconcile, and prove it
Rate limits, input validation as a server-side control, audit trails, backups that have been restored at least once, and a monitoring story — plus, on anything financial, the reconciliation and compliance reports run against real data before launch rather than after the first month-end.
Hand over, or keep running it
Containerised, documented and reproducible so your own team could take it tomorrow. If you would rather we kept operating it, we will — but that should be your preference, not your only option.
Engineering principles
The defaults we do not re-argue.
Reconcile to the cent
Every system that moves money ships with daily reconciliation and exception reporting. A difference should name itself and its origin, not wait to be discovered during an audit.
Compliance is a build artifact
Audit trails and regulator-facing reports are designed in at the schema, because reconstructing them afterwards from application logs is both expensive and unconvincing.
Journal before you dispense
On unattended hardware the failure always lands between the intent and the physical action. Record the intent first, so a half-completed dispense is a known state rather than a mystery.
Local-first where it matters
Systems should degrade, not die, when the network does. Commit locally, distribute afterwards, and make the delivery guarantee explicit.
Idempotency is not optional
Any operation a client might retry gets a key and a defined replay behaviour. Duplicates should be structurally impossible, not merely unlikely.
Validation is a server concern
Client-side checks are a courtesy to the user. The server assumes every request is hostile, because eventually one is.
Audit trails you can prove
A log tells you what a process claimed. A hash-chained record tells you what happened. For anything touching money, health or identity, insist on the second.
Secrets never live in the repo
Environment variables and a real secret manager. Hashed at rest, revealed once, rotatable. We built a product for this because we needed one.
Own your hosting
Docker on infrastructure you control, behind a proxy you can read. Portability is a business asset, and a platform you cannot leave is a platform that can reprice you.
The other list
Things we will not do.
- Bill for a discovery phase that produces a slide deck and no architecture.
- Put AI in a product because the category expects it, with no evaluation of whether the output is good.
- Build something we cannot hand over, on infrastructure you cannot access.
- Quote a fixed price on a scope that is still moving, then litigate every change.
- Tell you a rewrite is necessary when the honest answer is that we would prefer one.