Кейс
malin.kiev.ua: сайт городского транспорта, сверенный с десятью принципами
Малин — город в Житомирской области Украины, к северо-западу от Киева. Его табло остановок и расписания автобусов живут на malin.kiev.ua. В октябре 2026 года мы сверили сайт с манифестом, принцип за принципом, и исправили то, что смогли. Эта страница говорит, что сделано, что сделано наполовину и чего мы решили не делать.
С чего всё началось
Рецензент этого манифеста указал на принцип 5: каждая важная система должна корректно деградировать и работать без центральных серверов. Если табло остановок malin.kiev.ua не открывается без сети, сильная фраза на этом сайте оборачивается против его автора. 15 октября 2026 года проверка была честной: ни service worker, ни манифеста приложения, расписание жило только в открытой вкладке. Четыре этапа работы спустя сайт другой — и эта страница последний этап: сказать ровно то, что верно сейчас.
Десять принципов, честно
Статусы: сделано — проверено на боевом сайте и имеет адрес, который можно открыть; частично — код есть, но известные границы остаются; не касается — принцип об аппаратуре.
| Принцип | Статус | На чём держится |
|---|---|---|
| 1. Цифровая собственность — настоящая собственность | частично | Установленное приложение и сохранённое расписание работают, когда наш сервер лежит, — и когда перестаёт отвечать сам домен: табло, расписания и схема открываются из собственного кеша браузера (проверено с остановленным хостом: smoke, «Без домену»); всю базу можно забрать как GTFS или JSON. Границы честные: только в том браузере, где приложение установили, пока он хранит кеш, без обновлений и без живых данных. Домен и хостинг по-прежнему наши. |
| 2. Устройства должны работать без разрешения | не касается | Аппаратуры нет. Раздел транспорта не требует аккаунта и устанавливается из браузера без магазина приложений: собственный баннер Chrome на Android, карточка с тремя шагами на iPhone. |
| 3. ПО — это инфраструктура | сделано | Открытые стандарты насквозь: GTFS Static (валидатор MobilityData: 0 ошибок), OpenAPI 3.1 со страницей справки, RSS, iCalendar для каждого маршрута (пример), HTTP-кеш с ETag. |
| 4. Данные принадлежат человеку | сделано | Расписание — да: GTFS и JSON под указанной лицензией, с журналом ревизий каждого изменения. Собственные данные человека — тоже: на странице профиля (вход через Telegram) одна кнопка скачивает всё, что сайт и бот знают о вас, одним JSON-файлом, другая удаляет аккаунт (имя, телефон и Telegram стираются сразу, будущие бронирования и объявления снимаются, прошлые поездки остаются без привязки к вам); то же в боте — /mydata и /deleteme. Сохранённые остановки планировщика живут только в браузере, их можно сохранить в файл, восстановить или стереть без сервера. Что хранится и сколько — в политике конфиденциальности. |
| 5. Сначала офлайн | сделано для /transport | Табло остановок, расписания маршрутов, схема и планировщик /transport открываются без сети и без бэкенда после первого посещения; то, что человек отмечает на табло (факт прибытия, сканирование наклеек), ждёт в очереди и отправляется, когда сеть вернётся. Карта, междугороднее табло и бронирование требуют сети — см. ниже. |
| 6. Совместимость — право | сделано | Фид подан в Transitland и Mobility Database (на рассмотрении); id остановок и маршрутов — опубликованный контракт, объединённая остановка отвечает 301 (карта перенаправлений). |
| 7. Прозрачность вместо доверия | сделано | Код сайта, бэкенда и бота открыт, каждое решение записано (см. «Документация по-честному»), каждое изменение данных оставляет строку в журнале. Бандл подписан subresource integrity (в исходнике страницы — integrity="sha384-…"), а /about#build показывает хеш загруженной сборки, BUILD_ID service worker и ревизию сохранённых данных — те же значения каждый прочитает через curl -s https://malin.kiev.ua/transport-sw.js | grep BUILD_ID и на /transport/revisions. |
| 8. Ремонт, изменение, форк | частично | Мы не запрещаем: репозиторий и данные открыты под OwnNet Source License 1.1, которая разрешает ремонт, изменения и форк на тех же условиях, репозиторий полный. Мы не упрощаем и не призываем — инструкции «поднять свою копию» нет и не планируется; это решение, а не пробел, который мы собираемся закрыть. |
| 9. Протоколы важнее платформ | сделано для /transport | Данные уходят с сайта через GTFS, RSS, iCal и обычный HTTP — чтобы ими пользоваться, не нужны ни наше приложение, ни наш бот; Telegram-бот — один канал из нескольких. Доска попутчиков по-прежнему берёт источник из групп Viber и Telegram, но публикует в открытом вебе. |
| 10. Цифровой суверенитет — гражданский суверенитет | частично | Базу собрали жители, а не купили у платформы; город может бесплатно взять фид и разместить у себя. Хостинг арендован (Railway, Cloudflare): данные и код переносимы, инфраструктура не наша. |
Откуда данные
Структурированного расписания для Малина не было. Автор сайта стоял на остановке «Прожектор» и не знал, какие автобусы здесь останавливаются и когда придёт следующий. То, что было открыто, — названия остановок, примерный перечень маршрутов, сфотографированные «листочки» горсовета — собрали вручную, структурировали и проверили на месте; QR-наклейки на остановках теперь ведут на табло этой остановки. Историю рассказала местная газета: «Стоял на остановке и не знал, какой автобус ждать» (malyn.media, 7 октября 2026) и её продолжение (14 октября 2026).
Эта собранная руками база — причина, почему лицензия OwnNet, а не Creative Commons: бесплатно для людей, обучения, города и малого бизнеса; производные базы остаются на тех же условиях; корпорациям и платным приложениям — по договору. Цена известна и принята: Google Maps и Apple Maps не берут фиды с такими условиями. Сами факты — что автобус №5 отправляется в 7:10 — не принадлежат никому; лицензия касается собранной базы, а не фактов.
Что работает без сети, а что нет
Без интернета и без нашего сервера (после одного посещения онлайн, во вкладке браузера или в установленном приложении «Як доїхати» — «Как доехать»): табло остановки, расписание маршрута, планировщик между остановками, схема маршрутов. Полоса состояния говорит правду — «Без сети — расписание по состоянию на …» или «Сервер не отвечает — показываем сохранённое расписание»; когда сеть возвращается, данные обновляются сами, а новая версия сайта никогда не ломает открытую страницу.
Требует сети: подложка карты (тайлы OpenStreetMap не кешируем — их политика использования; карту заменяет схема), междугороднее табло (попутки, маршрутки, электрички — живые данные), бронирование, справка, админка и самое первое открытие. «Расписание» — плановая таблица по состоянию на последнюю синхронизацию, а не живое положение автобусов: его на сайте и не было.
Чего мы сознательно не сделали
- Карта офлайн: тайлы OpenStreetMap не кешируем по их политике; эту работу делает схема маршрутов.
- Google Maps и Apple Maps: им нужна лицензия без share-alike; для собранной руками базы мы оставили условия OwnNet.
- GTFS-Realtime: данных о том, где автобусы, нет — только отметки пассажиров «автобус здесь» на сайте.
- Междугородние рейсы в GTFS: фид покрывает только городские автобусы.
- Инструкция «поднять свою копию»: лицензия не запрещает форк и ремонт, репозиторий открыт — этого достаточно; мы не упрощаем и не призываем.
Документация по-честному
Каждый этап вёлся из «файла восстановления» в репозитории: проблема как есть, решения с причинами, чеклист и находки — включая то, что пошло не так. Это и есть документация кейса: табло без сети и приложение, записи без сети, открытые данные и решение о лицензии, открытые протоколы, журнал ревизий, проверяемая сборка, эта страница. Для тех, кому нужны данные: transport-open-data.md.