Capability requirements · ships in all five language ports
Check what your AI actually built.
Your agent reports the feature done. The columns are there, the types line up, the tests pass — and no code path ever writes the value. Nothing is broken, so nothing fails. MetaObjects makes a capability a node in your model, so the distance between what your software claims to do and what it does becomes something a build can check.
The failure that comes after drift
Drift is when your code stops agreeing with your model. MetaObjects already turns that into a build error across your code, your prompts and your schema.
This is the other one. Your code can agree with your model perfectly, and the model can still describe a capability your software does not have. Agent-written work fails that way in three recognisable shapes:
Duplicate
It builds a capability that already exists, because nothing in the repo told it the capability was there.
Missing
It reports the feature done. Every layer is present and nothing ever writes the value. Nothing is wrong enough to fail.
Partial
It ships the legible 80% and reports done. The recovery path, the rollup, the retire operation — invisible to everyone.
A test suite cannot see any of this. A test exercises code that exists; there is no test that fails because a column nobody writes was never wired to anything. The absence has no address.
So give it one
A requirements document does not help — prose goes stale silently and nothing in your build has an opinion about it. Neither does a requirements tool, where the link from a claim to the code is a string: rename the class and the link still looks fine.
A capability declared in MetaObjects is a node in the same model as your entities, so its link is resolved, not trusted:
- requirement.functional:
name: botCanBeRetiredWithoutErasingItsResults
title: Bot retirement preserves results
level: 5
status: live
statement: "A bot's participation is switched by its own status, so it
can be withdrawn while the hands it played stay on the record."
counterexample: "A misbehaving bot that can only be stopped by deleting
it, taking every hand it played with it."
description: >-
A status on the bot rather than a deletion, so withdrawing a
misbehaving entrant leaves every hand it played on the record.
implementedBy: [arena::Bot.status]
titleis the short label,statementis what the capability is in a sentence, anddescriptionis the longer form. Three fields because an index, a doc page and a test each want a different one.- The counterexample says what a violation looks like. That single line is what an assertion gets written against.
implementedBynames the member — the real field in the real model — that carries it. Rename or delete that field and the build tells you which capability just lost its implementation.
That is a real entry from a live product, and it is wrong.
arena::Bot.status exists, so the link resolved and every structural check
passed. Nothing in the codebase ever writes that column, so every bot holds the default
forever. The bot could not be retired.
Consistency is not completeness. Knowing where a capability is supposed to live is what lets you ask the only question that finds these: what would I have to break to make this claim fail?
What it found
Declared across three shipping products — roughly 700 capabilities — against suites that were passing at the time. A sample:
status column with no writer
anywhere. Every bot held the default, permanently.These were found in a codebase with 1,280 tests passing and zero failures, and the deepest retrofit of the three — a codebase where 93% of the history predates its requirements — produced the most of them. You do not need a greenfield project. This works on the code you already have, and best on the parts you are least sure about.
What you get, and what you build
Being exact about this matters, because the last part is the important one and it is not in the box yet.
MetaObjects ships
- Capabilities as registered vocabulary in all five language ports — a project that declares none sees no change at all.
implementedByresolved by the loader. Dangling is an error, not a note.meta verifyreporting claims whose implementation vanished, entities no claim covers, and gaps recorded versus gaps nobody has ruled on — with a summary on every run.requirementTests()scaffolding a test stub per claim, statement and counterexample carried in, kept from rotting byverify --codegen.meta docsrendering the ledger for humans and for agents — including a machine-readable index carrying a declared count.
You write
- The assertion. A generated stub is a place to put a proof and a guarantee it stays in step with the claim. It is not a proof.
- The proof that the assertion bites — a declared edit to your source that must turn that test red. If it survives, the test is decoration.
That second one is not a MetaObjects feature today. Two projects built their own independently, which is the best argument for moving it into the library — and the reason we would rather tell you now than have you find the gap.
Where it sits
- Codegen turns a model change into a compile error on the exact line your own logic needs updating. Refactoring stops being a bug hunt.
meta verifyturns divergence between your code, prompts, schema and the model into a failed build. Drift cannot merge.- Capabilities put what you said the software does on that same spine — resolved links, coverage reporting, and a generated check per claim.
The AI writes the code. The model keeps all of it honest — and now honest covers whether the feature is actually there.
Start with one capability you are not certain is fully built. Write down what it means and what a violation would look like, link it to the field or endpoint that carries it, then try to write a test that would fail if it were false. In three codebases, that exercise is where every finding above came from.
Get started · Full reference · Spec npm i @metaobjectsdev/cli && npx meta init