@metaobjectsdev/sdk 0.24.1 → 0.24.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -71,10 +71,33 @@ code behind a grep hit; a "duplicate" validator's *divergence* is the finding.
71
71
  design, and a real estate reports most of itself unclaimed on day one). Score the prose
72
72
  it would replace, and prefer ONE concrete claim an author can check over a
73
73
  whole-estate migration.
74
+ **First establish the estate LOADS.** An older ledger fails the load outright, so
75
+ `meta verify` never runs and everything in `references/requirements.md` is unavailable:
76
+ `@violation` and `@verifiedBy` were retired in **0.24.0** (no deprecation shim — ADR-0023
77
+ seals the registry); an index key declaring both `@fields` and `@expr` is refused since
78
+ **0.24.1**; and **0.24.2** turned `@status: abandoned | superseded` into **`retired`**. Run
79
+ **`meta upgrade`** (previews by default; `--apply` writes) before auditing, and audit the
80
+ upgraded tree. It rewrites only what has one correct answer and **refuses the rest, exiting
81
+ non-zero** — a refusal is itself a finding, naming the entries whose disposition nobody
82
+ recorded.
83
+ **Then audit the `retired` entries specifically.** They are where the ledger earns its
84
+ keep — agents proposing a retired capability's rebuild is the one failure this mechanism
85
+ has controlled evidence against — and they are also where it decays quietly. Check that
86
+ each `@statement` reads as a PROHIBITION rather than a diary entry ("X is never done",
87
+ not "we used to do X"), and that `@counterexample` describes the REVIVAL. A retired entry
88
+ whose statement narrates history is invisible to the reader it exists to stop.
74
89
  - [ ] **F. Drift-gate adoption.** Is `meta verify` wired into CI / pre-commit? Which
75
90
  subverbs (`--codegen` / `--templates` / `--db`)? Committed-codegen freshness gate?
76
91
  Advisories heeded? Routine `--no-verify` bypass? Loader `ERR_*` / warnings addressed?
77
92
  Parse the stable `code` field, not message text (ADR-0009).
93
+ **Is any advisory half switched OFF?** A gate can be wired, green, and muted. The
94
+ requirements authoring lint is advisory and mutable — `--no-requirement-lint` or
95
+ `META_NO_REQUIREMENT_LINT=1` silences it while the gate above it still runs and can still
96
+ exit 1, so a green pipeline says nothing about the seven `WARN_REQUIREMENT_*` authoring
97
+ checks. Grep CI config and scripts for both spellings. Muted is not automatically a defect —
98
+ a project that made a deliberate call is fine — but it moves those checks back onto the
99
+ auditor (see `references/requirements.md`), so **say which it is** rather than reporting the
100
+ ledger as lint-clean.
78
101
  **Coverage completeness — "wired" is not "covers it all."** A gate can be present
79
102
  yet blind to a whole artifact class. Confirm each subverb actually covers what the
80
103
  project HAS: (a) **templates** — `verify --templates` on a CLI before the #193 fix
@@ -114,8 +114,10 @@ classify it (using the classification scheme in `SKILL.md`) and route the cutove
114
114
 
115
115
  - **`identity.primary`** (`@generation`) — hunt hand-assigned primary keys / ID generation
116
116
  the primary identity's `@generation` strategy models.
117
- - **`identity.secondary`** (`@fields`; physical escapes `@using`/`@expr`/`@where`/`@orders`)a
118
- UNIQUE alternate key (uniqueness is the type — the legacy `@unique` attr was removed from it);
117
+ - **`identity.secondary`** (keys off `@fields` XOR `@expr` — exactly one, never both; physical
118
+ escapes `@using`/`@where`/`@orders`) — a UNIQUE alternate key (uniqueness is the type — the
119
+ legacy `@unique` attr was removed from it, and the same XOR rule as `index.lookup` applies here
120
+ because ADR-0040 puts uniqueness in the TYPE: a secondary identity IS a unique index);
119
121
  hunt hand-rolled unique constraints or raw-SQL partial/functional unique indexes it models.
120
122
  - **`identity.reference`** (`@references`, `@enforce`) — hunt hand-written FK constraints /
121
123
  reference enforcement the reference identity already declares.
@@ -238,6 +240,12 @@ subtypes with opposite polarity: `requirement.functional` fails when NOTHING imp
238
240
  recorded as a TODO, or a ticket number in a comment. **ABSENT disposition means UNDECIDED**,
239
241
  which is the point: a gap nobody has ruled on reads differently from one deliberately
240
242
  accepted.
243
+ - **The doc slots behave differently on this node type** — do not audit them by the generic
244
+ rule below. `title` is CHARTERED here (a requirement's `name` is an identifier, so the label
245
+ lives in `title`) and is **rendered** by `meta docs`, heading each entry after its dotted
246
+ path; `summary` is INERT (nothing reads it, and `@statement` is already the required
247
+ one-liner) and `meta verify` warns on one. So an absent `summary` is correct, a populated
248
+ one is the finding, and a populated `title` is correct.
241
249
  - **CALIBRATION — entirely opt-in and warning-only where it counts.** A project declaring no
242
250
  `requirement.*` nodes sees no diagnostics at all, and object coverage ("entities claimed
243
251
  by nothing") ships as a WARNING deliberately — a real estate carrying one requirement will
@@ -9,8 +9,38 @@ effort re-deriving what a green run already proves.
9
9
 
10
10
  ## What verify has already proven (do not re-check by hand)
11
11
 
12
- Links sit at or below the L4 floor, nesting agrees with levels, `@status` values are legal,
13
- and references resolve (with dangling allowed on `planned`, whose nodes do not exist yet).
12
+ A run answers two different questions in two separate sections. Both are already-proven
13
+ ground; neither is worth an auditor's time.
14
+
15
+ **The gate** settles referential integrity: links sit at or below the L4 floor, nesting
16
+ agrees with levels, `@status` values are legal, and references resolve (with dangling allowed
17
+ on `planned`, whose nodes do not exist yet).
18
+
19
+ **The authoring lint** — printed under its own heading, advisory, and unable to change the
20
+ exit code — settles the naming and prose defects you would otherwise find by reading every
21
+ entry:
22
+
23
+ | code | what a clean run has already proven |
24
+ |---|---|
25
+ | `WARN_REQUIREMENT_NAME_NOT_ADDRESSABLE` | no `name` is blank, space-padded, or carries a `.` / `/` / `:` that breaks the dotted path or the generated stub's filename |
26
+ | `WARN_REQUIREMENT_NAME_READS_AS_PROSE` | no `name` is a sentence sitting in the address slot |
27
+ | `WARN_REQUIREMENT_NAME_RESTATES_STATEMENT` | no `name` and `@statement` are the same sentence written twice |
28
+ | `WARN_REQUIREMENT_PROSE_EMPTY` | no `@statement` / `@counterexample` is declared-but-blank (the loader enforces presence, never content) |
29
+ | `WARN_REQUIREMENT_PROSE_DUPLICATED` | no `description` repeats `@statement` whole or as its opening sentence, and no `@counterexample` repeats it |
30
+ | `WARN_REQUIREMENT_INERT_DOC_SLOT` | no `summary` is set — nothing reads it, and `@statement` is already the required one-liner |
31
+ | `WARN_REQUIREMENT_TITLE_IS_AN_ID` | no `title` leads with a catalogue or ticket id |
32
+
33
+ Two limits, and each puts something back on your list:
34
+
35
+ - **The lint is mutable.** `--no-requirement-lint` / `META_NO_REQUIREMENT_LINT=1` silences the
36
+ advisory half while the gate still runs. **Establish whether the project mutes it** (§F of
37
+ the checklist asks this) — against a muted lint the whole table above proves nothing.
38
+ - **It reports only EXACT repeats.** A paraphrase — `description` restating `@statement` in
39
+ different words, a `@counterexample` that merely negates it — is invisible to the tool and
40
+ is a legitimate hand finding.
41
+
42
+ `title` is deliberately NOT flagged as an inert slot; it is chartered as the entry's label and
43
+ is rendered (see item 6). Do not report a populated `title` as a defect.
14
44
 
15
45
  ## What only a human or an agent reading the code can catch
16
46
 
@@ -40,6 +70,14 @@ functional one.
40
70
  model. If the tree is flat, or every entry is L3, the levels are carrying no information and
41
71
  should be simplified rather than defended.
42
72
 
73
+ **6. Titles that are not labels.** `title` is now **rendered** — `meta docs` heads each entry
74
+ `## checkout.payment — Payment capture`, the path first and the label after it — so a slot
75
+ that used to be inert is read by every reader of the generated page. The lint catches only
76
+ the id-shaped case (`FR-448 …`); it cannot tell a *useless* label from a good one. Read the
77
+ titles: one that restates `@statement`, or repeats the path in prose, is now visible noise in
78
+ a heading rather than a private authoring habit. An absent `title` is NOT a finding — the
79
+ entry heads by its path alone, which is what every sibling surface addresses it by.
80
+
43
81
  ## Scope — do NOT flag these as defects
44
82
 
45
83
  - **Unclaimed `object.value` / `object.projection`.** Exempt by design: a value is a shape, a
@@ -596,6 +596,11 @@ Declaring **neither** — or **both** — is `ERR_INVALID_INDEX`. This applies t
596
596
  `identity.secondary` as well as `index.lookup`: uniqueness lives in the type, so a unique
597
597
  index keys itself the same way.
598
598
 
599
+ Legacy metadata declaring both used to load, with `@fields` **silently discarded**. If you
600
+ meet one, `meta upgrade --apply` drops `@fields` — do not hand-pick the survivor: the index
601
+ in the database is the expression one, so keeping `@expr` reproduces it and keeping
602
+ `@fields` would emit a migration against live data.
603
+
599
604
  The db provider contributes physical-tuning attrs alongside either form: `@orders`
600
605
  (per-column sort direction), `@using` (access method — `gin`/`gist`/`hash`; default
601
606
  `btree`), and `@where` (partial-index predicate).
@@ -5,13 +5,31 @@ describe. Read the existing requirement nodes before designing anything. Two rul
5
5
  more than the rest.
6
6
 
7
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.
11
-
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.
8
+ `status` is `planned | live | partial | retired`, and every one of them states something
9
+ meant to be true. `retired` is not the exception: it means **"this was built, then
10
+ deliberately removed, and must not be rebuilt"** a prohibition in force, falsifiable by
11
+ exactly one observable, the capability reappearing.
12
+
13
+ So write a retired entry as a rule, not a diary. `statement: "An unpaid order is never
14
+ expired by a wall-clock timer"` is a prohibition; `"We used to expire orders on a timer"` is
15
+ history wearing a requirement's shape, and no gate will catch it. On a retired entry
16
+ `counterexample` describes **the revival** — the thing a future reader is about to propose.
17
+
18
+ **Do not delete a retired capability's entry.** Keeping it is the single most load-bearing
19
+ thing this vocabulary does: given a brief for a capability that had been retired, agents
20
+ working from the model alone proposed rebuilding it 24 times out of 24. The entry is what
21
+ stops that, so a retirement is a status change plus a rewritten statement, never a deletion.
22
+
23
+ Three rules the loader enforces on `retired`:
24
+
25
+ - **`implementedBy` is REFUSED** — a retired capability has no implementation by definition.
26
+ What used to implement it goes in `notes`. If deleting the references feels wrong, the
27
+ capability is probably not retired: an entry whose nodes are still there is `live` or
28
+ `partial`.
29
+ - **`supersededBy` names the requirement that replaced it**, legal here only, and RESOLVED —
30
+ so the chain still works when the replacement is itself retired later.
31
+ - It never counts toward object coverage, and is exempt from the architectural universality
32
+ check.
15
33
 
16
34
  Leaving a dangling `implementedBy` is **correct only on `planned`**: the entry precedes the
17
35
  nodes. On `live` or `partial` the same dangling reference is an error — the model moved and
@@ -27,10 +45,9 @@ rule kills *"the system is reliable"*. If you cannot say what breaking it looks
27
45
  delete it.
28
46
 
29
47
  **Four prose slots, and `statement` is the one that means "description".** A requirement can
30
- also carry the common `title`, `description` and `notes`, and they overlap badly unless you
31
- decide the split before writing any of them:
48
+ also carry the common `description` and `notes`, and they overlap badly unless you decide the
49
+ split before writing any of them:
32
50
 
33
- - `title` — a short **noun-phrase** label (`name` is an identifier; this is what an index shows)
34
51
  - `statement` — **the claim**. This IS the description of what the requirement is
35
52
  - `counterexample` — **what would falsify the claim**, which is what makes it checkable
36
53
  - `description` — **the scope**: what the claim covers, what it deliberately does not, and
@@ -38,12 +55,33 @@ decide the split before writing any of them:
38
55
  - `notes` — **the evidence**: how you know the `status` is true — citations, vocabularies, the
39
56
  control you ran to prove an absence was real
40
57
 
58
+ **`title` is a LABEL and is chartered here; `summary` is not.** A requirement's `name` is an
59
+ identifier and its address renders as a dotted camelCase path, so a short noun-phrase `title` is
60
+ what an index shows — the requirement attribute table in `spec/capability-ledger.md` says so by
61
+ name, and the generated requirements page renders it in the heading after the path
62
+ (`## checkout.payment — Payment capture`). `summary` is different: `statement` is already the
63
+ required one-line sentence, so a `summary` can only repeat it and nothing reads it (`verify` warns,
64
+ `WARN_REQUIREMENT_INERT_DOC_SLOT`). `notes` is unrendered on purpose — chartered internal-only.
65
+
66
+ **Never put a catalogue or ticket id in `title`.** A title is a noun phrase; an id is not a name.
67
+ `title: "FR-467 — Order recording"` is two things in one slot — put the id in `trackedBy` and
68
+ keep the phrase as the title. `verify` warns (`WARN_REQUIREMENT_TITLE_IS_AN_ID`).
69
+
70
+ **The `name` is an address, so write it as an identifier.** It is the segment of the dotted
71
+ path (`Ordering.Placement.Recorded`) and the filename of the generated test stub. A `.` in a
72
+ name is indistinguishable from nesting — `Orders.Recorded` and `Orders` containing `Recorded`
73
+ produce the same path — and a sentence for a name puts the claim somewhere nothing reads it.
74
+ Both load; `verify` warns (`WARN_REQUIREMENT_NAME_NOT_ADDRESSABLE`,
75
+ `WARN_REQUIREMENT_NAME_READS_AS_PROSE`, `WARN_REQUIREMENT_NAME_RESTATES_STATEMENT`).
76
+
41
77
  Two failure modes, both of which look like diligence. A `description` that **paraphrases the
42
78
  statement** is padding, and it makes every later reader trust the ledger less — leave it off
43
79
  instead, it is optional. A `description` that **narrates the evidence** belongs in `notes`;
44
80
  the tell is a fact you had to read the implementation to learn. Mechanical test for the last
45
81
  line: *would this sentence have to change if the code changed but the model did not?* Then it
46
- is `notes`.
82
+ is `notes`. `verify` warns on the exact repeats (`WARN_REQUIREMENT_PROSE_DUPLICATED`), and on
83
+ a `statement` or `counterexample` that is present but blank (`WARN_REQUIREMENT_PROSE_EMPTY`) —
84
+ the loader requires those attrs to exist, never to say anything.
47
85
 
48
86
  **Hierarchy is nesting, and links live at the bottom.** L1 solution, L2 segment, L3
49
87
  service — these never reference the model. **L4** binds a declared top-level node — an
@@ -133,10 +133,10 @@ cross-subtype 404. Conformance-gated by `fixtures/api-contract-conformance/tph`
133
133
  (HTTP wire shape) and `fixtures/persistence-conformance/tph-*` (single-table
134
134
  runtime semantics).
135
135
 
136
- ## Docs — `meta docs` (one door, two surfaces)
136
+ ## Docs — `meta docs` (one door, three surfaces)
137
137
 
138
138
  Documentation is NOT a `meta gen` generator. The single door is the `meta docs`
139
- command, which emits two cross-linked **surfaces** under one output dir (default
139
+ command, which emits three cross-linked **surfaces** under one output dir (default
140
140
  `./docs`):
141
141
 
142
142
  - **model surface** (`./docs/<Entity>.md`, `./docs/<Template>.md`) — the neutral
@@ -146,11 +146,19 @@ command, which emits two cross-linked **surfaces** under one output dir (default
146
146
  `./docs/api/AGENT-API.md`) — the SDK/API reference: the concrete imports,
147
147
  function signatures, payload field shapes, and runnable examples for *this*
148
148
  project's generated code.
149
+ - **requirements surface** (`./docs/requirements.md`, `./docs/requirements.toon`) —
150
+ the declared `requirement.*` ledger as documentation, with each entry headed by its
151
+ dotted path and its `title` where it has one, and each entity page naming the
152
+ requirements that claim it. Metadata-alone like the model surface, so it needs no
153
+ gen config. **On by default since 0.24.0** — a project declaring no `requirement.*`
154
+ nodes writes no requirements file and the run says nothing about the surface at all,
155
+ deliberately: reporting "0 requirement pages" would advertise a surface that never ran.
149
156
 
150
157
  ```bash
151
- npx meta docs # both surfaces → ./docs (model) + ./docs/api (api)
158
+ npx meta docs # all three → ./docs (model) + ./docs/api + ./docs/requirements.*
152
159
  npx meta docs --model # model surface only
153
160
  npx meta docs --api # api surface only
161
+ npx meta docs --requirements # requirement ledger only
154
162
  npx meta docs --out ./site-docs # write under a different root
155
163
  ```
156
164
 
@@ -18,13 +18,14 @@ DEFERRED, ON PURPOSE — capability requirements (`requirement.functional` /
18
18
  `requirement.architectural`). This skill deliberately says NOTHING about them, and that is a
19
19
  decision, not an omission.
20
20
 
21
- Why not now: (1) the grounding rule above requires every capability claim be verifiable
22
- against the CURRENT RELEASE, and `requirement.*` is not in one yet. (2) The signal->feature
23
- mapping is unvalidated against this skill's own kill criterion: the controlled evidence
24
- (0/24 model-only revivals) measures POST-adoption model-reading, and the feature's premise --
25
- that the disproof lives nowhere in the model -- cuts against pre-adoption detectability. If
26
- the reasoning was never written down, there is nothing for an evidence-cited assessment to
27
- cite. (3) Whether anyone fills the ledger in is itself untested; advertising it pre-adoption
21
+ Why not now: (1) SPENT -- this read "not in a release yet", and it is now. `requirement.*`
22
+ shipped in 0.22.0 and has evolved twice since (0.23.0 added `planned` / `@disposition` /
23
+ `@trackedBy`; 0.24.0 made the vocabulary prescriptive-only). Do not repeat this reason.
24
+ (2) The signal->feature mapping is unvalidated against this skill's own kill criterion: the
25
+ controlled evidence (0/24 model-only revivals) measures POST-adoption model-reading, and the
26
+ feature's premise -- that the disproof lives nowhere in the model -- cuts against pre-adoption
27
+ detectability. If the reasoning was never written down, there is nothing for an evidence-cited
28
+ assessment to cite. (3) Whether anyone fills the ledger in is itself untested; advertising it pre-adoption
28
29
  is the brochure failure this skill exists to avoid.
29
30
 
30
31
  TRIGGER to revisit — the release carrying `requirement.*` has shipped, AND either:
@@ -34,6 +35,23 @@ TRIGGER to revisit — the release carrying `requirement.*` has shipped, AND eit
34
35
  Arm B a team that adopted via this assessment hits a resurrection the ledger would have
35
36
  caught, or asks why the assessment never mentioned it.
36
37
 
38
+ ARM A HAS FIRED (dogfooded adopter estate, 2026-08-13). Three retired or reversed capability
39
+ decisions were each citable PRE-adoption at file:line -- a removal commit, two explicit
40
+ do-not-reintroduce comments on the very constant an agent would revive, an .env.example line
41
+ saying the knob does not exist. The ANTI-TRIGGER did NOT fire: every one traced to committed
42
+ prose. Bound it honestly -- n=1, and that estate is unusually disciplined about recording
43
+ reversals in co-located prose, which is precisely the manual work `requirement.*` systematizes,
44
+ so it is a confound rather than a clean sample. It also partly falsifies the feature's own
45
+ premise that the disproof lives nowhere in the model.
46
+
47
+ SO THE DEFERRAL NOW RESTS ON (3) ALONE, which makes it a judgement about VALUE rather than a
48
+ fact about the release. Unprompted uptake is still unmeasured, and a later cross-estate reading
49
+ sharpened the question: ledger value tracks whether the ledger is EXECUTABLE -- whether some
50
+ mechanism can falsify a claim -- not when it was written. The largest ledger measured carried
51
+ no harness and produced no defect found by any mechanism. Advertising that to a team that will
52
+ hand-maintain it is the brochure failure this skill exists to avoid. Revisiting means deciding
53
+ the SHAPE below is worth spending, not re-checking whether the capability exists.
54
+
37
55
  ANTI-TRIGGER (defer -> never): if dogfooded entries trace only to tribal knowledge with no
38
56
  repo artifact, this assessment structurally cannot speak to the feature as a finding.
39
57
 
@@ -46,8 +64,10 @@ R0 verdict line: no verdict may turn on it.
46
64
 
47
65
  # MetaObjects Fit & Migration Assessment
48
66
 
49
- _Assessment prompt v1 (post-Phase-0 refinement). Grounded against MetaObjects npm `0.17.x` /
50
- Maven `7.9.x` verify every capability claim against the current release before asserting it._
67
+ _Assessment prompt v1 (post-Phase-0 refinement). Body originally grounded against MetaObjects
68
+ npm `0.17.x` / Maven `7.9.x`; capability claims re-swept against npm `0.24.x` / Maven `7.24.x`
69
+ on 2026-08-25, but not every line has been re-derived since — verify every capability claim
70
+ against the current release before asserting it._
51
71
 
52
72
  You are an AI assistant running a **pre-adoption fit assessment** for MetaObjects
53
73
  (https://github.com/metaobjectsdev/metaobjects — the cross-language metadata standard:
@@ -265,11 +285,13 @@ signature class. Hunt all ten classes:
265
285
  **necessity test**: expressible when every output column is a passthrough
266
286
  (`origin.passthrough @from/@via`), a count/sum/avg/min/max (`origin.aggregate
267
287
  @agg/@of/@via`, row-scoped with `@filter`), a predicate quantifier (`origin.aggregate
268
- @agg: any|all`), an array rollup (`origin.aggregate @agg: collect`), a non-aggregate
269
- derived scalar (`origin.computed @expr`), an argmax-style "one related row's column"
270
- pick (`origin.first @via` covers the common `DISTINCT ON` / lateral-join shape),
271
- a soft-delete/status/type row-scope (an
272
- object-level `@filter` on `object.projection`), or `extends`-borrowed and joins
288
+ @agg: any|all`), an array rollup (`origin.aggregate @agg: collect` with `@of`
289
+ naming one column, or `@of` OMITTED since 0.24.1 to collect each related row as the
290
+ carrying field's declared value object, which is the `json_agg(row_to_json(...))` shape),
291
+ a non-aggregate derived scalar (`origin.computed @expr`), an argmax-style "one related
292
+ row's column" pick (`origin.first @via` covers the common `DISTINCT ON` / lateral-join
293
+ shape), a soft-delete/status/type row-scope (an object-level `@filter` on
294
+ `object.projection`), or `extends`-borrowed — and joins
273
295
  follow declared relationships/`identity.reference` FKs. Expressible → projection
274
296
  candidate (note: an unmodeled hand view is *unmanaged* — invisible to `verify --db`;
275
297
  modeling it is what makes it gateable). `DISTINCT ON` and lateral join are **not**
@@ -313,7 +335,7 @@ concrete — lead with it.
313
335
  | schema vs model (5 + the DDL copy in 6) | spine owns DDL via `meta migrate`; **`meta verify --db`** (Node `meta` only; PG/SQLite/D1) |
314
336
  | opaque JSON columns (6-implicit) | `object.value` + `field.object @storage: jsonb` — the shape gets ONE checkable declaration + generated codecs |
315
337
  | read-model SQL (8) | `object.projection` + `origin.*` generate the view DDL |
316
- | scattered prompts (10) | `template.prompt` + typed payload VO + external text; **`meta verify --templates`** / `Renderer.verify` fails when a `{{field}}` no longer matches the payload; `template.output` (FR-006) + the tolerant `extract` parser and output-format fragment (FR-010) generate both sides of the tag contract |
338
+ | scattered prompts (10) | `template.prompt` + typed payload VO + external text; **`meta verify --templates`** / `Renderer.verify` fails when a `{{field}}` no longer matches the payload; a **responding** `template.prompt` — one carrying `@responseRef` — generates parser-on-receipt (FR-006) plus the output-format fragment and tolerant `extract` (FR-010), i.e. both sides of the tag contract. A `template.output` is **outbound only** and emits no parser (ADR-0052, 0.24.0) |
317
339
  | the metadata itself | strict provenance (ADR-0023): unknown attrs fail load |
318
340
 
319
341
  State the honest limits in the same section: `verify` cannot catch semantic mismodeling
@@ -428,8 +450,11 @@ promising it or counting it in benefits:
428
450
  count them).
429
451
  - email/URL/IP regexes → `@stringFormat: email` / `field.uri` / `field.inet`.
430
452
  - opaque JSON columns → `object.value` + `field.object @storage: jsonb` (from P2-b).
431
- - inline prompts / ad-hoc payload dicts / regex output parsing → `template.prompt` /
432
- `template.output` / `template.toolcall`.
453
+ - inline prompts / ad-hoc payload dicts → `template.prompt` / `template.toolcall`; a
454
+ hand-rolled regex or JSON-scrape of an LLM **reply** → a *responding* `template.prompt`
455
+ (one carrying `@responseRef`), never `template.output` — the subtype's axis is DIRECTION
456
+ and an output is outbound only (ADR-0052). Rendered emails / documents / config exports
457
+ are what `template.output` is for.
433
458
  - doc comments hand-written in migrations (`COMMENT ON`) → the common `description` attr.
434
459
  - a recurring closed variant-set as N sibling modules → project-registered provider
435
460
  subtype — VOCAB CANDIDATE, advisory only, never load-bearing for the verdict.
@@ -572,9 +597,11 @@ Every prose prediction gets a claim. Ceiling statements (P5-b) MUST carry
572
597
  runtime; consumer hand-wires the FastAPI router + repository impl; `metaobjects`
573
598
  console script `gen`/`verify` (no migrate).
574
599
  - **Prompt pillar** (all five ports): render + payload-VO codegen + `verify` templates +
575
- `template.output` parser-on-receipt (FR-006) + output-format fragment & tolerant
576
- `extract` (FR-010). MCP exposure of declared prompts/tools: not shipped never
577
- promise it.
600
+ parser-on-receipt for a **responding `template.prompt`** (FR-006) + output-format fragment
601
+ & tolerant `extract` (FR-010). Since 0.24.0 that whole inbound tier keys off `@responseRef`
602
+ on a `template.prompt`; a `template.output` is outbound-only and emits none of it
603
+ (ADR-0052) — do not promise a parser for one. MCP exposure of declared
604
+ prompts/tools: not shipped — never promise it.
578
605
  - **Not shipped, never promise**: `api.*`/`operation.*`/`binding.*` declared-API surface;
579
606
  the cut `byte`/`short`/`class` field stubs (non-functional, removed from the registry);
580
607
  `index.fulltext`/`vector`/`spatial` (reserved, unregistered); native PG enums /
@@ -46,6 +46,28 @@ Two more are caught structurally rather than by a command: **generated-edited**
46
46
  **migration-vs-metadata** (migrations are emitted *from* metadata diffs, so they
47
47
  can't drift by construction).
48
48
 
49
+ ## Before any of this: the metadata has to LOAD — `meta upgrade` for retired vocabulary
50
+
51
+ `verify` cannot report drift in a model it could not read. The registry is sealed
52
+ (ADR-0023), so a retired name has **no deprecation shim** — it is a load error, and
53
+ the run stops before the first drift check. Retirements that bite an existing estate:
54
+ `@readOnly` → `@mutability` and `origin.collection` → `origin.aggregate @agg: collect`
55
+ (0.24.0); `@violation` → `@counterexample`, plus `@verifiedBy`, `@supersededBy` and
56
+ `@status: abandoned | superseded` on `requirement.*` (0.24.0, FR-038); and an index key
57
+ declaring **both** `@fields` and `@expr`, refused since 0.24.1.
58
+
59
+ ```
60
+ meta upgrade # previews every rewrite; writes nothing
61
+ meta upgrade --apply # makes them
62
+ ```
63
+
64
+ Node-only, because it edits the metadata documents every port shares — a Java, Python
65
+ or C# project runs `npx meta upgrade` against its own sources. Canonical JSON and YAML
66
+ alike. It rewrites from the same table the loader's errors are generated from, fixes
67
+ only what has one correct answer, and **refuses the rest with a non-zero exit** so a
68
+ pipeline cannot record a partial migration as done. See
69
+ `references/requirements.md` for what it refuses on a ledger and why.
70
+
49
71
  ## Run `meta verify` before you call a build done
50
72
 
51
73
  Make a bare `meta verify` the last step before you consider any MetaObjects work
@@ -13,6 +13,37 @@ subverb**: requirements are metadata, so they are checked on *every* `meta verif
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.
15
15
 
16
+ ## A load failure on RETIRED vocabulary — run `meta upgrade`, do not hand-sweep
17
+
18
+ `@status` is not the only way the load fails before any of this runs. **0.24.0** retired
19
+ `@violation` (→ `@counterexample`) and `@verifiedBy`; **0.24.1** made an index key `@fields`
20
+ **XOR** `@expr`, so a node declaring both is now `ERR_INVALID_INDEX`. **0.24.2 reversed half
21
+ of the 0.24.0 cut**: `abandoned` and `superseded` become **`@status: retired`**, and
22
+ `@supersededBy` is registered again — this time as a reference the loader resolves. There is no deprecation shim for any of them —
23
+ the registry is sealed (ADR-0023), so a legacy document does not load at all and every check on
24
+ this page is unreachable until it does.
25
+
26
+ ```
27
+ meta upgrade # previews every rewrite; writes nothing
28
+ meta upgrade --apply # makes them
29
+ ```
30
+
31
+ It rewrites from the same table the loader's error text is generated from, so the fix you are
32
+ told about and the edit the tool makes cannot drift apart. It fixes only what has one correct
33
+ answer and **refuses the rest, exiting non-zero** — a partial migration can never be recorded
34
+ as finished by CI. Two refusals are expected on a real ledger and both are yours to decide:
35
+
36
+ - **`@status: abandoned` / `superseded` are no longer refused** — they rewrite to `retired`,
37
+ and the same run drops the `@implementedBy` that a retired entry may not carry. This was the
38
+ canonical judgement case in 0.24.0; FR-039 made it determinate.
39
+ - **`origin.collection`.** Retired to `origin.aggregate @agg: collect`; the attribute sets
40
+ differ, so the tool will not guess.
41
+
42
+ `@fields` beside `@expr` **is** rewritten, and the survivor is not a coin toss: that pair loaded
43
+ before 0.24.1 with `@fields` silently discarded, so the index already in your database is the
44
+ expression one. `upgrade` drops `@fields` — which reproduces the object that exists and emits no
45
+ DDL change. Do not hand-pick the other survivor; that invents a new index and migrates live data.
46
+
16
47
  ## The status asymmetry — the one that surprises people
17
48
 
18
49
  The **same** unresolved `@implementedBy` reference is:
@@ -49,6 +80,8 @@ name the remedy; this table exists so you can act on one without re-deriving the
49
80
  | `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. |
50
81
  | `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. |
51
82
  | `ERR_REQUIREMENT_BAD_LEVEL` | `@level` is not an integer inside the allowed range | levels are L1–L5 and nothing else. |
83
+ | `ERR_REQUIREMENT_RETIRED_HAS_IMPLEMENTORS` | a `@status: retired` entry declares `@implementedBy` | a retired capability has no implementation by definition. Delete the attribute and put what used to implement it in `notes`; if the nodes are still there, the capability is `live` or `partial`, not retired. |
84
+ | `ERR_REQUIREMENT_SUPERSEDED_BY_NOT_RETIRED` | `@supersededBy` on a status other than `retired` | it names what REPLACED a withdrawn capability, so on a live one there is nothing for it to say. |
52
85
  | `ERR_MISSING_REQUIRED_ATTR` | a required attr is absent | `@statement`, `@status` and `@counterexample` are required on both subtypes; `@level` is required on `functional` and optional on `architectural`. |
53
86
  | `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. |
54
87
 
@@ -56,6 +89,40 @@ name the remedy; this table exists so you can act on one without re-deriving the
56
89
  reasons, so reading only the code you hit can send you the wrong way. The question is not
57
90
  "is this ref valid?" — usually it is — but "does the LEVEL match the shape of the ref?"
58
91
 
92
+ ## The authoring lint — a second section, never an error
93
+
94
+ `verify` also prints an **authoring lint** under its own heading, after the gate's own
95
+ warnings:
96
+
97
+ ```
98
+ meta verify — requirements: 6 authoring warning(s) (advisory — does not fail the build):
99
+ ```
100
+
101
+ Read it as a different claim from everything above. The gate says the ledger **disagrees with
102
+ the model**; the lint says it agrees but **records less than its author thinks**. Every finding
103
+ is a warning and none can change the exit code, so a lint-only run is a passing run.
104
+
105
+ | code | what it means | fix |
106
+ |---|---|---|
107
+ | `WARN_REQUIREMENT_NAME_NOT_ADDRESSABLE` | the `name` holds a character that breaks the dotted path or the generated stub filename | rename it. A `.` is the common one and the worst: `Orders.Recorded` and `Orders` containing `Recorded` produce the **same** path, so the address stops identifying one node. |
108
+ | `WARN_REQUIREMENT_NAME_READS_AS_PROSE` | the `name` is a sentence | the name is an address — make it an identifier and put the prose in `@statement`. |
109
+ | `WARN_REQUIREMENT_NAME_RESTATES_STATEMENT` | `name` and `@statement` say the same thing | the claim is written twice. Keep `@statement` (every surface reads it) and shorten the name. |
110
+ | `WARN_REQUIREMENT_PROSE_EMPTY` | `@statement` or `@counterexample` is present but blank | the loader requires the attr to EXIST, never to say anything. Write the sentence, or delete the entry. |
111
+ | `WARN_REQUIREMENT_PROSE_DUPLICATED` | `description` repeats `@statement` (whole, or as its opening sentence), or `@counterexample` does | `@statement` is already the description. `description` holds the SCOPE; drop it entirely if the scope is obvious. |
112
+ | `WARN_REQUIREMENT_INERT_DOC_SLOT` | `summary` is set on a requirement | `@statement` is already the required one-line sentence, so a summary can only repeat it, and nothing reads it. Delete it. (`title` is NOT flagged — it is chartered as the entry's label.) |
113
+ | `WARN_REQUIREMENT_TITLE_IS_AN_ID` | `title` holds a catalogue or ticket id | a title is a noun phrase and an id is not a name. **Split** it — the id to `@trackedBy`, the phrase stays the title. Do not move the whole string; that throws the label away. |
114
+
115
+ Findings from both sections are addressed by the requirement's **dotted path**, never its
116
+ bare name — two branches of a ledger may reuse a name, so a bare one can be ambiguous.
117
+
118
+ Mute the lint with `--no-requirement-lint` or `META_NO_REQUIREMENT_LINT=1`. That silences the
119
+ advisory half **only** — the gate above still runs and can still exit 1.
120
+
121
+ Two deliberate silences. The lint reports only **exact** repeats, never a paraphrase — a
122
+ similarity threshold on prose produces findings you can argue with. And it never judges
123
+ whether a statement is true or a counterexample sufficient; those are the judgements the
124
+ ledger exists to record.
125
+
59
126
  ## `verify` does not look at your tests
60
127
 
61
128
  It used to. `@verifiedBy` asked you to name a test, and `verify` checked that the **name**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaobjectsdev/sdk",
3
- "version": "0.24.1",
3
+ "version": "0.24.2",
4
4
  "description": "Workspace helpers and agent-docs utilities for MetaObjects projects.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -56,7 +56,7 @@
56
56
  "access": "public"
57
57
  },
58
58
  "dependencies": {
59
- "@metaobjectsdev/metadata": "0.24.1",
59
+ "@metaobjectsdev/metadata": "0.24.2",
60
60
  "zod": "^3.23.0"
61
61
  },
62
62
  "devDependencies": {