Process: from your first description to handover
2 items
How we work and what you get along the way
Six moments in time, each with a file or an address you physically receive. Below them, six decisions from our own systems — for the technical person on your side who wants to know how we make choices.
What you get and when
Six moments, each with a file or an address you get. No timeline graphic: a list you can check off.
01026 moments · a list to check off
Your description
Day 0
What you doyou describe the project
What we dowe reply within 2 working days
What you getquestions, or a proposed scope and timing
an e-mail replyAnalysis
Week 1–2
What you dotwo online meetings, two hours each
What we doscreen drawings, the list of data, a list of risks
What you geta fixed build price in the analysis document
the analysis documentStart
Week 3
What you doyou pick a channel: e-mail, Teams or Slack
What we doan account with the code, in your name
What you geta test version at your own web address
the test version addressDemo
Every week
What you doa 30-minute demo every week
What we dowe show the working version
What you geta list of changes in plain words
a list of changesAcceptance
Acceptance
What you doyou accept a trial version standing next to the live one
What we doa copy before, a check of the main screens after
What you getwritten instructions and every password
the written launch instructionsAftercare
After acceptance
What you doyou choose: a monthly fee or a handover
What we do30 days of free fixes included
What you getmaintenance or the full set of materials
the full set of materials
Six decisions from our own systems
Each one cost us time in zaflache or in FufuTool, before it could cost a client. The heading says what it means for you; the technical details are below for anyone who wants to check.
02026 decisions · zaflache and FufuTool
A network outage mid-operation doesn't cost you money
FufuTool · background jobs and publishing
In FufuTool, publishing a listing to Allegro passes through three independent safeguards against a duplicate listing, because on Allegro a duplicate is a paid listing and a breach of the terms of service.
- What we did
- three independent safeguards
- Why
- on Allegro a duplicate is a paid listing
Without this decision
A dropped connection → the program retries → a second, paid listing appears on Allegro and you learn about it from the invoice.
With this decision
Three independent layers: a fingerprint held in the database, the same fingerprint passed to the portal, and a question to the portal before every retry. One listing is created.
- Context
- The program sends the listing to Allegro, the portal creates it, but the answer is lost in a dropped connection. The program sees an error and tries again. With no safeguard a second, paid listing appears, and the user finds out from the invoice. Dropped connections when talking to someone else's system are the norm, not the exception.
- Decision
- Layer 1: every job in the waiting list carries a fingerprint made of the item, the listing version and the operation, and the database refuses a second job with the same fingerprint; a double click, or two browser tabs racing each other, produce the same fingerprint and nothing happens twice. Layer 2: where the portal accepts such a fingerprint (zaflache remembers one for 24 h), we pass it along; on Allegro the listing number we save before every retry does the same work. Layer 3: before we call the portal we write the attempt down with the result "unknown", and after a dropped connection we first ask the portal whether the listing was created, and only then try again.
- What we did not do
- We do not question the portal after every error: an error the portal actually answers with means the listing was not created, so we go asking only when no answer came at all. We do not retry forever either: how many attempts there are, and how long we wait between them, depends on the kind of error, and a job that runs out of attempts lands in the log with a reason.
We don't sell AI where measurement says it doesn't work
zaflache · demand–supply matching
Before we switched on the matching of "looking for" listings with "selling" listings in zaflache, we measured how good the matches were on a real catalogue; the result decided that the model does not make the decision, it only sorts the list.
- What we did
- the model ranks the list, doesn't decide
- Why
- no cut-off line separated hits from misses
Without this decision
A demonstration of "smart matching" looks good; in the live version the model confirms 6 out of 6 wrong pairs and pushes irrelevant listings at people.
With this decision
The model only sorts the candidates; category, price, location and a list of brand names decide the match. A test on your own data before we switch it on.
- Context
- Comparing listings by how close their meanings looked to a program put a wrong match above a right one: a Bosch washing machine and a tool set scored 0.606, while a "car up to 10k zł" and a Hyundai for 5k zł scored 0.543. No cut-off line separates those two cases.
- Decision
- We took meaning-matching out of the first version of the feature. In the second, that score only sorts the candidates, and the match itself is decided by fixed rules: category, price, location and a list of brand names, all checked before the model is asked anything. The model then judges one candidate per question, because in our test judging six at once confirmed 6 of 6, and one at a time confirmed 0 of 5.
- What we did not do
- We did not ship "smart matching" on closeness of meaning alone, even though the demonstration looked good. We do not hide this measurement from clients either: when we propose AI in your project, we first show this kind of test on your own data, with numbers.
Billing data doesn't leak through a stray field
zaflache and FufuTool · database permissions
In both of our systems, who sees what and who can change it is decided by the database itself, not the application code, so a bug in one form doesn't open up someone else's data or someone else's balance.
- What we did
- the database decides access, not the code
- Why
- a form bug doesn't open someone else's data
Without this decision
A bug in the form, or in the code behind it, exposes someone else's data, because the only guard was the screen.
With this decision
The database itself guards permissions, column by column; after every change to its layout a permission review whose result has to be empty.
- Context
- Take a table with a column that decides permissions or money (plan, role, status, limit), and a user allowed to write to the whole row. That user can simply set the value for themselves: the rule that checks whether the row belongs to them says nothing about which columns they changed.
- Decision
- Every table carries a rule saying which rows a given role may touch, and only roles with such a rule get any access at all. We grant write access to named columns, never to a whole table; a plan, a role or a balance can be changed only by a function running on the server. After every change to the database layout we compare the permissions in the live version against the ones we wrote down; the difference has to be empty.
- What we did not do
- We do not rely on checks made on screen or in the code behind it; those make a system pleasant to use, they do not protect it. We do not use a master key to read someone's account on their behalf: the AI assistant in zaflache reads a balance with that person's own permissions and nothing wider.
A release has a way back before it starts
FufuTool · releases and backups
Every FufuTool release starts with a copy of the database; the new version starts up next to the running one and takes over visitors only after a check. We proved the copies work by loading one back on 29 August 2026.
- What we did
- copy, new version beside the live one, check, go
- Why
- a copy nobody loaded back is not a backup
Without this decision
The copy exists, but nobody has ever loaded it back; during an outage it turns out to be incomplete or unreadable.
With this decision
A copy before every release, the new version next to the live one, a check of the main screens, and a trial load compared with the live data row by row.
- Context
- A backup nobody has ever loaded back is not a backup. FufuTool copies the whole database every day at 3:15, keeps seven days of copies on the server and one more copy away from it; until 29 August 2026 nobody had checked whether such a copy could actually be loaded back.
- Decision
- The order of every release: copy the database and the website before the change, start the new version beside the live one, check on it that the main screens open, and only then send visitors there. We loaded the copy into a separate database and counted its rows against the live version, row for row, in the tables holding accounts, listings, publication targets and portal connections.
- What we did not do
- We do not keep the copies together with the code: they hold personal data and the keys to the portals. We do not yet have a copy in a third place, away from both the server and a co-founder's computer; that is planned, and we say so plainly.
One less part to look after
FufuTool · background jobs
FufuTool's background jobs (publishing, refreshing portal connections, retries) wait in the same database as the rest of the system, so there is no separate job server to watch, update and back up.
- What we did
- the waiting list lives in the same database
- Why
- a separate server means its own outages
Without this decision
A separate job server: its own backups, its own outages and one more thing to watch at night.
With this decision
The waiting list lives in the same database, target and job written together in one go; the rest of the system sees three functions: schedule, take, finish.
- Context
- The usual answer to "we need a job queue" is a separate server built for it (Redis, RabbitMQ). For a system with one background worker that is one more part with its own watching, its own backups and its own failures, and the load here runs to thousands of jobs a day, not millions.
- Decision
- The waiting list is a table in Postgres. A background worker takes the next job by locking its row and skipping rows already locked, taking users in turn; a failed job waits longer and longer before the next attempt, depending on the kind of error; a job running on a timer clears locks left behind by a crash; and every change of a job's state lands in a log. One single write puts the publication target and the job into the database together, so no job exists without a target and no target without a job.
- What we did not do
- We did not add a separate job server "just in case". When the number of jobs outgrows what one table can carry, the change will touch one separate part of the code, because the rest of the system only ever sees three functions: schedule, take, finish.
Law written into the code, not into a PDF
zaflache and FufuTool · legal compliance
In zaflache, reporting content, justifying a decision and appealing it are functions of the system, in line with the Digital Services Act (DSA), and FufuTool generates the price report in the dane.gov.pl format that the law requires of property developers.
- What we did
- reporting and appeals as system functions
- Why
- a legal duty is a button or a function
Without this decision
Legal compliance locked in a PDF nobody reads; reports and appeals handled by hand in the database.
With this decision
Reporting, the reasoned decision and the appeal are features of the system; content written with AI carries a permanent label.
- Context
- The DSA requires platforms to accept content reports (Art. 16), to justify decisions with notice of the right to appeal (Art. 17), and to run an internal appeals system (Art. 20); the AI Act requires labelling content prepared with the help of AI (Art. 50). The Polish law on transparency of housing prices (Art. 19a–19b) requires property developers to publish a price list every day in the dane.gov.pl harvester format.
- Decision
- In zaflache anyone can report content, signed in or not; a moderation decision goes into a table that can only be added to, together with its reason and a note about the right to appeal; an appeal lifts the restriction without anyone editing the database by hand; and whoever reported the content is told the outcome. Listings written with the help of the AI assistant carry a permanent label. In FufuTool the part built for companies selling flats keeps the history of unit prices and publishes, at a fixed address, an index file and a daily price list in exactly the shape dane.gov.pl expects.
- What we did not do
- We did not bury compliance in a PDF nobody reads: where the law requires an action, that action is a button or a function. And we never judge the text that arrived with the request; we read it back from the database by its number, so nobody can get their own listing approved by swapping the text on the way in.