Skip to content

ТЗ — Ядро (cms/core) + API ядра

Слой: 🟢 ядро (всегда загружено, не выключается) · Статус: ТЗ к разработке Контракты подсистем ядра детально описаны в спеках фазы 0 — этот файл сводит поверхность ядра: подсистемы, полный REST API, группы настроек, компоненты, события. Пара к этому ТЗ — реестр критериев ядра: проверяемые критерии/инварианты/гипотезы, граница «ядро vs модуль» и пробелы ⬜, из которых собирается следующая ревизия контрактов. Новая возможность ядра — сначала строка в реестре, потом ревизия здесь, потом код.

Состав ядра (раздел A каталога)

ГруппаПодсистемыСпека
Базовые механизмыKernel/DI, реестр модулей, события/фильтры, очереди, планировщик, кеш-слой, settings-store, движок полей, API-слой (REST), i18n строк, логи, файлы, медиатека, миграции данных, /localтри оси · performance · lifecycle
Контент-движокСтраницы+блоки (BlockRegistry), типы контента (JSONB+GIN), таксономии, черновики/ревизии, меню, виджеты, конструктор форм, заявки, Emailмодель данных · блоки · виджеты · поля
БезопасностьАутентификация, RBAC (Shield+spatie), Policies, rate-limit, защита форм, шифрование полей, согласия ПДн (база), фильтрация 5 границ входабезопасность
SEO-базаМета/шаблоны/canonical, sitemap, robots/редиректы, Schema.org/OG, ЧПУSEO
РедакторыСущностей, типов данных, файловый менеджер, пользователи, структура/разделы, меню, блоков (drag&drop), админ-shell+дашбордsubsystems §7a

Контракты ядра для модулей (cms/core-contracts)

Модуль зависит только от интерфейсов — не от реализации ядра. Канонический набор:

КонтрактЧто даёт модулю
BlockRegistry / WidgetRegistry / FieldTypeRegistryРегистрация блоков, виджетов, типов полей
PageTemplateRegistryРегистрация шаблонов страницы (раскладок рендера): имя → подпись; значение живёт в cms_pages.template, вьюха — каскадом темы layouts/<name>.blade.php с fallback на дефолт (реализован волной A корп-MVP)
RegionContextТекущий город запроса (id/slug/название). Ядро биндит Null-реализацию («один город»); cms/multicity перекрывает живой — потребители (SEO, каталог, head-scripts) не зависят от модуля (реализован волной B корп-MVP; мастер-план §10.3)
Enum GrammaticalCase + Cms\Core\Text\DeclensionEngine/HasDeclensions6 падежей и эвристика склонений (правила — config('cms.declensions.rules')); трейт даёт declensions jsonb на сущности с автозаполнением. Поднято из cms/services-seo волной B (мастер-план §10.4)
SettingsStoreЧтение/декларация настроек (группа модуля, кеш групп)
PageRepository / ContentRepository / TaxonomyRepositoryЧтение контента ядра без raw SQL
MediaServiceЗагрузка/конверсии/привязка файлов
LeadServiceСоздание лида (форма/чат/UGC → заявка)
FilterBusВстраивание в конвейеры (канонические фильтры, приоритеты); события издаются/слушаются нативным Event + #[CmsListener] — отдельного EventBus-интерфейса нет (три оси)
CacheTagsОбъявление и инвалидация тегов кеша модуля
RequestContextТекущие site_id / locale / city_id / пользователь
NotificationDispatchОтправка через шину уведомлений (если модуль включён — иначе log-fallback)
ScheduleRegistrarРегистрация cron-задач (registerScheduleVia)
SitemapRegistryДекларативная регистрация URL-источников модуля в /sitemap.xml (ревизия 14.07.2026)
RedirectServiceЦентральные 301/410-редиректы: смена URL любой сущности — через него (ревизия 14.07.2026)
PrivacyRegistryРегистрация обработчиков «выгрузить всё по субъекту» / «забыть по запросу»: модуль декларирует, какие свои данные экспортирует и как обезличивает; ядро агрегирует в cms:privacy:export / cms:privacy:forget --user= --dry-run (ревизия 14.07.2026)

Каналы обмена и запреты — обмен данными.

Модель данных ядра (свод)

Эталонная схема и решения по хранению — модель данных; здесь свод для навигации (ядро владеет cms_*, модули в них не пишут):

ГруппаТаблицыКлючевое
Страницы и ревизииcms_pages, cms_page_revisionspublished_revision_id (nullable) на странице; блоки — JSONB ревизии; lock_version — optimistic lock публикации (⬜ спроектировано, миграции пока нет — 2026_07_11_000002_create_cms_pages_table колонку не заводит)
Типы контентаcms_content_types, cms_content_entriesдекларации полей; данные JSONB + GIN; slug уникален в типе
Связи типов контента (N:N)cms_content_relations⬜ спроектирована, в БД не заведена — engine §5.2
Навигация и виджетыcms_menus/cms_menu_items, cms_widgetsдерево пунктов; область + условия показа + props JSONB
Настройкиcms_settingsgroup+key uniq, измерения locale/city nullable; донор universal 1:1
SEOcms_redirects (реализовано); cms_seo_meta/cms_seo_templates — ⬜ спроектировано, таблиц нетредиректы — таблица RedirectService (from uniq, hit-счётчик); SEO-мета временно живёт в таблицах модуля cms/services-seo (cms_services_seo_meta_overrides и др.), консолидация в ядровые cms_seo_meta/cms_seo_templates не проведена
Формы и лидыcms_forms, cms_leadsдекларации движка полей; payload JSONB, consent_at, status Enum
Медиаmedia (spatie/medialibrary)конверсии, коллекции; удаление — с проверкой использования в ревизиях
Служебныеcms_module_versions, cms_module_history, cms_block_usage (mat. view), cms_privacy_requestsверсии/журнал обновлений; карта блоков для data-миграций; журнал запросов субъектов ПДн (PrivacyRegistry)

Все cms_* следуют спекам моделей: $casts на каждый JSONB/enum/date, FK constrained() + index(), измерения site_id/locale/city_id nullable там, где сущность контентная.

Контекст запроса (site / locale / city)

Middleware ядра резолвит RequestContext до контроллеров: сайт (при cms/multisite), локаль (цепочка резолва), город (при cms/multicity), пользователь. Модули не парсят URL/заголовки сами — берут контекст; ядро автоматически включает эти измерения в ключи кеша и в скоупы репозиториев.

Порядок резолва зафиксирован (ревизия 14.07.2026): tenant (при cms/multitenancy) и site резолвятся до обращения к page-cache — ключ page-cache всегда включает tenant/site/locale/city; попадание в чужой кеш исключено конструктивно, а не дисциплиной модулей.

Жизненный цикл публичного запроса

запрос → page-cache (hit → ответ без БД) → RequestContext → маршрутизация (ЧПУ)
  → страница + опубликованная ревизия → рендер блоков (BlockRegistry, barrier:
    сбой блока → fallback, не 500) → SEO-резолвер (мета/Schema.org)
  → ответ + теги кеша (страница, области, использованные сущности)

Персонализация и page-cache (ревизия 14.07.2026). Два штатных режима, самодельная сегментация в модулях запрещена:

  • блочный — блок объявляет себя personalized в контракте BlockRegistry (сегмент feature-flag, содержимое корзины): кешируется каркас страницы, персонализированный фрагмент рендерится отдельно (островом на клиенте или сегментированным фрагмент-кешем ядра);
  • полностраничный — модуль декларирует дополнительное измерение ключа page-cache для конкретных страниц (вариант A/B-теста, сегмент кампании) через механизм сегментации ядра — по аналогии со встроенными измерениями tenant/site/locale/city; число значений измерения ограничено (защита от взрыва кардинальности кеша).

API ядра (REST, /api/v1)

Общие правила — API-контракт: единый конверт {data, meta} / ошибки {error: {type, code, message, fields, doc_url}}, Sanctum-токены с abilities, rate-limit per-граница, версия в пути. Публичные эндпоинты читают опубликованный контент; /admin/* — под токеном с ability и permission.

Конвенции, обязательные и для API модулей (модуль расширяет API в своём неймспейсе /api/v1/<slug>/… и /api/v1/admin/<slug>/…):

  • конверт: {"data": …, "meta": {…}}; коллекции — keyset-пагинация (meta.next_cursor, параметр cursor=), OFFSET-пагинация в API запрещена;
  • конверт ошибок — по API-контракту: {"error": {"type": …, "code": …, "message": …, "fields": {…}, "doc_url": …}}; поле code — стабильная машиночитаемая строка (token_expired, rate_limited, version_conflict), клиенты ветвятся по code, не по тексту message (подтверждено ревизией 14.07.2026);
  • фильтры filter[поле]=, сортировка sort=-created_at — строго по whitelist FormRequest; неизвестный параметр → 422, не игнор;
  • коды: 401 нет токена · 403 нет права · 404 нет ресурса/не опубликован · 409 конфликт версии (optimistic lock) · 422 валидация · 429 rate-limit (+Retry-After);
  • деградация публичного чтения — не 5xx: если взаимозаменяемый провайдер (поиск, подсказки, курсы) недоступен, эндпоинт отвечает 200 с fallback-данными или пустым data и флагом meta.degraded: true (+meta.degraded_reason для логов); 5xx допустим только при отказе самого ядра;
  • мутации с денежным/внешним эффектом (включая выпуск токенов и ключей) — заголовок Idempotency-Key;
  • депрекация: поле/эндпоинт помечается в OpenAPI + заголовок Deprecation, удаление — только в мажоре ядра;
  • persistent-роуты (ревизия 14.07.2026, п. 14): модуль может пометить комплаенс-роут persistent — он остаётся жив при выключенном модуле, запросы складываются в очередь/журнал и применяются при включении (обязательный кейс — One-Click Unsubscribe RFC 8058, отзыв согласия);
  • каждый эндпоинт покрыт OpenAPI-атрибутами (автоспека) и feature-тестом.

Аутентификация и пользователи

МетодПутьДоступНазначение
POST/api/v1/auth/tokenпублич. (строгий rate-limit)Выдача Sanctum-токена (email+пароль → token c abilities)
DELETE/api/v1/auth/tokenтокенОтзыв текущего токена
GET/api/v1/auth/meтокенПрофиль текущего пользователя, роли, abilities
GET/PUT/api/v1/admin/users, /users/{id}users.manageCRUD пользователей, назначение ролей

Страницы, ревизии, preview

МетодПутьДоступНазначение
GET/api/v1/pagesпублич.Список опубликованных страниц (фильтры: раздел, тип; пагинация)
GET/api/v1/pages/{slug}публич.Страница + разрешённые блоки опубликованной ревизии
GET/api/v1/preview/{token}подписанный токенPreview черновой ревизии (не индексируется)
GET/POST/PUT/DELETE/api/v1/admin/pages…pages.manageCRUD страниц (черновик-ревизия)
GET/api/v1/admin/pages/{id}/revisionspages.manageИстория ревизий
POST/api/v1/admin/pages/{id}/revisions/{rev}/publishpages.publishПубликация ревизии
POST/api/v1/admin/pages/{id}/revisions/{rev}/rollbackpages.publishОткат на ревизию
PUT/api/v1/admin/pages/{id}/blockspages.manageСохранение состава блоков черновика (валидация схем по BlockRegistry)

Типы контента и записи

МетодПутьДоступНазначение
GET/api/v1/content/{type}публич.Записи типа (фильтры по полям JSONB — whitelist, GIN)
GET/api/v1/content/{type}/{slug}публич.Одна запись
CRUD/api/v1/admin/content/{type}…content.{type}.manageУправление записями
CRUD/api/v1/admin/content-types…studio (конфигурируемо настройкой content-types.type_management_roleengine §11)Управление самими типами (декларации полей)
GET/api/v1/content-types/{type}/schemaпублич.JSON Schema полей типа (движок полей → JSON Schema) — интегратор видит структуру без чтения кода (ревизия №2, п. 3)
GET/api/v1/taxonomies/{tax}/termsпублич.Термины таксономии (дерево)

Публичные list/detail-роуты типов с включённым seo_enabled регистрируются в SitemapRegistry по событию ContentTypeSaved — тип попадает в /sitemap.xml без ручного добавления источника; детали резолва схемы и роутинга — engine, «Маршрутизация list/detail».

Меню, виджеты, темы

МетодПутьДоступНазначение
GET/api/v1/menus/{slug}публич.Дерево меню (headless-рендер)
CRUD/api/v1/admin/menus…menus.manageУправление меню
CRUD/api/v1/admin/widgets…widgets.manageВиджеты: тип, область, условия показа, is_active
GET/api/v1/admin/themesstudio-рольСписок тем + манифесты
POST/api/v1/admin/themes/{name}/activatestudio-рольАктивация темы (preflight → ThemeChanged)

Медиа

МетодПутьДоступНазначение
POST/api/v1/admin/mediamedia.uploadЗагрузка (MIME-whitelist, лимиты, re-encode изображений; при включённой реализации upload-scanner файл до вердикта — в карантине pending, ревизия №2 п. 4)
GET/api/v1/admin/mediamedia.viewПоиск/список, коллекции
DELETE/api/v1/admin/media/{id}media.manageУдаление (с проверкой использования в ревизиях)

Формы и заявки

МетодПутьДоступНазначение
GET/api/v1/forms/{slug}публич.JSON-схема формы (для headless-рендера)
POST/api/v1/forms/{slug}/submitпублич. (rate-limit, honeypot, CSRF/капча)Отправка → лид; consent_at обязателен при сборе ПДн
GET/PUT/api/v1/admin/leads…leads.manageПросмотр/статусы заявок
CRUD/api/v1/admin/forms…forms.manageКонструктор форм (декларации движка полей)

Настройки, система, служебное

МетодПутьДоступНазначение
GET/PUT/api/v1/admin/settings/{group}settings.manageЧтение/запись группы настроек (секреты не хранятся — только .env)
GET/api/v1/system/healthhealth-токенАгрегат health-чеков ядра + модулей (для мониторинга/Caddy)
GET/api/v1/admin/system/mapstudio-рольМашинная карта: модули, версии, зависимости, роуты (= cms:map --json)
GET/api/v1/searchпублич.Поиск по контенту ядра (PG tsvector; модуль Поиск заменяет провайдера)
CRUD/api/v1/admin/redirects…redirects.manageРучные 301/410 + просмотр автосозданных (RedirectService); hit-счётчик, поиск петель
GET/sitemap.xml, /robots.txtпублич.SEO-база (вне /api, генерирует ядро)

Группы настроек ядра (settings-store)

ГруппаПримеры ключейaffectsPageCache
siteназвание, логотип, контакты, телефон, режим обслуживания
seoшаблоны мета по типам, суффикс title, счётчик robots
mediaпресеты конверсий, webp/avif, лимиты загрузки
formsкапча вкл/провайдер, честный текст согласия ПДн
cacheTTL page-cache, исключения путей, debug-token
mailтранспорт (не секреты), from-имя/адрес
localeлокали сайта, дефолт, fallback-цепочка
securityрежим CSP (off/report-only/enforce), отчётный endpoint, SRI вкл/выкл (ревизия №2, п. 5)
systemпороги диска для health (disk_warn_percent=15, disk_critical_percent=5), ретеншн ревизий (ревизия №2, п. 2)
content-typestype_management_role (studio/admin — кто создаёт типы), max_types, max_indexed_properties_total (лимиты — защита общей таблицы cms_content_entries от неограниченного числа индексируемых свойств) — engine §11

Компоненты ядра

  • Блоки (BlockRegistry): hero, текст, изображение/галерея, CTA, форма (по slug), колонки/контейнер, секция (варианты оформления), HTML (доверенный, studio-роль), designed-секция (доверенный HTML с плейсхолдерами , значения экранируются; studio-роль), меню-якоря — стартовый набор темы; репитер-блоки общего назначения (волна A корп-MVP): преимущества (features), шаги (steps), карточки/CTA-сетка (cards), аккордеон, табы (CSS-only), таблица, статичные отзывы (reviews_inline) — декларативные схемы на repeater-полях движка, barrier-safe; content-list (волна C, engine §6) — список записей выбранного типа контента на произвольной странице (тип/сортировка/лимит/ вид карточки; barrier-safe при неизвестном типе);
  • Виджеты: меню, контакты, соцсети, произвольный текст;
  • Filament: ресурсы страниц/типов/меню/виджетов/медиа/пользователей/лидов, страница настроек по группам, дашборд (заявки, здоровье), редактор блоков drag&drop;
  • Команды: cms:doctor, cms:map, cms:hooks, cms:cache:inspect, cms:theme:resolve, cms:blocks:migrate, cms:postupgrade, cms:content:cleanup-orphans (уборка осиротевших связей типов, волна C; cms:content:migrate {type} — к реализации вместе с ломающими миграциями схем), cms:privacy:export / cms:privacy:forget --user= --dry-run (агрегация по PrivacyRegistry, п. 15 ревизии) — все с --json; восстановительные команды идемпотентны и допустимы на живом сайте.

События ядра

Канон — три оси: PageSaved/PagePublished, ContentEntrySaved, LeadCreated, MediaUploaded, SettingChanged, MenuSaved, WidgetSaved/WidgetDeleted, ThemeChanged, ModuleEnabled/ModuleDisabled, UserRegistered, CacheFlushRequested.

Расширение канона (ревизия 14.07.2026, по итогам углубления ТЗ модулей):

  • события удаления: PageDeleted, ContentEntryDeleted, MediaDeleted, UserDeleted, MenuDeleted — без них невозможны purge CDN, деиндексация поиска, чистка рекомендаций и ПДн-каскад «забыть по запросу»; payload — облегчённый (id + slug/путь удалённого), не полный объект: сущности уже нет;
  • события аутентификации: UserLoggedIn, UserLoggedOut, UserPasswordChanged — канонические факты канала 1 (ядро транслирует framework-события в свои DTO); потребители: session, audit, attack-monitor, two-factor — слушают их, а не Illuminate\Auth\Events\* напрямую;
  • UserMerged(primary_user_id, secondary_user_id) — слияние аккаунтов; потребители переносят свои данные (баллы, заказы, согласия) со вторичного на первичный;
  • LeadStatusChanged(lead, from, to) — смена статуса заявки: двусторонняя синхронизация с CRM и триггеры маркетинга без опроса таблицы лидов;
  • payload смены адреса: PagePublishedContentEntrySaved при смене slug) несёт previous_slug (nullable) — потребители (cdn, seo-редиректы) делают purge и 301 старого URL без самодельного диффа.

Права ядра

pages.view/manage/publish · content.{type}.view/manage · menus.manage · widgets.manage · media.view/upload/manage · forms.manage · leads.manage · settings.manage · users.manage · redirects.manage · studio-роль (темы, типы, HTML-блок, произвольные regex, privacy-операции cms:privacy:*, карта системы /system/map).

content.{type}.view гейтит просмотр записей типа в админке (read-only роли, генерик- ресурс ContentEntryResource в режиме чтения); публичные GET-эндпоинты (/api/v1/content/{type}…) остаются без прав — они читают опубликованный контент как и раньше. ⬜ В коде RbacSynchronizer::desiredPermissions() пока генерирует только content.{type}.manage на каждый тип — .view заводится доработкой при реализации движка типов контента (engine §11).

Безопасность ядра (свод)

Полная модель — безопасность; контрольные точки ядра:

  • 5 границ входа фильтруются стандартизированными обработчиками ядра: формы (rules движка полей → FormRequest), админка (FormRequest + Policy), API (whitelist фильтров/сортировок, 422 на неизвестный параметр), файлы (MIME-whitelist, re-encode изображений, лимиты), вебхуки (подпись, только через cms/webhooks-in).
  • Rich-text — двойной барьер: санитизация на сохранении и на выводе; {!! !!} — только HTML-блок под studio-ролью. Пользовательские regex — через ReDoS-валидатор.
  • Шифрование полей: encrypted-cast для чувствительных столбцов; секреты — только .envconfig(), settings-store их не принимает (валидация схемы настройки).
  • Sanctum-токены: abilities ∩ permissions (двойная проверка), строгий rate-limit на /auth/token, отзыв всех токенов при смене пароля (UserPasswordChanged).
  • Preview — только подписанный токен с TTL, noindex; черновики недоступны без него.
  • ПДн: consent_at на границе форм; каскады «выгрузить/забыть» — PrivacyRegistry (п. 15); ПДн и секреты не попадают в логи и телеметрию.
  • CSP/SRI — дефолт ядра (ревизия №2, п. 5): middleware ядра отдаёт CSP с nonce для инлайн-вставок; источники собираются каноническим фильтром security.csp (тема декларирует свои в theme.json, модули-вставщики — pixels, analytics, integration-* — добавляют фильтром); внедрение — через report-only, перевод в enforce — осознанное действие; SRI — на ассеты темы из манифеста сборки.
  • Скан загрузок (ревизия №2, п. 4): медиатека резолвит upload-scanner (provides-контракт) при каждой загрузке; вердикт «заражён» → файл в карантин + событие + алерт; 0 реализаций — скан пропускается (деградация штатна, managed-парку реализация рекомендована для UGC-сайтов).

UX-требования админки ядра

  • Пустые состояния каждого ресурса — подсказка «что нажать» (нет страниц → «Создать страницу», нет форм → ссылка на конструктор), не пустая таблица.
  • Конфликт 409 (optimistic lock) — диалог «страница изменена другим пользователем: обновить / сравнить / перезаписать», не немой сбой сохранения.
  • Массовые действия на списках: публикация/снятие, смена раздела, удаление с подтверждением и числом затронутых.
  • Ошибки — на человеческом языке (аудитория «админ» из lifecycle): «что случилось и что нажать», код — в раскрываемых деталях для поддержки.
  • Необратимое (удаление страницы с ревизиями, медиа, пользователя) — подтверждение с перечнем последствий (где используется, что станет с данными).
  • Инвалидация кеша после SettingChanged — индикатор «кеш обновится в течение N с», не мгновенная блокировка интерфейса.
  • Вся админка мультиязычна (lang/), доступна с клавиатуры; drag&drop-редактор имеет клавиатурную альтернативу порядка блоков.

Эксплуатация ядра (ранбук)

  • Метрики (Pulse): p95 ответа публичных страниц, hit-rate page-cache, глубина очередей, частота сбоев слушателей/фильтров; суточный пинг телеметрии — в центр обновлений.
  • Типовые инциденты: 500 после деплоя → cms:doctor --json + cms:postupgrade; устаревшие страницы → cms:cache:inspect (теги, последняя инвалидация); очередь встала → воркер/queue:retry, отставание видно в health; блок «упал» → barrier уже отдал fallback, смотреть отчёт рендера в логе канала cms.
  • Бэкап/рестор: в бэкап входят все cms_*, media и .env; после рестора обязательны cms:postupgrade (кеши, cms_block_usage) и переиндексация модулей поиска — рестор без них не считается завершённым.
  • Данные ядра ретеншнятся: ревизии страниц — политика хранения N последних (настройка), cms_module_history и журнал редиректов — партиции/очистка по возрасту.
  • Диск под контролем (ревизия №2, п. 2): встроенный health-чек ядра — свободное место на дисках БД/медиа/логов; warn при ≤ system.disk_warn_percent, critical при ≤ system.disk_critical_percent (алерт до падения БД, не после). Расширенные чеки (inode, прогноз роста, тренды) — зона cms/health.
  • Staging-клон (ревизия №2, п. 6): проверка обновления перед продом — клон сайта командой cms/backup (cms:backup:clone-to-staging): рестор бэкапа в staging-окружение
    • обязательные трансформации (домен, robots noindex, включение kill-switch всех модулей с внешними эффектами — почта, вебхуки, пиксели, платежи — по стандарту §6 kill-switch обязателен именно поэтому). Клон без глушения внешних эффектов — инцидент (staging рассылает письма клиентам).

Производительность ядра (бюджеты — контрактные тесты)

Горячий путьБюджет
Публичная страница, page-cache hit0 запросов к БД
Публичная страница, page-cache miss≤ 15 запросов (страница+ревизия+блоки+меню+виджеты+SEO; бюджет — performance), без N+1
Чтение группы настроек на горячем пути0 запросов (кеш группы)
Админ-список любого ресурсаkeyset, ≤ 15 запросов, счётчики без COUNT(*) по всей таблице
/api/v1/system/health≤ 2 с при таймауте каждого чека ≤ 500 мс

Инвалидация — только тегами по событиям; Cache::flush() в ядре и модулях запрещён.

Крайние случаи ядра (инварианты поведения)

  • Параллельная публикация одной страницы двумя админами → optimistic lock по ревизии, второй получает 409 и диалог «обновить и повторить», не молчаливую перезапись.
  • Откат на ревизию, ссылающуюся на удалённое медиа → страница публикуется, битые медиа-блоки рендерят fallback (barrier), в админке — предупреждение со списком.
  • Выключение модуля во время запросов «в полёте» → текущие запросы дорабатывают со старым составом реестров; новые запросы видят fallback блоков/виджетов модуля.
  • SettingChanged с affectsPageCache → инвалидация page-cache батчем по тегу, не синхронно в запросе админа; админ видит «кеш обновится в течение N секунд».
  • Страница из 100+ блоков → рендер укладывается в бюджет за счёт кеша областей и фрагментов; редактор предупреждает о превышении рекомендуемого размера.
  • Сущность без измерений (сайт без multisite/multicity/локалей) → nullable-измерения, один и тот же код-путь; контрактный тест ядра гоняет оба режима.
  • Slug-коллизия (страница ↔ тип контента ↔ модульный роут) → детерминированный приоритет: точный роут ядра → модульный префикс → страница; конфликт при сохранении ловится валидацией, а не 500 в рантайме.
  • Сбой слушателя события → изолируется (report + продолжение потока), бизнес-операция издателя не откатывается; сбой фильтра FilterBus — пропуск фильтра с алертом.
  • Удаление медиа, используемого в опубликованной ревизии → блокируется с перечнем страниц («используется на N страницах»); форс — только с подтверждением, блоки переходят на fallback (barrier).
  • Истёкший preview-токен → страница «ссылка предпросмотра устарела, запросите новую» (не 403 без объяснений и не показ черновика).
  • Смена slug опубликованной страницы → автоматический 301 через RedirectService (по previous_slug), petля A→B→A схлопывается сервисом до одного прыжка.
  • cms:privacy:forget по пользователю с заказами/финансовыми записями → обезличивание ПДн-полей вместо удаления строк (append-only не нарушается); обработчик каждого модуля сам решает «удалить или обезличить», ядро агрегирует отчёт.
  • Два воркера очередей обрабатывают одно событие (ретрай после таймаута) → слушатели ядра идемпотентны; инвалидация кеша повторно — no-op.
  • Redis недоступен (ревизия №2, п. 1) → сайт живёт без кеша: кеш-слой падает на null-store с флагом degraded (страницы рендерятся с miss-бюджетом, медленнее — не 500), rate-limit деградирует на database-драйвер, health красный по чеку redis. Сессии админки Redis не требуют (database-драйвер по умолчанию — осознанно ради этого инварианта). Проверка — chaos-тест в cms/testing: поднять playground, убить Redis, публичные страницы и вход в админку живы.

Ревизия контрактов 14.07.2026 (сводка для модулей)

Изменения этого файла по итогам углубления ТЗ модулей (v2.1/v2.2); ТЗ с пометками «⚠️ Противоречие» приводить в соответствие с этой ревизией:

#ИзменениеПотребители
1Конверт ошибок: обязательное машиночитаемое поле code (форма конверта — по API-контракту)api-tokens, mobile-api, все клиенты API
2Конвенция деградации публичного чтения: 200 + meta.degraded, не 5xxsearch, dadata, cbr-rates, любые provides-провайдеры
3События удаления PageDeleted/ContentEntryDeleted/MediaDeleted/UserDeletedcdn, search, related-content, ПДн-каскады (consents)
4События аутентификации UserLoggedIn/UserLoggedOut/UserPasswordChangedsession, audit, attack-monitor, two-factor
5previous_slug в payload PagePublished/ContentEntrySavedcdn, SEO-редиректы
6Порядок резолва: tenant/site → page-cache; измерения в ключе кешаmultitenancy, multisite, multicity
7Флаг personalized в контракте блока; сегментация — только механизмом ядраfeature-flags, ab-testing, commerce-cart, кабинеты
8Idempotency-Key распространён на выпуск токенов/ключейapi-tokens
9Реестр provides пополнен: geo-provider, import-target; резолв дисков медиатеки — через существующий storage-backendsession, multicity, import, storage-s3
10SitemapRegistry в cms/core-contracts: модуль декларативно регистрирует свои URL-источники для /sitemap.xml (по аналогии с BlockRegistry)services-seo, seo-filter, blog, multicity, landings
11Канонический фильтр FilterBus seo.meta — единая точка, где модули дописывают/переопределяют мета страницыservices-seo, seo-filter, multicity, ab-testing
12Центральный сервис редиректов ядра (RedirectService): «изменён URL → 301» доступен модулям как сервис, а не самописные таблицыfaq, landings, seo-engine, multicity
13Событие UserMerged(primary_user_id, secondary_user_id) — слияние аккаунтов (гостевой → зарегистрированный, дубли)loyalty, referral, кабинеты, consents, commerce-orders
14Комплаенс-роуты: модуль может пометить роут persistent — он жив и при выключенном модуле (обработка в очередь/лог); обязательный кейс — One-Click Unsubscribe (RFC 8058)newsletter, email-marketing, consents
15PrivacyRegistry в cms/core-contracts + команды cms:privacy:export/forget --user= --dry-run: единый каскад «выгрузить/забыть» по всем модулям (152-ФЗ)consents и каждый модуль с ПДн-паспортом
16Событие LeadStatusChanged(lead, from, to) — синхронизация статусов заявокintegration-crm, email-marketing, analytics

Ревизия контрактов №2 (14.07.2026): закрытие пробелов реестра

Первый плановый цикл механизма «реестр критериев → ревизия → код»: закрыты все накопленные пробелы ⬜ (2.8, 2.9, 4.7, 6.8, 6.9, 8.8):

#ПробелРешениеПотребители
12.8 chaos без RedisИнвариант «Redis недоступен → сайт живёт без кеша»: null-store + degraded, rate-limit на database-драйвер, сессии в БД по умолчанию; chaos-тест в cms/testingядро, health, все модули с кешем
22.9 дискВстроенный health-чек free-space с порогами system.disk_warn_percent / disk_critical_percent; расширенные чеки — cms/healthhealth, backup, sentry
34.7 схема типовGET /api/v1/content-types/{type}/schema — JSON Schema полей типа из движка полейmobile-api, integration-*, headless-фронты
46.8 скан загрузокНовый provides-контракт upload-scanner (реестр §2 стандарта); медиатека: карантин pending до вердикта, 0 реализаций = скан пропускается; модуль-реализация (ClamAV) — кандидат каталогамедиатека ядра, ugc, moderation
56.9 CSP/SRICSP-middleware ядра (nonce, режимы off/report-only/enforce, группа настроек security), канонический фильтр security.csp для источников модулей, SRI на ассеты темыpixels, analytics, integration-yandex/google, темы
68.8 staging-клонcms:backup:clone-to-staging в cms/backup: рестор + трансформации (домен, noindex, kill-switch всех внешних эффектов — §6 стандарта)backup, updates (проверка обновлений)
7дрейф реестра providesЛегализованы контракты, фактически объявленные ТЗ, но отсутствовавшие в реестре §2: cabinet-shell, domain-management, fleet-monitoring, marketplace-catalog, two-factor-auth, ugc-publication, update-client (найдено новым CI-скриптом scripts/check-deps.mjs; впредь дрейф ловится им автоматически)cabinet-b2c/b2b, domains, fleet-dashboard, marketplace-modules, two-factor, ugc, updates

Критерии приёмки

  • [ ] Контрактные тесты cms-testing зелёные (блоки, поля, API-конверт, бюджет запросов);
  • [ ] все публичные границы валидируют вход (FormRequest, whitelist сортировок/фильтров);
  • [ ] page-cache отдаёт страницу без запросов к БД; инвалидация тегами по событиям;
  • [ ] cms:doctor --json диагностирует ядро без модулей; playground-профиль сидится;
  • [ ] OpenAPI-спека генерируется из кода и покрывает все таблицы этого файла;
  • [ ] все 16 пунктов ревизии 14.07.2026 реализованы и покрыты контрактными тестами (code в ошибках, meta.degraded, события удаления/аутентификации/UserMerged/ LeadStatusChanged, previous_slug, порядок tenant/site → page-cache, personalized, SitemapRegistry, RedirectService, PrivacyRegistry, persistent-роуты);
  • [ ] все 6 пунктов ревизии №2 реализованы и покрыты тестами (chaos-тест без Redis, health-чек диска, schema-endpoint типов, карантин upload-scanner, CSP report-only→enforce + фильтр security.csp, staging-клон с глушением внешних эффектов);
  • [ ] каждый инвариант из «Крайних случаев ядра» — отдельный тест (409 при параллельной публикации, fallback битого медиа, изоляция сбоя слушателя, slug-коллизии, обезличивание при privacy:forget);
  • [ ] cms:privacy:export/forget --dry-run агрегируют по всем зарегистрированным обработчикам и печатают отчёт без записи;
  • [ ] UX-требования админки проверены на playground: пустые состояния, диалог 409, подтверждения необратимого, индикатор инвалидации кеша.

Внутренняя база знаний студии