Four systems in production · sole developer on each
Четыре системы на проде · в каждой один разработчик
Systems that hold money, roles and other people's data.
Системы, в которых деньги, роли и чужие данные.
I take a problem from the first conversation to a running service and stay for the incidents. Backend, interfaces, bots, AI agents, deployment — on each project I was the only developer.
Веду задачу от первого разговора до работающего сервиса и остаюсь на инцидентах. Сервер, интерфейсы, боты, ИИ-агенты, выкладка — в каждом проекте я единственный разработчик.
Ten years in audit before engineering. It shows in what I check twice: the arithmetic of money, who may see which number, and what happens to someone else's data during a migration.
До разработки — десять лет в аудите. Это видно по тому, что я проверяю дважды: арифметику денег, права доступа к суммам и судьбу чужих данных при переносе.
4systems in productionсистемы на проде
10 851automated testsавтотеста написано
10years in auditлет в аудите
0seconds of downtimeсекунд простоя
Readings from production
Показания с прода
01 — 05
Client names, domains and personal data withheld. Sector, scope, decisions and volumes are given as they are.
Названия заказчиков, домены и персональные данные не раскрыты. Отрасль, состав работ, решения и объёмы — как есть.
Three breaches nobody could find — and the shop that stopped falling over
Три взлома, которые никто не мог найти, и магазин, переставший падать
In production
На проде
E-commerce · 2026
A shop on an end-of-life platform, ~13 000 products. Reinfected three times in a month. Two previous investigations found nothing.
Магазин на снятой с поддержки платформе, около 13 000 товаров. Трижды за месяц возвращалось заражение. Два предыдущих разбора ничего не нашли.
- Signature scans were clean and the request log was empty, yet the infection came back after every cleanup. I ran a watcher snapshotting container processes twice a second with the full parent chain — 3 611 hits in a day — and matched launch times against the web server log.
- Сигнатурные проверки давали ноль, в журнале запросов было пусто, а заражение возвращалось после каждой чистки. Поставил наблюдателя, снимавшего процессы контейнера дважды в секунду с полной цепочкой родителей — 3 611 срабатываний за сутки — и сопоставил моменты запуска с журналом веб-сервера.
- The first malicious process had the PHP handler as its parent while the log was clean for that window. So the entry bypassed the web server. A port check confirmed it: the FastCGI service port was listening on all interfaces, reachable from the internet — arbitrary code execution with web-server rights, around nginx.
- У первого вредоносного процесса родителем был обработчик PHP, а в журнале за это окно было чисто. Значит вход шёл мимо веб-сервера. Проверка портов подтвердила: служебный порт FastCGI слушал все интерфейсы и был доступен из интернета — выполнение произвольного кода с правами веб-сервера в обход nginx.
- Closed and pinned with firewall rules wrapped in a system unit so they survive a reboot. Miner and traffic-resale processes removed, fail2ban installed, PHP error logging enabled where there had been none.
- Закрыто и закреплено правилами брандмауэра, оформленными системной службой, чтобы пережить перезагрузку. Майнер и сервисы перепродажи трафика сняты, поставлен fail2ban, включено журналирование ошибок PHP, которого не было вовсе.
- A replacement platform built in parallel. The redirect map first matched 0 of 11 055 live sitemap URLs; after fixing the address scheme, all 11 055. Search positions survive the move.
- Параллельно собрана замена платформы. Карта переадресаций сначала совпадала с 0 из 11 055 адресов живой карты сайта, после исправления схемы — со всеми 11 055. Позиции в поиске переезд переживают.
Server load average
Средняя нагрузка на сервер
beforeбыло
9.70
afterстало
0.27
on 2 cores · 36× lower
на двух ядрах · в 36 раз ниже
Product page response
Ответ товарной страницы
beforeбыло
11.6 s
afterстало
0.5 s
template rebuild on every request removed
убрана пересборка шаблонов на каждый запрос
Crawler response (62% of traffic)
Ответ поисковым роботам (62% трафика)
beforeбыло
8.6 s
afterстало
0.6 s
cache for bots only — carts stay intact
кэш только для роботов — корзина не ломается
Old URLs kept alive
Сохранено старых адресов
first tryсначала
0
fixedпосле
11 055
of 11 055 in the live sitemap
из 11 055 в живой карте сайта
0 sec downtimeсек простоя
12 996 products migratedтоваров перенесено
354 testsтеста
13 tables vs 373 beforeтаблиц против 373
Python · FastAPI · SQLAlchemy · PostgreSQL · Alembic · Pillow · React · TypeScript · Docker Compose · nginx · iptables · fail2ban · PrestaShop / MariaDB on the migration side
Grocery delivery: an office panel and two storefronts inside messengers
Доставка продуктов: панель офиса и два магазина внутри мессенджеров
In production
На проде
Retail · 2026
The service ran on chat messages and spreadsheets. Orders, operators, purchasing, delivery and money had to become one system — without taking the familiar messenger away from the customer.
Служба работала на переписке и таблицах. Заказ, операторов, закупку, доставку и деньги нужно было собрать в одну систему, оставив клиенту привычный вход через мессенджер.
- Two storefronts with identical capability. Telegram on aiogram, and a second messenger with no ready library at all — I wrote the API client from scratch.
- Два бота-магазина с одинаковыми возможностями. Telegram на aiogram и второй мессенджер, где готовой библиотеки нет вообще — клиент к его API написан с нуля.
- Order numbers that finally agreed. The bot showed one number and the panel another for the same order. Day-scoped numbering is now issued atomically under a lock and pinned by a database constraint; 383 historic orders renumbered in one query.
- Номера заказов, которые наконец сошлись. Бот показывал один номер, панель — другой на тот же заказ. Нумерация внутри дня выдаётся атомарно под блокировкой и закреплена ограничением в базе; 383 старых заказа пронумерованы одним запросом.
- Customer money as a ledger. Every balance change writes an entry under a row lock, so there is a history rather than a number. Cash reconciliation on handover is idempotent — pressing twice does not double the sum.
- Деньги клиента — журналом проводок. Правка баланса идёт под блокировкой строки и всегда пишет проводку, поэтому есть история, а не просто число. Сверка наличных при выдаче идемпотентна — повторное нажатие не удваивает сумму.
- Risky features ship switched off. Group purchasing — a wholesale box split between customers by weight — went to the server behind a settings flag, so production stayed untouched until the client decided.
- Опасное выкатывается выключенным. Совместная закупка, где коробка делится между клиентами по весу, выложена за признаком в настройках — прод не тронут до решения заказчика.
648 testsтестов
~600 buyersпокупателей
22 migrationsмиграции
2 messengers, one feature setмессенджера, один набор функций
Python 3.12 · FastAPI · SQLAlchemy async · Alembic · PostgreSQL · Redis · aiogram · custom messenger API client · React 18 · TypeScript · Ant Design · Vite · TanStack Query · Docker Compose · nginx
Cleaning company: office CRM and a field app inside Telegram
Клининг: CRM офиса и приложение исполнителя в Telegram
Delivered
Сдана заказчику
Field services · 2026
One-off consumer jobs and contracted commercial sites lived in spreadsheets and chats. Off-the-shelf systems did not fit the settlements with field staff or the access rules around money.
Разовые заказы и объекты на договоре велись в таблицах и мессенджерах. Коробочные решения не ложились на расчёты с исполнителями и на разграничение доступа к деньгам.
- The field worker's wallet is a ledger. The balance always equals the sum of entries and is never stored as a field. The original bug: the sign came from the button, so handing in cash doubled the debt instead of clearing it. Now the button sets direction, the operation decides the sign, and the resulting balance is shown before confirmation.
- Кошелёк исполнителя — журнал проводок. Баланс всегда равен сумме операций и отдельным полем не хранится. Исходная ошибка: знак брался от кнопки, поэтому сдача наличных удваивала долг вместо погашения. Теперь кнопка задаёт направление, знак определяет сама операция, а перед подтверждением показан будущий баланс.
- Access to money enforced on the server, not in the interface — including the side doors: the calculator returns figures without sums, Excel exports, PDF quotes, the site card, the field app. The "view as role" mode had been swapping only the menu while data still came back under the manager's real role, leaving the sums on screen.
- Доступ к суммам закрыт на сервере, а не в интерфейсе — вместе с обходными путями: калькулятор отдаёт расчёт без сумм, выгрузки в Excel, коммерческие предложения в PDF, карточка объекта, приложение исполнителя. Режим «посмотреть глазами роли» подменял только меню, а данные приходили под настоящей ролью управляющего, и суммы оставались на экране.
- Two years of the client's data migrated. Traps found on live data: sums computed by spreadsheet formulas arrived as 6094.999999999999 and, once rounded, the order stopped recognising itself on a re-run; a crew-lead flag written as a word tagged the whole crew; and two near-identical station names turned out to be different places a kilometre apart.
- Перенесена база заказчика за два года. Ловушки на живых данных: суммы из формул приходили как 6094,999999999999 и после округления заказ переставал узнавать сам себя при повторном прогоне; признак бригадира словом в ячейке метил всю бригаду; два почти одинаковых названия станций оказались разными местами в километре друг от друга.
- Two-way phone system integration. One authenticated entry point takes four event types: call log with recording, the customer card popping up while the phone is still ringing, a routing reply naming the responsible manager, and the call rating. Outbound: dial by click.
- Двусторонняя связь с виртуальной АТС. Одна точка входа с проверкой ключа принимает четыре типа событий: звонок в журнал с записью, всплывание карточки клиента пока телефон ещё звонит, ответ с ответственным менеджером для автоперевода и оценку разговора. Обратно — звонок по клику.
1000+ testsтестов
6 rolesролей
4 acceptance roundsкруга приёмки
~550 customers migratedклиентов перенесено
Python 3.11 · FastAPI · SQLAlchemy 2.0 · Alembic · PostgreSQL 16 · Redis · arq · aiogram · MinIO · PyJWT · reportlab · openpyxl · React · TypeScript · Ant Design · Telegram Mini App · Docker Compose · Caddy
Country estate: an installable app that reads receipts and listens
Загородная резиденция: приложение, которое читает чеки и слушает
In daily use
В ежедневной работе
Household · 2026
The owner and the estate manager tracked expenses, tasks and purchases in chat and on paper. They needed something that opens from a phone icon, works without an app store, and makes sense to a non-technical person.
Владелица и управляющий вели расходы, задачи и закупки в переписке и на бумаге. Нужно было приложение, которое открывается с телефона как иконка, работает без магазина приложений и понятно человеку без технического опыта.
- Push notifications that had never once arrived. Subscriptions were created, the key was served, the send error was silently swallowed — a convincing illusion of a working feature. The private key was stored in one format and the library accepted only another. I wrote a converter for any format, added a self-check button, and caught it end-to-end on a real phone rather than a stub.
- Уведомления, которые не доходили ни разу. Подписки создавались, ключ отдавался, ошибка отправки молча гасилась — убедительная иллюзия рабочей функции. Закрытый ключ хранился в одном формате, а библиотека принимала только другой. Написал приведение ключа любого формата, добавил кнопку самопроверки и поймал это на живом телефоне, а не на заглушке.
- Receipts read by a chain of recognisers. Neural providers first, local Tesseract if they are unreachable, a retry on a straightened copy if nothing legible came back. Recognised line items attach themselves to the pending grocery order on a confident match.
- Чеки разбираются цепочкой распознавателей. Сначала нейросетевые провайдеры, затем локальный Tesseract, если внешние недоступны, и повтор на выправленной копии, если ничего внятного не вышло. Позиции сами привязываются к ожидающему заказу при уверенном совпадении.
- Voice input moved to the server. Browser speech recognition behaved unpredictably on iOS, so audio is transcribed with Whisper server-side and works the same on every phone.
- Голосовой ввод перенесён на сервер. Браузерное распознавание вело себя непредсказуемо на iOS, поэтому запись расшифровывается через Whisper на сервере и работает одинаково на всех телефонах.
70 testsтестов
23 screens verifiedэкрана проверено
3 rolesроли
0 app stores involvedмагазинов приложений
Python 3.12 · FastAPI · PostgreSQL · React · TypeScript · Ant Design · PWA (Workbox, Service Worker) · Web Push (VAPID) · Whisper · Tesseract · multi-provider LLM fallback · Docker Compose · Caddy
An AI engine that audits financial statements
Движок, который проверяет отчётность с ИИ
Flagship · built solo
Флагман · построен один
AI · 2026
Auditors went through accounting exports by hand to find unusual entries. Reviewing one period took days. I spent ten years doing exactly this work, so the methodology here is mine rather than researched — and the system has grown to industrial size.
Аудиторы вручную перелопачивали бухгалтерские выгрузки, чтобы найти нетипичные операции. Проверка одного периода занимала дни. Я десять лет делал ровно эту работу, поэтому методика здесь своя, а не вычитанная, — и система выросла до промышленного размера.
8 779tests collectedтестов собирается
133klines of sourceстрок исходника
173klines of testsстрок тестов
8packages in one workspaceпакетов в пространстве
3 041commits since Aprilкоммита с апреля
More test code than product code. One developer, five months.
Кода тестов больше, чем кода продукта. Один разработчик, пять месяцев.
- A two-phase agent instead of one expensive pass. Discovery runs on a light model to locate suspicious areas; a strong model then works through each in parallel. Model spend dropped by more than half against pushing the whole volume through the strong model.
- Двухфазный агент вместо одного дорогого прохода. Разведка на лёгкой модели находит подозрительные области, затем сильная модель разбирает каждую параллельно. Расходы на модель упали более чем вдвое против разбора всего объёма сильной моделью.
- Fifteen analytical procedures built in — sampling, period comparison, materiality, factor analysis — carrying the audit methodology I used to apply by hand.
- Пятнадцать аналитических процедур внутри — выборка, сравнение периодов, существенность, факторный анализ — с той методикой, которую я раньше применял руками.
- Anonymisation before anything leaves the perimeter, at four levels up to fully air-gapped with no external calls at all.
- Обезличивание перед отправкой в модель — четыре уровня вплоть до полного отказа от внешних вызовов.
- Eight packages in one workspace — core, CLI, HTTP API, web, UI, MCP and two data services — checked by a single command: build, type checking, linting, an OpenAPI contract diff and the full test run across all of them.
- Восемь пакетов в одном рабочем пространстве — ядро, командная строка, HTTP-интерфейс, веб, интерфейсная библиотека, MCP и два сервиса данных — проверяются одной командой: сборка, проверка типов, линтеры, сверка контракта OpenAPI и полный прогон тестов по всем сразу.
- An MCP server written from scratch so external AI assistants can reach audit results: the payload is limited by the perimeter mode, every request is logged, and the log travels with the final document pack. Verified by a live protocol run over stdio, not only by unit tests.
- Написан с нуля MCP-сервер, чтобы внешние ИИ-ассистенты получали доступ к результатам аудита: состав выдачи ограничен режимом контура, каждое обращение пишется в журнал, журнал уезжает вместе с итоговым пакетом документов. Проверено живым прогоном по протоколу через stdio, а не только модульными тестами.
15 audit proceduresпроцедур аудита
4 anonymisation levelsуровня обезличивания
−50%+ model spendрасходов на модель
1 developerразработчик
Python 3.12 · FastAPI · PostgreSQL · pgvector · Claude API · MCP · Typer · openpyxl · pytest · mypy · ruff
Operating rules
Рабочие правила
06
Habits that came out of production, not out of a style guide.
Привычки, которые взялись из прода, а не из руководства по стилю.
R-01
Verified on production
"Deployed" counts only after a request to the running service. I once had a fix committed while the production container still ran the old function.
Проверяю на проде
«Выложено» считается только после запроса к работающему сервису. Ловил у себя случай, когда правка была в коммите, а в контейнере функция оставалась старой.
R-02
Tests on business rules
Wallet arithmetic, role permissions, order numbering, parsing someone else's exports. I do not chase a coverage percentage.
Тесты на бизнес-правила
Расчёты кошелька, права ролей, нумерация заказов, разбор чужих выгрузок. За процентом покрытия не гонюсь.
R-03
Root cause, not symptom
Three breaches were hunted as a file backdoor until a process watcher showed the entry was bypassing the web server entirely.
Разбор до причины
Три взлома искали закладкой в файлах, пока наблюдатель за процессами не показал, что вход шёл мимо веб-сервера.
R-04
Dangerous things ship disabled
Behind a settings flag until the client decides. Production is not touched in the meantime.
Опасное выкатывается выключенным
За признаком в настройках, пока заказчик не решит включить. Прод при этом не трогается.
R-05
A database copy before every migration
And a restore that has actually been rehearsed, not assumed.
Копия базы до миграции
И восстановление, проверенное на практике, а не в теории.
R-06
AI agents with a harness around them
Project instructions, decomposition into steps an agent does reliably, verification loops, cross-checking by models from different vendors. Directing and verifying, not prompting and pasting.
ИИ-агенты с обвязкой вокруг
Инструкции проекта, разбиение на шаги, которые агент делает надёжно, проверочные циклы, перекрёстная проверка моделями разных вендоров. Направлять и проверять, а не спрашивать и вставлять.