A2v10 Metadata-Driven Skill
1. What a metaendpoint is
Every A2v10 application is metadata-driven. A metaendpoint is an endpoint declared by its metadata.json: the platform generates schema, behavior and forms from that file.
The marker is metadata.json in the endpoint folder. Present → this skill; it is the only entity here. Absent → not a metaendpoint, not ours. No flag inside any file.
Absence decides because it has exactly one meaning: an endpoint folder is never without one (§4). Nothing is inferred from the other files in the folder — whatever else lies there is a materialization question, not an ownership one → references/materialize.md.
The engine does not distinguish endpoint types: the distinction is ours, and it exists to route between the two skills without asking.
2. Tooling
One CLI, a2, in the form a2 <object> <action>: it reads the project and applies metadata to the database. Whether an application is metadata-driven at all is its answer too, not a file in the repository. → references/cli.md
3. Mechanics
An endpoint is one model — one business entity the engine processes as a single unit, backed by one or more related tables. A document with its rows is one endpoint, not two; that is why addressing stops at the endpoint and never reaches inside.
- URL =
/[$<module>]/<kind>/<endpoint>/<action>/<id> — the platform's addressing model: /document/waybillin/edit/2222, or /$admin/document/waybillin/edit/2222 inside a module.
$<module> (optional) relocates the path's root — a $-prefixed segment is the one thing the runtime interprets. Modules and their roots come from a2 app config.
<kind>/<endpoint> locates the folder. For a raw endpoint both segments are opaque — free names the runtime does not read. For us the first one is neither free nor opaque: kind is the parent folder name (catalog/agent/ → catalog), it comes from a fixed set, and it is interpreted — it decides what gets generated. → references/kinds.md. Other first segments are possible in an application; they are not ours — pay them no attention.
- Endpoint = a
<kind>/ subfolder. → references/metadata.md
<action> and <id> are never authored. For a raw endpoint an action is an element name, freely declared. For a metaendpoint the set is fixed and interpreted by the runtime (index, edit, …) and declared nowhere — you will not find it in metadata.json, and there is nothing to add there. The names surface in exactly one place: model.json, and only for an artifact that has been materialized → references/materialize.md. What you ever write is /<kind>/<endpoint> — in the menu, in a link, everywhere.
4. Must — break it and it does not work
- The schema is converged, never edited. It comes from
metadata.json through a2 meta deploy — the same sequence the runtime runs. Do not write DDL to fix it: a change made by hand is invisible to the deploy hash, so the drift is silent and stays. What deploy will not do by itself — drops, narrowing, renames, changes to data — is proposed in diff.sql or written as a data migration, and the author applies it. → references/deploy_and_migrations.md
- Generated files are products, not sources.
deploydatabase.sql is regenerated from the metadata byte for byte; an edit inside it is gone on the next deploy. diff.sql is a proposal — never execute it as part of a task.
- A
<kind>/ subfolder without metadata.json, or with {}, is an error. Legacy endpoints live in the classic layout, never under a <kind>/.
- Every
metadata.json says where its shape comes from, explicitly. One axis, three keys: table — its own table; storage — a table declared elsewhere that it writes into; surface — a shape it only reads. The folder decides which of them is legal (document — one of table/storage; report — surface; everything else — table), the file decides which one is used. No defaults, never two, never none. → references/metadata.md, checks → references/validate.md
- An operation owns no table, therefore declares no structure — only behavior. It is not a rule to remember: an operation is the file that wrote
storage instead of table.
5. Discipline — orthogonal to the engine
§4 is the engine's contract; this one is yours. Nothing here fails loudly — a metaendpoint that renders proves only that it rendered.
- Do not invent surface. An unknown key is not rejected: the loader drops it silently, so a misspelled or invented key gives no error and no effect — the endpoint works, your rule is simply not there. Not in
references/, not in an existing metadata.json → it does not exist. What is caught before deploy, and what is not → references/validate.md.
- Two markers in
references/, and they say different things. 🟡 — the decision is provisional; the form may still change. 🚧 — the form is settled and the platform does not execute it yet. A 🚧 surface is written down so the decision is not taken twice, not so it can be used: never put it in a metadata.json. Say which construct is missing and stop there — the norm names no substitute, and one improvised in its place is invented surface (above).
- An endpoint with no files of its own is finished, not unfinished. Every scaffolder you have ever seen generates once and lets go, so a folder holding only a
metadata.json reads as a draft and invites XAML or SQL "so that something is there". A file exists only because that artifact was ejected on purpose; a file with nothing behind it is an error, not a spare. → references/materialize.md
- Declared is not reachable. The rule above is about the files inside the folder; this one is about the application around it. An endpoint no
menu.json item names loses its own entrance, not its existence — a catalog still opens in a Ref lookup, and records are still created there — but nothing leads the user to its register, and nothing says so: deploy succeeds, validation passes, the URL works when typed by hand. Every endpoint you declare, you also place → references/menu.md.
- An operation file does not describe its entity completely, and must not try. Every file you have seen elsewhere is self-contained, so repeating
fields from the storage will feel like completeness. Those keys are read by nobody and reported by nothing: the endpoint works, and the copy quietly rots next to the original.
- Do not carry priors from 1C, Frappe or an ORM. This format splits what they merge, and the merged reading drifts back under pressure.
6. Workflow
The project has a CLAUDE.md → read it first, before any action. What it fixes, do not ask again.
No CLAUDE.md → onboarding, exactly once → references/onboarding.md. It forks there on one check — are any endpoints already declared? — because an application that exists must never have the shell written over it.
Open the project with a2 app config, never with the file tree — it is what says where the files go (hostRoot, modules). Reading that off the tree is guessing.
Unsure → ask; never guess.
7. Dispatch — find the task
| Action |
Go to |
create an application — the user has not said metadata-driven or classic → ask; classic → the a2v10 skill |
references/onboarding.md |
| create an endpoint — which kind is it |
references/kinds.md → references/metadata.md |
| add or change a field |
references/metadata.md |
| set initial values, or how a reference is picked |
references/metadata.md |
| turn on a trait |
references/metadata.md |
| make a field required, conditional, computed, inherited |
references/rules.md |
| add an operation to a document family |
references/metadata.md |
| make an operation post into a journal |
references/metadata.md → references/journal.md |
| declare what a journal stores |
references/journal.md |
| number documents with a series |
references/kinds.md → references/metadata.md |
| declare a chart of accounts |
references/accplan.md |
| declare a ledger, make a document post double-entry |
references/ledger.md |
| declare a report over a journal |
references/report.md |
| attach a print blank to an endpoint |
references/print.md |
| lay out a form |
references/forms.md |
| eject / materialize |
references/materialize.md |
| apply metadata to the database, plan a migration |
references/deploy_and_migrations.md |
| wire app navigation |
references/menu.md |
| restrict access — declare roles, grant verbs, hide a menu item |
references/permissions.md — 🚧 the whole subsystem; read it before promising anything |
| add or change localization keys |
references/localization.md |
| check metadata before deploying |
references/validate.md — 🚧 the command does not exist yet |
| build the application and bring up its database |
references/onboarding.md → Phase 3 |
| call the CLI, read its output |
references/cli.md |
No row matches? Do not force-fit. Route by what you are declaring: the shape of a record → references/metadata.md; a rule tying its fields together → references/rules.md; what it looks like on screen → references/forms.md. Still unclear → ask.
Materialized artifacts — XAML, SQL, template.ts — are written in the a2v10 skill's formats; their syntax is that skill's job, not this one's. Which artifact is materialized at all is stated in model.json → references/materialize.md.
1---2name: a2v10meta3description: A2v10 endpoints declared in metadata.json (catalog, document, journal, report) and deployed with the a2 CLI. USE if the project contains metadata.json files, or the request mentions metadata.json, metaendpoint, metadata-driven, or a2 meta. ALSO USE, ahead of the a2v10 skill (it matches the same request on the word A2v10), when asked to create or start a new A2v10 application and the folder has no model.json and no metadata.json yet, unless the request already says classic: this skill asks whether metadata-driven or classic; classic then hands off to the a2v10 skill. Endpoint folder without metadata.json: a2v10 skill.4---56# A2v10 Metadata-Driven Skill78## 1. What a metaendpoint is910Every A2v10 application is metadata-driven. A **metaendpoint** is an endpoint declared by its `metadata.json`: the platform generates schema, behavior and forms from that file.1112**The marker is `metadata.json` in the endpoint folder.** Present → this skill; it is the only entity here. Absent → not a metaendpoint, not ours. No flag inside any file.1314Absence decides because it has exactly one meaning: an endpoint folder is never without one (§4). Nothing is inferred from the other files in the folder — whatever else lies there is a materialization question, not an ownership one → `references/materialize.md`.1516The engine does not distinguish endpoint types: the distinction is ours, and it exists to route between the two skills without asking.1718## 2. Tooling1920One CLI, **`a2`**, in the form `a2 <object> <action>`: it reads the project and applies metadata to the database. Whether an application is metadata-driven at all is its answer too, not a file in the repository. → `references/cli.md`2122## 3. Mechanics2324An endpoint is **one model** — one business entity the engine processes as a single unit, backed by one or more related tables. A document with its rows is one endpoint, not two; that is why addressing stops at the endpoint and never reaches inside.2526- **URL = `/[$<module>]/<kind>/<endpoint>/<action>/<id>`** — the platform's addressing model: `/document/waybillin/edit/2222`, or `/$admin/document/waybillin/edit/2222` inside a module.27- **`$<module>`** (optional) relocates the path's root — a `$`-prefixed segment is the one thing the runtime interprets. Modules and their roots come from `a2 app config`.28- **`<kind>/<endpoint>`** locates the folder. For a raw endpoint both segments are opaque — free names the runtime does not read. **For us the first one is neither free nor opaque**: `kind` is the parent folder name (`catalog/agent/` → `catalog`), it comes from a fixed set, and it is **interpreted** — it decides what gets generated. → `references/kinds.md`. Other first segments are possible in an application; they are not ours — pay them no attention.29- **Endpoint** = a `<kind>/` subfolder. → `references/metadata.md`30- **`<action>` and `<id>` are never authored.** For a raw endpoint an action is an element name, freely declared. For a metaendpoint the set is **fixed and interpreted** by the runtime (`index`, `edit`, …) and declared nowhere — you will not find it in `metadata.json`, and there is nothing to add there. The names surface in exactly one place: `model.json`, and only for an artifact that has been materialized → `references/materialize.md`. **What you ever write is `/<kind>/<endpoint>`** — in the menu, in a link, everywhere.3132## 4. Must — break it and it does not work3334- **The schema is converged, never edited.** It comes from `metadata.json` through `a2 meta deploy` — the same sequence the runtime runs. Do not write DDL to fix it: a change made by hand is invisible to the deploy hash, so the drift is silent and stays. What deploy will not do by itself — drops, narrowing, renames, changes to data — is proposed in `diff.sql` or written as a data migration, and **the author applies it**. → `references/deploy_and_migrations.md`35- **Generated files are products, not sources.** `deploydatabase.sql` is regenerated from the metadata byte for byte; an edit inside it is gone on the next deploy. `diff.sql` is a proposal — never execute it as part of a task.36- **A `<kind>/` subfolder without `metadata.json`, or with `{}`, is an error.** Legacy endpoints live in the classic layout, never under a `<kind>/`.37- **Every `metadata.json` says where its shape comes from, explicitly.** One axis, three keys: `table` — its own table; `storage` — a table declared elsewhere that it writes into; `surface` — a shape it only reads. **The folder decides which of them is legal** (`document` — one of `table`/`storage`; `report` — `surface`; everything else — `table`), **the file decides which one is used**. No defaults, never two, never none. → `references/metadata.md`, checks → `references/validate.md`38- **An operation owns no table, therefore declares no structure** — only behavior. It is not a rule to remember: an operation is the file that wrote `storage` instead of `table`.3940## 5. Discipline — orthogonal to the engine4142§4 is the engine's contract; this one is yours. Nothing here fails loudly — a metaendpoint that renders proves only that it rendered.4344- **Do not invent surface.** An unknown key is not rejected: the loader drops it silently, so a misspelled or invented key gives no error and no effect — the endpoint works, your rule is simply not there. Not in `references/`, not in an existing `metadata.json` → it does not exist. What is caught before deploy, and what is not → `references/validate.md`.45- **Two markers in `references/`, and they say different things.** 🟡 — the decision is provisional; the form may still change. 🚧 — the form is settled and **the platform does not execute it yet**. A 🚧 surface is written down so the decision is not taken twice, not so it can be used: never put it in a `metadata.json`. Say which construct is missing and stop there — the norm names no substitute, and one improvised in its place is invented surface (above).46- **An endpoint with no files of its own is finished, not unfinished.** Every scaffolder you have ever seen generates once and lets go, so a folder holding only a `metadata.json` reads as a draft and invites XAML or SQL "so that something is there". A file exists **only** because that artifact was ejected on purpose; a file with nothing behind it is an error, not a spare. → `references/materialize.md`47- **Declared is not reachable.** The rule above is about the files inside the folder; this one is about the application around it. An endpoint no `menu.json` item names loses its **own entrance**, not its existence — a catalog still opens in a Ref lookup, and records are still created there — but nothing leads the user to its register, and nothing says so: deploy succeeds, validation passes, the URL works when typed by hand. Every endpoint you declare, you also place → `references/menu.md`.48- **An operation file does not describe its entity completely, and must not try.** Every file you have seen elsewhere is self-contained, so repeating `fields` from the storage will feel like completeness. Those keys are read by nobody and reported by nothing: the endpoint works, and the copy quietly rots next to the original.49- **Do not carry priors from 1C, Frappe or an ORM.** This format splits what they merge, and the merged reading drifts back under pressure.5051## 6. Workflow52531. **The project has a `CLAUDE.md`** → read it first, before any action. What it fixes, do not ask again.542. **No `CLAUDE.md`** → onboarding, exactly once → `references/onboarding.md`. It forks there on one check — **are any endpoints already declared?** — because an application that exists must never have the shell written over it.5556 **Open the project with `a2 app config`, never with the file tree** — it is what says where the files go (`hostRoot`, `modules`). Reading that off the tree is guessing.5758Unsure → ask; never guess.5960## 7. Dispatch — find the task6162| Action | Go to |63|---|---|64| create an application — the user has not said metadata-driven or classic → ask; classic → the `a2v10` skill | `references/onboarding.md` |65| create an endpoint — which kind is it | `references/kinds.md` → `references/metadata.md` |66| add or change a field | `references/metadata.md` |67| set initial values, or how a reference is picked | `references/metadata.md` |68| turn on a trait | `references/metadata.md` |69| make a field required, conditional, computed, inherited | `references/rules.md` |70| add an operation to a document family | `references/metadata.md` |71| make an operation post into a journal | `references/metadata.md` → `references/journal.md` |72| declare what a journal stores | `references/journal.md` |73| number documents with a series | `references/kinds.md` → `references/metadata.md` |74| declare a chart of accounts | `references/accplan.md` |75| declare a ledger, make a document post double-entry | `references/ledger.md` |76| declare a report over a journal | `references/report.md` |77| attach a print blank to an endpoint | `references/print.md` |78| lay out a form | `references/forms.md` |79| eject / materialize | `references/materialize.md` |80| apply metadata to the database, plan a migration | `references/deploy_and_migrations.md` |81| wire app navigation | `references/menu.md` |82| restrict access — declare roles, grant verbs, hide a menu item | `references/permissions.md` — 🚧 the whole subsystem; read it before promising anything |83| add or change localization keys | `references/localization.md` |84| check metadata before deploying | `references/validate.md` — 🚧 the command does not exist yet |85| build the application and bring up its database | `references/onboarding.md` → Phase 3 |86| call the CLI, read its output | `references/cli.md` |8788**No row matches?** Do not force-fit. Route by what you are declaring: the shape of a record → `references/metadata.md`; a rule tying its fields together → `references/rules.md`; what it looks like on screen → `references/forms.md`. Still unclear → ask.8990**Materialized artifacts** — XAML, SQL, `template.ts` — are written in the **`a2v10`** skill's formats; their syntax is that skill's job, not this one's. Which artifact is materialized at all is stated in `model.json` → `references/materialize.md`.91