Image: AI generated
Изображение: сгенерировано ИИ
Если ИИ постоянно перезаписывает ваш код, если вайб-кодинг развалился на 200 эндпоинтах, если вы хотите перенести нагрузку ИИ с кода на спецификации — yongol и есть этот киль.
200-й эндпоинт
Вы строите SaaS с помощью вайб-кодинга. Поначалу быстро. 5 таблиц, 12 эндпоинтов — двадцать минут и работает.
Но после 50 эндпоинтов начинается странное. ИИ создаёт сегодня паттерн, противоречащий вчерашнему. После 100 — существующие функции тихо ломаются. После 200 — добавление одной функции стоит в 10 раз дороже первых десяти.
Отчёт DORA 2025 подтвердил это эмпирически — инструменты ИИ увеличивают пропускную способность на 2-18%, но одновременно повышают частоту сбоев при изменениях и объём переделок[1]. ИИ — это «зеркало и усилитель», который усиливает слабые места существующих процессов.
Не потому что модель глупая.
Решения и реализация
В исходном коде переплетены три вещи:
- Решения пользователя — эта колонка
BIGINT, этот эндпоинт только для владельца, пагинация курсорная. - Бизнес-логика — ценообразование, воркфлоу, правила жизненного цикла.
- Детали реализации — имена переменных, порядок вызовов библиотек, оборачивание ошибок.
Когда ИИ читает этот код, он не может определить, какая строка — решение, а какая — деталь. Поэтому при «рефакторинге» или «очистке» он тихо перезаписывает решения, приняв их за детали. Пользователь замечает, только когда поведение уже нарушено. В 1972 году Парнас сказал: «скройте проектные решения, которые могут измениться, за интерфейсами»[2] — он обращался к людям. Но теперь, когда ИИ редактирует код, если различие между решениями и деталями не существует в самом медиуме, никто — ни человек, ни модель — не сможет его поддерживать.
Вот почему вайб-кодинг рушится на 200 эндпоинтах. Более крупная модель не решит проблему. Медиум — сырой код — просто не сохраняет решения. Каждая модель в конечном итоге упирается в ту же стену.
Киль
Киль — это первая кость, которую кладут при постройке корабля. Он несёт вес корпуса, предотвращает бортовую качку, и все остальные конструкции строятся поверх него. Корабль, построенный без киля, плавает в спокойной воде, но деформируется при волнах.
SaaS, построенный вайб-кодингом, — то же самое. Плавает, пока маленький. Деформируется, когда растёт.
yongol — это киль SaaS, написанного с помощью ИИ.
Harness with reins — не более крупная модель, а более точные поводья. Детерминированный валидатор оценивает каждый артефакт, храповик обеспечивает прогресс, и машина решает, завершена ли работа.
Вынести решения из кода
Суть yongol проста. Отделить решения от кода.
Десять декларативных спецификаций (SSOT) каждая отвечает за одну задачу:
| SSOT | Ответственность |
|---|---|
| features.yaml | Каталог функций — что строить |
| manifest.yaml | Конфигурация проекта — аутентификация, middleware, инфраструктура |
| OpenAPI | Контракт API — маршруты, параметры, ответы |
| SQL DDL + sqlc | Модель данных — таблицы, колонки, ограничения, запросы |
| SSaC | Поток сервиса — последовательность решений внутри эндпоинта |
| Rego | Авторизация — кто что может делать |
| Mermaid stateDiagram | Переходы состояний — жизненные циклы сущностей |
| FuncSpec | Пользовательские функции — логика, не выражаемая через CRUD |
| Hurl | Тестовые сценарии — трихотомия smoke, scenario, invariant |
| STML | Фронтенд — Semantic Template Markup Language (HTML на основе атрибутов data-*) |
Восемь из десяти — отраслевые стандарты (OpenAPI, SQL, sqlc, Rego, Mermaid, Hurl, YAML). Только SSaC и STML — DSL, созданные yongol. Минимизируется то, что ИИ должен выучить с нуля.
Каждый SSOT содержит только решения. Никаких деталей реализации. ИИ редактирует SSOT; yongol generate рендерит код из них. Решения живут постоянно в SSOT; код — одноразовая проекция.
Обеспечение согласованности
Решения теперь распределены по десяти файлам, поэтому могут возникнуть противоречия. DDL говорит BIGINT, а OpenAPI — string? SSaC объявляет @auth, а в Rego нет соответствующего правила? В диаграмме состояний есть переход, а в SSaC нет соответствующей функции?
Противоречивый SSOT — это испорченное решение. Каким бы чистым ни был код, если решения конфликтуют — поведение нарушается.
yongol validate ловит это.
✓ manifest ✓ openapi_ddl ✓ ssac_rego
✓ openapi ✓ openapi_ssac ✓ ssac_authz
✓ ddl ✓ hurl_openapi ✓ ssac_sqlc
✓ query ✓ hurl_statemachine ✓ ddl_statemachine
✓ ssac ✓ hurl_manifest ✓ ddl_rego
✓ statemachine ✓ openapi_manifest ✓ rego_manifest
✓ rego ✓ ssac_ddl ✓ stml_openapi
✓ hurl ✓ ssac_statemachine
✓ funcspec ✓ ssac_func
0 errors, 0 warnings
Сначала валидирует каждый SSOT отдельно, затем выполняет перекрёстные проверки между слоями. ~287 правил инспектируют каждую символическую ссылку между всеми десятью SSOT. Если существует хотя бы одно противоречие, компиляция отклоняется. Систематический обзор литературы Torres et al.[3] отметил, что большинство инструментов управления моделями обрабатывают только внутримодельную согласованность, оставляя перекрёстную верификацию между гетерогенными моделями нерешённой задачей — yongol validate заполняет именно этот пробел.
ИИ пишет свободно. Сошёл с рельсов — validate мгновенно ловит. Свобода на рельсах.
yongol next — Команда-храповик
Если yongol validate показывает все ошибки сразу, то yongol next показывает ошибки по одной. Это храповик.
$ yongol next specs/
[ERROR] DDL-003: users.id must be BIGINT, got INT
file: specs/db/users.sql:2
▶ Fix this error. Then run `yongol next specs/`.
Единственная инструкция, которая нужна ИИ-агенту, — одно предложение: «Запусти yongol next specs/ и исправляй ошибки, пока не станет 0.»
Исправь ошибку — появится следующая. Пройди все — остановится:
$ yongol next specs/
✓ All validations passed. 0 errors.
Агент не заявляет «я закончил». Машина выносит вердикт «ещё осталось» или «всё пройдено». Агент не имеет полномочий на решение о завершении.
Создание проекта и управление функциями
yongol init
Автоматически генерирует SSOT-скаффолдинг из features.yaml.
yongol init Myapp features.yaml "My workflow automation SaaS"
cd Myapp && yongol validate specs # 0 errors
Manifest, заглушки operationId OpenAPI, файлы-заглушки SSaC, правила авторизации Rego, smoke-тесты Hurl и конфигурация sqlc генерируются за один раз. Проект сразу стартует в состоянии прохождения yongol validate.
yongol features add / remove
Добавить или удалить функции:
yongol features add new_features.yaml # Сгенерировать заглушки SSaC для новых operationId
yongol features remove ExportWorkflow --yes # Удалить operationId + заглушки SSaC
yongol import
Сгенерировать Go-клиентские пакеты из внешних OpenAPI-спецификаций (Stripe, GitHub и др.):
yongol import https://api.stripe.com/openapi.yaml ./external/
Вызывать сгенерированные функции в SSaC через @call <pkg>.<Func>({...}).
operationId — замковый камень
Как связать десять слоёв? Одним идентификатором в PascalCase.
Введите operationId ExecuteWorkflow:
── Feature Chain: ExecuteWorkflow ──
OpenAPI api/openapi.yaml POST /workflows/{id}/execute
SSaC service/workflow/execute_workflow.ssac @get @empty @auth @state @call @publish @response
DDL db/workflows.sql CREATE TABLE workflows
DDL db/execution_logs.sql CREATE TABLE execution_logs
Rego policy/authz.rego resource: workflow
StateDiag states/workflow.md diagram: workflow → ExecuteWorkflow
FuncSpec func/billing/check_credits.go @func billing.CheckCredits
FuncSpec func/billing/deduct_credit.go @func billing.DeductCredit
FuncSpec func/worker/process_actions.go @func worker.ProcessActions
FuncSpec func/webhook/deliver.go @func webhook.Deliver
Hurl tests/scenario-happy-path.hurl scenario: scenario-happy-path.hurl
От спецификации API до схемы базы данных, от политики авторизации до переходов состояний, от реализации функций до тестовых сценариев — полная топология одной функции на одном экране. Десятки grep заменяются одной командой.
operationId — замковый камень, потому что в полностековом приложении единица функции — это API-эндпоинт. Пользователь нажимает кнопку, вызывается API, и этот API пронизывает все остальные слои. Одно имя физически связывает десять слоёв.
SSaC — Почему кастомный DSL
Восемь из 10 SSOT yongol — отраслевые стандарты. Только SSaC (Service Sequences as Code) и STML были созданы yongol. SSaC фиксирует решения потока сервиса.
Пробел, который заполняет SSaC. Посмотрите на спектр декларативных инструментов: на одном конце — стандарты контрактов (OpenAPI, SQL, Rego) — они объявляют что, но не в каком порядке. На другом конце — среды выполнения воркфлоу (Temporal, Inngest, Restate) — это код. Решения и детали реализации рекомбинируются в одном файле. SSaC занимает пробел между ними: «внутри одного эндпоинта, что происходит, в каком порядке, с какими гардами.»
SSaC содержит всего 16 аннотаций. Можно выучить по одностраничному руководству.
Полный список аннотаций SSaC
| Аннотация | Роль | Формат |
|---|---|---|
@get | Чтение из БД | Type var = Model.Method({args}) |
@post | Создание строки | Type var = Model.Method({args}) |
@put | Обновление строки (без возврата) | Model.Method({args}) |
@delete | Удаление строки | Model.Method({args}) |
@empty | Гард nil → 404 | var "message" [STATUS] |
@exists | Гард not-nil → 409 | var "message" [STATUS] |
@auth | Проверка авторизации | "action" "resource" {inputs} "message" [STATUS] |
@state | Переход машины состояний | diagram {inputs} "transition" "message" [STATUS] |
@call | Вызов функции | [Type var =] pkg.Func({args}) |
@eval | Предикатный гард (true → ошибка) | pkg.Func({args}) "message" STATUS |
@publish | Публикация в очередь | "topic" {payload} |
@subscribe | Триггерная функция очереди | "topic" |
@verify-password | Логин (timing-safe) | Model.col=source Model.hash vs source -> var STATUS "msg" |
@response | Возврат JSON | { field: var, ... } или var |
@no-pagination | Освобождение от правила пагинации | (уровень функции) |
@state-neutral | Освобождение от правила машины состояний | (уровень функции) |
Пример SSaC — AcceptProposal
Авторизация + двойная машина состояний + эскроу + очередь:
package service
import "github.com/org/project/internal/billing"
// @get Proposal p = Proposal.FindByID({ID: request.id})
// @empty p "Proposal not found" 404
// @get Gig gig = Gig.FindByID({ID: p.GigID})
// @empty gig "Gig not found" 404
// @auth "AcceptProposal" "gig" {ResourceID: request.id} "Forbidden" 403
// @state proposal {status: p.Status} "AcceptProposal" "Cannot accept" 409
// @state gig {status: gig.Status} "AcceptProposal" "Cannot accept on gig" 409
// @put Proposal.UpdateStatus({ID: p.ID, Status: "accepted"})
// @put Gig.AssignFreelancer({ID: gig.ID, FreelancerID: p.FreelancerID, Status: "in_progress"})
// @call billing.HoldEscrowResponse escrow = billing.HoldEscrow({GigID: gig.ID, Amount: gig.Budget})
// @publish "proposal.accepted" {GigID: gig.ID, FreelancerID: p.FreelancerID}
// @get Proposal updated = Proposal.FindByID({ID: p.ID})
// @response { proposal: updated }
func AcceptProposal() {}
16 строк. 10 аннотаций. Две машины состояний, авторизация, эскроу, событие очереди, ответ — каждое решение видно, каждая деталь отсутствует.
Бенчмарк: ZenFlow
ZenFlow — мультитенантный SaaS автоматизации воркфлоу.
| Этап | Описание | Время | Накопительно |
|---|---|---|---|
| Начальная сборка | 10 эндпоинтов, 6 таблиц, auth, машина состояний | 13 мин | 13 мин |
| + Версионирование | клонирование воркфлоу, список версий | 6 мин | 19 мин |
| + Вебхуки | webhook CRUD, бэкенд очереди | 6 мин | 25 мин |
| + Маркетплейс шаблонов | курсорная пагинация, клонирование между организациями | 3 мин | 28 мин |
| + Файловые вложения | отчёты выполнения, файловый бэкенд | 4 мин | 32 мин |
| + Планирование | cron-планирование, бэкенд сессий | 6 мин | 38 мин |
| + Журналы аудита | пагинация по смещению, бэкенд кэша | 3 мин | 41 мин |
| + Дашборд | join-ы связей, типы ответов func | 7 мин | 48 мин |
| + Пакетные операции | массовая вставка jsonb | 14 мин | 62 мин |
| + Внешний API | func геокодирования, добавление колонки | 3 мин | 65 мин |
| + Условное обновление | паттерн-сентинел, автоназначение | 4 мин | 69 мин |
Итог: 32 эндпоинта, 14 таблиц, 47 Hurl-запросов. 11/11 этапов пройдено.
Добавление функций никогда не замедлялось. Существующие тесты никогда не ломались. Стены 200 эндпоинтов не существовало.
Бенчмарк Opus 4.7 — 32 эндпоинта, 14 таблиц, 47 Hurl-запросов, ~69 мин. Бенчмарк Sonnet 4.6 — 32 эндпоинта, 9 таблиц, 37 Hurl-запросов, ~43 мин.
yongol agent
LLM автоматически исправляет файлы SSOT через цикл validate-fix.
yongol agent specs/ --model ollama:gemma4:e4b --max-rounds 20
Ошибки validate передаются LLM, LLM исправляет их, и валидация запускается снова. Цикл повторяется до 0 ошибок. Работает даже с локальной моделью 4.5B (Gemma4).
Поддерживаемые бэкенды: ollama (локально), xai (Grok), gemini.
Можно ли редактировать сгенерированный код
Да. yongol generate сохраняет пользовательские правки при повторном запуске:
- Каждый сгенерированный файл получает аннотацию
//yg:checked llm=yongol-gen hash=<8hex>. - Если хеш отличается, файл помечается как preserved и пропускается при следующем
generate. yongol statusпоказывает preserved-файлы и дрейф контракта (ошибкиPRV-01/PRV-02).- Для записи намерения добавьте
//yg:preserve reason="..."(необязательно). Для отмены preserve — удалите файл.
Встроенные функции и модели
Встроенные функции (вызываются через @call в SSaC)
| Пакет | Функция | Описание |
|---|---|---|
auth | hashPassword, verifyPassword | Хеширование/проверка bcrypt |
auth | issueToken, verifyToken, refreshToken | JWT-токены |
auth | generateResetToken | Сброс пароля |
crypto | encrypt, decrypt | AES-256-GCM |
crypto | generateOTP, verifyOTP | TOTP |
storage | uploadFile, deleteFile, presignURL | S3 |
mail | sendEmail, sendTemplateEmail | SMTP |
text | generateSlug, sanitizeHTML, truncateText | Обработка текста |
image | ogImage, thumbnail | Генерация изображений |
Встроенные модели (настраиваются через manifest.yaml)
| Пакет | Интерфейс | Бэкенд | Использование в SSaC |
|---|---|---|---|
session | SessionModel (Set/Get/Delete + TTL) | PostgreSQL, Memory | session.Session.Get({key: ...}) |
cache | CacheModel (Set/Get/Delete + TTL) | PostgreSQL, Memory | cache.Cache.Set({key: ..., value: ..., ttl: ...}) |
file | FileModel (Upload/Download/Delete) | S3, LocalFile | file.File.Upload({key: ..., body: ...}) |
queue | singleton Pub/Sub (Publish/Subscribe) | PostgreSQL, Memory | @publish "topic" {payload} |
Автоматическая генерация миграций DDL
yongol generate обнаруживает изменения DDL и автоматически генерирует файлы миграций.
specs/db/
└── users.sql # SSOT — редактируйте здесь
artifacts/db/
├── .latest_schema.sql # Базовый снимок
└── migrations/
├── 0001_initial.up.sql
├── 0001_initial.down.sql
├── 0002_add_users_email.up.sql
└── 0002_add_users_email.down.sql
Неоднозначные изменения (переименование колонок, приведение типов, backfill NOT NULL) уточняются через подсказки в комментариях DDL (-- @rename, -- @cast, -- @backfill, -- @data_migration, -- @allow_destructive). Шесть правил (MIG-001 по MIG-006) контролируют опасные изменения. Фактическое применение к БД делегируется стандартным инструментам типа golang-migrate, flyway и др.
Почему более крупная модель — не ответ
«GPT-6 это решит.»
Не решит. Проблема не в интеллекте модели — а в медиуме.
Код как медиум не различает решения и реализацию. Какая бы модель ни читала код, она видит текст, в котором решения и детали переплетены. Какой бы умной ни была модель, если медиум не предоставляет различие, модель не может его провести.
yongol меняет медиум. Он переносит то, что редактирует ИИ, с кода на декларативные спецификации. Поскольку спецификации содержат только решения без деталей реализации, ИИ никогда не спутает решение с деталью. Выживание решений становится независимым от размера модели.
Маленькая LLM, редактирующая только SSOT, с validate, дающим точную обратную связь на каждую ошибку, может поддерживать ту же целостность решений, что и гораздо более крупная модель, редактирующая сырой код. yongol восполняет этот разрыв.
Тесты времени выполнения
Тесты Hurl полностью пишутся пользователем. Пишите их в specs/tests/, и yongol generate зеркалирует их в artifacts/tests/. При валидации правила XOH-01 — XOH-09 перекрёстно проверяют Hurl против OpenAPI, машин состояний и manifest.auth.
hurl --test --variable host=http://localhost:8080 artifacts/my-project/tests/*.hurl
Три категории:
- smoke.hurl — Smoke-тесты эндпоинтов
- scenario-*.hurl — Тесты бизнес-сценариев
- invariant-*.hurl — Тесты инвариантов между эндпоинтами
Текущий статус
Генерация бэкенда Go+Gin: Beta — работает от начала до конца. Генерация фронтенда React: Alpha (в разработке).
Начало работы
Способ 1: Установить Skill (Рекомендуется)
npx skills add park-jun-woo/yongol
Установите skill yongol в вашего ИИ-агента (Claude Code, Cursor, Copilot и другие). Агент изучает рабочий процесс при установке.
/yongol Построй мультитенантный todo SaaS с аутентификацией и CRUD.
Способ 2: Прямая установка
Требуется Go 1.25+ и gcc (зависимость cgo: pg_query_go линкует libpg_query для парсинга DDL).
git clone https://github.com/park-jun-woo/yongol && cd yongol
make install
yongol validate examples/zenflow
0 errors, 0 warnings.
Направьте ИИ на эти спецификации и скажите добавить функцию. validate прокладывает рельсы; ИИ бежит по ним. Стены нет.
Связанные статьи
- SSaC — Service Sequences as Code — Ключевой DSL yongol. Объявляет решения внутри эндпоинтов.
- Feature Chain — Отследить весь стек по одному operationId — Отслеживание всех восьми слоёв через один operationId.
- Ratchet Pattern — Как заставить агента довести дело до конца — Теоретическое обоснование того, как validate даёт обратную связь агентам.
- Ratchet-код, эксплуатирующий IFEval — Цикл генерации кода, эксплуатирующий склонность к угодничеству, и Reins.
Код: github.com/park-jun-woo/yongol
Источники
- Google DORA Team. DORA State of AI-Assisted Software Development 2025. Google Cloud, 2025. dora.dev/dora-report-2025
- David L. Parnas. “On the Criteria to Be Used in Decomposing Systems into Modules.” Communications of the ACM 15(12): 1053-1058, 1972. doi:10.1145/361598.361623
- Weslley Torres, Mark G.J. van den Brand, Alexander Serebrenik. “A Systematic Literature Review of Cross-Domain Model Consistency Checking by Model Management Tools.” Software and Systems Modeling 20(3): 897-916, 2021. doi:10.1007/s10270-020-00834-1
- Deepak Babu Piskala. “Spec-Driven Development: From Code to Contract in the Age of AI Coding Assistants.” arXiv:2602.00180, January 2026. arxiv.org/abs/2602.00180
- Ehsani et al. “When AI Code Doesn’t Stick: An Empirical Study on Reverted Changes Introduced by AI Coding Agents.” MSR 2026 Mining Challenge, April 2026. 2026.msrconf.org
- Anton Jansen, Jan Bosch. “Software Architecture as a Set of Architectural Design Decisions.” EWSA 2005, LNCS 3527, Springer, 2005. semanticscholar.org
- Marco Brambilla, Jordi Cabot, Manuel Wimmer. Model-Driven Software Engineering in Practice. 2nd ed., Springer, 2017. doi:10.1007/978-3-031-02546-4
- GitClear. AI Copilot Code Quality 2025. February 2025. gitclear.com
История изменений
| Дата | Изменения |
|---|---|
| 2026-05-18 | Первая публикация |
| 2026-05-19 | Добавлен бенчмарк Opus. Обновлены 10 SSOT |
| 2026-05-21 | Синхронизация README: обновление бенчмарков (Opus 32ep/14tbl/47hurl/69min, Sonnet 32ep/9tbl/37hurl/43min), добавлено заявление “Harness with reins”, добавлен пример SSaC (AcceptProposal), добавлена команда yongol agent, добавлена система Preserve, добавлен список встроенных функций/моделей, добавлена автоматическая генерация миграций DDL, добавлено описание STML, добавлена ссылка на статью ifeval-ratchet |
| 2026-05-26 | Синхронизация v0.6.10: добавлена команда-храповик yongol next, добавлены yongol init/features add/features remove создание и управление проектами, добавлен yongol import импорт внешнего OpenAPI, добавлен полный список аннотаций SSaC (16) (@eval, @subscribe, @verify-password, @no-pagination, @state-neutral), трихотомия тестов Hurl (smoke/scenario/invariant), раздел тестов времени выполнения, детали Preserve (коды ошибок PRV), расширение подсказок миграции DDL (@data_migration, @allow_destructive, правила MIG), текущий статус (Go+Gin Beta, React Alpha), установка разделена на 2 метода |