Skip to content

Commit d2f51e5

Browse files
dmealingclaude
andcommitted
site: a requirements page, and the landing-page path into it
The first public article about MetaObjects leads on capability requirements, and it ends by pointing at this site. There was nowhere for it to land -- docs/features/requirements.md is reference, not persuasion -- so the funnel dead-ended. NEW /requirements.html. Value page, not reference. Framed as the failure AFTER drift, which is the distinction the whole thing turns on: drift is your code disagreeing with your model and meta verify already makes that a build error; this is your code agreeing with your model perfectly while the model describes a capability the software does not have. The three shapes an agent produces -- duplicate, missing, partial -- then the real ledger entry that is WRONG, since arena::Bot.status exists, the link resolved, every structural check passed, and nothing ever writes it. Positioning guard, deliberate: the page argues that this works BECAUSE a capability is a node in the same model as the entities, so its link is resolved rather than trusted. That is what keeps MetaObjects out of the "requirements tool" category, where the competition is a list with string links. It is the reason the mechanism works, not a footnote. Honest about the boundary, in a two-column split rather than buried: MetaObjects ships the vocabulary, resolved implementedBy, verify's coverage reporting, requirementTests() STUBS and the meta docs surfaces. You write the assertion, and you build the proof that the assertion bites. That last part is not a MetaObjects feature today -- two projects built their own -- and the page says so rather than letting an adopter discover the gap after installing. LANDING PAGE gains a nav link and a short section placed immediately after the drift table, because that is where the argument actually continues. The section carries the strongest evidence and links onward rather than restating the page. Checked rather than assumed: both files parse with balanced tags, every internal link resolves to a file that exists, the linked reference doc exists in the metaobjects repo, and both scan clean against all 19 denylist patterns. The install command matches getting-started.html exactly (`npm i`, not `-D`) so the path a visitor walks stays consistent; the site's wider npm-i-vs-install-D inconsistency is left alone as out of scope. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015WgNA6uDN9qxu2nA471rAq
1 parent 9e79115 commit d2f51e5

2 files changed

Lines changed: 310 additions & 0 deletions

File tree

www/index.html

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
</a>
3131
<nav>
3232
<a href="/getting-started.html" class="nav-link">Get started</a>
33+
<a href="/requirements.html" class="nav-link">Requirements</a>
3334
<a href="/story.html" class="nav-link">Story</a>
3435
<a href="/videos.html" class="nav-link">Videos</a>
3536
<a href="/reference/" class="nav-link">Reference docs</a>
@@ -167,6 +168,20 @@ <h2 class="section-label">The #1 reason in the AI era: drift breaks the build.</
167168
</p>
168169
</section>
169170

171+
<!-- The failure after drift — capability coverage. Full page: /requirements.html -->
172+
<section class="drift" id="requirements">
173+
<h2 class="section-label">The failure after drift: the feature your agent said it built.</h2>
174+
<p class="drift-intro">
175+
Drift is your code disagreeing with your model, and <code>meta verify</code> already makes that a build error. There is a second failure it cannot see: your code agrees with your model perfectly, and the model describes a capability your software does not have. The columns exist, the types line up, the tests pass — and no code path ever writes the value. Nothing is broken, so nothing fails.
176+
</p>
177+
<p class="drift-intro">
178+
Declared across three shipping products, that check found a bot that could not be retired, a season that could not be run privately, private tables silently running at the database default, and an interrupted-work recovery path that was never built — in a codebase with <strong>1,280 tests passing and zero failures</strong>. A capability declared in MetaObjects is a node in the same model as your entities, so its link to the field that implements it is resolved rather than trusted.
179+
</p>
180+
<p class="drift-payoff">
181+
<a href="/requirements.html">How capability requirements work →</a>
182+
</p>
183+
</section>
184+
170185
<!-- Fit assessment — prove it on your own repo, zero install -->
171186
<section class="drift" id="assess">
172187
<h2 class="section-label">Don't take the table's word for it. Ask your own git history.</h2>

www/requirements.html

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Capability requirements — check what your AI actually built | MetaObjects</title>
7+
<meta name="description" content="Your agent reports the feature done. The columns exist, the tests pass, and nothing ever writes the value. MetaObjects makes a capability a node in your model, so the gap between what your software claims and what it does becomes a build failure instead of a surprise.">
8+
9+
<link rel="icon" href="/images/mo-logo-only.png" type="image/png">
10+
11+
<link rel="preconnect" href="https://fonts.googleapis.com">
12+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
13+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
14+
15+
<link rel="stylesheet" href="/styles.css">
16+
17+
<meta property="og:title" content="Check what your AI actually built">
18+
<meta property="og:description" content="The columns exist, the tests pass, and nothing ever writes the value. Make a capability a node in your model, and the gap becomes a build failure.">
19+
<meta property="og:type" content="website">
20+
<meta property="og:url" content="https://metaobjects.dev/requirements.html">
21+
<meta property="og:image" content="https://metaobjects.dev/images/mo-logo.png">
22+
<meta name="twitter:card" content="summary_large_image">
23+
<link rel="canonical" href="https://metaobjects.dev/requirements.html">
24+
25+
<style>
26+
/* Page-scoped styles — do not touch the shared landing styles. */
27+
.page { max-width: var(--reading-width); margin: 0 auto; padding: 3.5rem 1.5rem 4rem; }
28+
.page-eyebrow { font-family: var(--font-mono); font-size: 0.8rem; letter-spacing: 0.04em;
29+
text-transform: uppercase; color: var(--medium-gray); margin: 0 0 0.75rem; }
30+
.page h1 { font-size: 2.4rem; line-height: 1.15; color: var(--primary-blue);
31+
margin: 0 0 1rem; letter-spacing: -0.02em; }
32+
.page-lede { font-size: 1.15rem; line-height: 1.65; color: var(--dark-gray); margin: 0 0 2rem; }
33+
.page h2 { font-size: 1.5rem; line-height: 1.25; color: var(--primary-blue);
34+
margin: 3rem 0 1rem; letter-spacing: -0.01em; }
35+
.page h3 { font-size: 1.05rem; color: var(--primary-blue); margin: 1.75rem 0 0.5rem; }
36+
.page p { margin: 0 0 1.25rem; }
37+
.page ul { margin: 0 0 1.4rem; padding-left: 1.4rem; }
38+
.page li { margin: 0 0 0.6rem; }
39+
.page strong { color: var(--primary-blue); }
40+
.page blockquote { margin: 1.6rem 0; padding: 0.4rem 0 0.4rem 1.4rem;
41+
border-left: 3px solid var(--accent-gold); color: var(--dark-gray);
42+
font-size: 1.1rem; font-style: italic; }
43+
.page pre { background: var(--code-bg); color: var(--code-fg); border-radius: 8px;
44+
padding: 1.25rem 1.4rem; overflow-x: auto; font-family: var(--font-mono);
45+
font-size: 0.86rem; line-height: 1.6; margin: 0 0 1.6rem; box-shadow: var(--shadow); }
46+
.page pre code { background: none; color: inherit; padding: 0; font-size: inherit; }
47+
.tok-key { color: #8FB7E8; } .tok-val { color: #A7E0C0; }
48+
.tok-cmt { color: #7A8AA8; } .tok-err { color: #F0A0A0; }
49+
.page hr { border: none; border-top: 1px solid var(--rule); margin: 3rem 0; }
50+
51+
.shapes { display: grid; gap: 1rem; margin: 0 0 1.5rem; }
52+
@media (min-width: 720px) { .shapes { grid-template-columns: repeat(3, 1fr); } }
53+
.shape { background: var(--light-gray); border: 1px solid var(--rule);
54+
border-radius: 10px; padding: 1.1rem 1.2rem; }
55+
.shape h3 { margin: 0 0 0.4rem; font-size: 0.95rem; font-family: var(--font-mono);
56+
letter-spacing: 0.02em; }
57+
.shape p { margin: 0; font-size: 0.93rem; line-height: 1.6; color: var(--dark-gray); }
58+
59+
.findings { border: 1px solid var(--rule); border-radius: 10px; overflow: hidden;
60+
margin: 0 0 1.5rem; }
61+
.findings div { padding: 0.95rem 1.2rem; border-bottom: 1px solid var(--rule);
62+
font-size: 0.95rem; line-height: 1.6; }
63+
.findings div:last-child { border-bottom: none; }
64+
.findings b { color: var(--primary-blue); display: block; margin-bottom: 0.15rem; }
65+
.findings span { color: var(--dark-gray); }
66+
67+
.split { display: grid; gap: 1.25rem; margin: 0 0 1.5rem; }
68+
@media (min-width: 720px) { .split { grid-template-columns: 1fr 1fr; } }
69+
.split section { border: 1px solid var(--rule); border-radius: 10px; padding: 1.2rem 1.35rem; }
70+
.split section.you-add { background: var(--light-gray); }
71+
.split h3 { margin: 0 0 0.7rem; font-size: 0.82rem; font-family: var(--font-mono);
72+
text-transform: uppercase; letter-spacing: 0.05em; color: var(--medium-gray); }
73+
.split ul { margin: 0; padding-left: 1.1rem; }
74+
.split li { font-size: 0.93rem; line-height: 1.6; }
75+
76+
.page-cta { background: var(--light-gray); border: 1px solid var(--rule);
77+
border-radius: 10px; padding: 1.5rem 1.75rem; margin: 2.5rem 0 0; }
78+
.page-cta p { margin: 0 0 0.9rem; font-size: 0.98rem; }
79+
.page-cta p:last-child { margin: 0; }
80+
.page-cta .cmds { font-family: var(--font-mono); font-size: 0.85rem;
81+
display: block; color: var(--royal-blue); }
82+
</style>
83+
</head>
84+
<body>
85+
<header class="site-header">
86+
<a href="/" class="brand">
87+
<img src="/images/mo-logo-only.png" alt="" class="brand-mark">
88+
<span class="brand-name">MetaObjects</span>
89+
</a>
90+
<nav>
91+
<a href="/getting-started.html" class="nav-link">Get started</a>
92+
<a href="/story.html" class="nav-link">Story</a>
93+
<a href="/videos.html" class="nav-link">Videos</a>
94+
<a href="https://github.com/metaobjectsdev/metaobjects" class="nav-link">GitHub</a>
95+
<a href="https://github.com/metaobjectsdev/metaobjects/tree/main/spec" class="nav-link nav-cta">Spec</a>
96+
</nav>
97+
</header>
98+
99+
<main>
100+
<article class="page">
101+
<p class="page-eyebrow">Capability requirements · ships in all five language ports</p>
102+
<h1>Check what your AI actually built.</h1>
103+
<p class="page-lede">
104+
Your agent reports the feature done. The columns are there, the types line up, the tests
105+
pass — and no code path ever writes the value. Nothing is broken, so nothing fails.
106+
MetaObjects makes a capability a node in your model, so the distance between what your
107+
software <em>claims</em> to do and what it does becomes something a build can check.
108+
</p>
109+
110+
<h2>The failure that comes after drift</h2>
111+
<p>
112+
Drift is when your code stops agreeing with your model. <a href="/videos.html">MetaObjects
113+
already turns that into a build error</a> across your code, your prompts and your schema.
114+
</p>
115+
<p>
116+
This is the other one. Your code can agree with your model perfectly, and the model can
117+
still describe a capability your software does not have. Agent-written work fails that way
118+
in three recognisable shapes:
119+
</p>
120+
121+
<div class="shapes">
122+
<div class="shape">
123+
<h3>Duplicate</h3>
124+
<p>It builds a capability that already exists, because nothing in the repo told it the
125+
capability was there.</p>
126+
</div>
127+
<div class="shape">
128+
<h3>Missing</h3>
129+
<p>It reports the feature done. Every layer is present and nothing ever writes the
130+
value. Nothing is wrong <em>enough</em> to fail.</p>
131+
</div>
132+
<div class="shape">
133+
<h3>Partial</h3>
134+
<p>It ships the legible 80% and reports done. The recovery path, the rollup, the retire
135+
operation — invisible to everyone.</p>
136+
</div>
137+
</div>
138+
139+
<p>
140+
A test suite cannot see any of this. A test exercises code that exists; there is no test
141+
that fails because a column nobody writes was never wired to anything. <strong>The absence
142+
has no address.</strong>
143+
</p>
144+
145+
<h2>So give it one</h2>
146+
<p>
147+
A requirements document does not help — prose goes stale silently and nothing in your build
148+
has an opinion about it. Neither does a requirements tool, where the link from a claim to
149+
the code is a <em>string</em>: rename the class and the link still looks fine.
150+
</p>
151+
<p>
152+
A capability declared in MetaObjects is a node in the same model as your entities, so its
153+
link is <strong>resolved, not trusted</strong>:
154+
</p>
155+
156+
<pre><code><span class="tok-cmt">- requirement.functional:</span>
157+
<span class="tok-key">name</span>: <span class="tok-val">botCanBeRetiredWithoutErasingItsResults</span>
158+
<span class="tok-key">level</span>: <span class="tok-val">5</span>
159+
<span class="tok-key">status</span>: <span class="tok-val">live</span>
160+
<span class="tok-key">statement</span>: <span class="tok-val">"A bot's participation is switched by its own status, so it
161+
can be withdrawn while the hands it played stay on the record."</span>
162+
<span class="tok-key">counterexample</span>: <span class="tok-val">"A misbehaving bot that can only be stopped by deleting
163+
it, taking every hand it played with it."</span>
164+
<span class="tok-key">implementedBy</span>: [<span class="tok-val">arena::Bot.status</span>]</code></pre>
165+
166+
<ul>
167+
<li><strong>The statement</strong> says what the capability is, in a sentence.</li>
168+
<li><strong>The counterexample</strong> says what a violation looks like. That single line
169+
is what an assertion gets written against.</li>
170+
<li><strong><code>implementedBy</code></strong> names the <em>member</em> — the real field
171+
in the real model — that carries it. Rename or delete that field and the build tells you
172+
which capability just lost its implementation.</li>
173+
</ul>
174+
175+
<p>
176+
That is a real entry from a live product, and it is <span class="tok-err">wrong</span>.
177+
<code>arena::Bot.status</code> exists, so the link resolved and every structural check
178+
passed. Nothing in the codebase ever writes that column, so every bot holds the default
179+
forever. The bot could not be retired.
180+
</p>
181+
182+
<blockquote>
183+
Consistency is not completeness. Knowing where a capability is supposed to live is what
184+
lets you ask the only question that finds these: what would I have to break to make this
185+
claim fail?
186+
</blockquote>
187+
188+
<h2>What it found</h2>
189+
<p>
190+
Declared across three shipping products — roughly 700 capabilities — against suites that
191+
were passing at the time. A sample:
192+
</p>
193+
194+
<div class="findings">
195+
<div><b>A bot could not be retired.</b><span>A <code>status</code> column with no writer
196+
anywhere. Every bot held the default, permanently.</span></div>
197+
<div><b>A season could not be run privately.</b><span>Owner and password columns exist. No
198+
code path sets either one.</span></div>
199+
<div><b>Private tables ignored their own stakes.</b><span>The blinds column is never
200+
written, so every private table silently ran at the database default.</span></div>
201+
<div><b>Interrupted work was never recovered.</b><span>No lease, no timeout, and both
202+
recovery queries select only pending rows — caught while production still had zero
203+
stranded rows.</span></div>
204+
<div><b>A metrics rollup was marked done with an empty table.</b><span>Its only insert path
205+
was generated CRUD that nothing called.</span></div>
206+
</div>
207+
208+
<p>
209+
These were found in a codebase with <strong>1,280 tests passing and zero failures</strong>,
210+
and the deepest retrofit of the three — a codebase where 93% of the history predates its
211+
requirements — produced the most of them. <strong>You do not need a greenfield project.</strong>
212+
This works on the code you already have, and best on the parts you are least sure about.
213+
</p>
214+
215+
<h2>What you get, and what you build</h2>
216+
<p>
217+
Being exact about this matters, because the last part is the important one and it is not in
218+
the box yet.
219+
</p>
220+
221+
<div class="split">
222+
<section>
223+
<h3>MetaObjects ships</h3>
224+
<ul>
225+
<li>Capabilities as registered vocabulary in all five language ports — a project that
226+
declares none sees no change at all.</li>
227+
<li><code>implementedBy</code> resolved by the loader. Dangling is an error, not a note.</li>
228+
<li><code>meta verify</code> reporting claims whose implementation vanished, entities no
229+
claim covers, and gaps recorded versus gaps nobody has ruled on — with a summary on
230+
every run.</li>
231+
<li><code>requirementTests()</code> scaffolding a test stub per claim, statement and
232+
counterexample carried in, kept from rotting by <code>verify --codegen</code>.</li>
233+
<li><code>meta docs</code> rendering the ledger for humans and for agents — including a
234+
machine-readable index carrying a declared count.</li>
235+
</ul>
236+
</section>
237+
<section class="you-add">
238+
<h3>You write</h3>
239+
<ul>
240+
<li><strong>The assertion.</strong> A generated stub is a place to put a proof and a
241+
guarantee it stays in step with the claim. It is not a proof.</li>
242+
<li><strong>The proof that the assertion bites</strong> — a declared edit to your source
243+
that must turn that test red. If it survives, the test is decoration.</li>
244+
</ul>
245+
<p style="margin:0.9rem 0 0;font-size:0.9rem;color:var(--medium-gray);">
246+
That second one is not a MetaObjects feature today. Two projects built their own
247+
independently, which is the best argument for moving it into the library — and the
248+
reason we would rather tell you now than have you find the gap.
249+
</p>
250+
</section>
251+
</div>
252+
253+
<h2>Where it sits</h2>
254+
<ul>
255+
<li><strong>Codegen</strong> turns a model change into a compile error on the exact line
256+
your own logic needs updating. Refactoring stops being a bug hunt.</li>
257+
<li><strong><code>meta verify</code></strong> turns divergence between your code, prompts,
258+
schema and the model into a failed build. Drift cannot merge.</li>
259+
<li><strong>Capabilities</strong> put what you said the software does on that same spine —
260+
resolved links, coverage reporting, and a generated check per claim.</li>
261+
</ul>
262+
<p>
263+
The AI writes the code. The model keeps all of it honest — and now <em>honest</em> covers
264+
whether the feature is actually there.
265+
</p>
266+
267+
<hr>
268+
269+
<div class="page-cta">
270+
<p><strong>Start with one capability you are not certain is fully built.</strong> Write down
271+
what it means and what a violation would look like, link it to the field or endpoint that
272+
carries it, then try to write a test that would fail if it were false. In three codebases,
273+
that exercise is where every finding above came from.</p>
274+
<p>
275+
<a href="/getting-started.html">Get started</a> ·
276+
<a href="https://github.com/metaobjectsdev/metaobjects/blob/main/docs/features/requirements.md">Full reference</a> ·
277+
<a href="https://github.com/metaobjectsdev/metaobjects/tree/main/spec">Spec</a>
278+
<span class="cmds">npm i @metaobjectsdev/cli &amp;&amp; npx meta init</span>
279+
</p>
280+
</div>
281+
</article>
282+
</main>
283+
284+
<footer class="site-footer">
285+
<div class="site-footer-inner">
286+
<p>
287+
<strong>MetaObjects</strong> · <a href="https://github.com/metaobjectsdev/metaobjects">GitHub</a> · <a href="https://github.com/metaobjectsdev/metaobjects/blob/main/LICENSE">Apache 2.0</a> · <a href="https://github.com/metaobjectsdev/metaobjects/blob/main/spec/roadmap.md">Roadmap</a> · <a href="/llms.txt">llms.txt</a>
288+
</p>
289+
<p class="site-footer-meta">
290+
Commercial inquiries: <a href="https://metaobjects.com">metaobjects.com</a> · Created by <a href="https://dougmealing.com">Doug Mealing</a>
291+
</p>
292+
</div>
293+
</footer>
294+
</body>
295+
</html>

0 commit comments

Comments
 (0)