# Конструктор замороженных шаблонов

> Авторитетная стратегия, с первого принципа: как платформа добавляет на сайт **целую структуру** (ленту
> новостей, дерево документации, каталог) — **собирая** её из проверенных замороженных строительных блоков,
> **без единой строки генерации кода**, так что любая ИИ-модель даёт одинаковый результат. Англоязычный
> оригинал — `CRUD-DOCS/workspace-standards/frozen-template-constructor.md`. Спутник — `content-engine.md`
> (как устроена одна готовая страница внутри). Этот документ замещает заметку-прототип `frozen-archetypes.md`.

---

## 1. Проблема, честно

Мы хотим, чтобы ИИ-агент добавлял целую структуру — «сделай раздел новостей», «добавь документацию» — **без**
ручного написания десятков файлов (медленно, дорого по токенам, расходится со стандартом) и **без генерации
кода** (недетерминизм, непротестированные комбинации, разный результат у разных моделей).

Наивное решение — **каталог готовых шаблонов**: отдельный для новостей, для блога, для документации, для
2-уровневого дерева, для каталога из базы… Не масштабируется. Реальное пространство структур имеет несколько
независимых измерений (насколько глубока иерархия, откуда данные, кому видно, какие языки). Каталогу
понадобился бы шаблон на **каждую комбинацию** — сотни; каждое новое измерение умножает их число. Тысяча
шаблонов — не стратегия.

Второе наивное решение — **генерировать структуру ИИ каждый раз** — возвращает ровно то, от чего мы бежим:
непротестированный код, разный результат у разных моделей, риск в каждом создании.

Ответ — ни то, ни другое. Ответ — **конструктор**.

---

## 2. Суть: конструктор, а не каталог

> **Конструктор — это детерминированная функция, которая СОБИРАЕТ структуру из небольшого базиса проверенных
> замороженных примитивов, параметризованная описанием того, что нужно. Она никогда не генерирует код.**

Представьте LEGO: горстка хорошо сделанных кирпичей собирается в огромное пространство моделей. Мы не строим
каждую модель заранее; держим маленький набор **проверенных кирпичей** и маленький набор **правил сборки**.
Комбинаторный взрыв уходит из «сколько шаблонов» в «насколько мало и ортогональны кирпичи».

Два следствия делают это безопасным:
- **Детерминизм.** Сборка копирует и связывает проверенные замороженные части — не сочиняет код. Встроенный
  мозг (Hermes) и каждый кодящий агент дают **одинаковый** результат. *Замораживай паттерн, а не догадку.*
- **Open/Closed.** Новая способность — это **новый кирпич в базисе**, никогда не переписывание ядра
  конструктора. Ядро закрыто для модификации, открыто для расширения.

Существующий эмиттер — уже зародыш этого: берёт параметры, собирает два слоя. Конструктор обобщает зародыш с
одной ручки (`format`) до полного описания.

---

## 3. 🔒 Закон двух слотов (вот что делает стоимость аддитивной, а не N²)

Каждое свойство структуры обязано жить **ровно в одном из двух слотов — и никогда не пересекаться между ними**:

| Слот | Держит | Примеры |
|---|---|---|
| **A. Поставщик списка** (данные) | *откуда берутся дети* | скан файловой системы · запрос к БД на сборке · (в будущем) рантайм |
| **B. Единообразный аспект** (layout) | *сквозное правило, применённое одинаково на каждом уровне* | i18n · роли/доступ · тема |

**Закон:**

> Свойство — это либо **поставщик списка**, либо **единообразный аспект**. Третьего слота нет. Единообразный
> аспект встраивается **одинаково на каждом уровне** структуры (он живёт в layout), **независимо от глубины
> иерархии и источника данных**. **Взаимодействие между слотами запрещено.**

Почему это важно: это разница между **аддитивной** и **квадратичной** стоимостью. Если бы аспекты вели себя
по-разному в зависимости от глубины или источника, каждая комбинация (аспект × глубина × источник) была бы
спец-случаем — ловушка N², убивающая product-lines. По **закону** этого не происходит: роль-гейт — одно
правило, встроенное в каждый уровень layout одинаково, дерево 1 уровня или 4, на файлах или на БД.

**Принуждение (смелл — отвергать, не аккомодировать):** если предлагаемая фича *требует* спец-случая по
глубине или источнику — это дизайн-смелл. Мы **отвергаем** её, не гнём конструктор под неё. Два слота — весь
контракт; больше ничего не добавляется.

---

## 4. Базис: базовая сетка + динамический дескриптор

### 4.1 База = {источник данных} × {глубина}

Структурный скелет фиксируется двумя базовыми измерениями:

```
            глубина 1       глубина 2         глубина 3          глубина 4
          (плоский список) (раздел→элементы) (таксономия)       (глубокое дерево)
файлы     ▢ новости/блог   ▢ docs-по-катег.  ▢ база знаний       ▢ глубокий мануал
БД        ▢ список из БД   ▢ разделы из БД    ▢ каталог           ▢ глубокий каталог
```

- **Источник данных** — сменный **поставщик списка** (Slot A): сегодня скан файловой системы (`parser-fs`
  строит список детей); для БД — **запрос на сборке** (вывод всё равно статический). Один шов, разные
  поставщики.
- **Глубина** — структурная вложенность (сколько индекс-уровней). **Рекурсивный параметр** одного скелета,
  не четыре написанных вручную файла: каждый уровень — хаб, перечисляющий уровень ниже; лист — документ.

Это **8 статических скелетов** — каждая клетка `(источник, глубина)`, и static-first держится во всех (каталог
из БД читает БД **на сборке** и отдаёт статические страницы; см. `static-first.md`).

### 4.2 Девятое: динамический дескриптор

Когда структура **по-настоящему не может быть статической** (данные на пользователя, на запрос,
транзакционные — живой дашборд, корзина), конструктор **не** гнёт статический скелет. Он возвращает
**динамический дескриптор**: не страницы, а **спецификацию** — контекст обращения к данным, правила доступа,
границы — которая передаёт работу классической разработке по правилам платформы. Это честный выход, не плохая
подгонка.

### 4.3 Static-first управляет осью данных

Ось данных — спектр: `файлы → БД-на-сборке (+ISR) → БД-на-запрос → запись ввода → транзакции`. Канон платформы
(`static-first.md`) держит дефолт **слева/посередине** (файлы · БД-на-сборке · ISR); правая часть (динамика на
запрос, запись, транзакции) допускается только при **абсолютной необходимости** и с двойным подтверждением
архитектора. Значит «шаблон с БД» по умолчанию = **БД-на-сборке → статический вывод**.

---

## 5. Аспекты (Slot B): единообразные, тумблеры, независимые от уровня

Аспекты — сквозные тумблеры, каждый встроен **одинаково на каждом уровне** через layout:

- **i18n** — каждый уровень локализован одинаково (`[lang]` + UI-хром вкладки с поключевым EN-fallback).
  Доказан в прототипе; это канонический пример единообразного аспекта.
- **Роли / доступ** — кому видна структура. Два слоя: **enforced-тиры** (`guest` / `user` / `architect`) и
  **бизнес-RBAC** (`ALL_ROLES`: `vip_user`, `subscriber_lite/standard/max`, `buyer`, `manager`, …). Структура
  объявляет доступ; гейт — **одно правило, встроенное в каждый уровень layout**, одинаковое на глубине 1 и 4.
  (Доступ решается *до* постройки структуры — см. `HOW-USE-AUTH.md`.)
- (в будущем) тема и любое правило, по-настоящему одинаковое на каждом уровне.

Аспект, который нельзя выразить как «одно правило на каждом уровне», — это **не аспект**: ему не место в Slot B,
а по Закону двух слотов — нигде. Отвергаем.

---

## 6. Envelope: матч запроса к примитиву

Примитив объявляет свой **envelope** — позицию по каждой оси. Запрос матчится **проекцией на те же оси**:

| Ось | Примеры значений | Слот |
|---|---|---|
| источник данных | файлы · БД-на-сборке · рантайм | A |
| глубина | 1 · 2 · 3 · 4 | база |
| статика vs динамика | статика · динамик-дескриптор | база |
| роли/доступ | public · guest · `[роль,…]` | B (аспект) |
| i18n | моно · мульти | B (аспект) |

> **Тест 100%-fit.** Запрос матчит примитив, только если укладывается по **каждой** оси. Частичное совпадение —
> это **не** тот примитив. Поэтому отказ **объясним конкретной осью** («не подходит: ось *роли* = `architect`,
> примитив = `public`»).

Это машинно-проверяемая форма «100% или никак» и контентный частный случай **выбора способности на масштабе**:
матч по реальной декларации, никогда по ключевым словам. (Ключевые подсказки — проза «fits/does-not-serve» —
могут сопровождать envelope как человеческая опора, но авторитет — envelope.)

---

## 7. Decision-flow: собрать, найти другой, или честно отказать

```
запрос
  └─ проекция на оси envelope
       ├─ примитив подходит на 100% ──────► СОБРАТЬ его (детерминированно, без кодогена)
       ├─ подходит другой примитив ───────► собрать его
       └─ не подходит ни один ────────────► ЧЕСТНЫЙ ОТКАЗ (назвать провалившую ось), затем ОДНО из:
             (a) предложить архитектору ЗАМОРОЗИТЬ НОВЫЙ ПРИМИТИВ
                   — только если форма ПРОВЕРЕНА живой разработкой, ПОВТОРЯЕТСЯ (rule-of-three)
                     и чисто параметризуется. Агент ПРЕДЛАГАЕТ, не создаёт сам; тяжёлый
                     харвест-анализ — только после «делаем» архитектора (не спекулятивно).
             (b) КЛАССИЧЕСКАЯ РАЗРАБОТКА в рамках имеющейся архитектуры
                   — если форма новая / разовая / нестабильная / рискованная.
```

(a) и (b) — **не «или-или»**, а **градиент зрелости**. Новая форма начинается с классической разработки (b);
доказав себя и повторяясь, она **зарабатывает** право быть замороженной в примитив (a). *Замораживай паттерн,
а не догадку.*

---

## 8. Версионирование движка: side-by-side, с пиннингом

Рендер-движок эволюционирует. Эволюция не должна ломать уже существующую структуру.

- **Установленная версия движка неизменна.** Ломающий мажор приезжает **рядом** со старым, в свой неймспейс:
  `lib/content-v1/**` + `components/content-page-v1/**`, затем `lib/content-v2/**` через год.
- **Каждая структура прибита к версии, под которую собрана** (импорты на `@/lib/content-v1/…`), поэтому старые
  «Новости» работают на v1 даже после прихода v2. Конфликт невозможен — неймспейсы разные.
- **Сторож по-версионный** (ставит copy-if-absent *для этой версии*; чужую не трогает).
- **Личность НЕ версионируется.** `brand` и `author` — одна личность сайта на все версии; набор языков и
  тривиальная инфраструктура тоже общие и не версионируются.

Осознанный размен: две структуры на разных мажорах движка **могут выглядеть по-разному**, пока владелец явно
не **мигрирует** одну. Меняем «единый вид везде» на «старая структура не ломается никогда» — верный размен для
само-эволюционирующей ИИ-системы, где агенту год спустя нельзя доверить совместимость, поэтому мы убираем само
требование. (Классика: side-by-side versioning + consumer pinning — .NET SxS, Go modules, Nix.)

---

## 9. 🛑 Дисциплина харвеста (самое важное правило)

Сетка из §4 — **карта для матча и дорожная карта**, а **не план построить все клетки**. Шаблон под каждую
клетку заранее — та же не-масштабируемость с другого конца (преждевременная абстракция).

> Асфальтируй дорогу там, где трафик. **Замораживай клетку в примитив, только когда она (1) проверена живой
> разработкой, (2) повторяется, (3) чисто параметризуется.** Остальное обслуживает классическая разработка,
> пока не заработает свой примитив. Конструктор **Open/Closed**: расти базис по одному проверенному кирпичу.

Сегодня в базисе **один** собранный примитив — `(файлы × глубина-1)` — эталон. Дорожная карта (строить каждый
когда доказан, не сейчас): `(файлы × глубина-2)` (docs-по-категориям) · `(БД-на-сборке × глубина-1)` (каталог)
· динамический дескриптор.

---

## 10. Эталонный примитив — `(файлы × глубина-1, i18n вкл, роли выкл)`

Первый и эталонный кирпич: плоский, на файлах, многоязычный список документов (лента новостей / блог). Он
демонстрирует **швы** конструктора, чтобы следующий кирпич встал без касания ядра:

- **Шов поставщика списка (Slot A):** список детей приходит из скана файловой системы на сборке (`parser-fs`).
  Подмена на поставщика БД-на-сборке меняет только этот шов.
- **Шов аспекта (Slot B):** i18n встроен единообразно в layout. Включение аспекта ролей добавляет гейт в тот
  же шов layout — без изменения глубины или поставщика.
- **Глубина:** глубина-1 — базовый случай рекурсивного параметра глубины.
- **Версионный движок:** рендерер ставится в `lib/content-v1/…`; структура прибита к v1.

Это контент, не фича: собранная структура отдаёт документы-заглушки (Lorem-текст + картинка-заглушка), которые
владелец заменяет; добавление следующего документа — одна папка.

---

## 11. Инварианты (не нарушать)

- Только сборка — **никогда не генерация кода ИИ**; одинаковый результат у любой модели.
- **Закон двух слотов:** свойство — поставщик списка (данные) или единообразный аспект (layout); **никакого
  взаимодействия слотов**; отвергать фичи, которым нужен спец-случай по глубине/источнику.
- **Envelope 100%-fit** или это не тот примитив; отказы называют провалившую ось.
- **Static-first / no-JS**; единственный динамический путь — примитив-дескриптор, по правилам.
- **Версионирование side-by-side, с пиннингом; личность не версионируется.**
- **Дисциплина харвеста:** замораживать проверенную, повторяющуюся, чисто параметризуемую форму — никогда
  догадку; растить базис Open/Closed, по одному кирпичу.
- **Самодостаточная способность** (навык + MCP + композер у каждого агента; работает для одиночного агента,
  без оркестратора) — см. `authoring-skills-instructions-mcp.md`.
