Short write‑ups of problems we hit running a real commerce operation and what we changed in the code because of them. No product announcements; just the reasoning.
Almost every piece of paper the business touches is a PDF we generated — invoices, delivery notes, picking sheets, pallet returns, timesheets, barcode labels in five widths, carrier labels from three APIs. How one rule decides between mpdf and headless Chrome, how a single invoice template serves shops in different countries, languages and tax regimes without forking, and the gotchas: a bakery called "& Fils" that broke shipping, labels that must scale from 55 mm to 125 mm, and why we merge PDFs with a renderer that never renders them.
Aurora has no framework, because in 2008 there was nothing worth adopting — so it grew its own. A 5,864-line router, an active-record ORM with a built-in audit trail, a process-forking job runner years before queues were a composer require, translations in sixteen languages with a homemade string extractor, and a Content-Security-Policy assembled by string concatenation. A tour of what one developer hand-rolls in fourteen years, what each piece is called today, and why the answer to "was it bad?" is more interesting than yes or no.
We did not embed the business into a vector store and hope the right chunk came back. We gave the model forty‑five tools — the same actions the staff app runs, under the same permissions — and a guide on how to use them. The result is a report that used to take three people a day, or a ticket to engineering, arriving in thirty seconds with real figures. The reactions, in order: disbelief, checking whether the numbers were random, and then a very quiet "oh".
In 2022 we picked Laravel because it was the easiest way to find programmers for a system that would take years to build. In 2026 we would pick it again, for a reason that did not exist then — the models know it better than any hire ever would. What the choice bought us, what it cost, and what "is Laravel still worth it" actually asks.
A million invoices and fifty thousand credit notes, and the rule that governs all of them: an invoice is a frozen picture of an order at the moment it was billed, and nothing that happens later — a price change, a tax change, a product rename — can move it. How invoices are minted from orders, how credit notes reference the lines they return, how a paid status is derived rather than set, how invoices are categorised for the accountants, and how a PDF, an e‑invoice and an export all read from the same rows.
Four years, 1,659 migrations, 793 tables, twenty thousand columns, five thousand indexes. How the aiku schema got this big without getting lost — the group/organisation/shop spine on every row, a stats table beside every entity, slugs as public identity, jsonb where the business is still deciding, and the bridge columns that let us migrate off a twenty‑year‑old system one table at a time.
We did not rewrite the twenty‑year‑old system and flip a switch. We built the new one beside it, pulled the data across entity by entity with 106 fetchers, ran both for years, and moved one company at a time — 588,000 customers, 1.08 million invoices, eventually 88 million rows of history. The "source id" on every row, the fetchers that could not be allowed to overwrite what people had edited in the new system, the guard that froze the catalogue, the import of two decades of audit trail, and the rule that a closed shop must rest in peace.
Every count, total and trend in aiku is precomputed — by ~650 small "hydrator" jobs that run at the point of change and a nightly sweep that catches what they missed, into ~150 stats tables and 35 partitioned time series at five frequencies. Why we chose that over computing on read, the bug where a single‑day redo flattened a whole month, and the index traps in partitioned tables.
The same candle is one thing to the buyer, a box of twelve to the warehouse, and a litre of base to the factory. aiku models that with three corners — the trade unit (the physical thing), the stock (that thing, in this warehouse), the product (that thing, as sold in this shop) — and a number on every edge. Why a fraction is sometimes right and sometimes a bug, and why changing a number on an edge while an order is in flight is the most dangerous edit in the system.
The group catalogue — 62,000 master products feeding 317,000 shop products across 34 open shops — is where a thing is described once and pushed to every shop that sells it, so that in theory one person at one desk can run the lot. Majors and minors for multi‑currency prices, a "follow the master" switch at three levels, the scalar column we are retiring because it silently mixed currencies, and a price cascade that ran for a month multiplying RRPs by pack size before a repair put 1,635 values back.
A card payment arrives three ways at once — the browser says "done", the redirect says "done", and a webhook says "captured" — and only one of them is money. The payments layer we built this summer: server‑verified status, one processing path, locked idempotency, a recoverable state machine, a gateway log for every event, a sweeper, and a playbook that made the next provider a week's work.
Staff access in aiku is granted by job position — "dispatch clerk in the Spanish warehouse", "customer‑service supervisor for the UK shop" — not by ticking permissions. Positions expand to roles, roles to permissions, all scoped to the group, organisation, shop or warehouse they name. Why we chose that, how a permission is checked in the same place for the UI, the API and the MCP server, the "team id" that makes it all silently false if forgotten, and the things we refuse to do with an admin flag.
A trade buyer reordering forty lines, a dropshipper running a shop on someone else's platform, and a brand whose pallets we store — all log into the same customer portal, built on the same code as the staff app, living on the storefront's own domain. What each sees, what they share, why the dropshipper's order is the most complicated object in the system, and the two things the portal refuses to let a customer do.
The search box in the staff app finds a customer, an order, a pallet, a mailshot, an employee, a chat message — forty‑two model types — scoped to what you may see, typo‑tolerant, in one keystroke. How it runs on Typesense next to the application, why three places must change in lockstep when a new thing becomes searchable, the reindex command that rebuilds a section while people keep working, and the afternoon the search engine's raft log got stuck.