You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 415549d
Browse filesBrowse the repository at this point in the historyBrowse files
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
Copy file name to clipboardExpand all lines: agent-context/skills/metaobjects-authoring/SKILL.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1007,7 +1007,7 @@ Reach for it when the project needs to answer any of:
1007
1007
1008
1008
- **"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.
1009
1009
- **"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.
1011
1011
- **"What have we committed to build?"** — `@status: planned`. Its references may dangle, and it never counts toward object coverage.
|**`verify`**|`@implementedBy`/ `@verifiedBy`resolution — **severity depends on `@status`**|
11
+
|**`verify`**|`@implementedBy` resolution — **severity depends on `@status`**|
12
12
13
13
A typo'd `@status` fails the **load** ("failed to load metadata"), before verify runs. If you
14
14
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-
18
18
The **same** unresolved `@implementedBy` reference is:
19
19
20
20
- 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
22
22
the entry doing its job, not drift.
23
23
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.
27
26
28
27
## Exit codes
29
28
30
29
| situation | exit |
31
30
|---|---|
32
31
| clean tree, or no `requirement.*` nodes at all | 0 |
33
32
| dangling `@implementedBy` on `live`/`partial`| 1 |
34
-
| the same reference on `abandoned`/`superseded`| 0 |
33
+
| the same reference on `planned`| 0 |
35
34
|`@implementedBy` above the L4 link floor | 1 |
36
35
| 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) |
40
36
| an entity no requirement claims | 0 (warning) |
41
37
42
38
## 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
53
49
|`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. |
54
50
|`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. |
55
51
|`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. |
57
52
|`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`. |
58
53
|`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. |
59
54
60
55
**A note on the L4/L5 pair.** They are enforced at the same site and fail for opposite
61
56
reasons, so reading only the code you hit can send you the wrong way. The question is not
62
57
"is this ref valid?" — usually it is — but "does the LEVEL match the shape of the ref?"
63
58
64
-
## What counts as a test file is YOUR project's call
59
+
## `verify` does not look at your tests
65
60
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.
0 commit comments