Invoicing software for French freelancers and small businesses: quotes, invoices, credit notes, reminders. The challenge fitted on one line — produce an invoice from a sentence written in French, without a text invented by an artificial intelligence becoming a legally false document.
Sector: software publishing
Period: 2023
Role: front-end, and a contribution to the AI-assisted feature
Status: team product, engagement completed
The context
An invoice is not a screen, it is an accounting record. It carries a number that must run without gaps, a VAT rate that commits you, a company identity that must match a public register, and once issued it is not corrected: it is cancelled by a credit note. These are old, well-known rules, and most of the work of invoicing software consists of preventing its users from breaching them unknowingly. The product issues its invoices in the Factur-X format, BASIC profile: a PDF the recipient’s accounting software reads directly.
The requested feature was appealing and at exactly the right level of danger: write a sentence such as “invoice three days of consulting to Martin SAS” and get the invoice. A language model can produce that text. It can also produce, with the same assurance and in the same second, a VAT rate that does not exist, an invented legal form, or a company number belonging to somebody else. Nothing in the shape of its answer distinguishes the two cases — and that is the whole difficulty: the error does not look like an error, it looks like an invoice.
The question was never the quality of the model. It was where the boundary runs between what an artificial intelligence proposes and what the software records, and how to make that boundary verifiable by machine on every write.
The scope, stated precisely
Dalya is a team product. The repository has eight contributors; I signed roughly one commit in three, from February to November 2023. The architecture this page describes — the sealing between client companies, the modelling of invoice states — was in place before I arrived and belongs to those who set it. What I carried there: the front-end, the format constraint imposed on the language model, and a test that audits what that model returns.
This page therefore describes a system I served, rather than a system I decided. It is here because being able to read a rigorous architecture, explain it to a non-technical audience and say where it stops is exactly the work you buy in an audit — and because a supplier who claims other people’s decisions gets contradicted on the first reference call.
The rules that hold this system
What the AI produces is not business data, it is a proposal.
Its answer is checked against the rules of the trade — units, VAT rates, legal forms. If it fails, the error is sent back to it for a second attempt, which suffices in most cases. The client company’s identity is verified again in the official company register before any write: the source of truth on whether a company exists is the register, not the model.
Finally, nothing is recorded until a person has clicked — and the proposal then takes exactly the same path as keyboard entry. The same write door, the same checks, in the same order.
This is the point that decides all the rest. When an AI-assisted feature is granted a write path of its own, shorter because it is simpler to build, it thereby bypasses every check accumulated on the normal path over years — and nobody notices, since the result looks correct. Sending the proposal through the common door takes work at build time. It is that work which guarantees that a rule added next year will apply too, without anyone thinking about it, to everything the AI produces.
Sealing between client companies does not rest on the developer’s memory.
Invoicing software hosts all its clients in a shared database. The leak, in this kind of product, almost never comes from an intrusion: it comes from a filter forgotten in a query, one day when someone was adding a statistics screen.
The filtering is therefore entrusted to the database engine itself: thirty-six rules across twenty-two tables, applied on every read, whatever the question asked and whatever piece of code asks it. Forgetting no longer has an effect.
The difference between the two approaches is one of kind, not of degree. In the first, confidentiality depends on the vigilance of whoever writes the next query — including when that party is an AI assistant that has never read your internal conventions and produces plausible code in quantity. In the second, the question no longer arises: the database does not know how to answer anything else.
Draft, validated and paid are not three values of one flag, but three different objects.
The usual way is to put a status on the invoice. It has a discreet flaw: nothing prevents writing an operation that makes no sense for that status — collecting payment on a draft, modifying an already numbered invoice. The guardrail then exists as a condition, somewhere, that can be forgotten in the copying.
By treating each state as a distinct object, the absurd operation can no longer be written: the software refuses to build. The error is no longer discovered at run time, at the client’s, by a user calling support; it is refused on the developer’s machine, before it exists. The numbering counter, for its part, can be neither created nor deleted from the application.
An error a program refuses to let you write never leaves the machine where it was typed. The same error discovered by the client’s accountant costs a corrective invoice, an explanation, and a notch in the trust placed in the software.
What is not locked
Validating an invoice and incrementing the numbering counter form an application-level sequence: the software performs the two operations one after the other. This is not a transaction guaranteed by the database, meaning an all-or-nothing the machine stands behind. In an edge case — an interruption between the two operations — a discrepancy is possible. I have not observed it, and I do not write it to cover myself. I write it because an invoicing system claiming to have no zone of this kind would be lying, and because knowing precisely where they are is exactly what you buy in an audit.
THE GUIDING PRINCIPLE
An artificial intelligence is not a trusted colleague, it is a source. Everything coming out of it is checked against the rules of the trade before committing anyone, and waits for a person’s decision in order to exist.
The stack
TypeScript, Next.js, React, Effect-TS and fp-ts, PostgreSQL (self-hosted Supabase), Prisma, Puppeteer and pdf-lib, Factur-X BASIC profile, Stripe, Jest, Cypress, GitHub Actions, Docker.
What the client says
« Quentin adapted to the architecture very quickly and shipped features from the second day. […] Full of initiative, a good communicator. »
— Valentin Rabot, NEXAS
L’ÉTAPE SUIVANTE
If you are in this situation — an AI-assisted feature already in service, or about to be, and no certainty about what it can write into your data — the three-day audit establishes what can today be breached with nothing to signal it.
AI-generated codebase audit: what holds, what has to be redone — 3 days, €2,400 excl. VAT
We use cookies solely for the site to work properly. With your agreement, we also enable the Calendly scheduling module, which sets its own cookies. You can change your mind at any time from the footer. Learn more