Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@ here.**

## [Unreleased]

### Changed

- **Requirements guidance: a feature commit updates its requirement.** The agent context
(the authoring and verify skills and the always-on file `meta init` writes) now tells an
agent that a commit adding, changing or removing behaviour updates the ledger in the same
commit, or says why not. `verify` checks that the ledger's links resolve, not that it is
current, so nothing else enforces it.
- **The adoption docs recommend the strict requirements check** (`--require-implementers`, or
its per-port form) in the project's verify script or pre-push hook once the ledger is clean.

### Fixed

- The 1.1.0 entry's handler-code figure of 439 lines retired is corrected to about 460, as
measured in one adopter's pull request.

## [1.1.2] — 2026-10-24

_npm `1.1.2` (full lockstep across all 14 `@metaobjectsdev/*` publish candidates), PyPI `1.1.2`, NuGet `1.1.2` and Maven Central `8.1.2`. Fixes #454 and #455. A bug-fix release: no vocabulary or `metamodelVersion` change._
Expand Down Expand Up @@ -205,7 +220,7 @@ every port's `verify`), the derived-key parity fix across the ports, and the lis
nested-average fixes found by the first adopter to map real report pages onto the feature.

**How much smaller the code gets.** The size reduction comes in two steps, and each is its own
number. The first landed on 1.0.x: 439 lines of handler code retired. The second is reporting,
number. The first landed on 1.0.x: about 460 lines of handler code retired, measured in one adopter's pull request. The second is reporting,
and it is a SQL reduction only: in an adopter, hand-written SQL went from 75 lines to 15 (-80%).
Total hand-written lines did not fall, so do not read it as a total line reduction.

Expand Down
2 changes: 2 additions & 0 deletions agent-context/skills/metaobjects-authoring/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,8 @@ Reach for it when the project needs to answer any of:

Every requirement must be **violable**: if you cannot say what breaking it looks like, it is a description, not a requirement.

**A commit that adds, changes or removes behaviour updates the ledger in the same commit** (the new or changed claim, its `@status` and `@implementedBy`), or says in the commit message why it does not. `verify` checks that links resolve, not that the ledger is current.

If the project declares any `requirement.*` node, `references/requirements.md` is installed with the full authoring rules. If it does not and you are adding the first one, the shape is:

```yaml
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ more than the rest.
- A requirement is PRESCRIPTIVE — it states what should be true, never what happened
- Do not delete a retired capability's entry
- When you add an entity, claim it
- A feature commit updates the ledger in the same commit
- Every requirement states its counterexample
- Four prose slots, and `statement` is the one that means "description"
- `title` is a LABEL and is chartered here; `summary` is not
Expand Down Expand Up @@ -56,6 +57,14 @@ the requirement went stale.
**2. When you add an entity, claim it.** Every `object.entity` should appear in some
requirement's `implementedBy`, or `verify` says so.

**A commit that adds, changes or removes behaviour updates the ledger in the same commit.**
`verify` checks that the ledger's links resolve, never that it is current, so nothing fails
when a feature ships and its requirement does not move. In the same commit, write the new or
changed claim, set its `@status` (`planned` to `live` when it lands, `retired` when it is
removed) and keep its `@implementedBy` pointing at the real nodes (a retired entry carries
none). If the commit should not
touch the ledger, say why in the commit message.

**Every requirement states its counterexample.** One sentence: what breaking it looks like.
*"Every entity has a uuid primary key"* is violable — point at one with a composite string
key. *"Things are persisted"* is not, and is a description rather than a requirement. Same
Expand Down
12 changes: 12 additions & 0 deletions agent-context/skills/metaobjects-verify/references/requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ subverb**: requirements are metadata, so they are checked on *every* `meta verif
- The status asymmetry — the one that surprises people
- Exit codes
- The strict switch — `--require-implementers`
- Keeping the ledger current
- The error codes, and the fix for each
- The authoring lint — a second section, never an error
- `verify` does not look at your tests
Expand Down Expand Up @@ -113,6 +114,17 @@ warning. If CI went red on this code, check whether the switch is set before tre
as a new defect: the fix is to add the `@implementedBy` link (or drop the entry to
`planned`), not to unset the switch.

Once the ledger is clean, set the switch where the project already runs `verify`: its verify
script, CI job or pre-push hook. Until then a stale `live` claim is only a warning that
nobody reads.

## Keeping the ledger current

`verify` proves the ledger's links resolve, not that the ledger is current. A commit that
adds, changes or removes behaviour updates the ledger in the same commit: the new or changed
claim, its `@status` and its `@implementedBy`. If the commit should not touch the ledger, the
commit message says why.

## The error codes, and the fix for each

Match on the stable `code`, never the message text (ADR-0009). The runtime messages already
Expand Down
1 change: 1 addition & 0 deletions agent-context/templates/always-on.md.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ from.
- Pattern-derivable from metadata = codegen, never hand-write — FKs, CRUD, validators, finders, and the database schema and migrations. The schema is a disposable, generated artifact: change the metadata and regenerate. Don't hand-write divergent SQL; where a stack owns its own migration files, GENERATE them — `meta migrate --migration-format flyway` emits the paired `V<n>__`/`U<n>__` files a Flyway runner expects, and leaves applying them to Flyway. The one exception is a schema this project deliberately owns outside MetaObjects (hand-written migrations by rule, or another team's database): when the root `CLAUDE.md` says so, follow it, and keep `meta verify --db` catching drift.
- **Once this project declares any `template.prompt`, every LLM prompt is one** — the text in the template, the data in a payload value object, rendered by the render engine. A structured reply is read through a response model (`@responseRef` and its generated extractor), never a regex or string search. This covers code that does not look like MetaObjects: a format string, text block, template literal or string builder sent to an LLM, and it covers every module, including test harnesses and scripts. Changing a prompt that is still hand-built means migrating it first (snapshot its current output and keep the rendered text byte-identical), not adding a line to it; the `metaobjects-prompts` skill has the steps.
- The **live database** is a derived artifact too — never hand-apply a schema change to a running DB (ad-hoc `psql`/console `ALTER`/`CREATE`/`DROP`), not even to preview a column or unblock a boot. Drive every schema change from the metadata, never ad-hoc: on the Node/TS-owned migrate stack that is `meta migrate` (metadata → DDL, ADR-0015); on a **Flyway**-owned JVM stack that is `meta migrate --migration-format flyway`, which emits the migration and deliberately refuses `--apply` because Flyway owns applying it. Hand-authoring a migration to match the generated schema is the LAST resort — for a runner MetaObjects has no writer for — never the default for Flyway. A hand-applied ad-hoc change drifts the live DB from the metadata + migration history and collides at the next migrate/boot ("column already exists") — a state no migration can reproduce. Run `meta verify --db <url>` after any DB-touching work to catch that drift early — the URL is required, and the bare form exits 2.
- **If this project declares `requirement.*` nodes, a commit that adds, changes or removes behaviour updates them in the same commit** (the claim, its `@status` and `@implementedBy`), or says in the commit message why not. `meta verify` checks that the links resolve, not that the ledger is current.
- Never hand-edit generated **output** — change the metadata and regenerate. (That bounds the files codegen *emits*; it says nothing about the generator that emits them — next bullet.) What survives a regen DIFFERS BY TOOLCHAIN, so never rely on it: the Node/TS `meta gen` path three-way-merges, so an edit inside a generated file is preserved (and is refused rather than guessed at when it cannot tell yours from its own stale output); the JVM generators overwrite, and several Kotlin ones — including `<Entity>.kt` — truncate unconditionally with no marker check. Put your own code in a subclass or a separate file, never in the generated one.
- **Nothing is generated until you wire it — one generator list per port, documentation included.** No port ships a default generator suite, and there is no separate docs command or docs config block on any port: a docs page is one more entry in the same list as every other generator. The list lives in `generators` in `metaobjects.config.ts` (TypeScript; a fresh `meta init` writes `generators: []`), `codegen/Generators.cs` (C#, scaffolded by `dotnet meta init`), a target's `generators` in `metaobjects.config.yaml` (Python), or `<generators>` in the Maven plugin's `<configuration>` (Java, Kotlin). `meta gen --list --probe` is the catalog — it runs every generator against your model and reports how many files each would emit — and each generator runs as shipped from its package: import it on the Node/TS path (the catalog row prints the line), select it by name on the others. `--generators <names>` (TypeScript, C#, Python; a second Maven `<execution>` of `generate`) NARROWS a run to entries the list already declares — it never adds one, and naming an undeclared entry is refused. Copy nothing you are not going to change. `meta eject <name>...` copies one into your repo when you need to change what it emits.
- **Need an output the model describes that nothing here emits? WRITE A GENERATOR — that is the primary path, not an escape hatch.** OpenAPI, JSON Schema, Zod, DTOs, a client, a service layer, docs: MetaObjects is the core (model, loader, `verify`, `migrate`, render, extract), and the generators on it are yours to write. A generator is a name plus a function from the model to `{ path, content }` files, about 20 lines; `verify --codegen` gates it with nothing to register. Read the model through the RESOLVING accessors (`fields()`, `attr()`, `resolvedIsArray()` — never `own*()`), skip abstract objects, and resolve `@objectRef` with the port's helper. On the Node/TS path, `meta generator new <name>` scaffolds a working one and wires it into `metaobjects.config.ts`; the `metaobjects-codegen` skill has every port's shape. Run a reference generator as shipped when it already emits what you want, and eject one only when it is close and you need to change it; never hand-write output the model fully describes.
Expand Down
6 changes: 6 additions & 0 deletions docs/features/requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,12 @@ more than it links. A project whose ledger has caught up can make it fail the bu
| Java, Kotlin | `mvn metaobjects:verify -Dmeta.verify.requireImplementers=true` | `META_REQUIRE_IMPLEMENTERS=1` |
| C# | `dotnet meta verify ./metadata --templates ./prompts --require-implementers` | `META_REQUIRE_IMPLEMENTERS=1` |

**Recommended once the ledger is clean:** set the switch in the project's verify script or
pre-push hook. Every adopter ledger we measured passed the default gate, because the gate
checks that links resolve, not that the ledger is current, and as a warning the finding goes
unread. Pair it with the authoring rule that a commit adding, changing or removing behaviour
updates its requirement in the same commit.

The switch raises that one finding to an error and keeps its code, so a report or a
suppression keyed on the code still matches. No other warning changes severity. It is not
called `--strict`, because `verify` already has `--lax` on a different axis and a `--strict`
Expand Down
1 change: 1 addition & 0 deletions docs/ports/csharp.md
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,7 @@ are core and are not ejectable.
# The gate: runs on every verify, beside whichever drift gate it was given
dotnet meta verify ./metaobjects --codegen --out ./Generated
dotnet meta verify ./metaobjects --codegen --out ./Generated --require-implementers
# Recommended once the ledger is clean: use this form in the project's verify script or pre-push hook

# The tests: the same run as everything else codegen/Generators.cs declares. The entry's
# OutDir puts them in the test project, which references xunit; the app's --out does not.
Expand Down
2 changes: 1 addition & 1 deletion docs/ports/java.md
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ checks them on every run, in either mode: it logs the ledger summary, and an err
dangling reference on a live requirement, a link above the floor, a live policy applied to
nothing) fails the build. `-Dmeta.verify.requireImplementers=true` (or
`META_REQUIRE_IMPLEMENTERS=1`) also fails it on a live functional requirement that nothing
implements. Those checks are core and are not ejectable.
implements; once the ledger is clean, set it in the build or pre-push hook that runs the goal. Those checks are core and are not ejectable.

This generator is the other half, and it is a reference helper and a recommended approach,
not a contract: it writes one JUnit Jupiter test per tested requirement. The test does not
Expand Down
2 changes: 1 addition & 1 deletion docs/ports/kotlin.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ plugin generated; it has to be told about `generated-sources/kotlin` and your ow
The `KotlinSpringConfigGenerator` reads its model from the classpath (`<metadataResource>`), so the
same file goes under `src/main/resources/` as well as `src/main/metaobjects/`.

**Requirement tests.** `mvn metaobjects:verify` checks the model's `requirement.*` nodes on every run (the Java goal; Kotlin has no CLI of its own), and `-Dmeta.verify.requireImplementers=true` or `META_REQUIRE_IMPLEMENTERS=1` also fails the build on a live functional requirement that nothing implements. Those checks are core and are not ejectable. `KotlinRequirementTestsGenerator` is the other half: a reference helper and a recommended approach, not a contract. It is the Kotlin sibling of the Java `JUnitRequirementTestsGenerator` (the same args, the same test identities, one conformance corpus for both). It writes one JUnit Jupiter test per tested requirement; the test calls a **witness**, a function you write, and a live requirement with no witness fails. A JUnit 4 project replaces the text through the `renderer` arg, or ejects the generator.
**Requirement tests.** `mvn metaobjects:verify` checks the model's `requirement.*` nodes on every run (the Java goal; Kotlin has no CLI of its own), and `-Dmeta.verify.requireImplementers=true` or `META_REQUIRE_IMPLEMENTERS=1` also fails the build on a live functional requirement that nothing implements; once the ledger is clean, set it in the build or pre-push hook that runs the goal. Those checks are core and are not ejectable. `KotlinRequirementTestsGenerator` is the other half: a reference helper and a recommended approach, not a contract. It is the Kotlin sibling of the Java `JUnitRequirementTestsGenerator` (the same args, the same test identities, one conformance corpus for both). It writes one JUnit Jupiter test per tested requirement; the test calls a **witness**, a function you write, and a live requirement with no witness fails. A JUnit 4 project replaces the text through the `renderer` arg, or ejects the generator.

```xml
<generator>
Expand Down
3 changes: 2 additions & 1 deletion docs/ports/python.md
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,8 @@ Declare what the software must do as `requirement.*` nodes and `metaobjects veri
them on every run: it prints the ledger summary, and an error (a dangling reference on a
live requirement, a link above the floor, a live policy applied to nothing) exits non-zero.
`--require-implementers` (or `META_REQUIRE_IMPLEMENTERS=1`) also fails the run on a live
functional requirement that nothing implements. Those checks are core and are not
functional requirement that nothing implements; once the ledger is clean, put it in the
project's verify script or pre-push hook. Those checks are core and are not
ejectable. A model with no requirement sees no change. Metadata that does not load is not
that: `verify` prints the load errors and exits non-zero, also when no drift gate ran and so
none loaded it.
Expand Down
3 changes: 2 additions & 1 deletion docs/ports/typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -812,7 +812,8 @@ edit). The other four ports do not merge: their generated tests call a *witness*
See [Generated requirement tests and witnesses](../features/requirements.md#generated-requirement-tests-and-witnesses).

`meta verify --require-implementers` (or `META_REQUIRE_IMPLEMENTERS=1`) raises
`WARN_REQUIREMENT_NOTHING_IMPLEMENTS` to an error. See
`WARN_REQUIREMENT_NOTHING_IMPLEMENTS` to an error; once the ledger is clean, put it in the
project's verify script or pre-push hook. See
[Requiring implementers](../features/requirements.md#requiring-implementers).

## Capability snapshot
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,8 @@ Reach for it when the project needs to answer any of:

Every requirement must be **violable**: if you cannot say what breaking it looks like, it is a description, not a requirement.

**A commit that adds, changes or removes behaviour updates the ledger in the same commit** (the new or changed claim, its `@status` and `@implementedBy`), or says in the commit message why it does not. `verify` checks that links resolve, not that the ledger is current.

If the project declares any `requirement.*` node, `references/requirements.md` is installed with the full authoring rules. If it does not and you are adding the first one, the shape is:

```yaml
Expand Down
Loading
Loading