
The 200th Endpoint
You build a SaaS with vibe coding. At first it’s fast. 5 tables, 12 endpoints — twenty minutes and it runs.
But past 50 endpoints, something strange happens. The AI produces a pattern today that contradicts yesterday’s. Past 100, existing features silently break. Past 200, adding a single feature costs 10x what the first ten cost.
It’s not that the model is stupid.
Decisions and Implementation
Three things are tangled in source code:
- User decisions — this column is
BIGINT, this endpoint is owner-only, pagination is cursor-based. - Business logic — pricing, workflows, lifecycle rules.
- Implementation details — variable names, library call order, error wrapping.
When AI reads this code, it cannot tell which line is a decision and which is a detail. So when it “refactors” or “cleans up,” it silently overwrites decisions it mistook for details. The user doesn’t notice until the behavior is already wrong.
This is why vibe coding collapses at 200 endpoints. A larger model doesn’t fix it. The medium — raw code — simply doesn’t preserve decisions. Every model eventually hits the same wall.
The Keel
The keel is the first bone laid when building a ship. It bears the hull’s weight, prevents side-to-side rolling, and every other structure is built on top of it. A ship built without a keel floats in calm water but warps when the waves come.
A SaaS built with vibe coding is the same. It floats when small. It warps when it grows.
yongol is the keel of AI-coded SaaS.
Move Decisions Out of Code
yongol’s core is simple. Separate decisions from code.
Ten declarative specs (SSOTs) each handle a single concern:
| SSOT | Concern |
|---|---|
| features.yaml | Feature catalog — what to build |
| manifest.yaml | Project config — auth, middleware, infra |
| OpenAPI | API contract — routes, parameters, responses |
| SQL DDL + sqlc | Data model — tables, columns, constraints, queries |
| SSaC | Service flow — decision sequence inside an endpoint |
| Rego | Authorization — who can do what |
| Mermaid stateDiagram | State transitions — entity lifecycles |
| FuncSpec | Custom functions — logic that can’t be expressed as CRUD |
| Hurl | Test scenarios — runtime verification |
| STML | Frontend — page structure and data binding |
Eight of the ten are industry standards (OpenAPI, SQL, sqlc, Rego, Mermaid, Hurl, YAML). Only SSaC and STML are DSLs created by yongol. What the AI must learn from scratch is minimized.
Each SSOT contains only decisions. No implementation details. The AI edits SSOTs; yongol generate renders code from them. Decisions live permanently in the SSOTs; the code is a disposable projection.
Enforcing Consistency
Decisions are now spread across ten files, so contradictions can appear. DDL says BIGINT but OpenAPI says string? SSaC declares @auth but Rego has no matching rule? The state diagram has a transition but SSaC has no corresponding function?
A contradicted SSOT is a corrupted decision. No matter how clean the code is, if the decisions conflict, the behavior is wrong.
yongol validate catches this.
✓ 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
It validates each SSOT individually first, then runs cross-layer checks. ~287 rules inspect every symbolic reference across all ten SSOTs. If a single contradiction exists, compilation is refused.
The AI writes freely. Step off the rails and validate catches it instantly. Freedom on rails.
operationId Is the Keystone
How do you bind ten layers together? With a single PascalCase identifier.
Enter the 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
From API spec to DB schema, authorization policy to state transitions, function implementations to test scenarios — a single feature’s entire topology on one screen. Dozens of greps replaced by one command.
operationId is the keystone because in a full-stack application, the unit of a feature is the API endpoint. A user presses a button, an API is called, and that API cuts through every other layer. One name physically chains ten layers together.
Benchmark: ZenFlow
ZenFlow — a multi-tenant workflow automation SaaS. Claude Sonnet 4.6 wrote the SSOTs; yongol validated them.
| Stage | Description | Time | Cumulative |
|---|---|---|---|
| Initial build | multi-tenant, auth, state machine, 6 tables, 10 endpoints | 23 min | 23 min |
| + Versioning | workflow clone, version list, INSERT…SELECT action copy | 16 min | 39 min |
| + Webhooks | event publish, webhook CRUD, queue backend | 8 min | 47 min |
| + Template marketplace | cursor pagination, cross-org clone, public endpoints | 7 min | 54 min |
| + File attachments | execution reports, file backend | 7 min | 61 min |
| + Scheduling | session-based cron schedule, TTL | 10 min | 71 min |
| + Audit logs | cache backend, pagination, filters | 6 min | 77 min |
| + Dashboard | aggregate API, relation joins, detail views | 14 min | 91 min |
| + Batch operations | bulk action save, JSON serialization | 10 min | 101 min |
| + External API | geocoding API import, coordinate storage | 14 min | 115 min |
| + Conditional update | auto-assign, confidence scoring, conditional branching | 16 min | 131 min |
Final: 30 endpoints, 12 tables, 64 test requests. All green.
Ten features added sequentially. Adding features never slowed down. Existing tests never broke. The 200-endpoint wall didn’t exist.
Running the same spec with Opus: 30 endpoints, 73 test requests, ~76 min. The model changes, the rails stay the same.
Why a Bigger Model Isn’t the Answer
“GPT-6 will fix this.”
It won’t. The problem isn’t model intelligence — it’s the medium.
Code as a medium doesn’t distinguish decisions from implementation. Whatever model reads the code sees text where decisions and details are interleaved. No matter how smart the model is, if the medium doesn’t provide the distinction, the model can’t make it.
yongol changes the medium. It moves what the AI edits from code to declarative specs. Since specs contain only decisions with no implementation details, the AI can never mistake a decision for a detail. Decision survival becomes independent of model size.
A small LLM editing only SSOTs, with validate providing precise feedback on every miss, can maintain the same decision integrity as a much larger model editing raw code. yongol bridges that gap.
Get Started
npx skills add park-jun-woo/yongol
Install the yongol skill into your AI agent (Claude Code, Cursor, Copilot, and more). The agent learns the workflow on install.
To use the CLI directly:
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.
Point an AI at these specs and tell it to add a feature. validate lays the rails; the AI runs on them. There is no wall.
Related
- SSaC — Service Sequences as Code — yongol’s keystone DSL. Declares decisions inside endpoints.
- Feature Chain — Trace a Full Stack with One operationId — Trace all eight layers through a single operationId.
- Ratchet Pattern — How to Make an Agent Finish the Job — The theoretical foundation of how validate feeds back to agents.