Models write code well and write the same code twice badly. So put the model where it is strong and a compiler where it is not: the model writes a declarative specification, a deterministic compiler turns that specification into the application, and a shared runtime carries what every business app needs identically. The format, the compiler, the CLI and the generator are Apache-2.0. It is early, lots to work on, expect bugs.
Switch between the tabs. The first file is what a person writes: one aggregate per file, top-level entity:, role: or lifecycle:. The other three are what the dotnet-vue generator emits from it, unedited.
type: money becomes a decimal property, a numeric(18,4) column and a migration. indexed: true becomes a HasIndex. type: reference with onDelete: setNull becomes a nullable foreign key and the delete behaviour behind it. Role files become the authorization the controller inherits, so there is no route where a permission check can be forgotten, because no route is hand-written.
What is absent from the input is the point. No SQL, no DTO, no mapper, no controller, no form. All of it is derivable from the declaration, and anything derivable is the compiler's job.
grants: per entity, enforced at the routeAsk a model for the same application twice and you get two of them. Different file layout, different names, a different opinion about which layer owns the permission check, and a bug in one that is absent from the other. Nothing differed between the runs except the path taken through the distribution. That is what sampling is, and no amount of prompting removes it.
cordango build has no such freedom. Given a definition and a generator version the output tree is fixed: no timestamps, no generated identifiers, no absolute paths, no culture-sensitive formatting anywhere in the emitted files. CI builds the same fixture twice and diffs the trees. Seeded sample data is reproducible from --seed 42, and SEED_DATE=today is an explicit run-time opt-out so the build itself stays pure.
What this buys is reviewability, not correctness. A diff between two builds contains your change and nothing else, which is what makes a generated tree something you can put under review at all. A wrong definition still compiles into a wrong application, very reliably.
Two_runs_produce_byte_identical_trees()The_build_metadata_carries_no_clock_and_no_machine()definitionHash lands in the build record, so two builds compare in one lineCORD21xx/CORD23xx and fail the build, rather than emitting less than you declaredIf the definition is the generated artefact and the application is compiled from it, the expensive half is never sampled. We measured that across five applications on Anthropic's token counting endpoint, comparing definition source against a from-scratch implementation of the same app: between 90.6 and 98.4 per cent of output tokens are never spent. Output tokens only, so treat the figure as a floor.
The comparison only holds if the runtime is counted on the codegen side, and it is. An agent starting from an empty directory has no package to depend on, so it emits identity handling, record access, permission enforcement and the audit trail into the application, then emits them again into the next one. That is 8,152 lines and 141,596 tokens, paid per application on that side and once on this one.
The claim is narrower than the percentages suggest. Scaffolding cost collapses; domain cost does not move, because somebody still has to decide what an expense claim is. What changes is the ratio between them, and the absolute figures stay small: under ten euros on the largest app we measured.
And they did the hard part. JHipster has generated Spring applications from a domain language since 2013. Wasp compiles a declarative spec into a working React and Node app. Lowdefy builds real applications out of YAML, Apache-2.0 and mature, and for internal tools over a database you already have it is genuinely good. All of it works.
We wanted more than it gave us. The declaration to own the data model rather than point at one, so schema and migrations are derived instead of maintained. The output to be source code a company keeps and deploys with us nowhere in the loop. And one input to produce one set of bytes, so a generated tree reviews like any other diff.
So Cord YAML declares a domain rather than a screen. Entities, relationships, roles and lifecycles, and no query anywhere in it. The screens, the API, the schema and the migrations are derived from that, and cordango build emits them as an application you own.
Both are worth reading if this is your problem. Wasp compiles main.wasp into a React and Node application. JHipster shipped an MCP server in 2026 so an agent can author the JDL and drive the CLI, which is the same division of labour we are betting on.
The obvious way to split a codebase for open core is by feature: open what is cheap to give away, withhold what took longest. What that produces is something nobody can finish anything with unless they pay you. A demo wearing a licence file.
This is split by job instead. Authoring an application and proving it correct is one job, and all of it is open and runs with no account and no network, because a validator you can only run against somebody else's server is not a validator, it is an API call. Running many companies on one system is a different job, and that is the platform.
The line falls in an awkward place, which is why it is a test rather than a convention. Cordango.Compiler owns the grammar, the parser, the typed AST and author-time checking, because an author has to be told an expression is wrong whether or not it ever runs here, and a generator in any language needs the tree to translate. Evaluation lives in the platform. The build fails if an Eval method appears next to the parser, which is exactly where somebody would reach for it. Apache-2.0 cannot be revoked once it has shipped.
dotnet-vue targetEverything above this line ships today and can be verified from the repository. This part cannot. The bet is that the artefact a team maintains stops being the code and becomes the specification, and that once it does, the step from specification to running software has to be a compiler rather than a model, because that is the step you need to re-run and diff.
It is also why the toolchain is open. If the bet is right, the format matters more than any implementation of it, and a format nobody else can parse or target is not a format. If it is wrong, you still have an application and its source, which is what you would have had anyway.
Three questions we have no finished answer to.
dotnet-vue: ASP.NET Core, EF Core, PostgreSQL, Vue 3 and Vuetify, with a Dockerfile and readable migrationsCordango.SourceGen is the generator contract a target implements. It is what makes a second one possible at all--runtime source vendors that away tooThe argument rests on the definition being smaller than the code and readable by a person. That is easy to show on an expenses app and unproven at two hundred entities. We do not know where the ceiling is, or whether it is a property of the format or of how an application is split across files and aggregates.
The 8,152 shared lines are the ones every application needs identically. We drew that line by hand, from the apps we have built. We do not know which side of it the next unusual requirement falls on, and both failure modes are expensive: a runtime that absorbs domain logic, or a domain that keeps reimplementing the runtime.
If a model authors the definition and a compiler emits the application, the interesting work moves up a layer. Authoring a specification is a different task from writing code, and it is not obvious what a format should expose to make a model good at it. Nobody has a settled answer, us included.
Unregistered targets are listed so the direction is visible, not so you can pass them to --target. When one lands it appears on the roadmap first.
Sixteen applications, written the way we write them. Run cordango build twice and compare the trees before you take our word for any of this.