monetize-product 0.1.1

The product plugin trait for monetize: how a metered product reports Usage and receives an Entitlement. Types and one trait, serde only — no ledger, no vendor, no network.
Documentation
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
//! **Reading a DAILY COUNTER out of a series of readings** — the arithmetic
//! behind the console's SILENT and FRICTION lists.
//!
//! Pure. No store, no clock, no product — [`crate::trend`]'s shape, and for the
//! same reason: the fold has to be assertable without a wire.
//!
//! # The problem this exists to solve
//!
//! gunnar's flow meters are `*_today` counters. `refused_today` is *how many
//! transfers this namespace has had refused since midnight UTC*: it starts at 0
//! every day, only ever grows within a day, and drops back at the boundary. The
//! poller reads it every fifteen minutes and `UsageStore` keeps every reading
//! for 400 days.
//!
//! So the question *how many refusals last week* has an obvious wrong answer and
//! a correct one:
//!
//! * **Wrong: sum the readings.** A tenant refused once at 09:00 has
//!   `refused_today = 1` in every one of the day's remaining sixty readings, and
//!   summing them reports sixty refusals. The busier the poller, the bigger the
//!   lie — a monitoring interval would be silently multiplying a customer's
//!   friction.
//! * **Right: the MAXIMUM reading of each UTC day is that day's total**, because
//!   the counter is monotonic within the day; then add the days. That is
//!   [`daily_totals`].
//!
//! # Why the maximum and not the last reading of the day
//!
//! The last reading of a day is the natural choice and it is wrong at exactly
//! one moment: the poll that lands a few milliseconds after midnight, whose
//! reading belongs to the NEW day but whose `measured_at` may still be
//! attributed to the old one by a clock that disagrees by a hair. That reading
//! is a small number where the day's real total is large, and taking it would
//! erase the day. The maximum cannot be wrong in that direction — a counter
//! that only grows within a day has its total at its peak — and its failure
//! mode if a reading is ever attributed to the wrong day is to keep the larger
//! of two true numbers rather than to invent one.
//!
//! # What is NOT here
//!
//! This does not fold `last_push_unix_ms`. That meter is not a daily counter —
//! it is an instant, and gunnar already looks back over its whole kept window to
//! produce it — so the answer to *when was this tenant last seen* is the latest
//! reading's value, read straight off, and folding it would be arithmetic on a
//! timestamp. [`quiet_for_ms`] is the whole of what the SILENT list needs.

use std::collections::BTreeMap;

use crate::Usage;

/// Milliseconds in a day. UTC, no leap seconds: the wire carries Unix
/// milliseconds and a Unix millisecond has never counted one.
pub const DAY_MS: u64 = 86_400_000;

/// The UTC day a reading falls in, as whole days since the epoch.
pub fn day_of(unix_ms: u64) -> u64 {
    unix_ms / DAY_MS
}

/// **Per UTC day, the total of one `*_today` counter** — the day's maximum
/// reading. See the module doc for why the maximum.
///
/// Readings that do not carry the meter are skipped, not counted as zero: a
/// product that stopped reporting a meter and a product reporting zero are
/// different facts, and only the second is a day with no refusals.
///
/// A day with no reading at all is ABSENT from the map. The caller knows which
/// days it asked about, and a zero for a day nobody measured is the one number
/// that would make an unreachable product look like a quiet week.
pub fn daily_totals(readings: &[Usage], meter: &str) -> BTreeMap<u64, u64> {
    let mut days: BTreeMap<u64, u64> = BTreeMap::new();
    for reading in readings {
        let Some(value) = reading.meters.get(meter).copied() else {
            continue;
        };
        let day = days.entry(day_of(reading.measured_at_unix_ms)).or_insert(0);
        *day = (*day).max(value);
    }
    days
}

/// **The total of one `*_today` counter over the readings whose day falls in
/// `[since_unix_ms, until_unix_ms]`.**
///
/// The window is resolved to whole UTC days at both ends, because the counter
/// is a per-day quantity and half a day of one is not a number that exists.
pub fn window_total(readings: &[Usage], meter: &str, since_unix_ms: u64, until_unix_ms: u64) -> u64 {
    let (from, to) = (day_of(since_unix_ms), day_of(until_unix_ms));
    daily_totals(readings, meter)
        .range(from..=to)
        .map(|(_, total)| *total)
        .fold(0u64, |acc, n| acc.saturating_add(n))
}

/// **How long a tenant has been quiet**, or `None` when it has not been.
///
/// `last_seen_unix_ms` is the later of the two `last_*_unix_ms` meters, which
/// gunnar computes over its WHOLE kept window rather than over today — so a
/// tenant with a today of zeroes and a push six weeks ago is six weeks quiet
/// and not silent forever.
///
/// **`0` means NEVER, not 1970.** proto3 cannot tell an unset timestamp from a
/// zero one, and gunnar spells "this namespace has never been touched inside
/// the kept window" as 0. Rendering that as an epoch date would put 1970 on a
/// console beside real dates, which reads as a very old customer rather than as
/// one nobody has ever used — so `None` for `last_seen == 0` is refused here
/// and [`Quiet::Never`] says it out loud.
pub fn quiet_for(last_seen_unix_ms: Option<u64>, now_unix_ms: u64) -> Quiet {
    // **NOTHING WAS MEASURED.** Not a silence — an absence of the meters that
    // would have measured one. See [`last_seen`] and [`Quiet::NoData`].
    let Some(last_seen_unix_ms) = last_seen_unix_ms else {
        return Quiet::NoData;
    };
    if last_seen_unix_ms == 0 {
        return Quiet::Never;
    }
    match now_unix_ms.checked_sub(last_seen_unix_ms) {
        // A reading from the future is a clock disagreement, not a visit that
        // has not happened yet. Treated as "just now", which is the closest
        // true statement and never puts a working tenant on the SILENT list.
        None => Quiet::For(0),
        Some(ms) => Quiet::For(ms),
    }
}

/// [`quiet_for`], as bare milliseconds — the ordering key the SILENT list sorts
/// by, descending.
///
/// `Never` is `u64::MAX`, so the tenant nobody has ever used sorts above the one
/// last seen a year ago. [`Quiet::NoData`] is `0` and therefore sorts LAST,
/// which is defensive only: it is not a member of that list at all
/// ([`Quiet::at_least_days`] is false for it), and if a caller ever puts one
/// there anyway it must not displace a real finding from the top of the page.
pub fn quiet_for_ms(last_seen_unix_ms: Option<u64>, now_unix_ms: u64) -> u64 {
    match quiet_for(last_seen_unix_ms, now_unix_ms) {
        Quiet::NoData => 0,
        Quiet::Never => u64::MAX,
        Quiet::For(ms) => ms,
    }
}

/// **How long since a tenant was last seen** — three arms, because there are
/// three facts and only two of them are about the customer.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum Quiet {
    /// **The product did not report the flow meters at all**, so nothing is
    /// known about whether this tenant is being used.
    ///
    /// Ordered first so it can never outrank a measured silence.
    ///
    /// This is NOT [`Quiet::Never`] and the difference is the whole point:
    /// `Never` is a MEASUREMENT — the product counts flow, and it counted none
    /// — while `NoData` is a GAP, in a plugin or a poller or an older product
    /// build. Rendering a gap as `never seen` publishes a finding about a
    /// customer that nobody measured, which is exactly the sentence an operator
    /// would act on by ringing them up.
    NoData,
    /// Nothing has ever moved through this tenant inside the product's kept
    /// window. **Not the same as "a very long time ago"**, and a console must
    /// not render it as a date.
    Never,
    /// Milliseconds since the last push or fetch.
    For(u64),
}

impl Quiet {
    /// Whole days, or `None` for the two arms that have no number of days.
    ///
    /// **`None` is ambiguous on purpose and must not be the only thing a
    /// renderer gets**: both [`Quiet::NoData`] and [`Quiet::Never`] answer
    /// `None` here, and they are different facts. Pass the `Quiet` itself to
    /// anything that puts words on a screen; this is for arithmetic.
    pub fn days(self) -> Option<u64> {
        match self {
            Quiet::NoData | Quiet::Never => None,
            Quiet::For(ms) => Some(ms / DAY_MS),
        }
    }

    /// Has this tenant been quiet for at least `days`?
    ///
    /// `Never` always has. **`NoData` never has** — the SILENT list is a list of
    /// MEASURED silences, and a tenant whose product does not count flow has not
    /// been shown to be quiet, only to be unmeasured.
    pub fn at_least_days(self, days: u64) -> bool {
        match self {
            Quiet::NoData => false,
            Quiet::Never => true,
            Quiet::For(ms) => ms >= days.saturating_mul(DAY_MS),
        }
    }

    /// Is this an absence of measurement rather than a measurement?
    pub fn is_no_data(self) -> bool {
        matches!(self, Quiet::NoData)
    }
}

/// **The later of a tenant's two last-seen timestamps**, from a usage reading —
/// or `None` when the reading carries NEITHER of them.
///
/// One writer, because "last seen" is a word two lists and one panel use and a
/// second `max` somewhere else is how they would come to disagree about whether
/// a fetch counts as being seen. It does: a customer who clones every morning
/// and never pushes is using the product.
///
/// # Why this is an `Option` and not a `u64`
///
/// It returned `u64` until 2026-09-10, with `.unwrap_or(0)` on each meter —
/// which folded a meter that was **absent** into a meter that **read zero**,
/// and those are the two facts this module exists to keep apart. [`daily_totals`]
/// three functions up already refuses exactly that fold, in those words: *"a
/// product that stopped reporting a meter and a product reporting zero are
/// different facts"*. This function contradicted it.
///
/// The consequence was on the screen. A tenant whose product does not count
/// flow at all — an older gunnar, the `Admin.StorageInfo` fallback path, or one
/// the poller has never read — carries neither meter, folded to `0`, and came
/// out of [`quiet_for`] as `Quiet::Never`: **`never seen`, at the top of the
/// fleet page's SILENT list**, sorted above every real finding by
/// [`quiet_for_ms`]'s `u64::MAX`. That is a claim about a customer manufactured
/// from a gap in a plugin, and an operator acts on it by ringing them up.
///
/// The tenant page had already settled this the other way — it draws *"this
/// product does not report flow"* when neither key is present, and its comment
/// says why: *"`never seen` about a product that does not count flow would be a
/// claim about a customer made from a gap in a plugin"*. Two views of one fact
/// disagreed; this makes the type carry the distinction so they cannot.
///
/// `Some(0)` is still meaningful and still means NEVER: the product reported the
/// meters and they read zero. Only `None` is *nobody measured*.
pub fn last_seen(usage: &Usage) -> Option<u64> {
    let at = |meter: &str| usage.meters.get(meter).copied();
    match (at("last_push_unix_ms"), at("last_fetch_unix_ms")) {
        // Neither meter reported: this product does not count flow.
        (None, None) => None,
        // At least one reported. A missing twin is `0` for the `max` only —
        // a product may report a push meter and no fetch meter.
        (push, fetch) => Some(push.unwrap_or(0).max(fetch.unwrap_or(0))),
    }
}

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

    fn reading(at: u64, meters: &[(&str, u64)]) -> Usage {
        Usage {
            meters: meters.iter().map(|(k, v)| ((*k).to_string(), *v)).collect(),
            measured_at_unix_ms: at,
        }
    }

    const NOON: u64 = 1_789_041_600_000;

    /// ★ **Sixty readings of one refusal are one refusal, not sixty.**
    ///
    /// The defect this module exists to prevent, and the one a naive `sum` has:
    /// a `*_today` counter is repeated in every reading of the day it belongs
    /// to, so summing readings reports a number proportional to the POLL RATE
    /// rather than to what happened.
    #[test]
    fn a_daily_counter_is_read_once_per_day_however_often_it_is_polled() {
        let midnight = day_of(NOON) * DAY_MS;
        // One refusal at 09:00, then the counter sits at 1 for the rest of the
        // day across sixty more polls.
        let mut readings = vec![reading(midnight + 8 * 3_600_000, &[("refused_today", 0)])];
        for tick in 0..60u64 {
            readings.push(reading(
                midnight + 9 * 3_600_000 + tick * 900_000,
                &[("refused_today", 1)],
            ));
        }
        let totals = daily_totals(&readings, "refused_today");
        assert_eq!(totals.len(), 1, "one day: {totals:?}");
        assert_eq!(
            totals[&day_of(NOON)],
            1,
            "summing the readings would say 60; the counter is per day, not per poll"
        );
    }

    /// The maximum, not the last reading — see the module doc for the midnight
    /// poll that makes the last reading wrong.
    #[test]
    fn the_days_total_is_its_peak_and_not_its_last_reading() {
        let midnight = day_of(NOON) * DAY_MS;
        let readings = vec![
            reading(midnight + 3_600_000, &[("refused_today", 4)]),
            reading(midnight + 7_200_000, &[("refused_today", 9)]),
            // A reading attributed to this day whose counter has already
            // rolled: taking the last would erase eight of the nine.
            reading(midnight + DAY_MS - 1, &[("refused_today", 1)]),
        ];
        assert_eq!(daily_totals(&readings, "refused_today")[&day_of(NOON)], 9);
    }

    /// ★ **A window adds whole days, and a day with no reading is absent
    /// rather than zero.**
    #[test]
    fn a_window_adds_the_days_it_names_and_invents_none() {
        let day = |n: u64| day_of(NOON) * DAY_MS + n * DAY_MS + 3_600_000;
        let readings = vec![
            reading(day(0), &[("refused_today", 2)]),
            reading(day(1), &[("refused_today", 5)]),
            // day 2: the product was unreachable and the poller stored nothing.
            reading(day(3), &[("refused_today", 1)]),
        ];
        let totals = daily_totals(&readings, "refused_today");
        assert_eq!(totals.len(), 3, "three days were measured: {totals:?}");
        assert!(
            !totals.contains_key(&(day_of(NOON) + 2)),
            "a day nobody measured must not appear as a day with no refusals"
        );
        assert_eq!(window_total(&readings, "refused_today", day(0), day(3)), 8);
        assert_eq!(window_total(&readings, "refused_today", day(1), day(3)), 6, "the window is honoured");
        assert_eq!(window_total(&readings, "refused_today", day(4), day(9)), 0, "a window with nothing in it");
    }

    /// A meter the readings do not carry is skipped, never counted as zero: a
    /// product that stopped reporting and a product reporting nothing are
    /// different facts.
    #[test]
    fn a_meter_that_is_not_reported_is_not_a_zero() {
        let readings = vec![reading(NOON, &[("bytes_in_today", 4_096)])];
        assert!(
            daily_totals(&readings, "refused_today").is_empty(),
            "a meter nobody reported must not produce a day of zero refusals"
        );
        assert_eq!(window_total(&readings, "refused_today", 0, NOON), 0);
    }

    /// ★ **NEVER is its own answer, and it sorts above every date.**
    ///
    /// `0` on the wire means "nothing has ever moved through this tenant". Read
    /// as a timestamp it is 1970, which on a console beside real dates reads as
    /// a very old customer rather than as one nobody has ever used.
    #[test]
    fn never_is_not_nineteen_seventy() {
        assert_eq!(quiet_for(Some(0), NOON), Quiet::Never);
        assert_eq!(quiet_for(Some(0), NOON).days(), None, "never has no number of days");
        assert!(quiet_for(Some(0), NOON).at_least_days(14));
        assert_eq!(
            quiet_for_ms(Some(0), NOON),
            u64::MAX,
            "the tenant nobody has ever used must sort above one last seen a year ago"
        );

        let three_days = NOON - 3 * DAY_MS;
        assert_eq!(quiet_for(Some(three_days), NOON), Quiet::For(3 * DAY_MS));
        assert_eq!(quiet_for(Some(three_days), NOON).days(), Some(3));
        assert!(!quiet_for(Some(three_days), NOON).at_least_days(14));
        assert!(quiet_for(Some(NOON - 14 * DAY_MS), NOON).at_least_days(14), "exactly at the threshold is quiet");
    }

    /// ★ **A tenant nobody MEASURED is NO DATA, and a tenant measured at zero is
    /// NEVER SEEN. They are different facts and nothing may fold them.**
    ///
    /// The distinction this whole module turns on, at the one function that used
    /// to destroy it. Until 2026-09-10 [`last_seen`] read each meter with
    /// `.unwrap_or(0)`, so a product that does not count flow at all — an older
    /// gunnar, the `Admin.StorageInfo` fallback, a tenant the poller has never
    /// read — was indistinguishable from one whose meters were reported and read
    /// zero. Both came out `Quiet::Never`.
    ///
    /// What that put on the screen: `never seen`, on the fleet page's SILENT
    /// list, sorted to the TOP by `quiet_for_ms`'s `u64::MAX` — above every real
    /// finding. A claim about a customer, manufactured from a gap in a plugin,
    /// ranked as the most urgent row on the page. The tenant page had already
    /// refused to make that claim (*"this product does not report flow"*); the
    /// fleet page made it.
    ///
    /// **Measure, never assert**: every arm below has its twin, so a fold in
    /// either direction goes red here.
    #[test]
    fn a_product_that_reports_no_flow_is_no_data_and_never_a_zero() {
        // MEASURED, and the measurement is zero: the meters are present.
        let measured_zero = reading(NOON, &[("last_push_unix_ms", 0), ("last_fetch_unix_ms", 0)]);
        // NOT MEASURED: this product reports a stock meter and no flow meter.
        let unmeasured = reading(NOON, &[("pack_bytes", 4_096)]);

        assert_eq!(last_seen(&measured_zero), Some(0), "reported, and it reads zero");
        assert_eq!(last_seen(&unmeasured), None, "no flow meter was reported at all");
        assert_ne!(
            last_seen(&measured_zero),
            last_seen(&unmeasured),
            "an absent meter and a meter reading zero must not be the same value"
        );

        assert_eq!(quiet_for(last_seen(&measured_zero), NOON), Quiet::Never);
        assert_eq!(quiet_for(last_seen(&unmeasured), NOON), Quiet::NoData);
        assert!(!quiet_for(last_seen(&measured_zero), NOON).is_no_data());
        assert!(quiet_for(last_seen(&unmeasured), NOON).is_no_data());

        // THE CONSEQUENCE, and the reason the arm exists: the SILENT list is a
        // list of MEASURED silences. A gap is not a finding about a customer.
        assert!(
            quiet_for(last_seen(&measured_zero), NOON).at_least_days(14),
            "a tenant measured as never used belongs on the SILENT list"
        );
        assert!(
            !quiet_for(last_seen(&unmeasured), NOON).at_least_days(14),
            "a tenant nobody measured must not be published as a silence"
        );

        // And it can never outrank a real finding if a caller lists it anyway.
        assert_eq!(quiet_for_ms(last_seen(&unmeasured), NOON), 0);
        assert!(quiet_for_ms(last_seen(&unmeasured), NOON) < quiet_for_ms(last_seen(&measured_zero), NOON));

        // `days()` answers `None` for BOTH, which is why it is not enough on its
        // own — a renderer that reads only this cannot tell them apart, and the
        // one that matters is `Quiet` itself.
        assert_eq!(quiet_for(last_seen(&measured_zero), NOON).days(), None);
        assert_eq!(quiet_for(last_seen(&unmeasured), NOON).days(), None);
    }

    /// A reading from the future is a clock disagreement and reads as "just
    /// now" — never as a negative age, and never as a tenant to put on the
    /// SILENT list.
    #[test]
    fn a_clock_that_runs_ahead_does_not_make_a_busy_tenant_silent() {
        assert_eq!(quiet_for(Some(NOON + DAY_MS), NOON), Quiet::For(0));
        assert!(!quiet_for(Some(NOON + DAY_MS), NOON).at_least_days(1));
    }

    /// **A fetch counts as being seen.** A customer who clones every morning
    /// and never pushes is using the product, and one writer of that decision
    /// is what stops two lists disagreeing about it.
    #[test]
    fn last_seen_is_the_later_of_the_two_and_a_fetch_counts() {
        let only_fetch = reading(NOON, &[("last_push_unix_ms", 0), ("last_fetch_unix_ms", NOON)]);
        assert_eq!(last_seen(&only_fetch), Some(NOON));
        let older_fetch = reading(
            NOON,
            &[("last_push_unix_ms", NOON), ("last_fetch_unix_ms", NOON - DAY_MS)],
        );
        assert_eq!(last_seen(&older_fetch), Some(NOON), "the later of the two");

        // ONE of the two reported is still a measurement — a product may count
        // pushes and not fetches. Only NEITHER is an absence.
        let push_only = reading(NOON, &[("last_push_unix_ms", NOON - DAY_MS)]);
        assert_eq!(last_seen(&push_only), Some(NOON - DAY_MS), "one reported meter is a measurement");

        let neither = reading(NOON, &[("pack_bytes", 1)]);
        assert_eq!(
            last_seen(&neither),
            None,
            "a product that reports neither has not been MEASURED — it has not `never been seen`"
        );
    }
}