- Клиент
- ООО «Балтик Транзит» раскрыто 11:04 · причина: звонок клиента
- Контактное лицо
- Кузнецова А. В. автоскрытие через 2:00
- Направление
- Таможенное оформление · импорт
- Операц. дело
- case_ref C-2026-0311
- Декларант
- поля нет в ответе API
Keel — веб-CRM для внешнеэкономической деятельности, в которой разграничение доступа держат сервер и физически раздельные базы, а не скрытые поля на экране. Если данные сотруднику не положены — поля не существует ни в интерфейсе, ни в ответе API, ни в поиске.
Одна и та же работа, две аудитории. Псевдоним контрагента — HMAC(ключ аудитории, субъект): у менеджера и у декларанта он разный для одного и того же клиента, поэтому сопоставить записи между аудиториями нельзя даже при полном доступе к операционной базе.
Почему типовая CRM не годится там, где заявку ведёт один человек, а оформление делает другой.
Каждая из этих ситуаций — не пробел в настройках, а следствие того, что разграничение доступа сделано на уровне интерфейса поверх общей базы, где все данные лежат рядом.
Поле спрятано настройкой формы, а эндпоинт отдаёт карточку целиком. Проверяется одним запросом мимо интерфейса.
→ В Keel ответ собирает сервер по правам вызывающего: состояния «поле есть, но заблокировано» в продукте нет.
Список контрагентов с телефонами выгружается одной кнопкой, и следа об этом не остаётся.
→ Право «знать клиента» (client.read) выдаётся отдельно от права вести заявку, а каждое раскрытие ПД попадает в append-only журнал.
Исполнителю нужны груз, коды и сроки — но вместе с заявкой ему достаются реквизиты и контакты. Это готовый мост, по которому сделка уходит мимо компании.
→ Операционное дело живёт в отдельном сервисе и отдельной базе, где нет ни одной колонки о клиенте и заявке.
На запрос «кто открывал данные этого контрагента 14 марта» система отвечает логами приложения — в которых те же самые ПД и лежат.
→ Раскрытие — отдельная операция с причиной и лимитом частоты; в операционной базе, логах и текстах ошибок персональных данных нет, и это проверяет CI.
Клиент пишет в Telegram и WhatsApp, отвечает конкретный менеджер со своего аккаунта. У компании этой истории нет — она есть у сотрудника.
→ Канальный контур принимает диалоги на учётные записи компании, привязывает их к заявке и псевдонимизирует контакт до записи в операционную базу.
CRM не знает про сроки задач, таск-трекер не знает про SLA заявки, документы лежат в облачном диске без версий и прав.
→ Заявка, дело, задачи, Гант, учёт времени, риски и документы с экспортом в DOCX и PDF — один контур с единой моделью прав.
Четыре правила продукта. Их нарушение считается дефектом, а не замечанием по стилю.
Формулировка «мы заботимся о данных» ничего не стоит без ответа на вопрос, чем именно она обеспечена и как это проверить снаружи. Ниже — обе половины ответа.
Кому продукт нужен, в каких ролях он живёт и чего в нём сознательно нет.
Таможенные представители, брокеры, экспедиторы, отделы ВЭД производственных компаний — от десятка до двух сотен сотрудников. Особенно там, где декларанты работают как подрядчики или в отдельном юрлице.
Знание клиента — отдельное право, а не побочный эффект доступа к заявке. Уход менеджера или смена подрядчика-декларанта не уносит контакты.
Ролевая матрица снимается запросами к живому стенду, а не выводится из чтения кода. Журнал аудита читают только администратор и офицер ИБ.
Кокпит с плитками проекта, просроченным и «горящим», ближайшей вехой и максимальным открытым риском; SLA заявки с эскалациями.
Заявка, дело, доска задач, Гант, учёт времени, документы с блочным редактором и экспортом, диалоги с клиентом и внутренний чат — в одном окне.
| Роль | Знает клиента | Персональные данные | Журнал аудита |
|---|---|---|---|
| Менеджер | да · client.read | раскрытие по причине · pii.reveal | нет |
| Декларант | нет — только c_2026_004417 | не существуют | нет |
| Финансист | да · client.read | видны постоянно · pii.view | нет |
| Офицер ИБ | нет | не существуют | да |
| Администратор | да · client.read | по настройке роли | да |
Чего в первом релизе нет намеренно. Мультитенантного SaaS — поставка идёт изолированной инсталляцией на заказчика, on-prem или в его облаке. Мобильного приложения — только адаптивная вёрстка. Публичного портала клиента. Каждый пункт отложен решением, а не забыт.
Ключевые решения и то, что было отвергнуто. Нумерация — из журнала решений проекта.
Тридцать шесть зафиксированных решений и двадцать ADR. Ниже — те, что видно снаружи: они определяют, что продукт может пообещать, а чего не станет обещать никогда.
Собственный OIDC-провайдер и шлюз. Сторонние identity-провайдеры и внешние авторизационные движки не используются: контур входа целиком свой, обновляется вместе с продуктом.
Подпись токенов — EdDSA (Ed25519). Многоключевой JWKS с выбором ключа по отпечатку.
× Отвергнут RS256: вместе с ним из графа зависимостей полностью убран криптографический крейт с открытыми уязвимостями — аудит зависимостей стал зелёным.
Сессия входа — строка в базе, а не подписанная кука. Секрет в httpOnly-куке, в базе — его SHA-256. Отзыв сессии срабатывает за миллисекунды.
× Отвергнута кука без состояния: её нельзя погасить до истечения срока, а список отозванных всё равно пришлось бы хранить в базе — два источника правды вместо одного.
Срок сессии: 12 часов абсолютный, 2 часа простоя. Скользит только простой; рабочий день — естественная единица, два часа отделяют обед от ухода домой.
× Отвергнут скользящий абсолютный срок: сессия, продлевающая сама себя, не истекает никогда.
Перезагрузка страницы не разлогинивает, но и не воскрешает права. Сессия хранит личность, не полномочия: после тихого входа права пересчитываются из базы на момент вызова, а каждый запрос перепроверяется отдельно.
Операционное дело — отдельный сервис с отдельной базой. CRM-ядро хранит только ссылку на дело, а база дел не хранит о клиенте и заявке ничего. Разграничение обеспечено физическим разделением, а не аккуратностью кода.
Аудитория псевдонима выводится из ролей вызывающего, а не приходит параметром. Попросить псевдоним чужой аудитории нельзя. Чужой и несуществующий псевдоним неотличимы снаружи.
Сквозного поиска нет. Каждый сервис ищет только в своих данных, результаты склеиваются на экране раздельными группами по источникам.
× Отвергнут поисковый агрегатор: сервис, видящий по одному запросу и заявку, и дело, и документ, — корреляционный оракул, отменяющий всё разделение хранения.
Комната внутреннего чата ни к чему не привязана. В её схеме нет колонок о клиенте, заявке, деле, задаче или документе: чат менеджера с декларантом существует, но вне комнаты они друг друга не видят.
× Отвергнута привязка комнаты к объекту: общий контекст стал бы мостом между аудиториями.
Содержимое документа — контент-адресуемый объект в S3, не поле в базе. Ссылки со сроком жизни к хранилищу не выдаются никогда: байты идут потоком через сервис, и право проверяется на каждый запрос, а не однажды при выдаче ссылки.
Канальный контур написан с нуля. Telegram, электронная почта и WhatsApp подключаются адаптерами по единому контракту: модель сообщения, политика вложений и матрица возможностей канала.
Тела внешних сообщений — третий класс хранилища. Отдельный инстанс с режимом защиты как у vault: входящее сообщение клиента считается персональными данными всегда.
× Отвергнуто хранение переписки в vault: сервис личности стал бы основным хранилищем переписки компании.
Привязка диалога к заявке — только однозначная. При сомнении система отказывается связывать и просит человека, а не угадывает. Возможности, которых у канала нет, не показываются кнопкой, которая всё равно не сработает.
На исчерпание лимита ожидающих запросов отвечает 503, а не пустая страница. Пустой ответ клиент воспринял бы как «время вышло» и повторил бы запрос немедленно — под нагрузкой это превратило бы каждого ждущего в плотный цикл ровно тогда, когда нагрузку надо снижать.
Расписание Ганта считает сервер. Перетаскивание задачи — предварительный расчёт без записи, применение — с проверкой ревизии плана; календарной арифметики в браузере нет, поэтому два человека не разъедутся в разные версии графика.
Экспорт DOCX и PDF — на Rust, внутри сервиса документов. Без внешних конвертеров и офисных пакетов на сервере; шрифты — только со свободной лицензией.
Контракты API после первой вехи меняются только аддитивно. Шестнадцать спецификаций в репозитории: обновление инсталляции не ломает интеграции заказчика.
В приёмку каждого релиза входит состязательная проверка «вытащи персональные данные» — поиск, автодополнение, ленты, уведомления, экспорт, каждый эндпоинт. Зелёные тесты и одобренное ревью доказательством не считаются.
Поставка — изолированная инсталляция. Собственный контур заказчика, свои ключи и бэкапы. Режим с несколькими арендаторами, если появится, будет инстансом на арендатора — признак арендатора в модель данных не закладывается.
Продукт в активной разработке. Ниже — фактические цифры, а не планы.
Разработка идёт волнами: каждая заканчивается приёмкой в собранном стенде — сценарии проходят кликами под всеми ролями, а не отметкой в задаче.
Волны 1–10 приняты с отчётами о приёмке в репозитории. Текущая работа — внешние диалоги с клиентами: очередь доставки, честные статусы отправки, псевдонимизация входящего контакта. Дальше — установка, обновление, мониторинг и документация к Релизу 1.
Клиент и контрагент с управляемым доступом к ПД, заявка со статусами и SLA, операционное дело декларанта, история полей, комментарии.
Проекты, доски с лимитом задач в колонке, зависимости и вехи, Гант с критическим путём, учёт времени, риски матрицей 5×5, кокпит.
Блочный редактор, версии, вложения, поиск, экспорт в DOCX, PDF, HTML и Markdown.
Журнал аудита, уведомления, конструктор справочников и ролей, админка, метрики и проверки готовности у каждого сервиса.