Check a model before you ship it
A model is not finished when it is written. It is finished when the checker accepts it. This chapter is the procedure for getting there — and the checker itself, running on this page, so you can put a document through it without installing anything.
The published validators
Two ES modules are published at fixed URLs on this site. They are bundled from
language/checker.ts and language/fixer.ts by
scripts/build-language-tools.ts — the same engines the command line runs, not a lighter
web edition — so a document that passes here is a document appwithai will accept.
| Module | URL | What it gives you |
|---|---|---|
checker.js |
https://www.appwithai.org/guide/checker.js |
Every diagnostic bun language/checker.ts prints, as check(source). |
fixer.js |
https://www.appwithai.org/guide/fixer.js |
The five auto-repairs, plus checkAndFix(source) — repair and re-check in one call. |
import { check, formatReport } from "https://www.appwithai.org/guide/checker.js";
import { checkAndFix } from "https://www.appwithai.org/guide/fixer.js";
const report = check(source);
// { ok, counts: { errors, warnings, infos }, issues: [...], languageVersion }
console.log(formatReport(report));
Both also attach themselves to globalThis as EMLChecker and
EMLFixer, so a page that loaded them with a bare import and no bindings can
still call them. They are built against EML 1.2.0, and every report says
which version produced it.
Check a model now
Everything below runs in this tab, against those two modules at those two URLs. Nothing is uploaded.
The document
Start from an example, break one on purpose, or paste your own.
The authoring protocol
This is the procedure the specification asks anyone — person or model — to follow when someone describes a business and asks for an application. Four steps, in order. The first is the one most often skipped, and skipping it is what produces a schema that validates and a business that does not run.
The deliverable is one file — <business-name>.mmd, every byte of it Mermaid.
Steps 1, 3 and 4 exist to make that file correct; only step 2 produces it. A reply that
describes the model instead — headings, an entity glossary, a lifecycle drawn as an arrow in a
sentence — is not a model however thorough it is. Paste one into the checker above and it answers
EML004, empty document: no entities, rules, or workflows found, because to the
parser it is empty.
-
Enhance the brief before modelling it
A business description is always thinner than the model it implies. Someone who asks for “a booking system for a dance studio” has not mentioned instructors, rooms, cancellation windows, waitlists, or what happens to a class when its instructor calls in sick — and every one of those is a table, a rule or a workflow.
Research first, and attack your own plan. Put on your thinking cap and research the domain as widely as you can — how such a business really runs, its roles, records and lifecycles, the unhappy paths, the regulation and standards that apply, how established products model the same records. Then review the resulting plan adversarially, as a sceptical domain expert would: what rests on weak evidence, what is missing, what the business does not need, what the generator would make of it. Record each objection and what you did about it, and repeat while a pass still changes the plan. This runs even when the user attached a model — it is the gaps in an existing model that are hardest to see. Only then start building and reviewing with the user (
llms.txt§1.1.2.1).So think the business through before writing a byte of Mermaid: an enhanced specification, stated back to the user in the reply. It is analysis, not the artifact — it never becomes the file, and it never carries the
.mmdname. It covers all five of:- The entity model — every entity the business needs, not only the ones named, with types, modifiers, keys, foreign keys and cardinalities, including the reference and lookup entities the described flow silently depends on.
- Each entity's workflow — the states a record moves through, the event on each edge, and who may make each move. An entity with a
statusfield has a state machine whether or not anyone drew it. - The business rules — what decides what, the inputs each reads, and whether it merely decides or must also act. A rule that must act needs
%%action. - Cross-entity effects — where changing one entity must create or update another: an order line decrementing stock, an approval writing a ledger entry, a cancellation releasing a slot. These are the ones most often missed.
- The multi-step workflows — any flow spanning more than one entity or actor, end to end, including what happens when a step fails and what compensates a step already taken.
State assumptions as assumptions. Where the description leaves a genuine fork — two plausible businesses, materially different models — name the fork, model the reading you recommend, and say what changes if the other was meant. A question raised instead of a file leaves the reader with nothing to correct; raised beside one they can already run, it costs them a sentence to answer.
-
Write the model file
Only now write the document: the ERD section, the rules sections, the workflow sections, and the directives that give them meaning. Every entity from step 1 appears; every rule is a
%%ruleflowchart or an%%action; every cross-entity effect is a hook, an action or a step; every multi-step flow is a saga.Name the file after the business —
acme-dance-studio.mmd, lower-case, hyphenated, one file. It opens on a%%line —%%meta name:before the first section keyword — and every line is a Mermaid statement, an EML%%directive, or blank: no Markdown headings, no bullets, no fences, no prose. Everything the language adds already rides in Mermaid comments, so anything you want to say goes in the reply or in a%%line.Check a directive's
statusbefore relying on it:%%entity,%%ruleand%%triggerare validated but not compiled, so do not promise behaviour that rides on them. -
Validate — three runs, three repairs
Run the checker at least three times. Not ceremony: a repair can uncover a problem the original error was masking, so a single clean pass immediately after an edit describes a document that no longer exists. The third pass over unchanged bytes is what makes “clean” mean clean.
If it fails, correct the cause in the model — not the symptom in the text — and re-run, up to three correction attempts. Do not deliver a document that still has errors after three. Deliver the best version you have, say plainly which diagnostics remain, and let the user decide.
-
Deliver
Hand back the finished
<business-name>.mmdas a downloadable file, not a fenced block someone has to copy out of a chat log — and the file that was validated, not a draft of it. Alongside it, in the reply rather than inside the file: the enhanced specification from step 1, so the user can correct what you inferred; the final checker result with counts; and anything you assumed or left unresolved.
// Pass 1 — repair the five auto-fixable codes, then re-check what survived.
let report = checkAndFix(model);
model = report.source;
// Passes 2 and 3 — check the repaired bytes, and confirm they are stable.
for (let pass = 2; pass <= 3 && report.ok; pass++) {
report = { ...check(model), source: model };
}
Silently shipping a model that fails validation is the one outcome worse than not finishing. If it does not come clean in three attempts, say so, with the diagnostics that remain and what you tried.
Reading a diagnostic
Every finding carries a code, a line and a hint, and the codes
are banded so the band alone tells you where the fault is.
| Band | Where the fault is |
|---|---|
EML0xx | The document — its structure, its sections, its metadata. |
EML1xx | Entities — attributes, types, keys, relationships. |
EML2xx | Directive-declared hooks, rules and workflows. |
EML3xx | Rule flowcharts. |
EML4xx | Workflow sections. |
EML5xx | Cross-section consistency — the two halves of the document disagreeing. |
Five codes can be repaired without being told what was meant, and the fixer repairs exactly those:
EML001, EML114, EML117, EML421 and
EML422. Everything else needs a decision, which is why checkAndFix reports
what it could not touch rather than guessing.
Warnings are not noise. Most describe something the generator accepts and quietly gets wrong — a dropped modifier, a state no enum backs, a rule that can decide but cannot act. Clear them, or be able to say why you left one.
The same check, from a command line
This page is one way in. If you have a shell — or you are a language model with one — the same three passes run in a single command, against the same two modules:
curl -sO https://www.appwithai.org/guide/check-model.mjs
node check-model.mjs my-business.mmd
It finds checker.js and fixer.js — beside itself if all three were
downloaded together, otherwise from this site — runs checkAndFix and then two
check passes over the repaired bytes, prints the report, and exits 0 when
the model is clean and 1 when the generator would refuse it. --write saves
the repaired document back over the input. It is a runner, not a second checker: every diagnostic it
prints comes from the published modules, which is the same reason this page has no validation logic
of its own.
Valid is not the same as finished
Everything above answers one question: would the generator refuse this model. It does not answer the other one. Nothing in the checker requires a model to have anything — a single entity with a primary key and a name is a valid EML document with no lifecycle, no rules, no access control and not one word of help text. Zero errors, zero warnings, and the verdict line says the generator accepts this model, which is true and is not the same as saying it is done.
The Audit button above runs the same twenty-three checks in this page, on whatever is
in the box — the exported audit() function of the published
audit-model.mjs, with this page's checker and fixer passed in. Nothing leaves your browser.
Its last line is the score; 23 passed, 0 failed is a finished model.
curl -sO https://www.appwithai.org/guide/audit-model.mjs
node audit-model.mjs my-business.mmd
That is the second runner, published beside the first and resolved the same way. It scores the model
against the delivery checklist — twenty-three checks: the file's shape, a primary key per entity, the
FK modifier on every reference column, an enum binding on every status, a state machine
with a beginning and an end, %%action, %%hook, kind: saga and
%%rbac all present, help on every entity and every column that says something rather
than restating its own name, every line item declared out of its category, and the three checker
passes over the file's own bytes. Its last line is the score, 23 passed, 0 failed, and it
exits 0 only when nothing failed.
It is a scorer, not a third checker. Every diagnostic it reads comes from the same
checker.js this page runs; it adds no codes of its own, and it cannot tell you the model
says what the business meant. The bare entity above passes check-model.mjs with exit
0 and fails eight of them — which is the whole reason it is published rather than kept
as a maintainer's script. Run both before you hand a model over, and report both.
The specification, for machines
The full machine-readable specification of the modelling language — every directive, type, modifier, cardinality, hook, step and diagnostic, plus the authoring protocol above in its authoritative form — is published as a single file. It describes the language and nothing else: a model is all you need to produce, so the generator, the framework and the shape of the emitted application are deliberately left out of it.
https://www.appwithai.org/llms.txt
Point a language model at it before asking for a model, and it has the language definition, the
directive table, the checker codes and this protocol without having to guess at any of them. When that
file and language/appwithai-language.json disagree, the definition wins; when the
definition and a shipped compiler disagree, the compiler wins and the definition is the bug.