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]

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:

A bot could not be retired.A status column with no writer anywhere. Every bot held the default, permanently.
A season could not be run privately.Owner and password columns exist. No code path sets either one.
Private tables ignored their own stakes.The blinds column is never written, so every private table silently ran at the database default.
Interrupted work was never recovered.No lease, no timeout, and both recovery queries select only pending rows — caught while production still had zero stranded rows.
A metrics rollup was marked done with an empty table.Its only insert path was generated CRUD that nothing called.

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.
  • implementedBy resolved by the loader. Dangling is an error, not a note.
  • meta verify reporting 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 by verify --codegen.
  • meta docs rendering 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

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