682f0abfec
metric_vocabulary() hand-lists 17 scalar metric names; a field added or renamed on RMetrics/RunMetrics/FamilySelection (aura-analysis) silently desynced the gate/stage checks — a new scalar stayed un-gateable until every roster was hand-updated (the 3-copy drift class #160 closed for node type ids). Pin the vocabulary against the types' real serde output instead of a second hand-copy: enumerate the scalar (JSON-Number) fields of real instances of the three metric-bearing types and assert set-equality with metric_vocabulary(). A new un-gated scalar (present in the type, absent from the vocabulary) or a stale vocabulary entry (present in the vocabulary, gone from every type) now goes red. RMetrics comes from the real producer summarize_r; RunMetrics and FamilySelection from exhaustive struct literals, so a new field also breaks the test's compilation until it is consciously classified. Selection provenance numbers (n_trials, raw_winner_metric) and the Optional annotation identifiers are excluded with rationale — the types mix scalar metrics with scalar provenance and carry no type-level marker between them, so that one distinction stays explicit. Green on arrival (the rosters agree today); the roster-macro maximal shape that would remove the hand-list entirely waits on #147 (where the metric vocabulary's home is decided). Guard test only, no production change. closes #190