Архитектура
Эта страница описывает каркас, на котором стоит приложение. Она написана сразу для двух читателей — человека, решающего, подходит ли продукт, и кодирующего агента, который будет его менять. Обоим нужно одно и то же: знать, какой слой за что отвечает, прежде чем к чему-либо прикасаться. Назад к Fractera.
Как всё связано
На вашем сервере бок о бок работают несколько процессов. Четыре из них отвечают наружу, и у каждого ровно одна задача. Граница между ними проходит по порту, а не по папке — именно поэтому сбой в одном из них не утягивает за собой остальные.
| Порт | Процесс | Для чего нужен |
|---|---|---|
| 3000 | Ваше приложение | Страницы, которые видят посетители. С ним вы работаете каждый день. |
| 3001 | Авторизация | Аккаунты, сессии, роли. Настраивается из панели управления, вы его не редактируете. |
| 3002 | Панель управления | То же самое: настраивается, а не редактируется. |
| 3300 | Слой данных | Строки, загруженные файлы, векторы — и единственная дверь ко всему остальному. Ваше приложение общается с ним. |
Рядом работают ещё три сервиса, и ни один из них не является самостоятельной дверью:
- карта — маршруты, матрицы расстояний и поиск адресов, порт 3400;
- каналы — Telegram и всё, что за ним последует, порт 3500;
- граф знаний — агентное RAG-хранилище, порт 9621.
Ни один из этих портов недоступен из интернета: фаервол пропускает только веб-порты, и всё публичное поступает через них. Ваше приложение обращается к трём сервисам через слой данных — /service/geo, /service/channels, /service/rag — с тем же ключом, который открывает сам слой данных.
Каждый слой переживает остальные
Раздельные процессы — это не схема в документации, а то, что происходит в плохой день. Любой из четырёх может остановиться без падения остальных.
| Если это остановится | Что продолжит работать |
|---|---|
| Ваше приложение | Панель, данные и аккаунты не затронуты; недоступен только сайт |
| Панель управления | Сайт продолжает обслуживать посетителей; подождать придётся только изменениям |
| Слой данных | Страницы, сгенерированные заранее, всё равно открываются — именно для этого и нужна статическая генерация |
| Авторизация | Публичные страницы не затронуты; закрывается только то, что находится за входом |
Панель намеренно живёт за пределами вашего репозитория. В ваш GitHub отправляется приложение; панель управления остаётся на сервере, поэтому ошибка при редактировании не может её сломать.
Сначала статика, и что это даёт
Страницы генерируются заранее, а не собираются при каждом запросе. Это не просто деталь производительности — это причина, по которой сайт остаётся дешёвым в обслуживании под нагрузкой, полностью читаемым для поисковых систем и функциональным с отключённым JavaScript.
- Маршрутизация выполняется на стороне сервера, поэтому посетитель с отключёнными скриптами всё равно может перемещаться по всему сайту.
- Контент перегенерируется по расписанию, а не при каждом посещении, поэтому всплеск трафика ничего не стоит дополнительно.
- Всё, что действительно зависит от того, кто смотрит — дашборд, личный кабинет —, рендерится при запросе, и только эта часть.
Один дизайн, решённый единожды
Цвета, шрифты и отступы не выбираются для каждой страницы отдельно. Вся шкала живёт в одном месте, палитра — в другом, а заголовок, написанный вручную, не пройдёт проверку ещё до того, как попадёт на сайт.
Закон, стоящий за этим, прост: ничто в том, как выглядит страница, не зависит от того, кто может её открыть. Публичная или приватная, витрина или таблица администратора — одни и те же заголовки, та же шкала, те же цвета. Доступ определяет, что человек может видеть, но никогда не то, как это оформлено.
Это записано потому, что у его отсутствия есть конкретная форма. Пока файл дизайна был пуст, агент, строивший этот проект, придумал второй стиль заголовков для «рабочих экранов» — две приватные страницы в итоге отличались по размеру в два раза и были оформлены разными гарнитурами. Ничего не сломалось; это просто читалось как два разных продукта.
Ваша палитра — это небольшой файл цветовых ролей, считываемый при отдаче страницы. Измените его, и весь сайт последует за изменениями — включая страницы, которые вы ещё не построили, и включая обе темы: светлая и тёмная — это одни и те же роли с разными значениями, а не два дизайна, которые нужно синхронизировать вручную.
Языки: доступно 82, а добавление нового ничего не стоит
Вместе с продуктом поставляются восемьдесят два языка. Вы включаете те, на которых говорит ваш рынок, а остальные ждут — включение нового языка позже является настройкой, а не пересборкой того, как работает сайт.
Часть, которую стоит понять — это то, чего добавление языка НЕ делает:
- Оно не делает ни одну страницу динамической. Каждый язык получает свои собственные страницы, сгенерированные заранее точно так же, как и первый — десять языков означают десять наборов статических страниц, а не одну страницу, собираемую при каждом запросе.
- Оно не размывает поисковый рейтинг. Каждая страница объявляет себя оригиналом на своём языке и указывает свои переводы, поэтому поисковая система относится к ним как к одной странице на десяти языках, а не как к десяти близким дубликатам, конкурирующим друг с другом.
- Оно не снижает скорость. Отдача предрендеренной страницы — это одна и та же работа, независимо от того, сколько языков существует рядом с ней.
Одноязычный сайт — это отдельный полноценный случай, а не урезанная версия: язык полностью исчезает из адресов, и сайт прекращает рекламировать переводы, которых у него нет.
Находится поисковиками, читается моделями
На современный сайт приходят два читателя, и им нужно разное. Поисковая система отправляет человека на страницу. Модель приходит сама, читает и пересказывает. Продукт построен для обоих, и это не одна и та же задача.
Для поисковых систем: страницы отдаются в виде готового HTML, каждая объявляет свой канонический адрес, переводы ссылаются друг на друга, метаданные собираются единым механизмом, а не для каждой страницы отдельно, а структурированные данные, карты сайта и правила robots поставляются по умолчанию. Автоматические проверки отклонят страницу, нарушающую любое из этих правил.
Для моделей: каждая публичная страница также существует в виде чистого текста. Есть карта по адресу /llms.txt, весь корпус по адресу /llms-full.txt и markdown-версия каждой страницы рядом с ней. Это важно, потому что верстка страницы — это наполовину шум для модели (меню, футер, баннер согласия, скрипты), и она расходует на всё это свой контекст.
Обе формы строятся из ОДНОГО И ТОГО ЖЕ контента. Нет отдельной «версии для ИИ», которая могла бы рассинхронизироваться: отредактируйте текст один раз, и изменятся обе формы. Копия, поддерживаемая вручную, разошлась бы при первой же правке, и никто бы этого не заметил, потому что никто не открывает её в браузере.
Настройки применяются без пересборки
Название, описание, логотип, цвета, языки и переключатели функций живут в конфигурационных файлах на сервере, за пределами кода. Приложение читает их при отдаче страниц, поэтому изменение в панели видно мгновенно — без деплоя и простоя.
Следствие здесь важнее удобства: одна и та же база кода обслуживает пекарню и маркетплейс, и ни одну из них не пришлось форкать, чтобы прийти к этому.
Ваш сервер, ваш код и путь наружу
Приложение принадлежит вам: клонируйте его, редактируйте локально, отправляйте обратно. Ничто здесь не обращается к сторонним серверам — нет вендора, у которого нужно просить разрешение, и нет подписки, которую можно отозвать.
Вы также можете уйти. Удалите зависимость от панели, и приложение будет работать где угодно. Вы потеряете части, живущие на сервере — настройки без пересборки, слой данных, векторный поиск, авторизацию на 82 языках, историю деплоев с откатом — но сохраните код. Это легитимный выход, а не отступление от архитектуры.
Создано для роста после исчерпания контекста
Жесткий лимит проекта, создаваемого ИИ — это не размер кода. Это то, сколько этого кода нужно понять за один раз, прежде чем можно будет сделать безопасное изменение. Проект, где каждая новая страница добавляется в центральный файл, быстро упирается в эту стену: в конце концов ни одна сессия не может вместить достаточно информации, чтобы изменить что-то, не сломав другое.
Структура здесь выбрана именно против этого. Каждая сущность владеет своей папкой — своими страницами, своими данными, своими текстами, своими приватными компонентами. Удалите папку, и ничто не останется сиротой в другом месте.
- Общий слой не растёт по мере добавления сущностей. Что-то поднимается в общее место только тогда, когда два элемента действительно это используют, и этот шаг является осознанным действием, а не привычкой.
- Права объявляются там, где они применяются, а не в реестре, о котором кто-то должен помнить.
- Группы маршрутов делают два типа страниц видимыми на диске: публичный контент с одной стороны, экраны с ограничением по ролям — с другой. Папка, не входящая ни в одну из групп — это неотвеченный вопрос, и проверка скажет об этом вслух.
Главное следствие: изменение одной сущности требует чтения только одной папки. Миллионы строк остаются рабочими не потому, что кто-то держит их в голове, а потому, что ни одно отдельное изменение никогда этого не требует.
Стартер — это та же идея, применённая к началу работы. Поставляется не пустой репозиторий, а рабочий пример каждого паттерна — страница, пост, каталог, приватный экран, диалог, языковая ячейка. Новая страница создаётся путём копирования рабочей, поэтому структура распространяется благодаря самой конструкции, а не дисциплине.
Документы, которым подчиняется агент
Кодирующий агент начинает каждую сессию без памяти о предыдущей. То, что выживает, записано внутри проекта и читается в начале каждой сессии. Этот корпус является такой же частью архитектуры, как и порты — именно он делает вторую сессию такой же компетентной, как и первую.
| Документ | Для чего нужен |
|---|---|
| Пользовательские сценарии | ДЛЯ ЧЕГО нужен продукт, по одному файлу на сценарий: кто приходит, что их привело, что должно быть верно по окончании. Нет подтверждённого сценария — нет разработки: агент обязан остановиться и спросить, а не гадать. |
| Шаги разработки | Сама работа в виде файлов. Шаг открывается перед выполнением и перемещается в папку завершённых с полным отчётом. Прерванная сессия ничего не теряет; чистая сессия возобновляется из файлов. |
| Тестирование | Как доказывается завершённость шага: два независимых доказательства из двух разных плоскостей, прописанные явно. Зелёная сборка никогда не является одним из них — лог сборки выглядит одинаково независимо от того, работает функция или нет. |
| Антипаттерны | Подходы, которые уже стоили времени, каждый с механизмом сбоя. Саморазвивающийся документ: агент дополняет его в момент осознания тупика. |
| Уроки | Ваши предпочтения и привычки, полученные из единожды допущенной ошибки. Если урок и настройки агента по умолчанию расходятся, побеждает урок — он существует потому, что настройки по умолчанию здесь уже подвели. |
| Дизайн | Как выглядят страницы, решённое вами и соблюдаемое агентом. Данность, не подлежащая эволюции. |
Два из них заслуживают отдельного упоминания о направлении записи. Антипаттерны и уроки пишутся агентом; документ дизайна пишется вами. Разница преднамеренная: агент может записывать то, чему научился, но не может решать, как должен выглядеть продукт.
Пользовательские сценарии переезжают из файлов в сервис. Диалог, создающий их, уже живёт в панели управления; далее они переместятся за интерфейс инструментов, поддерживаемый базой данных, чтобы агент запрашивал нужные сценарии, а не читал папку. Правило не меняется от способа хранения: нет подтверждённого сценария — нет разработки. Меняется то, что сценарии перестают быть документом, о чтении которого агент должен помнить.
Много продуктов на одном сервере
Сценарий должен к чему-то принадлежать. В этом продукте он принадлежит продукту — и один сервер несёт несколько из них: лендинг сегодня, запланированный мониторинг на следующей неделе, корпоративный мозг после этого.
Возражение справедливо, и его стоит озвучить перед ответом: веб-сайт обычно является одним продуктом. Если вы строите профессиональную продакшн-систему для компании, это правильно, и ничто здесь с этим не спорит — разместите один продукт на одном сервере, и остальная часть этого раздела ничего вам не стоит.
Но это больше не единственная вещь, которую строят люди. Всё больше и больше из того, что нужно человеку — это небольшой сервис для собственной эффективности: что-то, что работает по расписанию и сообщает об изменениях, что-то, что ищет по смыслу, а не по ключевым словам, что-то, что решает одну recurring-задачу в продажах, маркетинге или операциях. Каждый из них слишком мал, чтобы заслуживать отдельный сервер, свой домен и отдельный счёт — а вместе они образуют систему.
Таким образом, единицей работы является продукт, а не сайт. Группировка одного продукта на его собственной странице или горстке страниц — это то, что позволяет кодирующему агенту знать без вопросов, какой из них он меняет.
Почему бы просто не назвать это проектом
Потому что проект — это не место. У него нет адреса, папки и таблиц, поэтому сценарий, прикреплённый к нему, не может быть выполнен — агенту всё равно придётся гадать, куда идёт работа. У продукта есть все три составляющие, и в этом вся разница: сценарий, прикреплённый к продукту — это исполняемая инструкция.
Продукт владеет четырьмя корнями, и ни один из них не настраивается вручную — все четыре выводятся из его записи:
| Корень | Из чего выводится |
|---|---|
| Его страницы | Его адрес — в этом фреймворке имя папки И ЕСТЬ сегмент URL |
| Его логика | Его постоянный id |
| Его таблицы | Его постоянный id в качестве префикса имени |
| Его сценарии | Его постоянный id |
Работая над сценарием, агент пишет внутри этих четырёх корней и нигде больше. Общий код живёт в общем корне, и перемещение чего-либо туда — это осознанное действие, прописанное в шаге. Попытка взять компонент из соседнего продукта — это именно то движение, для остановки которого создано это правило, потому что именно так изменение одного владельца тихо ломает чужой продукт недели спустя.
Идентификатор намеренно лишён смысла — p1, p2 — и никогда не меняется. Он не может быть выведен из названия или структуры, потому что вы измените и то, и другое, а пути завязаны на id. Это было доказано в тот же день, когда было написано правило: продукт, чьим id было слово «store», на поверку оказался корпоративным мозгом.
Не у каждого продукта есть страница
Продукт объявляет одну из трёх поверхностей, и вариант по умолчанию всегда склоняется к закрытому:
- Публичный — имеет адрес, и посетители доходят до него.
- Приватный — живёт как вкладка в вашей панели управления, и внешнему миру вход закрыт.
- Headless — вообще не имеет экрана: работает через каналы и по расписанию, а вы встречаете его в Telegram или в его отчёте.
Продукт также несёт статус — описывается, строится, live. Перевод в статус live публикует его, и это просто настройка: ничего не пересобирается и не деплоится.
Как это выглядит на практике
Возьмём консультанта с одним сервером. Её первый продукт — лендинг: публичный, в корне, с одной целью — получить заявку. Его сценарии описывают, кто приходит и что должно быть верно, когда они уходят.
Её второй продукт не делит с первым ничего, кроме сервера. Каждое утро он считывает страницы, вакансии и цены четырёх конкурентов, сохраняет найденное и отправляет ей одно сообщение: что изменилось, когда и насколько. Он headless — без адреса, без страницы, без экрана. Его сценарии касаются её утра, а не посетителей.
Оба живут на одном сервере, и ни один из них не может тихо навредить другому: разные страницы, разная логика, разные таблицы, разные сценарии. Когда она просит агента изменить формулировку формы заявки, ничего из мониторинга не попадает в область изменений — не потому, что агент был осторожен, а потому, что граница была определена ещё до постройки любого из них.
План и факт специально держатся порознь. Страницы, которые продукт Должен иметь, записаны; страницы, которые он действительно ИМЕЕТ, подсчитываются путём обхода папок и никогда не сохраняются. Написанный вручную список существующего расходится с реальностью в первую же неделю — агент строит страницу и забывает обновит список. Разрыв между ними — это и есть ответ на вопрос «чего всё ещё не хватает», и он надёжен только потому, что одну из его половин невозможно подделать.