
꿀팁 — 이것만 알면 시킬 수 있다
3강에서 Hurl 테스트와 Git으로 드리프트를 막는 법을 배웠다. 50개 엔드포인트까지는 충분하다. 그런데 규모가 커지면 새로운 문제가 생긴다. AI가 “정리해줘"라고 시켰을 뿐인데 당신이 정한 결정을 세부사항으로 착각하고 덮어쓴다.
핵심 원리: 결정을 코드 밖으로 꺼내라. 코드 안에는 당신의 결정(“이 컬럼은 정수형이다”)과 세부사항(변수명, 에러 처리)이 뒤섞여 있다. AI는 이 둘을 구별하지 못한다. 결정을 별도 명세에 선언하면 AI가 덮어쓸 수 없다.
yongol 설치:
에이전트에게: “npx skills add park-jun-woo/yongol 설치해”
Login 기능을 선언적으로 만들어보자:
에이전트에게: “Login 기능을 SSOT로 선언해”
AI가 API 스펙, DB 스키마, 서비스 흐름, 인가 정책, 테스트 시나리오를 자동 생성한다.
에이전트에게: “yongol validate 돌려서 에러 0으로 만들어”
에러가 나오면 AI가 스스로 고친다. 10개 명세 사이의 모순을 287개 규칙이 교차 검증한다. 체크마크가 전부 녹색이면 코드 생성 준비 완료.
yongol은 현재 Go 프로젝트 전용이다. 하지만 “결정을 코드 밖으로 꺼내고, 교차 검증으로 모순을 잡는다"는 원리는 언어와 무관하다. 그리고 3강에서 배운 Hurl 테스트는 이미 언어에 관계없이 작동한다.
찍먹 체험
에이전트에게 yongol skill을 설치시킨다:
에이전트에게: “npx skills add park-jun-woo/yongol 설치해”
에이전트에게: “Login 기능을 SSOT로 선언해”
AI가 5개 파일을 생성할 것이다. specs/api/openapi.yaml, specs/db/users.sql, specs/service/auth/login.ssac, specs/policy/authz.rego, specs/tests/scenario-login.hurl.
에이전트에게: “yongol validate specs/ 돌려”
0 errors가 나오면 성공이다.
의도적으로 모순을 만들어보자:
에이전트에게: “OpenAPI의 email 필드를 mail로 바꿔”
에이전트에게: “yongol validate specs/ 돌려”
“OpenAPI에는 mail인데 DDL에는 email이다” — 이런 에러가 나올 것이다. 하나의 레이어만 보면 에러가 아니다. 두 레이어를 교차하면 모순이 보인다.
에이전트에게: “validate 에러를 고쳐”
AI가 필드명을 통일한다. 다시 validate. 0 errors.
왜 이렇게 시켜야 하는가
지난 시간 복습
3강에서 우리는 세 가지를 배웠다.
- Hurl로 API의 행위를 선언하고 검증하는 법
- Git으로 세이브 포인트를 만드는 법
- CI/CD로 검증을 자동화하는 법
이 세 가지만으로도 바이브 코딩의 가장 큰 적 — 드리프트 — 을 막을 수 있다. “새 기능 추가해. 단, 기존 Hurl 테스트 전부 통과해야 해.” 이 한 마디가 방어선이다.
그런데 50개 엔드포인트를 넘기면 새로운 문제가 생긴다.
50개를 넘기면 무슨 일이 일어나는가
바이브 코딩으로 SaaS를 만든다고 해보자. 처음에는 빠르다.
“회원가입 만들어” — 2분. “로그인 만들어” — 1분. “프로필 수정 만들어” — 1분.
12개 엔드포인트, 5개 테이블. 20분이면 돌아간다.
50개를 넘기면 이상한 일이 생긴다. AI가 어제 만든 패턴을 오늘 다르게 만든다. 100개를 넘기면 기존 기능이 조용히 깨진다. 200개를 넘기면 새 기능 하나 추가하는 데 처음 10개를 만들 때보다 10배가 걸린다.
왜?
AI가 멍청해서가 아니다.
코드에 섞여 있는 세 가지
소스 코드를 열면 세 가지가 뒤섞여 있다.
사용자 결정 — “이 컬럼은 정수형이다.” “이 API는 소유자만 접근한다.” “페이지네이션은 커서 방식이다.”
비즈니스 로직 — 가격 정책, 워크플로우, 생명주기 규칙.
구현 세부사항 — 변수명, 라이브러리 호출 순서, 에러 처리 코드.
AI가 이 코드를 읽을 때, 어떤 줄이 당신의 결정이고 어떤 줄이 세부사항인지 구별하지 못한다. 그래서 “리팩토링해줘"라고 시키면, 당신의 결정을 세부사항으로 착각하고 조용히 덮어쓴다.
비유하자면 이렇다. 당신이 집을 지을 때 “현관은 남향이어야 한다"고 결정했다. 그런데 인테리어 업자에게 “집 좀 정리해줘"라고 시켰더니, 현관 방향을 바꿔버렸다. “이쪽이 동선이 더 좋아서요.” 업자 입장에서는 최적화다. 당신 입장에서는 재앙이다.
AI가 하는 짓이 정확히 이것이다. 더 큰 모델을 써도 해결되지 않는다. 매체(소스 코드) 자체가 결정을 보존하지 못하기 때문이다.
결정을 코드 밖으로 꺼낸다
해결책은 간단하다. 결정을 코드에서 분리한다.
지금까지 당신이 AI에게 시켜온 방식:
"로그인 API 만들어" → AI가 코드를 쓴다 → 결정과 세부사항이 섞인다
yongol이 제안하는 방식:
당신이 결정을 선언한다 → AI가 선언을 편집한다 → yongol이 코드를 생성한다
결정은 선언적 명세에 살고, 코드는 일회용 투영이다. 결정이 바뀌면 선언을 고치고 코드를 다시 생성한다. 세부사항이 바뀌면 코드만 다시 생성한다. 서로 섞이지 않는다.
SSOT 10종 — 각자 하나의 관심사만 담당한다
yongol은 소프트웨어를 구성하는 결정을 10개의 선언적 명세(SSOT: Single Source of Truth)로 분리한다. 각 명세가 하나의 관심사만 담당한다.
여러분이 이 이름들을 외울 필요는 없다. 역할별로 묶으면 직관적이다:
데이터를 정의하는 것:
| SSOT | 담당하는 결정 | 쉽게 말하면 |
|---|---|---|
| features.yaml | 기능 카탈로그 | “뭘 만들 것인가” |
| manifest.yaml | 프로젝트 설정 | “인증은 JWT, DB는 PostgreSQL” |
| SQL DDL | 데이터 모델 | “이 테이블에 이 컬럼을 저장한다” |
| sqlc | DB 쿼리 | “이 SQL로 데이터를 조회한다” |
행동을 정의하는 것:
| SSOT | 담당하는 결정 | 쉽게 말하면 |
|---|---|---|
| OpenAPI | API 계약 | “이 주소로 이 데이터를 보내면 이 응답이 온다” |
| SSaC | 서비스 흐름 | “조회 → 검증 → 생성 → 응답 순서로 처리한다” |
| Mermaid stateDiagram | 상태 전이 | “주문은 대기 → 승인 → 완료 → 취소 순서로 변한다” |
검증하는 것:
| SSOT | 담당하는 결정 | 쉽게 말하면 |
|---|---|---|
| OPA Rego | 인가 정책 | “관리자만 삭제할 수 있다” |
| Hurl | 테스트 시나리오 | “이렇게 호출하면 이렇게 응답해야 한다” |
화면을 정의하는 것:
| SSOT | 담당하는 결정 | 쉽게 말하면 |
|---|---|---|
| STML (Service Template Markup Language) | 프론트엔드 | “화면에 이 데이터를 이렇게 보여준다” |
10종이 많아 보이는가? 겁먹을 필요 없다. 세 가지 사실을 알면 된다.
첫째, 10종 중 8종은 업계 표준이다. OpenAPI, SQL, sqlc, Rego, Mermaid, Hurl, YAML — 프로 개발자들이 쓰는 업계 표준 도구인데, 여러분이 직접 쓸 일은 없다. AI가 안다. yongol이 새로 만든 것은 SSaC(서비스 흐름)와 STML(프론트엔드) 두 가지뿐이다.
둘째, 당신이 이것을 배울 필요는 없다. AI가 안다. 당신은 “회원가입 기능 만들어"라고 하고, AI가 10개 명세를 편집한다. 당신이 보는 것은 결과뿐이다.
셋째, yongol은 현재 Go 프로젝트 전용이다. React+FastAPI나 Next.js 같은 스택에서는 아직 사용할 수 없다. 하지만 4강에서 배우는 원리 — 결정을 코드 밖으로 꺼내고, 교차 검증으로 모순을 잡는다 — 는 언어와 무관하다. 원리를 이해하면 도구가 확장되었을 때 바로 적용할 수 있다. 그리고 3강에서 배운 Hurl 테스트는 이미 언어에 관계없이 작동한다 — yongol 없이도 API 계약 검증은 지금 당장 할 수 있다.
10만 줄 vs 1만 2천 줄
왜 굳이 결정을 따로 빼야 하는가? 숫자로 보면 바로 이해된다.
| 규모 | 예시 | SSOT (결정만) | 구현 코드 |
|---|---|---|---|
| 소형 | 미용실 예약 | ~1,500줄 | ~1만 줄 |
| 중형 | Jira, Notion급 | ~12,500줄 | ~10만 줄 |
| 대형 | Shopify급 | ~30,000줄 | ~30만 줄 |
중형 SaaS를 예로 들면, 10만 줄의 코드 중 결정은 12,500줄이다. 나머지 87,500줄은 배선 — 에러 핸들링, 라이브러리 호출, 보일러플레이트다.
AI에게 10만 줄을 읽게 할 수 있다. 1M 토큰 컨텍스트라면 물리적으로 가능하다. 하지만 읽을 수 있다는 것과 정확하게 다룰 수 있다는 것은 다르다. 컨텍스트가 길어질수록 중간 정보를 놓치고, 불필요한 토큰이 판단을 흐린다.
결정만 분리하면 12,500줄이다. 노이즈 없이 핵심만 남은 컨텍스트에서 AI의 정확도는 올라간다. 같은 AI가 같은 작업을 하는데, 읽는 양이 8분의 1로 줄면 정확도가 올라간다. 컨텍스트를 ~10배 압축하는 효과다.
operationId — 전 레이어를 하나로 꿰는 열쇠
10개 명세가 따로 놀면 혼란이다. 연결해야 한다. 어떻게?
이름 하나로.
버튼 하나를 누르면 서버에 요청이 하나 간다. 그 요청 하나하나를 엔드포인트라고 부른다.
풀스택 애플리케이션에서 기능의 단위는 API 엔드포인트다. 사용자가 버튼을 누르면 API가 호출되고, 그 API가 서비스 로직을 실행하고, DB를 읽고, 권한을 확인하고, 상태를 전이시킨다. 이 흐름의 시작점이 operationId다.
하나의 기능이 어떤 파일들에 걸쳐 있는지 한눈에 보여준다.
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
이 출력을 읽을 줄 몰라도 된다. 중요한 건 operationId 하나로 전체를 추적할 수 있다는 것이다.
API 스펙부터 DB 스키마, 인가 정책, 상태 전이, 함수 구현, 테스트 시나리오까지 — 기능 하나의 전체 지형이 한 화면에 보인다.
이것이 yongol이 operationId를 **키스톤(keystone)**이라고 부르는 이유다. 건축에서 아치의 꼭대기에 마지막으로 끼우는 쐐기돌 하나가 아치 전체를 지탱하듯, PascalCase 식별자 하나가 10개 레이어를 물리적으로 묶는다.
코드 리뷰할 때 — “이 기능을 수정했으면 저 파일도 바뀌어야 하는 거 아니야?” 이 의심을 chain과 대조하면 바로 확인된다. 새 팀원이 합류해서 “ExecuteWorkflow가 어떻게 동작해요?“라고 물으면, Feature Chain 하나를 보여주면 된다. Grep 수십 번이 명령어 하나로 대체된다.
SSaC — 함수 내부의 결정을 캡처한다
10개 SSOT 중에서 가장 독특한 것이 SSaC(Service Sequences as Code)다.
기존 SSOT를 보면, OpenAPI는 “어떤 요청을 받고 어떤 응답을 주는가"를 선언한다. SQL DDL은 “뭘 저장하는가"를 선언한다. 그런데 함수 내부 — “조회 → 검증 → 생성 → 응답"이라는 비즈니스 흐름 — 은 선언할 곳이 없었다. 구현 코드를 읽어야만 알 수 있었다.
SSaC가 이 빈 자리를 채운다.
실제 예시를 보자. “제안서를 수락한다(AcceptProposal)“라는 기능이다.
먼저 한국어로 읽으면 이렇다:
- 제안서를 조회한다
- 제안서가 없으면 “찾을 수 없음” 에러를 낸다
- 제안서에 연결된 프로젝트를 조회한다
- 프로젝트가 없으면 “찾을 수 없음” 에러를 낸다
- 이 요청을 보낸 사람이 프로젝트 소유자인지 권한을 확인한다
- 제안서 상태가 수락 가능한 상태인지 확인한다
- 프로젝트 상태가 수락 가능한 상태인지 확인한다
- 제안서 상태를 “수락됨"으로 바꾼다
- 프로젝트에 프리랜서를 배정하고 상태를 “진행 중"으로 바꾼다
- 결제 대금을 에스크로에 묶는다
- “제안서 수락됨” 이벤트를 발행한다
- 업데이트된 제안서를 다시 조회해서 응답한다
이것을 SSaC로 쓰면 이렇게 된다:
아래 코드는 위의 한국어를 형식에 맞게 적은 것일 뿐이다. 읽을 필요 없다.
// @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() {}
여러분이 이 코드를 읽을 필요는 없다. AI가 쓴다. 여러분은 위의 한국어 문장이 맞는지만 확인하면 된다.
16줄이다. 10개 어노테이션(코드에 붙이는 표시). 이 안에 무엇이 있는가:
- 두 개의 리소스 조회 (
@get) - 존재 여부 검증 (
@empty) - 권한 확인 (
@auth) - 두 개의 상태 기계 검증 (
@state) - 두 개의 업데이트 (
@put) - 에스크로 처리 (
@call) - 이벤트 발행 (
@publish) - 최종 응답 (
@response)
이 16줄에서 구현 코드를 생성하면 100줄이 넘는다. 에러 핸들링, 트랜잭션 관리, 타입 변환, 응답 포맷팅 — 전부 코드 생성이 채운다. 당신이 신경 쓸 것은 “무슨 순서로 처리하는가"라는 결정뿐이다.
SSaC의 전체 어노테이션은 20개 미만이다. 한 페이지로 배울 수 있다. 그리고 다시 말하지만, 당신이 직접 배울 필요는 없다. AI가 쓴다.
yongol validate — 287개 규칙이 모순을 잡는다
결정을 10개 파일에 분산했으니, 파일 간 모순이 생길 수 있다.
- DDL에는
BIGINT인데 OpenAPI에는string이라면? - SSaC에서
@auth를 선언했는데 Rego에 해당 규칙이 없다면? - 상태 다이어그램에는 전이가 있는데 SSaC에 해당 함수가 없다면?
- Hurl에는 테스트가 있는데 features에 없는 엔드포인트를 참조한다면?
모순된 결정은 모순된 코드를 낳는다. 코드가 아무리 깔끔해도 결정이 어긋나면 동작이 틀어진다.
yongol validate가 이것을 잡는다. 10개 명세 사이의 모든 연결을 검사한 결과:
아래 출력을 전부 이해할 필요 없다. 체크마크가 전부 녹색이면 모순이 없다는 뜻이다.
✓ 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 사이의 모든 심볼 참조를 검사한다. 모순이 하나라도 있으면 코드 생성을 거부한다.
여기서 핵심을 짚자. 기존 도구들은 자기 레이어만 본다. OpenAPI 검증기는 OpenAPI 스펙이 유효한지 확인한다. SQL 검증기는 DDL이 유효한지 확인한다. 하지만 “OpenAPI에는 user_id가 string인데 DDL에는 BIGINT다” — 이런 레이어 간 모순은 아무도 잡지 않는다. yongol validate의 고유 가치는 이 교차 검증에 있다.
에러가 발생하면 이런 메시지가 나온다:
✗ SSaC CancelReservation
@model Reservation.SoftDelete — method not found in sqlc queries
✗ Cross 1 mismatch
FAILED: Fix errors before codegen.
“CancelReservation 함수가 Reservation.SoftDelete를 호출하는데, sqlc 쿼리에 SoftDelete 메서드가 없다.” 모호하지 않다. 정확히 어디가 어긋나는지 알려준다.
AI는 자유롭게 쓴다. 레일을 벗어나면 validate가 즉시 잡는다. 레일 위의 자유.
yongol agent — 4.5B 모델도 0 에러에 수렴한다
validate가 잡는 것까지는 좋다. 그런데 에러를 고치는 것도 사람이 해야 하나?
아니다. AI가 한다.
yongol agent specs/ --model ollama:gemma4:e4b --max-rounds 20
이 명령어 하나면 AI가 validate → 에러 확인 → 수정 → 다시 validate → 다시 수정을 반복한다. 0 에러가 될 때까지.
실험 결과가 있다. 로그인 엔드포인트 하나를 대상으로, 다양한 모델에게 9개 SSOT 파일을 작성시켰다:
| 모델 | 크기 | 환경 | 결과 |
|---|---|---|---|
| Grok 4.3 | 대형 | API | 첫 시도에 0 에러 |
| Gemini 2.5 Flash | 중형 | API (무료) | 피드백 1회로 0 에러 |
| Gemma4 | 4.5B | 로컬 (16GB VRAM) | 피드백 1회로 0 에러 |
| Qwen3 | 8B | 로컬 | 피드백 1회로 0 에러 |
4.5B 로컬 모델도 된다. 비용 0원. 오프라인. 인터넷 연결 없이.
왜 작은 모델도 되는가? validate가 주는 피드백이 결정론적 사실이기 때문이다. “line 41: field name mismatch, expected ‘user_id’, got ‘userId’” — 이것은 의견이 아니다. 사실이다. 사실에 대해서는 AI가 아첨할 여지가 없다. “네, 고치겠습니다"라고 수용하고 수정한다.
모델의 IQ가 아니라 피드백의 정밀도가 결과를 결정한다.
벤치마크: ZenFlow — 69분에 32 엔드포인트
이론이 아니다. 실측이다.
ZenFlow — 멀티테넌트 워크플로우 자동화 SaaS. 처음부터 끝까지 yongol로 만들었다.
| 단계 | 내용 | 시간 | 누적 |
|---|---|---|---|
| 초기 빌드 | 10 엔드포인트, 6 테이블, 인증, 상태 기계 | 13분 | 13분 |
| + 버전 관리 | 워크플로우 복제, 버전 목록 | 6분 | 19분 |
| + 웹훅 | 웹훅 CRUD, 큐 백엔드 | 6분 | 25분 |
| + 템플릿 마켓플레이스 | 커서 페이지네이션, 크로스 조직 복제 | 3분 | 28분 |
| + 파일 첨부 | 실행 리포트, 파일 백엔드 | 4분 | 32분 |
| + 스케줄링 | 크론 스케줄링, 세션 백엔드 | 6분 | 38분 |
| + 감사 로그 | 오프셋 페이지네이션, 캐시 백엔드 | 3분 | 41분 |
| + 대시보드 | 관계 조인, func 응답 타입 | 7분 | 48분 |
| + 일괄 작업 | jsonb 일괄 삽입 | 14분 | 62분 |
| + 외부 API | 지오코딩 func, 컬럼 추가 | 3분 | 65분 |
| + 조건부 업데이트 | 센티넬 패턴, 자동 할당 | 4분 | 69분 |
최종: 32 엔드포인트, 14 테이블, 47 Hurl 요청. 11/11 단계 통과.
이 숫자에서 가장 중요한 것은 “69분"이 아니다. 기능을 추가할수록 속도가 느려지지 않았다는 것이다.
첫 번째 기능(초기 빌드)은 13분 걸렸다. 열한 번째 기능(조건부 업데이트)은 4분 걸렸다. 바이브 코딩에서 “200 엔드포인트의 벽"이라고 부르는 현상 — 기능이 늘어날수록 추가 비용이 기하급수적으로 증가하는 것 — 이 존재하지 않았다.
기존 테스트도 깨지지 않았다. 47개 Hurl 요청이 매 단계마다 전부 통과했다.
생성된 코드를 편집할 수 있는가
“코드를 자동 생성하면, 제가 직접 고친 부분은 날아가는 거 아닌가요?”
가능하다. yongol generate는 재실행 시 사용자 편집을 보존한다.
- 모든 생성 파일에
//yg:checked llm=yongol-gen hash=<8hex>어노테이션이 붙는다. - 당신이 코드를 수정하면 해시가 달라진다.
- 해시가 달라진 파일은 보존(preserved) 상태로 표시되고, 다음 generate에서 건너뛴다.
yongol status로 보존 파일과 계약 드리프트를 확인할 수 있다.
SSOT가 진실이고, 생성된 코드는 투영이지만, 당신이 투영 위에 그린 그림은 지켜진다.
왜 더 큰 모델이 답이 아닌가
“GPT-6가 나오면 해결될 거야.”
해결되지 않는다. 문제는 모델의 지능이 아니라 매체다.
코드라는 매체는 결정과 구현을 구분하지 않는다. 어떤 모델이든 코드를 읽으면 결정과 세부사항이 뒤섞인 텍스트를 본다. 모델이 아무리 똑똑해도, 매체가 구분을 제공하지 않으면 구분할 수 없다.
yongol은 매체를 바꾼다. AI가 편집하는 대상을 코드에서 선언적 명세로 옮긴다. 명세에는 결정만 있고 구현 세부사항이 없으므로, AI가 결정을 세부사항으로 착각할 일이 없다.
작은 LLM이 SSOT만 편집하고, validate가 매 실수마다 정밀한 피드백을 주면, 훨씬 큰 모델이 raw code를 편집하는 것과 같은 수준의 결정 무결성을 유지할 수 있다.
더 큰 모델이 아니라, 더 정밀한 구조가 답이다.
에이전트 워크플로우 — 당신이 볼 것은 결과뿐이다
실제로 yongol을 사용하는 흐름은 이렇다:
1. "예약 기능 만들어"라고 시킨다
2. AI가 specs/ 안의 SSOT를 편집한다
3. yongol validate specs/ — 정합성을 확인한다
4. 에러가 있으면 → AI가 해당 SSOT를 수정 → 3번으로 돌아간다
5. 에러 0 → yongol generate — 코드를 생성한다
6. Hurl 테스트가 자동으로 실행된다
7. 통과 → 커밋. 다음 기능으로.
당신이 코드를 읽을 필요가 없다. SSOT를 읽을 필요도 없다. “만들어” → “됐어?” → “됐다” — 이 루프만 돌면 된다. 달라지는 것은 뒤에서 깨지지 않는다는 것이다.
바이브 코딩의 경험은 그대로다. 200 엔드포인트의 벽만 사라진다.
정리 — 이 강의에서 기억할 것
코드에는 결정과 세부사항이 섞여 있다. AI는 이 둘을 구별하지 못한다. 이것이 드리프트의 근본 원인이다.
결정을 코드 밖으로 꺼낸다. 10개 선언적 명세(SSOT)가 각각 하나의 관심사만 담당한다. 10종 중 8종은 업계 표준이다.
operationId가 키스톤이다. 이름 하나가 10개 레이어를 관통한다. Feature Chain 하나로 기능의 전체 지형을 볼 수 있다.
287개 규칙이 레이어 간 모순을 잡는다. 기존 도구는 자기 레이어만 본다. yongol validate는 레이어 사이의 균열을 잡는다.
4.5B 모델도 0 에러에 수렴한다. 모델의 IQ가 아니라 피드백의 정밀도가 결과를 결정한다.
실습: Login 엔드포인트를 SSOT로 선언하고 모순을 잡아보기
목표: yongol validate의 교차 검증을 직접 체험한다.
단계 1: 환경 준비
아래 명령어를 터미널에 복사해서 실행하면 yongol 기능이 설치된다.
npx skills add park-jun-woo/yongol
AI 에이전트에 yongol skill을 설치한다.
단계 2: Login 엔드포인트 선언
AI에게 시킨다: “Login 기능을 SSOT로 선언해.”
AI가 5개 파일을 생성할 것이다:
specs/api/openapi.yaml— POST /auth/loginspecs/db/users.sql— CREATE TABLE usersspecs/service/auth/login.ssac— @get → @empty → @call → @responsespecs/policy/authz.rego— 인가 정책specs/tests/scenario-login.hurl— 로그인 테스트
단계 3: 검증
yongol validate specs/
0 errors가 나오면 성공이다.
단계 4: 의도적으로 모순 만들기
AI에게 “OpenAPI의 email 필드를 mail로 바꿔"라고 시켜본다. DDL과 SSaC는 그대로 둔다.
yongol validate specs/
에러가 나올 것이다. “OpenAPI에는 mail인데 DDL에는 email이다.”
이것이 교차 검증이다. 하나의 레이어만 보면 에러가 아니다. 두 레이어를 교차하면 모순이 보인다.
단계 5: 모순 해소
AI에게 시킨다: “validate 에러를 고쳐.” AI가 필드명을 통일할 것이다. 다시 validate. 0 errors.
단계 6: 코드 생성
yongol generate specs/ artifacts/
Login 기능의 전체 코드가 생성된다. SSaC 16줄에서 100줄 이상의 구현 코드가 나온다.
이 실습에서 느꼈어야 하는 것:
- 결정(SSOT)과 구현(생성 코드)이 분리되는 감각
- 교차 검증이 레이어 간 모순을 잡는 순간
- “고쳐"라고 시키면 validate 피드백을 따라 AI가 수렴하는 과정
연관 글
- yongol — AI 코딩 SaaS의 용골 — yongol의 아키텍처, 10개 SSOT, 287개 교차 검증 규칙에 대한 기술적 상세
- Feature Chain — operationId 하나로 10개 레이어를 관통하는 추적 체계
- SSaC — Service Sequences as Code. 함수 내부의 비즈니스 흐름을 선언적으로 캡처하는 방법
Reins Engineering 전체 강의
| 강 | 제목 |
|---|---|
| 제 1강 | AI에게 시키는 법 |
| 제 2강 | AI를 못 믿는 법 |
| 제 3강 | 깨지지 않는 앱 |
| 제 4강 | 결정을 코드 밖으로 |
| 제 5강 | 고삐 있는 AI |
| 제 6강 | 통과하면 잠근다 |
| 제 7강 | 아첨을 뒤집는 법 |
| 제 8강 | 에이전트의 공장 |
| 제 9강 | 코드 너머의 자동화 |
| 제 10강 | 데이터의 법 |
근거 자료
- ZenFlow 벤치마크 — 69분에 32 엔드포인트, 14 테이블, 47 Hurl 요청. 11/11 단계 통과. 기능 추가 시 속도 저하 없음
- yongol agent 모델 실험 — Grok 4.3 (첫 시도 0 에러), Gemini 2.5 Flash (피드백 1회 0 에러), Gemma4 4.5B (피드백 1회 0 에러), Qwen3 8B (피드백 1회 0 에러)
- yongol validate — 287개 규칙, 10개 SSOT 간 교차 검증
- 중형 SaaS 코드 규모 비교 — SSOT 12,500줄 vs 구현 코드 10만 줄 (컨텍스트 ~10배 압축)