
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 | 프로젝트 설정 — 인증, 미들웨어, 인프라 |
| OpenAPI | API 계약 — 경로, 파라미터, 응답 |
| 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분 |
| + 스케줄링 | 세션 기반 크론 스케줄, TTL | 10분 | 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가 레일 위를 달린다. 벽은 없다.
관련 글
- SSaC — Service Sequences as Code — yongol의 키스톤 DSL. 엔드포인트 내부 결정을 선언한다.
- Feature Chain — operationId 하나로 풀스택을 추적한다 — operationId로 8개 레이어를 관통하는 추적.
- Ratchet Pattern — 에이전트가 끝까지 가게 만드는 방법 — validate가 에이전트에게 피드백을 주는 구조의 이론적 배경.