Архитектура

Эта страница описывает каркас, на котором стоит приложение. Она написана сразу для двух читателей — человека, решающего, подходит ли продукт, и кодирующего агента, который будет его менять. Обоим нужно одно и то же: знать, какой слой за что отвечает, прежде чем к чему-либо прикасаться. Назад к 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 — без адреса, без страницы, без экрана. Его сценарии касаются её утра, а не посетителей.

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

План и факт специально держатся порознь. Страницы, которые продукт Должен иметь, записаны; страницы, которые он действительно ИМЕЕТ, подсчитываются путём обхода папок и никогда не сохраняются. Написанный вручную список существующего расходится с реальностью в первую же неделю — агент строит страницу и забывает обновит список. Разрыв между ними — это и есть ответ на вопрос «чего всё ещё не хватает», и он надёжен только потому, что одну из его половин невозможно подделать.

Powered by Fractera