Skip to content

Commit 415549d

Browse files
dmealingclaude
andcommitted
feat(requirements)!: the requirement vocabulary becomes prescriptive-only
BREAKING (metamodel), in every port. Four pieces of `requirement.*` vocabulary retire in one change: @Verifiedby -> ERR_UNKNOWN_ATTR @supersededBy -> ERR_UNKNOWN_ATTR @status: abandoned -> ERR_BAD_ATTR_VALUE @status: superseded -> ERR_BAD_ATTR_VALUE `@status` is now `planned | live | partial`. Under the sealed strict registry (ADR-0023) there is no shim — affected metadata fails the LOAD. Migration: `docs/features/migrations/verified-by-retirement.md`. **The rule: a requirement is PRESCRIPTIVE.** It states what should be true; it is never a journal of what happened. Retiring a capability is DELETING its requirement. **What forced it — two SHIPPED statements contradicting each other about the same vocabulary, both load-bearing.** The byte-gated registry justified the dangling-`@implementedBy` exemption because those nodes "are meant to be gone, and that is the entry doing its job"; the authoring guidance said deleting the entry "destroys the record"; §4 says a requirement "is not a record of what happened". Only one could be the rule. **The deciding argument was second-order.** An adopting estate held 29 `@implementedBy` refs that could never resolve, across 14 entries, every one INVISIBLE because verify is silent on exactly those two statuses — zero dangling refs reported, true and incomplete at once. Retiring the statuses DELETES that bug class rather than making it visible: the exemption is the only thing that created it. Migration cost is measured, not estimated — three estates (262/75/288 entries): 0, 15 and 88 edits, ~85% on one ledger. **Where the record goes**, because deletion is the part adopters resist: version control, plus `notes` on the entries that survive. One estate had already moved retirement history out of `@implementedBy` into `notes` on its own initiative — this direction, reached independently. **Why `@verifiedBy` separately.** It asked you to name a test and checked only that the NAME occurred somewhere in the test sources, never running anything. Auditing one ledger (55 entries, 9 carrying it, 19 names) by opening each test found 4 of 19 did not verify their claim: a COMMENT, a dependency-injection KEY, a real test of a DIFFERENT claim, and a test of the entry's OUTPUT where the claim was about its SOURCE TEXT. verify reported zero errors throughout. The author picks the string, so the cheapest way to satisfy the check is to find a name that already exists. The scan tier goes with it — including `verify.testFiles` and `VerifyConfig`, which 0.23.1 shipped FOR this scan days ago, and `ERR_REQUIREMENT_TEST_MISSING` / `WARN_REQUIREMENT_TEST_COMMENT_ONLY`. **The contract prose was rehomed BEFORE the descriptions were deleted.** "verify checks each name EXISTS and is not skipped; it never runs them" was byte-gated only inside `@verifiedBy`'s own description — `docs/CONFORMANCE.md` pre-registered this exact hazard. It now lives in `spec/capability-ledger.md` and the migration guide, and CONFORMANCE.md records that the trap fired once and was handled. **No metamodelVersion move needed**, and that is the gate's design working: its baseline is the last release TAG, so `0.10` — already moved this cycle for ADR-0052 — covers all seven breaking classifications. The gate exits 0. Gated by two new fixtures (`error-requirement-verified-by-retired`, `error-requirement-status-retired`) in all five ports, plus a rewritten CLI e2e case pinning the LOAD failure — the project it uses names a real, present test, so under the old scan it exited 0. The `requirement-levels-and-nesting` fixture keeps its L5-sibling and architectural-no-claims shapes by re-expressing them on `planned`. Verified: TS 2408 · sdk 278 · C# four assemblies, 0 `error CS` · Python 1827 · Java ConformanceTest 572, RegistryManifest 3, RequirementTest 7. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DhpswkF1NvwxhFWMmdAT15
1 parent 6223717 commit 415549d

57 files changed

Lines changed: 709 additions & 1391 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

agent-context/skills/metaobjects-audit/references/requirements.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,13 @@ effort re-deriving what a green run already proves.
1010
## What verify has already proven (do not re-check by hand)
1111

1212
Links sit at or below the L4 floor, nesting agrees with levels, `@status` values are legal,
13-
references resolve (with dangling allowed on `abandoned`/`superseded`), and named
14-
`@verifiedBy` tests exist and are not skipped.
13+
and references resolve (with dangling allowed on `planned`, whose nodes do not exist yet).
1514

1615
## What only a human or an agent reading the code can catch
1716

1817
**1. Statuses that are false.** The highest-value finding in the whole dimension. A
1918
requirement marked `live` whose implementation was gutted; one marked `partial` that is now
20-
complete; one marked `abandoned` whose code is demonstrably still running. Sample the claims
19+
complete; one marked `planned` that was quietly built months ago. Sample the claims
2120
and read the nodes. `status` is the only payload with controlled evidence behind it — the
2221
resurrection protection rides entirely on it being true.
2322

@@ -49,7 +48,7 @@ should be simplified rather than defended.
4948
coverage is explicitly rejected — plumbing members are covered by architectural
5049
requirements with high fan-out. L5 exists so a member claim *can* be made where it means
5150
something, never so every member must carry one.
52-
- **Dangling links on `abandoned` / `superseded`.** Correct, and load-bearing.
51+
- **Dangling links on `planned`.** Correct — the plan precedes the nodes.
5352
- **A project with no requirements at all.** The feature is opt-in by declaration; absence is
5453
not a finding.
5554

agent-context/skills/metaobjects-authoring/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1007,7 +1007,7 @@ Reach for it when the project needs to answer any of:
10071007

10081008
- **"Why is this field here?"** — an L5 requirement binds a claim to a specific member. Authoring these exhaustively is what surfaces columns nothing reads and vocabularies nobody documented.
10091009
- **"What is broken but known?"** — `@status: partial` plus `@disposition: accepted | deferred`. Absent disposition means *undecided*, and `meta verify` counts those: the gaps nobody has ruled on.
1010-
- **"What did we build and then retire?"** — `@status: abandoned` is the one status where a dangling `@implementedBy` is *correct*, so the record survives the deletion.
1010+
- **"What did we say we would build and have not?"** — `@status: planned` is the one status where a dangling `@implementedBy` is *correct*, because the entry precedes the nodes.
10111011
- **"What have we committed to build?"** — `@status: planned`. Its references may dangle, and it never counts toward object coverage.
10121012
- **"Which ticket covers this?"** — `@trackedBy`.
10131013

agent-context/skills/metaobjects-authoring/references/requirements.md

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,18 @@ Capabilities are **metadata**, declared in `metaobjects/` beside the entities th
44
describe. Read the existing requirement nodes before designing anything. Two rules matter
55
more than the rest.
66

7-
**1. When you retire something, record it — at that moment.** Set the requirement's
8-
`status` to `abandoned` (built, then deliberately dropped) or `superseded` (something else
9-
does it now — name it in `supersededBy`), in the same change that removes the code.
7+
**1. A requirement is PRESCRIPTIVE — it states what should be true, never what happened.**
8+
So when a capability is retired, **delete its requirement** in the same change that removes
9+
the code. `status` is `planned | live | partial`; there is no member meaning "we used to do
10+
this", because every one of those three describes something meant to be true now or soon.
1011

11-
This is the one thing a requirement does that the rest of the model cannot. Given a brief
12-
matching a retired feature, agents reading only the model proposed **reviving** it 24 times
13-
out of 24, each believing it was reusing. A retired feature is *more* attractive than a
14-
live one: purpose-built for exactly the request, never complicated by production.
12+
What a deleted entry leaves behind is a diff, which is the right home for it. If something
13+
about the retirement is worth carrying forward — why it went, what replaced it — put that in
14+
`notes` on the entry that survives, where a reader looking at today's model will find it.
1515

16-
Leaving a dangling `implementedBy` on an `abandoned` or `superseded` requirement is
17-
**correct** — those nodes are supposed to be gone, and `verify` allows it deliberately. On
18-
`live` or `partial` the same dangling reference is an error: the model moved and the
19-
requirement went stale.
16+
Leaving a dangling `implementedBy` is **correct only on `planned`**: the entry precedes the
17+
nodes. On `live` or `partial` the same dangling reference is an error — the model moved and
18+
the requirement went stale.
2019

2120
**2. When you add an entity, claim it.** Every `object.entity` should appear in some
2221
requirement's `implementedBy`, or `verify` says so.
@@ -108,8 +107,9 @@ usually one nobody read carefully. Then say what was DECIDED, which is a separat
108107
- **absent** — undecided, and that is a real state. `verify` counts these, because
109108
*"which gaps has nobody ruled on?"* is the question a review exists to answer.
110109

111-
A `partial` nobody intends to finish is usually `abandoned` — built then deliberately
112-
retired, the one status where a dangling reference is correct.
110+
A `partial` nobody intends to finish is `partial` + `@disposition: accepted` — the gap is
111+
understood and deliberately not being closed. If the capability itself is gone, delete the
112+
requirement instead.
113113

114114
**`status: planned` locks in work you have not started.** Its references may dangle (write
115115
the requirement before the entity), and it never counts toward object coverage — otherwise
@@ -125,13 +125,13 @@ ambition rather than work.
125125
violation: "A scene that advances on a clock rather than on the story"
126126
children:
127127
- requirement.functional:
128-
name: TurnTimer
128+
name: BeatProgression
129129
level: 4
130-
status: abandoned # retired deliberately -- do NOT revive
131-
statement: "Pacing was driven by a per-turn wall-clock timer"
132-
violation: "Pacing driven by elapsed time instead of beat completion"
133-
supersededBy: BeatProgression
134-
implementedBy: ["game::turn::TurnTimer"] # gone, and that is the point
130+
status: planned # not built yet -- refs may dangle
131+
statement: "A scene advances when its beat completes"
132+
violation: "A scene that advances with its beat unresolved"
133+
trackedBy: ["acme/game#412"]
134+
implementedBy: ["game::turn::BeatProgression"] # does not exist YET
135135
136136
- requirement.architectural:
137137
name: UuidPrimaryKeys

agent-context/skills/metaobjects-fit-assessment/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ cite. (3) Whether anyone fills the ledger in is itself untested; advertising it
2828
is the brochure failure this skill exists to avoid.
2929
3030
TRIGGER to revisit — the release carrying `requirement.*` has shipped, AND either:
31-
Arm A a dogfooded project's `abandoned`/`superseded` entries are shown, retrospectively,
31+
Arm A a dogfooded project's retired capabilities are shown, retrospectively,
3232
to have been discoverable PRE-adoption from repo evidence at file:line standard
3333
(removal commit, dead flag, do-not-reintroduce comment);
3434
Arm B a team that adopted via this assessment hits a resurrection the ledger would have

agent-context/skills/metaobjects-verify/references/requirements.md

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ subverb**: requirements are metadata, so they are checked on *every* `meta verif
88
| | owns |
99
|---|---|
1010
| **loader** | `@status` enum, required attrs, child rules, levels — unconditional |
11-
| **`verify`** | `@implementedBy` / `@verifiedBy` resolution — **severity depends on `@status`** |
11+
| **`verify`** | `@implementedBy` resolution — **severity depends on `@status`** |
1212

1313
A typo'd `@status` fails the **load** ("failed to load metadata"), before verify runs. If you
1414
see that, no other diagnostic in the run is trustworthy — fix it first and re-run.
@@ -18,25 +18,21 @@ see that, no other diagnostic in the run is trustworthy — fix it first and re-
1818
The **same** unresolved `@implementedBy` reference is:
1919

2020
- an **error** on `live` / `partial` — the model moved and the requirement is stale;
21-
- **allowed** on `abandoned` / `superseded` — those nodes are *supposed* to be gone. That is
21+
- **allowed** on `planned` — those nodes do not exist YET. That is
2222
the entry doing its job, not drift.
2323

24-
So do not "fix" a dangling reference on an abandoned requirement by deleting it. Deleting it
25-
destroys the record that something was deliberately retired, which is the single thing this
26-
mechanism exists to preserve.
24+
So on a `planned` entry a dangling reference is the entry doing its job, not drift — do not
25+
"fix" it by deleting the reference, or you delete the plan.
2726

2827
## Exit codes
2928

3029
| situation | exit |
3130
|---|---|
3231
| clean tree, or no `requirement.*` nodes at all | 0 |
3332
| dangling `@implementedBy` on `live`/`partial` | 1 |
34-
| the same reference on `abandoned`/`superseded` | 0 |
33+
| the same reference on `planned` | 0 |
3534
| `@implementedBy` above the L4 link floor | 1 |
3635
| live `requirement.architectural` claimed by nothing | 1 |
37-
| `@verifiedBy` naming a test that exists nowhere | 1 |
38-
| `@verifiedBy` naming a name found only in an **unrecognised** test file | 0 (warning) |
39-
| `@verifiedBy` naming a test that is **skipped** | 0 (warning) |
4036
| an entity no requirement claims | 0 (warning) |
4137

4238
## The error codes, and the fix for each
@@ -53,34 +49,30 @@ name the remedy; this table exists so you can act on one without re-deriving the
5349
| `ERR_REQUIREMENT_ARCH_NO_IMPLEMENTERS` | a `live`/`partial` `requirement.architectural` that nothing implements | a policy declared and applied to nothing. Claim the nodes it governs, or drop it to `planned` — which is exempt, because it is not applied yet by definition. |
5450
| `ERR_REQUIREMENT_LEVEL_NESTING` | a node's `@level` disagrees with the level of the parent it nests under | nesting IS the hierarchy. Move the node to the right parent rather than editing the level to match where it happens to sit. |
5551
| `ERR_REQUIREMENT_BAD_LEVEL` | `@level` is not an integer inside the allowed range | levels are L1–L5 and nothing else. |
56-
| `ERR_REQUIREMENT_TEST_MISSING` | `@verifiedBy` names a test found nowhere | the test was renamed or deleted. If your project's test convention is simply unrecognised, declare it in `verify.testFiles` rather than deleting the claim. |
5752
| `ERR_MISSING_REQUIRED_ATTR` | a required attr is absent | `@statement`, `@status` and `@violation` are required on both subtypes; `@level` is required on `functional` and optional on `architectural`. |
5853
| `ERR_BAD_ATTR_VALUE` | a closed-enum attr has an unknown value | `@status` and `@disposition` are enforced by the LOADER, so a typo fails the load in every port rather than passing in some. |
5954

6055
**A note on the L4/L5 pair.** They are enforced at the same site and fail for opposite
6156
reasons, so reading only the code you hit can send you the wrong way. The question is not
6257
"is this ref valid?" — usually it is — but "does the LEVEL match the shape of the ref?"
6358

64-
## What counts as a test file is YOUR project's call
59+
## `verify` does not look at your tests
6560

66-
The scan ships patterns for jest/vitest/bun, JUnit, Maven Failsafe (`*IT`), xUnit/NUnit,
67-
pytest and Kotlin. Those are a convenience, **not an authority** — a built-in list is a guess
68-
about your repository, and a wrong guess reports a real test as a broken claim. Declare your
69-
conventions and they are added to the built-ins:
61+
It used to. `@verifiedBy` asked you to name a test, and `verify` checked that the **name**
62+
occurred somewhere in your test sources. That is existence evidence, never proof — an audit of
63+
one real 19-name ledger opened every named test and found **4 that did not verify their
64+
claim**: one matched a comment, one a dependency-injection key, one a real test of a
65+
*different* claim, and one a test of the entry's output where the claim was about its source
66+
text. `verify` reported zero errors throughout. The author picks the string, so the cheapest
67+
way to satisfy the check is to find any name that already exists.
7068

71-
```ts
72-
// metaobjects.config.ts
73-
export default defineConfig({ verify: { testFiles: ["**/*IT.kt", "**/*.feature"] } });
74-
```
75-
76-
If a named test is missing from the corpus but present in some other source file, `verify`
77-
warns and names that file rather than failing — an unrecognised convention is the tool's
78-
ignorance, not your mistake.
69+
The attribute is retired. Tying a requirement to a test is the job of a generator that emits
70+
the test **from** the requirement, so the link is structural rather than a name someone chose.
7971

8072
## What a green run does NOT prove
8173

8274
It proves **referential integrity**: statuses parse, levels are in range, links sit at or
83-
below the floor, references resolve, named tests exist and are not skipped.
75+
below the floor, references resolve.
8476

8577
It cannot prove a status is **true**, or that a node genuinely implements the requirement
8678
claiming it. No test can. That judgement is yours.

docs/CONFORMANCE.md

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,19 @@ in any port implemented the other half — two ports shipping halves of one prom
8787
fixture that could see it. A split is deliberate when no port makes a claim the corpus would
8888
have to check; it is a gap when some port already made one.
8989

90-
**A hazard for whoever ships the scheduled removal.** Contract prose lives *inside* attr
91-
descriptions in `expected-registry.json`, where five ports byte-match it and nothing else
92-
gates it — `@verifiedBy` carries "verify checks each name EXISTS and is not skipped; it
93-
never runs them", and `@trackedBy` carries "NOT resolved by verify, which does not reach the
94-
network". Deleting an attribute deletes its description, and with it the only gated
95-
statement of that guarantee. Sweep every description being removed for contract prose and
96-
rehome it in the **same** change, not after — the same class as the ADR-0047 renumbering
97-
trap, where a string that reads like prose is actually a gated artifact.
90+
**A hazard for whoever removes an attribute — and it has now fired once.** Contract prose
91+
lives *inside* attr descriptions in `expected-registry.json`, where five ports byte-match it
92+
and nothing else gates it. `@trackedBy` still carries "NOT resolved by verify, which does not
93+
reach the network". `@verifiedBy` carried "verify checks each name EXISTS and is not skipped;
94+
it never runs them" — and that attribute was **retired in `0.24.0` (FR-038)**, which deleted
95+
the only gated statement of that guarantee along with it.
96+
97+
That removal was done to the rule: the sentence was rehomed **in the same change**, to
98+
[`spec/capability-ledger.md`](../spec/capability-ledger.md) (§ "`verifiedBy` — RETIRED") and
99+
to the migration guide, before the description was deleted. Do the same for the next one.
100+
Sweep every description being removed for contract prose and rehome it in the **same**
101+
change, not after — the same class as the ADR-0047 renumbering trap, where a string that
102+
reads like prose is actually a gated artifact.
98103

99104
Per-port runners + commands:
100105

0 commit comments

Comments
 (0)