Image: AI generated
画像:AI生成
AIがコードを上書きし続けるなら、バイブコーディングが200エンドポイントで崩壊したなら、AIの作業対象をコードから仕様に移したいなら――yongolがその竜骨だ。
200番目のエンドポイント
バイブコーディングでSaaSを作る。最初は速い。テーブル5つ、エンドポイント12個——20分で動く。
だが50エンドポイントを超えると奇妙なことが起きる。AIが昨日作ったパターンを今日は別のものにする。100を超えると既存の機能がひっそりと壊れる。200を超えると、新機能を1つ追加するのに最初の10個を作った時の10倍かかる。
DORA 2025レポートがこれを実証した——AIツールはスループットを2-18%向上させるが、同時に変更失敗率と手戻りを増加させる[1]。AIは既存プロセスの弱点を増幅する「鏡であり乗数」だということだ。
モデルが馬鹿なのではない。
決定と実装
ソースコードには3つのものが混在している:
- ユーザーの決定 ——このカラムは
BIGINTだ。このエンドポイントは所有者だけがアクセスする。ページネーションはカーソル方式だ。 - ビジネスロジック ——価格設定、ワークフロー、ライフサイクルルール。
- 実装詳細 ——変数名、ライブラリ呼び出し順序、エラーラッピング。
AIがこのコードを読むとき、どの行が決定でどの行が詳細かを区別できない。だから「リファクタリング」や「整理」をするとき、決定を詳細と間違えてひっそりと上書きする。ユーザーは動作がすでにおかしくなった後に気づく。1972年、Parnasは「変更される可能性が高い設計決定をインターフェースの背後に隠せ」[2]と言った——彼は人間に向けて語ったのだ。だがAIがコードを編集する今、決定と詳細の区別が媒体自体に存在しなければ、誰も——人間もモデルも——その区別を守れない。
これが200エンドポイントでバイブコーディングが崩壊する理由だ。より大きなモデルを使っても解決しない。**媒体(生のコード)自体が決定を保存しないからだ。**すべてのモデルが結局同じ壁にぶつかる。
竜骨
船を造るとき最初に置く骨格が竜骨だ。船体の重さを支え、左右の揺れを防ぎ、他のすべての構造物が竜骨の上に乗る。竜骨なしで造った船は穏やかな海では浮くが、波が来ると歪む。
バイブコーディングで作ったSaaSもそうだ。小さいときは浮く。大きくなると歪む。
yongolはAIコーディングSaaSの竜骨だ。
Harness with reins ——より大きなモデルではなく、より精密な手綱。決定論的バリデーターがすべての成果物を判定し、ラチェットが進行を強制し、完了かどうかを機械が決定する。
決定をコードの外へ
yongolの核心はシンプルだ。決定をコードから分離する。
10個の宣言的仕様(SSOT)がそれぞれ1つの関心事だけを担当する:
| SSOT | 担当 |
|---|---|
| features.yaml | 機能カタログ——何を作るか |
| manifest.yaml | プロジェクト設定——認証、ミドルウェア、インフラ |
| OpenAPI | API契約——パス、パラメーター、レスポンス |
| SQL DDL + sqlc | データモデル——テーブル、カラム、制約、クエリ |
| SSaC | サービスフロー——エンドポイント内部の決定順序 |
| Rego | 認可ポリシー——誰が何をできるか |
| Mermaid stateDiagram | 状態遷移——エンティティのライフサイクル |
| FuncSpec | カスタム関数——CRUDで表現できないロジック |
| Hurl | テストシナリオ——smoke、scenario、invariantの3分類 |
| STML | フロントエンド——Semantic Template Markup Language(data-*属性ベースのHTML) |
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間のすべてのシンボル参照を検査する。矛盾が1つでもあればコンパイルを拒否する。Torresらの系統的文献レビュー[3]は、ほとんどのモデル管理ツールが単一モデル内部の整合性のみを扱い、異種モデル間の交差検証は未解決課題として残っていると指摘した——yongol validateが埋める空白がまさにそれだ。
AIは自由に書く。レールを外れればvalidateが即座に捕まえる。レール上の自由。
yongol next ——ラチェットコマンド
yongol validateがすべてのエラーを一度に表示するのに対し、yongol nextはエラーを1つずつ表示する。これがラチェットだ。
$ 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/`.
AIエージェントに必要な指示は一文だ:「yongol next specs/を実行し、エラーが0になるまで修正せよ。」
エラーを直すと次のエラーが出る。全部通過すれば止まる:
$ yongol next specs/
✓ All validations passed. 0 errors.
エージェントが「完了しました」と宣言するのではなく、機械が「まだ残っている」または「全部通過」を判定する。終了判断権がエージェントにない。
プロジェクト作成と機能管理
yongol init
features.yamlからSSOTスキャフォールディングを自動生成する。
yongol init Myapp features.yaml "My workflow automation SaaS"
cd Myapp && yongol validate specs # 0 errors
manifest、OpenAPI operationIdスタブ、SSaCスタブファイル、Rego認可ルール、Hurlスモークテスト、sqlc設定が一度に生成される。プロジェクトが即座にyongol validate通過状態で始まる。
yongol features add / remove
機能を追加または削除する:
yongol features add new_features.yaml # 新operationIdのSSaCスタブ生成
yongol features remove ExportWorkflow --yes # operationId削除 + SSaCスタブ削除
yongol import
外部OpenAPI(Stripe、GitHubなど)からGoクライアントパッケージを生成する:
yongol import https://api.stripe.com/openapi.yaml ./external/
生成された関数をSSaCで@call <pkg>.<Func>({...})として呼び出す。
operationIdがキーストーンだ
10個のレイヤーをどうやって束ねるか?PascalCaseの識別子1つで。
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仕様からDBスキーマ、認可ポリシーから状態遷移、関数実装からテストシナリオまで——機能1つの全体地形が一画面に見える。grep数十回が1コマンドに置き換わる。
operationIdがキーストーンである理由は、フルスタックアプリケーションにおける機能の単位がAPIエンドポイントだからだ。ユーザーがボタンを押すとAPIが呼ばれ、そのAPIが他のすべてのレイヤーを貫通する。この名前1つが10個のレイヤーを物理的にチェイニングする。
SSaC ——なぜカスタムDSLなのか
yongolの10 SSOTのうち8種は業界標準だ。SSaC(Service Sequences as Code)とSTMLだけがyongolが作った。SSaCはサービスフローの決定をキャプチャする。
**SSaCが埋める空白。**宣言的ツールのスペクトラムを見ると、一方の端に契約標準(OpenAPI、SQL、Rego)がある——何を宣言するがどの順序では宣言しない。反対の端にワークフローランタイム(Temporal、Inngest、Restate)がある——それらはコードだ。決定と実装詳細が同じファイルで再結合する。SSaCはその間の空白に座る:「1つのエンドポイント内部で何が、どの順序で、どのガードとともに起きるか。」
SSaCのアノテーション全体は16個だ。1ページマニュアルで学習可能だ。
SSaCアノテーション全リスト
| アノテーション | 役割 | 形式 |
|---|---|---|
@get | DB読み取り | 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 | ログイン(タイミング防御) | 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アノテーション。2つの状態機械、認可、エスクロー、キューイベント、レスポンス——すべての決定が見え、すべての詳細がない。
ベンチマーク:ZenFlow
ZenFlow——マルチテナント・ワークフロー自動化SaaS。
| 段階 | 内容 | 時間 | 累計 |
|---|---|---|---|
| 初期ビルド | 10エンドポイント、6テーブル、認証、状態機械 | 13分 | 13分 |
| + バージョン管理 | ワークフロー複製、バージョンリスト | 6分 | 19分 |
| + Webhook | 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
SSOTファイルをLLMがvalidate-fixループで自動修正する。
yongol agent specs/ --model ollama:gemma4:e4b --max-rounds 20
validateエラーをLLMにフィードバックし、LLMが修正し、再びvalidateする。0エラーになるまで繰り返す。ローカル4.5Bモデル(Gemma4)でも動作する。
対応バックエンド:ollama(ローカル)、xai(Grok)、gemini。
生成されたコードを編集できるか
できる。yongol generateは再実行時にユーザー編集を保持する:
- すべての生成ファイルに
//yg:checked llm=yongol-gen hash=<8hex>アノテーションが付く。 - ハッシュが異なれば、そのファイルは**保持(preserved)**状態となり、次の
generateでスキップされる。 yongol statusで保持ファイルと契約ドリフト(PRV-01/PRV-02エラー)を確認できる。- 意図を記録するには
//yg:preserve reason="..."を追加する(任意)。保持解除はファイル削除で。
ビルトイン関数とモデル
ビルトイン関数(SSaCで@callとして呼び出し)
| パッケージ | 関数 | 説明 |
|---|---|---|
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
曖昧な変更(カラム名変更、型キャスティング、NOT NULLバックフィル)はDDLコメントヒント(-- @rename、-- @cast、-- @backfill、-- @data_migration、-- @allow_destructive)で区別する。6つのルール(MIG-001〜MIG-006)が危険な変更をゲートする。実際のDB適用はgolang-migrate、flywayなどの標準ツールに委任する。
なぜより大きなモデルが答えでないのか
「GPT-6が出れば解決するよ。」
解決しない。問題はモデルの知能ではなく媒体だ。
コードという媒体は決定と実装を区別しない。どのモデルがコードを読んでも、決定と詳細が混在したテキストを見る。モデルがどれだけ賢くても、媒体が区別を提供しなければ区別できない。
yongolは媒体を変える。AIが編集する対象をコードから宣言的仕様に移す。仕様には決定だけがあり実装詳細がないので、AIが決定を詳細と間違える余地がない。決定の生存がモデルサイズと無関係になる。
小さなLLMがSSOTだけを編集し、validateが毎回のミスに精密なフィードバックを返せば、はるかに大きなモデルが生のコードを編集するのと同じレベルの決定無欠性を維持できる。yongolがその差を埋める。
ランタイムテスト
Hurlテストはすべてユーザーが作成する。specs/tests/に作成すればyongol generateがartifacts/tests/にミラーリングする。検証時にXOH-01〜09ルールがHurlをOpenAPI、状態機械、manifest.authと交差検証する。
hurl --test --variable host=http://localhost:8080 artifacts/my-project/tests/*.hurl
3つの分類:
- smoke.hurl ——エンドポイントスモークテスト
- scenario-*.hurl ——ビジネスシナリオテスト
- invariant-*.hurl ——エンドポイント間不変式テスト
現在の状態
Go+Ginバックエンド生成:Beta ——エンドツーエンド動作。Reactフロントエンド生成:Alpha(作業中)。
始め方
方法1:スキルインストール(推奨)
npx skills add park-jun-woo/yongol
AIエージェント(Claude Code、Cursor、Copilotなど)にyongolスキルをインストールする。エージェントがワークフローを自動で学習する。
/yongol 認証とCRUDのあるマルチテナントToDoアプリSaaSを作って。
方法2:直接インストール
Go 1.25+およびgcc(cgo依存:pg_query_goがDDLパース用にlibpg_queryをリンク)が必要。
git clone https://github.com/park-jun-woo/yongol && cd yongol
make install
yongol validate examples/zenflow
0 errors, 0 warnings。
この仕様の上でAIに機能を追加させてみよう。validateがレールを敷き、AIがレール上を走る。壁はない。
関連記事
- SSaC — Service Sequences as Code ——yongolのキーストーンDSL。エンドポイント内部の決定を宣言する。
- Feature Chain — operationId1つでフルスタックを追跡する ——operationIdで8つのレイヤーを貫通する追跡。
- Ratchet Pattern — エージェントを最後まで行かせる方法 ——validateがエージェントにフィードバックを与える構造の理論的背景。
- 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テスト3分類(smoke/scenario/invariant)、ランタイムテストセクション、Preserve詳細化(PRVエラーコード)、DDLマイグレーションヒント拡張(@data_migration、@allow_destructive、MIGルール)、現在の状態(Go+Gin Beta、React Alpha)、インストール方法2種類に分離 |