entropyfs 0.7.17

Entropy-native Linux filesystem: persist irreducible state, materialize structure, preserve exact bytes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
//! Engine metrics DTO (Phase 12E.1/12E.6).
//!
//! # PURPOSE
//!
//! The stable, versioned, machine-readable accounting surface of the
//! embeddable [`crate::engine::Engine`]. This module defines the *external
//! DTO* for `Engine::metrics()` and for the `entropyfs metrics --json`
//! CLI — deliberately NOT the internal Rust state types, so the public
//! schema can evolve (by `schema_version`) without leaking store internals.
//!
//! # BOUNDARY
//!
//! KNOWS: precisely-defined aggregates that the store exposes through
//! public accessors (`StoreStats`, `PhysicalReport`, DSFB stats, the perf
//! snapshot). NEVER KNOWS: how any aggregate is computed internally, the
//! record format, or any write path. Adding a field here REQUIRES a
//! definition-table entry (see [`METRIC_REGISTRY`]) and a `schema_version`
//! bump; removing or renaming a field is a breaking schema change.
//!
//! # MODEL
//!
//! Every metric is either a *snapshot* (the value at collection time, e.g.
//! physical bytes used) or *cumulative* (a monotonic counter since the
//! store was opened, e.g. model-cache hits). Units are always explicit in
//! the field name or the registry. The registry (`METRIC_REGISTRY`) is the
//! normative definition of every exposed metric: name, unit, snapshot-vs-
//! cumulative, scope, reset behavior, and authority (which store accessor
//! produced it). A test walks the registry and the DTO together so the
//! documentation cannot drift from the implementation.
//!
//! # PERSISTENT AUTHORITY
//!
//! None. Metrics are diagnostic; nothing here is persisted, and nothing
//! here affects bytes, durability, or recovery.
//!
//! # FAILURE MODES
//!
//! Collection can fail when the underlying store accessor fails (e.g. the
//! physical report against a torn store). The DTO is still returned with
//! the failing section omitted; `Engine::metrics` documents which
//! accessors may be absent. Metrics never panic.

#![forbid(unsafe_code)]

use serde::{Deserialize, Serialize};

/// Format identity as recorded in the superblock.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FormatInfo {
    /// On-disk format major (the compatibility contract level).
    pub format_major: u16,
    /// On-disk format minor.
    pub format_minor: u16,
    /// `compat` feature bits set by this store (unknown bits ignorable).
    pub compat: u64,
    /// `ro_compat` feature bits (unknown bits force read-only).
    pub ro_compat: u64,
    /// `incompat` feature bits (unknown bits refuse open).
    pub incompat: u64,
    /// Transport backend in use (`sync` | `uring`) — a runtime choice, not
    /// an on-disk format property.
    pub io_backend: String,
}

/// Core byte-accounting aggregates.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct AccountingMetrics {
    /// Sum of materialized logical bytes across all reachable inodes
    /// (snapshot; unit: bytes).
    pub logical_bytes: u64,
    /// Sum of physical record bytes of root-reachable objects (snapshot;
    /// unit: bytes).
    pub reachable_bytes: u64,
    /// Sum of segment-file lengths (snapshot; unit: bytes).
    pub physical_used_bytes: u64,
    /// statvfs-based physical capacity of the backing store, capped by any
    /// `capacity_override` (snapshot; unit: bytes).
    pub physical_capacity_bytes: u64,
    /// `physical_capacity − physical_used` (snapshot; unit: bytes).
    pub physical_free_bytes: u64,
    /// Entries in the derived object index (snapshot; unit: objects).
    pub object_count: u64,
    /// Number of reachable data records (snapshot; unit: records).
    pub data_record_count: u64,
    /// Files in the engine blob namespace (snapshot; unit: blobs; O(n) to
    /// collect — every blob is one file under the hidden `.engine` dir).
    pub blob_count: u64,
}

/// Phase-9H physical reconciliation aggregates (independent of the derived
/// index — the index-vs-physical drift surface).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct PhysicalMetrics {
    /// Sum of root-reachable canonical record bytes.
    pub live_bytes: u64,
    /// Sum of unreachable-but-indexed record bytes (reclaimable).
    pub dead_indexed_bytes: u64,
    /// Sum of index-hidden record bytes.
    pub index_hidden_bytes: u64,
    /// Sum of unindexed record bytes.
    pub unindexed_bytes: u64,
    /// Sum of torn bytes.
    pub torn_bytes: u64,
    /// Sum of zero padding.
    pub zero_padding_bytes: u64,
    /// Sum of format overhead (magic + unclassified).
    pub format_overhead_bytes: u64,
    /// Physical bytes the reconciliation cannot explain (must be 0 on a
    /// healthy store).
    pub unexplained_bytes: u64,
}

/// Garbage-collection accounting.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct GcMetrics {
    /// Last-known unreachable (reclaimable) bytes, as recorded in the
    /// store's stats — NOT recomputed on every call (a full mark walk is
    /// O(store)); run `compact()` or the CLI `gc`/`fsck` to refresh
    /// (snapshot; unit: bytes).
    pub unreachable_bytes: u64,
}

/// DSFB advisory-observer accounting (zero decoding authority).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct DsfbMetrics {
    /// Chunks currently tracked by the observer (snapshot).
    pub tracked_chunks: usize,
    /// Observer steps (cumulative since open).
    pub steps: u64,
    /// Drift events (cumulative).
    pub drift_events: u64,
    /// Slew events (cumulative).
    pub slew_events: u64,
    /// Narrowed searches (cumulative).
    pub narrowed_searches: u64,
    /// Candidate evaluations across the write path (cumulative).
    pub candidates_evaluated: u64,
}

/// Phase 12C-1-2/12C-1-3 pressure-deferral accounting (the operator's
/// optimization-debt witness — advisory; the background optimizer pays
/// the debt).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct PressureMetrics {
    /// Whether the foreground pressure gate is currently engaged
    /// (hysteresis-transitioned; snapshot).
    pub pressured: bool,
    /// Sampled pressure scalars in Focused mode (cumulative since open).
    pub samples: u64,
    /// Idle → pressured transitions (cumulative; the "the state machine
    /// fired" witness).
    pub enter_events: u64,
    /// Pressured → idle transitions (cumulative).
    pub leave_events: u64,
    /// Write-path-observed pressured duration (cumulative; unit:
    /// milliseconds).
    pub pressured_time_ms: u64,
    /// Cumulative rANS/configurational deferrals by the `Focused` gate
    /// (cumulative since open; class-gate + pressure-gate skips).
    pub rans_skips: u64,
    /// Pending pressure-deferred extents since the last completed
    /// background-pass generation (snapshot; the debt).
    pub deferred_extents: u64,
    /// Pending pressure-deferred logical bytes since the last completed
    /// background-pass generation (snapshot; the debt — the operator's
    /// "accepted writes quickly and has N bytes of optimization debt"
    /// number).
    pub deferred_logical_bytes: u64,
    /// Age of the oldest pending deferral (unit: milliseconds since the
    /// generation start or the first deferral; snapshot).
    pub deferred_age_ms: u64,
    /// High-water of the pending debt (cumulative peak; unit: bytes).
    pub peak_deferred_bytes: u64,
    /// Starvation-cap refusals (cumulative: the gate wanted to defer but
    /// the debt cap said no).
    pub debt_cap_engagements: u64,
}

/// Performance-cache accounting.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct CacheMetrics {
    /// Model-object cache hits (cumulative since open).
    pub model_cache_hits: u64,
    /// Model-object cache misses (cumulative since open).
    pub model_cache_misses: u64,
}

/// One write-path phase latency sample (snapshot of the perf ring).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PhaseMetrics {
    /// Phase name (the stable perf-row key, e.g. `prepare`, `append`,
    /// `commit_lock_wait`, `epoch_wait`).
    pub phase: String,
    /// Sample count (cumulative).
    pub count: u64,
    /// Cumulative total (unit: milliseconds; cumulative).
    pub total_ms: f64,
    /// p50 (unit: microseconds; snapshot over the bounded ring).
    pub p50_us: f64,
    /// p95 (unit: microseconds; snapshot).
    pub p95_us: f64,
    /// p99 (unit: microseconds; snapshot).
    pub p99_us: f64,
}

/// The complete engine metrics DTO. Schema-versioned; add fields with a
/// version bump, never rewrite.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EngineMetrics {
    /// DTO schema version (currently 2; the 12C-1-3 pressure
    /// state-machine fields extended the v1 pressure block).
    pub schema_version: u32,
    /// Format identity.
    pub format: FormatInfo,
    /// Byte accounting.
    pub accounting: AccountingMetrics,
    /// Physical reconciliation.
    pub physical: PhysicalMetrics,
    /// GC accounting.
    pub gc: GcMetrics,
    /// DSFB observer accounting.
    pub dsfb: DsfbMetrics,
    /// Phase 12C-1-2 pressure-deferral accounting (the optimization-debt
    /// witness; advisory).
    pub pressure: PressureMetrics,
    /// Performance-cache accounting.
    pub cache: CacheMetrics,
    /// Write-path phase latencies (only phases with samples are listed).
    pub write_path_phases: Vec<PhaseMetrics>,
}

impl EngineMetrics {
    /// Render as pretty JSON (the `metrics --json` surface).
    pub fn to_json(&self) -> String {
        serde_json::to_string_pretty(self).unwrap_or_else(|_| "{}".into())
    }
}

/// One row of the normative metric registry.
#[derive(Debug, Clone, Copy)]
pub struct MetricDef {
    /// Stable metric key (`section.field`).
    pub key: &'static str,
    /// Unit, or `count`/`ratio`.
    pub unit: &'static str,
    /// `snapshot` (value at collection time) or `cumulative` (monotonic).
    pub kind: &'static str,
    /// Scope (store / engine / per-phase).
    pub scope: &'static str,
    /// Reset behavior (e.g. `on close` for cumulative counters).
    pub reset: &'static str,
    /// Authority: which store accessor produced the value.
    pub authority: &'static str,
}

/// The normative definition of every exposed metric (12E.6: "Every metric
/// requires: name, unit, snapshot vs cumulative, scope, reset behavior,
/// authority/source"). Extend this list with every DTO field.
pub const METRIC_REGISTRY: &[MetricDef] = &[
    MetricDef {
        key: "format.format_major",
        unit: "version",
        kind: "snapshot",
        scope: "store",
        reset: "never (on-disk)",
        authority: "superblock format_major",
    },
    MetricDef {
        key: "format.format_minor",
        unit: "version",
        kind: "snapshot",
        scope: "store",
        reset: "never (on-disk)",
        authority: "superblock format_minor",
    },
    MetricDef {
        key: "format.compat",
        unit: "bitmask",
        kind: "snapshot",
        scope: "store",
        reset: "never (on-disk)",
        authority: "superblock compat",
    },
    MetricDef {
        key: "format.ro_compat",
        unit: "bitmask",
        kind: "snapshot",
        scope: "store",
        reset: "never (on-disk)",
        authority: "superblock ro_compat",
    },
    MetricDef {
        key: "format.incompat",
        unit: "bitmask",
        kind: "snapshot",
        scope: "store",
        reset: "never (on-disk)",
        authority: "superblock incompat",
    },
    MetricDef {
        key: "format.io_backend",
        unit: "enum (sync|uring)",
        kind: "snapshot",
        scope: "store",
        reset: "at open",
        authority: "StoreConfig.io_backend",
    },
    MetricDef {
        key: "accounting.logical_bytes",
        unit: "bytes",
        kind: "snapshot",
        scope: "store",
        reset: "at open",
        authority: "Store::logical_bytes",
    },
    MetricDef {
        key: "accounting.reachable_bytes",
        unit: "bytes",
        kind: "snapshot",
        scope: "store",
        reset: "at open",
        authority: "StoreStats.reachable_bytes",
    },
    MetricDef {
        key: "accounting.physical_used_bytes",
        unit: "bytes",
        kind: "snapshot",
        scope: "store",
        reset: "at open",
        authority: "Store::physical_used",
    },
    MetricDef {
        key: "accounting.physical_capacity_bytes",
        unit: "bytes",
        kind: "snapshot",
        scope: "store",
        reset: "at open",
        authority: "Store::physical_capacity",
    },
    MetricDef {
        key: "accounting.physical_free_bytes",
        unit: "bytes",
        kind: "snapshot",
        scope: "store",
        reset: "at open",
        authority: "capacity − used (derived)",
    },
    MetricDef {
        key: "accounting.object_count",
        unit: "objects",
        kind: "snapshot",
        scope: "store",
        reset: "at open",
        authority: "ObjectIndex::len",
    },
    MetricDef {
        key: "accounting.data_record_count",
        unit: "records",
        kind: "snapshot",
        scope: "store",
        reset: "at open",
        authority: "StoreStats.data_record_count",
    },
    MetricDef {
        key: "accounting.blob_count",
        unit: "blobs",
        kind: "snapshot",
        scope: "engine",
        reset: "at open",
        authority: "engine blob-namespace directory scan (O(n))",
    },
    MetricDef {
        key: "physical.live_bytes",
        unit: "bytes",
        kind: "snapshot",
        scope: "store",
        reset: "at open",
        authority: "physical_report (Phase-9H)",
    },
    MetricDef {
        key: "physical.dead_indexed_bytes",
        unit: "bytes",
        kind: "snapshot",
        scope: "store",
        reset: "at open",
        authority: "physical_report (Phase-9H)",
    },
    MetricDef {
        key: "physical.index_hidden_bytes",
        unit: "bytes",
        kind: "snapshot",
        scope: "store",
        reset: "at open",
        authority: "physical_report (Phase-9H)",
    },
    MetricDef {
        key: "physical.unindexed_bytes",
        unit: "bytes",
        kind: "snapshot",
        scope: "store",
        reset: "at open",
        authority: "physical_report (Phase-9H)",
    },
    MetricDef {
        key: "physical.torn_bytes",
        unit: "bytes",
        kind: "snapshot",
        scope: "store",
        reset: "at open",
        authority: "physical_report (Phase-9H)",
    },
    MetricDef {
        key: "physical.zero_padding_bytes",
        unit: "bytes",
        kind: "snapshot",
        scope: "store",
        reset: "at open",
        authority: "physical_report (Phase-9H)",
    },
    MetricDef {
        key: "physical.format_overhead_bytes",
        unit: "bytes",
        kind: "snapshot",
        scope: "store",
        reset: "at open",
        authority: "physical_report (Phase-9H)",
    },
    MetricDef {
        key: "physical.unexplained_bytes",
        unit: "bytes",
        kind: "snapshot",
        scope: "store",
        reset: "at open",
        authority: "physical_report::unexplained",
    },
    MetricDef {
        key: "gc.unreachable_bytes",
        unit: "bytes",
        kind: "snapshot",
        scope: "store",
        reset: "refreshed by compact/gc/fsck",
        authority: "StoreStats.unreachable_bytes (last-known)",
    },
    MetricDef {
        key: "dsfb.tracked_chunks",
        unit: "chunks",
        kind: "snapshot",
        scope: "store",
        reset: "at open",
        authority: "ShardedStorageObserver stats",
    },
    MetricDef {
        key: "dsfb.steps",
        unit: "events",
        kind: "cumulative",
        scope: "store",
        reset: "at open",
        authority: "ShardedStorageObserver stats",
    },
    MetricDef {
        key: "dsfb.drift_events",
        unit: "events",
        kind: "cumulative",
        scope: "store",
        reset: "at open",
        authority: "ShardedStorageObserver stats",
    },
    MetricDef {
        key: "dsfb.slew_events",
        unit: "events",
        kind: "cumulative",
        scope: "store",
        reset: "at open",
        authority: "ShardedStorageObserver stats",
    },
    MetricDef {
        key: "dsfb.narrowed_searches",
        unit: "searches",
        kind: "cumulative",
        scope: "store",
        reset: "at open",
        authority: "ShardedStorageObserver stats",
    },
    MetricDef {
        key: "dsfb.candidates_evaluated",
        unit: "candidates",
        kind: "cumulative",
        scope: "store",
        reset: "at open",
        authority: "Store::candidates_evaluated",
    },
    MetricDef {
        key: "pressure.pressured",
        unit: "flag",
        kind: "snapshot",
        scope: "store",
        reset: "at open",
        authority: "Store::pressure_state",
    },
    MetricDef {
        key: "pressure.rans_skips",
        unit: "extents",
        kind: "cumulative",
        scope: "store",
        reset: "at open",
        authority: "Store::focused_rans_skips",
    },
    MetricDef {
        key: "pressure.deferred_extents",
        unit: "extents",
        kind: "snapshot",
        scope: "store",
        reset: "at completed background pass",
        authority: "Store::deferred_debt",
    },
    MetricDef {
        key: "pressure.deferred_logical_bytes",
        unit: "bytes",
        kind: "snapshot",
        scope: "store",
        reset: "at completed background pass",
        authority: "Store::deferred_debt",
    },
    MetricDef {
        key: "pressure.deferred_age_ms",
        unit: "milliseconds",
        kind: "snapshot",
        scope: "store",
        reset: "at completed background pass",
        authority: "Store::deferred_debt",
    },
    MetricDef {
        key: "pressure.samples",
        unit: "samples",
        kind: "cumulative",
        scope: "store",
        reset: "at open",
        authority: "Store::pressure_trace",
    },
    MetricDef {
        key: "pressure.enter_events",
        unit: "events",
        kind: "cumulative",
        scope: "store",
        reset: "at open",
        authority: "Store::pressure_trace",
    },
    MetricDef {
        key: "pressure.leave_events",
        unit: "events",
        kind: "cumulative",
        scope: "store",
        reset: "at open",
        authority: "Store::pressure_trace",
    },
    MetricDef {
        key: "pressure.pressured_time_ms",
        unit: "milliseconds",
        kind: "cumulative",
        scope: "store",
        reset: "at open",
        authority: "Store::pressure_trace",
    },
    MetricDef {
        key: "pressure.peak_deferred_bytes",
        unit: "bytes",
        kind: "cumulative",
        scope: "store",
        reset: "at open",
        authority: "Store::pressure_trace",
    },
    MetricDef {
        key: "pressure.debt_cap_engagements",
        unit: "events",
        kind: "cumulative",
        scope: "store",
        reset: "at open",
        authority: "Store::pressure_trace",
    },
    MetricDef {
        key: "cache.model_cache_hits",
        unit: "objects",
        kind: "cumulative",
        scope: "store",
        reset: "at open",
        authority: "Store::model_cache_hits",
    },
    MetricDef {
        key: "cache.model_cache_misses",
        unit: "objects",
        kind: "cumulative",
        scope: "store",
        reset: "at open",
        authority: "Store::model_cache_misses",
    },
    MetricDef {
        key: "write_path_phases[]",
        unit: "ms (total) / µs (p50/p95/p99)",
        kind: "snapshot+cumulative",
        scope: "per-phase",
        reset: "at open",
        authority: "perf::Timings::snapshot",
    },
];

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn dto_json_roundtrip() {
        let m = EngineMetrics {
            schema_version: 2,
            format: FormatInfo {
                format_major: 1,
                format_minor: 0,
                compat: 0,
                ro_compat: 0,
                incompat: 0x8000,
                io_backend: "sync".to_string(),
            },
            accounting: AccountingMetrics {
                logical_bytes: 1024,
                reachable_bytes: 512,
                physical_used_bytes: 2048,
                physical_capacity_bytes: 1 << 30,
                physical_free_bytes: (1 << 30) - 2048,
                object_count: 3,
                data_record_count: 2,
                blob_count: 1,
            },
            physical: PhysicalMetrics {
                live_bytes: 512,
                dead_indexed_bytes: 0,
                index_hidden_bytes: 0,
                unindexed_bytes: 0,
                torn_bytes: 0,
                zero_padding_bytes: 0,
                format_overhead_bytes: 4,
                unexplained_bytes: 0,
            },
            gc: GcMetrics {
                unreachable_bytes: 0,
            },
            dsfb: DsfbMetrics {
                tracked_chunks: 0,
                steps: 0,
                drift_events: 0,
                slew_events: 0,
                narrowed_searches: 0,
                candidates_evaluated: 0,
            },
            pressure: PressureMetrics {
                pressured: false,
                samples: 0,
                enter_events: 0,
                leave_events: 0,
                pressured_time_ms: 0,
                rans_skips: 0,
                deferred_extents: 0,
                deferred_logical_bytes: 0,
                deferred_age_ms: 0,
                peak_deferred_bytes: 0,
                debt_cap_engagements: 0,
            },
            cache: CacheMetrics {
                model_cache_hits: 0,
                model_cache_misses: 0,
            },
            write_path_phases: Vec::new(),
        };
        let json = m.to_json();
        let back: EngineMetrics = serde_json::from_str(&json).unwrap();
        assert_eq!(back, m);
    }

    #[test]
    fn registry_is_nonempty_and_keyed() {
        assert!(!METRIC_REGISTRY.is_empty());
        for def in METRIC_REGISTRY {
            assert!(!def.key.is_empty());
            assert!(def.key.contains('.') || def.key.contains("[]"));
            assert!(matches!(
                def.kind,
                "snapshot" | "cumulative" | "snapshot+cumulative"
            ));
            assert!(!def.authority.is_empty());
        }
    }
}