yongol — AI 코딩 SaaS의 용골

200번째 엔드포인트

바이브 코딩으로 SaaS를 만든다. 처음에는 빠르다. 5개 테이블, 12개 엔드포인트 — 20분이면 돌아간다.

그런데 50개 엔드포인트를 넘기면 이상한 일이 생긴다. AI가 어제 만든 패턴을 오늘 다르게 만든다. 100개를 넘기면 기존 기능이 조용히 깨진다. 200개를 넘기면 새 기능 하나 추가하는 데 처음 10개를 만들 때보다 10배가 걸린다.

모델이 멍청해서가 아니다.


결정과 구현

소스 코드에는 세 가지가 섞여 있다:

  • 사용자 결정 — 이 컬럼은 BIGINT이다. 이 엔드포인트는 소유자만 접근한다. 페이지네이션은 커서 방식이다.
  • 비즈니스 로직 — 가격 정책, 워크플로우, 생명주기 규칙.
  • 구현 세부사항 — 변수명, 라이브러리 호출 순서, 에러 래핑.

AI가 이 코드를 읽을 때, 어떤 줄이 결정이고 어떤 줄이 세부사항인지 구별하지 못한다. 그래서 “리팩토링"이나 “정리"를 할 때, 결정을 세부사항으로 착각하고 조용히 덮어쓴다. 사용자는 동작이 이미 틀어진 뒤에야 알아챈다.

이것이 200 엔드포인트에서 바이브 코딩이 무너지는 이유다. 더 큰 모델을 써도 해결되지 않는다. 매체(raw code) 자체가 결정을 보존하지 못하기 때문이다. 모든 모델이 결국 같은 벽에 부딪힌다.


용골

배를 만들 때 가장 먼저 놓는 뼈대가 용골이다. 선체의 무게를 지탱하고, 좌우 흔들림을 막고, 나머지 모든 구조물이 용골 위에 올라간다. 용골 없이 만든 배는 잔잔한 바다에서는 뜨지만, 파도가 치면 뒤틀린다.

바이브 코딩으로 만든 SaaS가 그렇다. 작을 때는 뜬다. 커지면 뒤틀린다.

yongol은 AI 코딩 SaaS의 용골이다.


결정을 코드 밖으로

yongol의 핵심은 단순하다. 결정을 코드에서 분리한다.

10개의 선언적 명세(SSOT)가 각각 하나의 관심사만 담당한다:

SSOT담당
features.yaml기능 카탈로그 — 무엇을 만들 것인가
manifest.yaml프로젝트 설정 — 인증, 미들웨어, 인프라
OpenAPIAPI 계약 — 경로, 파라미터, 응답
SQL DDL + sqlc데이터 모델 — 테이블, 컬럼, 제약, 쿼리
SSaC서비스 흐름 — 엔드포인트 내부의 결정 순서
Rego인가 정책 — 누가 무엇을 할 수 있는가
Mermaid stateDiagram상태 전이 — 엔티티의 생명주기
FuncSpec커스텀 함수 — CRUD로 표현 안 되는 로직
Hurl테스트 시나리오 — 런타임 검증
STML프론트엔드 — 페이지 구조와 데이터 바인딩

10종 중 8종은 업계 표준(OpenAPI, SQL, sqlc, Rego, Mermaid, Hurl, YAML)이다. SSaC와 STML만 yongol이 만든 DSL이다. AI가 새로 배워야 하는 것을 최소화한다.

각 SSOT에는 결정만 들어간다. 구현 세부사항은 없다. AI는 SSOT를 편집하고, yongol generate가 SSOT에서 코드를 렌더링한다. 결정은 SSOT에 영구히 살고, 코드는 일회용 투영이다.


정합성을 강제한다

결정을 10개 파일에 분산했으니, 파일 간 모순이 생길 수 있다. 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개의 규칙이 10개 SSOT 사이의 모든 심볼 참조를 검사한다. 모순이 하나라도 있으면 컴파일을 거부한다.

AI는 자유롭게 쓴다. 레일을 벗어나면 validate가 즉시 잡는다. 레일 위의 자유.


operationId가 키스톤이다

10개 레이어를 어떻게 묶는가? PascalCase 식별자 하나로.

ExecuteWorkflow라는 operationId를 입력하면:

── 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 스펙부터 DB 스키마, 인가 정책, 상태 전이, 함수 구현, 테스트 시나리오까지 — 기능 하나의 전체 지형이 한 화면에 보인다. Grep 수십 번이 명령어 하나로 대체된다.

operationId가 키스톤인 이유는, 풀스택 애플리케이션에서 기능의 단위가 API 엔드포인트이기 때문이다. 사용자가 버튼을 누르면 API가 호출되고, 그 API가 나머지 모든 레이어를 관통한다. 이 이름 하나가 10개 레이어를 물리적으로 체이닝한다.


벤치마크: ZenFlow

ZenFlow — 멀티테넌트 워크플로우 자동화 SaaS. Claude Sonnet 4.6이 SSOT를 작성하고, yongol이 검증했다.

단계내용시간누적
초기 빌드멀티테넌트, 인증, 상태 기계, 6 테이블, 10 엔드포인트23분23분
+ 버전 관리워크플로우 복제, 버전 목록, INSERT…SELECT 액션 복사16분39분
+ 웹훅이벤트 발행, 웹훅 CRUD, 큐 백엔드8분47분
+ 템플릿 마켓플레이스커서 페이지네이션, 크로스 조직 복제, 공개 엔드포인트7분54분
+ 파일 첨부실행 리포트, 파일 백엔드7분61분
+ 스케줄링세션 기반 크론 스케줄, TTL10분71분
+ 감사 로그캐시 백엔드, 페이지네이션, 필터6분77분
+ 대시보드집계 API, 관계 조인, 상세 조회14분91분
+ 일괄 작업액션 일괄 저장, JSON 직렬화10분101분
+ 외부 API 연동지오코딩 API 임포트, 좌표 저장14분115분
+ 조건부 업데이트자동 할당, 신뢰도 판정, 조건 분기16분131분

최종: 30 엔드포인트, 12 테이블, 64 테스트 요청. 전부 통과.

10개의 기능을 순서대로 추가했다. 기능을 추가할수록 속도가 느려지지 않았다. 기존 테스트가 깨지지 않았다. 200 엔드포인트의 벽이 존재하지 않았다.

Opus로 같은 스펙을 실행하면 30 엔드포인트, 73 테스트 요청, ~76분. 모델이 바뀌어도 레일은 같다.


왜 더 큰 모델이 답이 아닌가

“GPT-6이 나오면 해결될 거야.”

해결되지 않는다. 문제는 모델의 지능이 아니라 매체다.

코드라는 매체는 결정과 구현을 구분하지 않는다. 어떤 모델이든 코드를 읽으면 결정과 세부사항이 뒤섞인 텍스트를 본다. 모델이 아무리 똑똑해도, 매체가 구분을 제공하지 않으면 구분할 수 없다.

yongol은 매체를 바꾼다. AI가 편집하는 대상을 코드에서 선언적 명세로 옮긴다. 명세에는 결정만 있고 구현 세부사항이 없으므로, AI가 결정을 세부사항으로 착각할 일이 없다. 결정의 생존이 모델 크기와 무관해진다.

작은 LLM이 SSOT만 편집하고, validate가 매 실수마다 정밀한 피드백을 주면, 훨씬 큰 모델이 raw code를 편집하는 것과 같은 수준의 결정 무결성을 유지할 수 있다. yongol이 그 차이를 메운다.


시작

npx skills add park-jun-woo/yongol

AI 에이전트(Claude Code, Cursor, Copilot 등)에 yongol skill을 설치하면, 에이전트가 워크플로우를 자동으로 학습한다.

직접 CLI를 사용하려면:

go install github.com/park-jun-woo/yongol/cmd/yongol@latest

git clone https://github.com/park-jun-woo/yongol && cd yongol
yongol validate examples/zenflow

0 errors, 0 warnings.

이 명세 위에서 AI에게 기능을 추가하라고 시켜보라. validate가 레일을 깔고, AI가 레일 위를 달린다. 벽은 없다.


관련 글

코드: github.com/park-jun-woo/yongol