Engineering standards

The rules we build to

These are not aspirations on a slide. They are the standards our own production systems are held to — the ones that decide how we argue in code review, and the reason we are comfortable putting payroll and client billing on our own software.

01

Money is never a floating-point number

Every amount is held as an exact integer in minor units, and additions are overflow-checked rather than assumed safe. Rates carry six decimal places, and the rounding rule that produced a figure is stored alongside it. A tenth of a paisa lost to binary floating point is a rounding error in a demo and a reconciliation failure in production.

02

Records are versioned, never overwritten

A salary structure, rate card, policy or reporting line is never edited in place. A change writes a new version with the dates it applies between and a pointer to what it replaced. There are no soft deletes, because effective dating already answers the question a delete flag was trying to answer: what was true on the day this decision was made.

03

A completed run is an immutable snapshot

When a payroll run is calculated, every input and the version of the rule pack that read them are frozen with it. Nothing is recomputed when the run is viewed again. A correction does not edit history — it creates a new run linked to the one it supersedes. This is why a run from eighteen months ago still reproduces exactly.

04

No one person moves money alone

Preparing a payroll run and approving it are separate permissions, and no role holds both — including the administrator, who additionally cannot see salary figures at all. An administrator decides who may do what; letting the same person also move the money makes the grantor and the beneficiary one. Combining the two requires two explicit, audited grants.

05

The audit trail is append-only, and written where the context is

Every change to pay, bank details or statutory data records who did it, from where, what the value was before and after, and the request that carried it. It is written in the application layer rather than by a database trigger, because a trigger can see the row that changed but not the business decision that changed it.

06

Tests run against the real database

Integration tests execute against real MySQL, never an in-memory substitute. A substitute passes on SQL the production engine would reject and hides the behaviour that actually matters — locking, isolation, collation, how a constraint fails under load. A test that only proves the code works against something you will never deploy is a test that has told you nothing.

And one we earned the hard way

A method that writes to the database and then fails loses the write — including the security bookkeeping written on the way out, so a lockout counter never increments and a detected session theft is silently not acted on. We hit that same seam six times across authentication and scheduling before we stopped treating it as something to remember and made it a question every review must ask of every failure path. The rules above exist for the same reason: not because they sound rigorous, but because the alternative cost us something.

Technology

We have a default stack, and reasons for it

Most of the technology on a project is decided before the project begins. We standardised on purpose, so that the decisions genuinely particular to your problem get the argument and the routine ones do not consume it. Where a system needs something outside this, you will be told what and why before it is introduced.

Web

ReactTypeScriptViteOpenAPIZod

Typed through to the API boundary, not merely inside the browser. The service publishes an OpenAPI specification and the client and its runtime validators are generated from it, so a breaking contract change fails a build instead of a customer's session. Rendering strategy is decided per surface: server-rendered where first paint is the product, client-rendered where the session is.

Services & APIs

Java 21 LTSSpring BootNode.jsPythonRESTGraphQL

Java where a calculation has to be defensible years after it ran — payroll, billing, anything an auditor may reopen — and long-term-support releases only, because a runtime that falls out of support hands you a security schedule you did not choose. Node and Python where the work is I/O-bound or sits next to a model and the ecosystem is genuinely further ahead.

Data

MySQLPostgreSQLRedisFlyway

Relational by default, with the schema designed before the first insert and migrations applied forward-only — never edited once they have run anywhere. Redis carries cache, locks and rate limits and is never the system of record. A document store has to win an argument against the first report finance asks for, and it usually loses it.

Mobile

KotlinAndroidReact Native

Native where the device is part of the product: background execution, hardware access, and behaviour that has to hold up on a real network rather than an office one. React Native where a single team maintaining a single codebase is the more honest economics, and the interface — not the platform — is the thing being built.

AI

ClaudeOpenAIRAGEvaluation harnesses

Retrieval grounded in your own data, with sources attached to the answer and a person in the approval path for anything consequential. The model sits behind an interface as a dependency, not an architecture, so switching provider is a configuration change and cost, latency and quality stay measurable rather than assumed.

Infrastructure

AWSCloudflareDockerCI/CDNginx

Every environment is built from the same image and released by pipeline; nothing reaches production from anybody's laptop. Secrets are supplied by the environment and the application refuses to start without them — a credential with a working default is a credential that eventually ships. Cloud accounts are created in your name on day one.

What we do not do

Adopt a framework because it trended this quarter, or add a dependency without a case for it. Every library taken on is a CVE feed, an upgrade path and a maintainer who may lose interest — so one arrives isolated behind an interface, selected by configuration, with the previous behaviour left intact as the default. A stack nobody can hire for in three years is not a technical decision. It is a liability handed to the client, and we are usually the ones still holding it.

If any of this is the argument you have been having internally, it is probably worth a conversation. We are happy to walk through how we would approach your system before anybody signs anything.

Start a project