From b9f2da92758f5b195f5f356ea84fb33b1afd6803 Mon Sep 17 00:00:00 2001 From: bracr10 Date: Thu, 30 Jul 2026 01:39:04 -0600 Subject: [PATCH] chore: trim comments in src/models Rewrites doc comments and inline comments across the models module to be shorter and drop internal spec/requirement/PR references. Comment-only change, no logic touched. --- src/models/core.rs | 111 ++++++++++++++++------------------ src/models/dedup.rs | 33 +++++----- src/models/dev_fee.rs | 19 +++--- src/models/dispute.rs | 22 +++---- src/models/instance_status.rs | 34 +++++------ src/models/mod.rs | 4 +- src/models/order.rs | 105 ++++++++++++++------------------ 7 files changed, 151 insertions(+), 177 deletions(-) diff --git a/src/models/core.rs b/src/models/core.rs index 0cc70be..2d86219 100644 --- a/src/models/core.rs +++ b/src/models/core.rs @@ -1,15 +1,15 @@ use nostr_sdk::prelude::*; use serde::{Serialize, Serializer}; -/// 002 FR-012 / plan.md's JSON output contract: the only type allowed to produce a JSON -/// `null` for a not-applicable metric. `serde_json` serializes a bare `f64` NaN or -/// infinity as `null` without erroring (see this module's own test proving that -/// empirically) — indistinguishable from a deliberate not-applicable value if a -/// degenerate float ever reached the serializer directly. Every `stats/` computation -/// already returns `None` at its own guard condition rather than letting that happen (see -/// PR 8's audit); `report::render::json` converts from those existing `Option` fields -/// into `MetricValue` only at its own serialization boundary, so `Report`/`ReportStats` -/// stay `Option` for the console/plain-text renderers. +/// The only type allowed to produce a JSON `null` for a not-applicable metric. +/// `serde_json` serializes a bare `f64` NaN or infinity as `null` without erroring (see +/// this module's own test proving that empirically) — indistinguishable from a +/// deliberate not-applicable value if a degenerate float ever reached the serializer +/// directly. Every `stats/` computation already returns `None` at its own guard +/// condition rather than letting that happen; `report::render::json` converts from those +/// existing `Option` fields into `MetricValue` only at its own serialization +/// boundary, so `Report`/`ReportStats` stay `Option` for the console/plain-text +/// renderers. #[derive(Debug, Clone, PartialEq)] pub enum MetricValue { Computed(T), @@ -40,10 +40,10 @@ impl Serialize for MetricValue { } } -/// 002 FR-014's progress-indicator port: a single method the fetch layer invokes once a -/// relay fetch has run past FR-014's latency threshold. `fetch` and `report` both depend -/// on this trait through their shared dependency on `models`, rather than `fetch` -/// depending on `report` directly — the concrete terminal implementation +/// A single method the fetch layer invokes once a relay fetch has run past the +/// progress-indicator's latency threshold. `fetch` and `report` both depend on this +/// trait through their shared dependency on `models`, rather than `fetch` depending on +/// `report` directly — the concrete terminal implementation /// (`report::progress::TerminalProgressReporter`) is bound into /// `fetch::client::RelayEventSource` only at construction time, in the library/binary /// wiring root, so `fetch` itself never depends on `report`. @@ -60,9 +60,8 @@ impl ProgressReporter for NoOpProgressReporter { fn report_slow_fetch(&self) {} } -/// PR 1 Step C: shared single-letter tag accessor, extracted from the repeated -/// `event.tags.iter().find(...)` pattern duplicated across dedup, dev-fee, and order -/// aggregation. Returns the tag's first value (index 1) as a borrowed `&str`. +/// Shared single-letter tag accessor. Returns the tag's first value (index 1) as a +/// borrowed `&str`. pub fn tag_value<'e>(event: &'e Event, name: &str) -> Option<&'e str> { event .tags @@ -97,30 +96,30 @@ pub fn amt_tag(event: &Event) -> Option<&str> { tag_value(event, "amt") } -/// `f` tag accessor (fiat currency, 001 FR-008). An order always carries exactly one -/// `f` value, so this is a single-value accessor, matching `amt_tag`/`s_tag`'s pattern. +/// `f` tag accessor (fiat currency). An order always carries exactly one `f` value, so +/// this is a single-value accessor, matching `amt_tag`/`s_tag`'s pattern. pub fn f_tag(event: &Event) -> Option<&str> { tag_value(event, "f") } -/// `premium` tag accessor (001 FR-011). The raw string value; parsing it as a signed -/// integer is the caller's job (`models::order::aggregate_order_events`), since an empty -/// or unparseable value is excluded per FR-013 rather than treated as an error here. +/// `premium` tag accessor. The raw string value; parsing it as a signed integer is the +/// caller's job (`models::order::aggregate_order_events`), since an empty or unparseable +/// value is excluded rather than treated as an error here. pub fn premium_tag(event: &Event) -> Option<&str> { tag_value(event, "premium") } -/// `pm` tag accessor (payment method, 001 FR-009). Unlike every other single-value tag -/// accessor in this module, `pm` is a multi-value Nostr tag: `order.payment_method` is -/// split on commas server-side (`mostro/src/nip33.rs`'s `order_to_tags`), but the -/// resulting `Vec` is passed directly as the tag's content, so each split token -/// becomes its own array element on the wire (`["pm", "SEPA", "Cash"]`), not one -/// comma-joined value — reading only index 1 (`tag_value`'s single-value convention) -/// would silently drop every method after the first. This returns every value from -/// index 1 onward, filtering empty ones defensively since relay data is untrusted -/// (FR-013), even though `order_to_tags` already filters them before publishing. Values -/// are never trimmed, re-split, or case-normalized: FR-009 requires byte-for-byte -/// comparison of method labels exactly as published. +/// `pm` tag accessor (payment method). Unlike every other single-value tag accessor in +/// this module, `pm` is a multi-value Nostr tag: `order.payment_method` is split on +/// commas server-side (`mostro/src/nip33.rs`'s `order_to_tags`), but the resulting +/// `Vec` is passed directly as the tag's content, so each split token becomes +/// its own array element on the wire (`["pm", "SEPA", "Cash"]`), not one comma-joined +/// value — reading only index 1 (`tag_value`'s single-value convention) would silently +/// drop every method after the first. This returns every value from index 1 onward, +/// filtering empty ones defensively since relay data is untrusted, even though +/// `order_to_tags` already filters them before publishing. Values are never trimmed, +/// re-split, or case-normalized: comparison of method labels must be byte-for-byte +/// exactly as published. pub fn pm_tag_values(event: &Event) -> Vec { event .tags @@ -136,9 +135,8 @@ pub fn pm_tag_values(event: &Event) -> Vec { .unwrap_or_default() } -/// PR 1 Step B seam: partition fetched events into dev-fee events (z=dev-fee-payment, -/// y=mostro) and order events (z=order). Extracted verbatim from the wrapped function -/// body; pure signature, no network, no I/O. +/// Partitions fetched events into dev-fee events (z=dev-fee-payment, y=mostro) and order +/// events (z=order). pub fn partition_by_z_y_tag(events: Vec) -> (Vec, Vec) { let mut dev_fee_events: Vec = Vec::new(); let mut order_events: Vec = Vec::new(); @@ -154,20 +152,18 @@ pub fn partition_by_z_y_tag(events: Vec) -> (Vec, Vec) { (dev_fee_events, order_events) } -/// PR 3 (T077): FR-015's full scoping rule for a single event — the event's author -/// (signer) must be the node's own pubkey, its `z` tag must match the expected subtype -/// for its kind, and its `y` tag's first value must be `mostro`. Shared across all four -/// scoped kinds (`8383`/`38383`/`38385`/`38386`) so a relay fetch never mixes another -/// node's events, or another application's/subtype's same-kind events, into this node's -/// report. +/// The full scoping rule for a single event — the event's author (signer) must be the +/// node's own pubkey, its `z` tag must match the expected subtype for its kind, and its +/// `y` tag's first value must be `mostro`. Shared across all four scoped kinds +/// (`8383`/`38383`/`38385`/`38386`) so a relay fetch never mixes another node's events, +/// or another application's/subtype's same-kind events, into this node's report. pub fn is_scoped_event(event: &Event, node_pubkey: &PublicKey, expected_z: &str) -> bool { event.pubkey == *node_pubkey && z_tag(event) == Some(expected_z) && y_tag(event) == Some("mostro") } -/// Filters a batch of events down to the ones scoped to `node_pubkey` for `expected_z`, -/// per FR-015. +/// Filters a batch of events down to the ones scoped to `node_pubkey` for `expected_z`. pub fn scope_events_to_node( events: Vec, node_pubkey: &PublicKey, @@ -179,9 +175,9 @@ pub fn scope_events_to_node( .collect() } -/// PR 3 (T079): FR-014's future-timestamp exclusion — any event whose `created_at` is -/// later than report-generation time cannot be a legitimate signing time relative to the -/// report and MUST be excluded from consideration entirely, not merely deprioritized. +/// Any event whose `created_at` is later than report-generation time cannot be a +/// legitimate signing time relative to the report and must be excluded from +/// consideration entirely, not merely deprioritized. pub fn exclude_future_events(events: Vec, report_generated_at: Timestamp) -> Vec { events .into_iter() @@ -356,11 +352,9 @@ mod tests { } /// Builds an event with a multi-value tag, e.g. `["pm", "SEPA", "Cash"]` — matching - /// the actual Nostr wire format for `pm` (`mostro/src/nip33.rs` passes the - /// already-comma-split `Vec` directly as the tag's content, so each method - /// is its own array element, not a single comma-joined string). The shared - /// `test_support::make_event` helper only builds single-value `(name, value)` tags, - /// which cannot represent this. + /// the actual Nostr wire format for `pm`. The shared `test_support::make_event` + /// helper only builds single-value `(name, value)` tags, which cannot represent + /// this. fn make_event_with_multi_value_tag(kind: u16, created_at: u64, values: Vec<&str>) -> Event { let keys = Keys::generate(); EventBuilder::new(Kind::Custom(kind), "") @@ -380,7 +374,7 @@ mod tests { } /// Defensive only: `order_to_tags` never publishes an empty value, but this is - /// untrusted relay data (FR-013), so an empty array element must still be excluded. + /// untrusted relay data, so an empty array element must still be excluded. #[test] fn pm_tag_values_defensively_filters_empty_values() { let event = make_event_with_multi_value_tag(38383, 100, vec!["pm", "SEPA", "", "Cash"]); @@ -413,8 +407,7 @@ mod tests { /// The custom `Serialize` impl's whole point: `Computed(value)` must serialize as the /// bare `value`, never as `{"Computed": value}` (`#[derive(Serialize)]`'s default - /// enum representation) — plan.md's JSON output contract fixes a computed metric's - /// JSON type as a bare number. + /// enum representation). #[test] fn metric_value_computed_serializes_as_the_bare_value_not_a_wrapped_object() { assert_eq!( @@ -431,11 +424,11 @@ mod tests { ); } - /// A real, surprising `serde_json` footgun (plan.md's JSON output contract): - /// serializing a bare `f64` NaN or infinity produces `null` without erroring, so a - /// degenerate float would be indistinguishable from a deliberate not-applicable - /// value if it ever reached the serializer directly instead of through - /// `MetricValue`. Confirmed here empirically, not just asserted from documentation. + /// A real, surprising `serde_json` footgun: serializing a bare `f64` NaN or infinity + /// produces `null` without erroring, so a degenerate float would be + /// indistinguishable from a deliberate not-applicable value if it ever reached the + /// serializer directly instead of through `MetricValue`. Confirmed here + /// empirically, not just asserted from documentation. #[test] fn raw_f64_nan_serializes_as_null_a_surprising_serde_json_footgun() { assert_eq!(serde_json::to_string(&f64::NAN).unwrap(), "null"); diff --git a/src/models/dedup.rs b/src/models/dedup.rs index 1cb589d..c2a2da6 100644 --- a/src/models/dedup.rs +++ b/src/models/dedup.rs @@ -2,11 +2,11 @@ use crate::models::core::d_tag; use nostr_sdk::prelude::*; use std::collections::{HashMap, HashSet}; -/// 002 FR-003: deduplicates events by event id (not by `d` tag), keeping the first -/// occurrence — for kinds like dev-fee payments that have no NIP-33 replaceable-event -/// semantics of their own, this is the only dedup axis that matters, guarding against -/// the same relay-delivered event being independently returned, and double-counted, by -/// more than one relay. +/// Deduplicates events by event id (not by `d` tag), keeping the first occurrence — for +/// kinds like dev-fee payments that have no NIP-33 replaceable-event semantics of their +/// own, this is the only dedup axis that matters, guarding against the same +/// relay-delivered event being independently returned, and double-counted, by more than +/// one relay. pub fn dedup_events_by_id(events: Vec) -> Vec { let mut seen_ids: HashSet = HashSet::new(); events @@ -15,10 +15,10 @@ pub fn dedup_events_by_id(events: Vec) -> Vec { .collect() } -/// Shared tie-break rule for FR-002/006/012/014: the event with the highest `created_at` -/// wins; when two candidates tie on `created_at`, the one with the lexicographically -/// greatest event id wins instead. `EventId`'s `Ord` impl compares its raw fixed-length -/// bytes, which agrees with lexicographic comparison of its hex encoding. +/// Shared tie-break rule: the event with the highest `created_at` wins; when two +/// candidates tie on `created_at`, the one with the lexicographically greatest event id +/// wins instead. `EventId`'s `Ord` impl compares its raw fixed-length bytes, which +/// agrees with lexicographic comparison of its hex encoding. fn is_more_current(candidate: &Event, existing: &Event) -> bool { match candidate.created_at.cmp(&existing.created_at) { std::cmp::Ordering::Greater => true, @@ -27,15 +27,14 @@ fn is_more_current(candidate: &Event, existing: &Event) -> bool { } } -/// PR 1 Step B seam, generalized in PR 3: deduplicate events by their `d` tag, keeping -/// the current/final event for each key per the shared `is_more_current` tie-break rule. -/// Used for order events (FR-002) and dispute events (FR-006) alike — both are NIP-33 -/// replaceable events keyed by `d`, republished at each status change. +/// Deduplicates events by their `d` tag, keeping the current/final event for each key +/// per the shared `is_more_current` tie-break rule. Used for order events and dispute +/// events alike — both are NIP-33 replaceable events keyed by `d`, republished at each +/// status change. pub fn dedup_events_by_d_tag(order_events: Vec) -> HashMap { let mut orders_map: HashMap = HashMap::new(); for event in order_events { - // If it's replaceable, map it by 'd' tag to get the final state. if let Some(order_id) = d_tag(&event) { match orders_map.get(order_id) { Some(existing) => { @@ -53,9 +52,9 @@ pub fn dedup_events_by_d_tag(order_events: Vec) -> HashMap orders_map } -/// PR 3 (T088/T089): select the single current/final event from a homogeneous candidate -/// set (e.g. all instance-status events already filtered to one node's own `d` tag), -/// applying the same tie-break rule as `dedup_events_by_d_tag`. +/// Selects the single current/final event from a homogeneous candidate set (e.g. all +/// instance-status events already filtered to one node's own `d` tag), applying the +/// same tie-break rule as `dedup_events_by_d_tag`. pub fn select_current_event(events: Vec) -> Option { events .into_iter() diff --git a/src/models/dev_fee.rs b/src/models/dev_fee.rs index da81317..4060fdc 100644 --- a/src/models/dev_fee.rs +++ b/src/models/dev_fee.rs @@ -1,8 +1,7 @@ use nostr_sdk::prelude::*; -/// PR 1 Step B seam: select the oldest dev-fee event (the longevity anchor), sorted by -/// `created_at` ascending. Extracted verbatim from the wrapped function body; pure -/// signature, no network, no I/O. +/// Selects the oldest dev-fee event (the longevity anchor), sorted by `created_at` +/// ascending. pub fn select_oldest_dev_fee_event(mut dev_fee_events: Vec) -> Option { if dev_fee_events.is_empty() { return None; @@ -12,11 +11,11 @@ pub fn select_oldest_dev_fee_event(mut dev_fee_events: Vec) -> Option, @@ -43,8 +42,8 @@ mod tests { assert!(select_oldest_dev_fee_event(vec![]).is_none()); } - /// FR-013: a dev-fee event carrying no tags at all must not panic anywhere in this - /// aggregation path — it is simply an event with an unknown longevity-relevant + /// A dev-fee event carrying no tags at all must not panic anywhere in this + /// aggregation path — it is simply an event with unknown longevity-relevant /// content, still ordered on `created_at` alone. #[test] fn aggregate_dev_fee_events_handles_a_tagless_event_without_panicking() { diff --git a/src/models/dispute.rs b/src/models/dispute.rs index 455e8af..61e7e90 100644 --- a/src/models/dispute.rs +++ b/src/models/dispute.rs @@ -1,12 +1,12 @@ -//! Kind `38386` dispute events (FR-006): dedup by the dispute's `d` tag to each dispute's -//! latest reported status — a NIP-33 replaceable event, republished at each status -//! change — then classify that deduplicated set into resolved, active, or unknown counts. +//! Kind `38386` dispute events: dedup by the dispute's `d` tag to each dispute's latest +//! reported status — a NIP-33 replaceable event, republished at each status change — +//! then classify that deduplicated set into resolved, active, or unknown counts. use crate::models::core::s_tag; use crate::models::dedup::dedup_events_by_d_tag; use nostr_sdk::prelude::*; -/// A deduplicated dispute's classified status, per FR-006's Clarifications. +/// A deduplicated dispute's classified status. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DisputeStatus { /// `s` = `settled`, `seller-refunded`, or `released`. @@ -18,8 +18,8 @@ pub enum DisputeStatus { Unknown, } -/// FR-006's per-dispute classification, applied to a single deduplicated dispute's -/// latest `s` value. +/// Per-dispute classification, applied to a single deduplicated dispute's latest `s` +/// value. pub fn classify_dispute_status(s_value: Option<&str>) -> DisputeStatus { match s_value { Some("settled") | Some("seller-refunded") | Some("released") => DisputeStatus::Resolved, @@ -38,10 +38,9 @@ pub struct DisputeAggregate { pub unknown: usize, } -/// FR-006/FR-015: dedup fetched dispute events by their `d` tag (highest `created_at`, -/// ties broken by the greatest event id), then classify each deduplicated dispute's -/// latest `s` value. Events without a `d` tag are safely excluded (FR-013), never -/// counted. +/// Dedups fetched dispute events by their `d` tag (highest `created_at`, ties broken by +/// the greatest event id), then classifies each deduplicated dispute's latest `s` +/// value. Events without a `d` tag are safely excluded, never counted. pub fn aggregate_dispute_events(dispute_events: Vec) -> DisputeAggregate { let deduped = dedup_events_by_d_tag(dispute_events); @@ -139,8 +138,7 @@ mod tests { assert_eq!(aggregate.unknown, 0); } - /// FR-013: a dispute event carrying no tags at all must be safely excluded, never - /// panic. + /// A dispute event carrying no tags at all must be safely excluded, never panic. #[test] fn aggregate_dispute_events_handles_a_tagless_event_without_panicking() { let tagless = make_event(38386, 100, vec![]); diff --git a/src/models/instance_status.rs b/src/models/instance_status.rs index b6c7496..5831592 100644 --- a/src/models/instance_status.rs +++ b/src/models/instance_status.rs @@ -1,14 +1,14 @@ -//! Kind `38385` instance-status events (FR-012): a NIP-33 replaceable event keyed by the -//! node's own pubkey as the `d` tag, republished on a timer. Selection restricts the -//! candidate set to events whose `d` tag equals the node's own pubkey, then picks the -//! highest `created_at` within that set (ties broken by the greatest event id). +//! Kind `38385` instance-status events: a NIP-33 replaceable event keyed by the node's +//! own pubkey as the `d` tag, republished on a timer. Selection restricts the candidate +//! set to events whose `d` tag equals the node's own pubkey, then picks the highest +//! `created_at` within that set (ties broken by the greatest event id). use crate::models::core::{d_tag, tag_value}; use crate::models::dedup::select_current_event; use nostr_sdk::prelude::*; -/// FR-012's tri-state read of the `bond_enabled` tag: `Unknown` covers both a missing -/// tag and a value that fails to parse as `true`/`false`, never defaulting to `false`. +/// Tri-state read of the `bond_enabled` tag: `Unknown` covers both a missing tag and a +/// value that fails to parse as `true`/`false`, never defaulting to `false`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BondEnabled { True, @@ -17,10 +17,9 @@ pub enum BondEnabled { } impl BondEnabled { - /// 001 FR-012, 002 FR-007: the report schema's three-valued bond-policy status - /// string, distinct from `NodeMetrics`'s trade-history fields (002 FR-007 requires - /// Bond Policy to be its own, distinctly named sub-object). A thin mapping of this - /// existing tri-state; no new selection or parsing logic. + /// The report schema's three-valued bond-policy status string, distinct from + /// `NodeMetrics`'s trade-history fields. A thin mapping of this existing tri-state; + /// no new selection or parsing logic. pub fn as_bond_policy_status(&self) -> &'static str { match self { BondEnabled::True => "enabled", @@ -45,10 +44,10 @@ pub struct InstanceStatusAggregate { pub bond_enabled: BondEnabled, } -/// FR-012/FR-015: restrict `instance_status_events` to the ones whose `d` tag equals -/// `node_pubkey`'s hex encoding, then select the current/final event among those -/// candidates (highest `created_at`, ties broken by the greatest event id). Events -/// without a `d` tag, or whose `d` tag does not match, are safely excluded (FR-013). +/// Restricts `instance_status_events` to the ones whose `d` tag equals `node_pubkey`'s +/// hex encoding, then selects the current/final event among those candidates (highest +/// `created_at`, ties broken by the greatest event id). Events without a `d` tag, or +/// whose `d` tag does not match, are safely excluded. pub fn select_instance_status_event( instance_status_events: Vec, node_pubkey: &PublicKey, @@ -235,8 +234,8 @@ mod tests { assert_eq!(aggregate.bond_enabled, BondEnabled::Unknown); } - /// FR-013: an instance-status event carrying no tags at all (no `d` tag either) must - /// never match any node's pubkey and must never panic. + /// An instance-status event carrying no tags at all (no `d` tag either) must never + /// match any node's pubkey and must never panic. #[test] fn select_instance_status_event_handles_a_tagless_event_without_panicking() { let node_pubkey = Keys::generate().public_key(); @@ -247,9 +246,6 @@ mod tests { assert!(selected.is_none()); } - /// 001 FR-012, 002 FR-007: the report schema's three-valued bond-policy status - /// string, a thin mapping of the existing `BondEnabled` tri-state — never a - /// re-implementation of its selection or parsing logic. #[test] fn bond_enabled_maps_to_the_report_schemas_three_valued_status_string() { assert_eq!(BondEnabled::True.as_bond_policy_status(), "enabled"); diff --git a/src/models/mod.rs b/src/models/mod.rs index 55cdc20..0fa4841 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -18,8 +18,8 @@ pub(crate) mod test_support { } /// Same as `make_event`, signed with a caller-supplied key pair, so tests exercising - /// author scoping (FR-015) can build multiple events sharing (or deliberately not - /// sharing) the same signer. + /// author scoping can build multiple events sharing (or deliberately not sharing) + /// the same signer. pub(crate) fn make_event_with_keys( keys: &Keys, kind: u16, diff --git a/src/models/order.rs b/src/models/order.rs index 56aa604..c2d051b 100644 --- a/src/models/order.rs +++ b/src/models/order.rs @@ -5,44 +5,43 @@ use nostr_sdk::prelude::*; use std::collections::HashMap; use std::str::FromStr; -/// PR 1 Step C: per-kind order aggregation, verbatim from the wrapped function body's -/// order-handling loop — raw time-range tracking and `s`-tag distribution over every -/// fetched order event, then `d`-tag dedup (via `models::dedup`) and the `s=success` -/// qualifying-order selection over the deduplicated set. Presentation (the debug block -/// and every report section) stays in `report/render/console.rs` (T037); this function -/// only computes the values those sections print. +/// Per-kind order aggregation: raw time-range tracking and `s`-tag distribution over +/// every fetched order event, then `d`-tag dedup (via `models::dedup`) and the +/// `s=success` qualifying-order selection over the deduplicated set. Presentation (the +/// debug block and every report section) stays in `report/render/console.rs`; this +/// struct only holds the values those sections print. pub struct OrderAggregate { pub total_order_count: usize, pub first_order_ts: i64, pub last_order_ts: i64, pub s_tag_distribution: HashMap, pub unique_order_count: usize, - /// PR 3: count of deduplicated orders whose `s` tag parses as a recognized - /// `OrderStatus` (pending, success, canceled, etc. — any known value, not just - /// success). An order that survives `d`-tag dedup but carries no `s` tag at all is - /// malformed (FR-013): real Mostro order events always publish a status, so a - /// missing one is incomplete data, not evidence of "an order this node placed." + /// Count of deduplicated orders whose `s` tag parses as a recognized `OrderStatus` + /// (pending, success, canceled, etc. — any known value, not just success). An order + /// that survives `d`-tag dedup but carries no `s` tag at all is malformed: real + /// Mostro order events always publish a status, so a missing one is incomplete + /// data, not evidence of "an order this node placed." pub recognized_status_order_count: usize, pub successful_orders: usize, pub total_volume_sats: u64, pub trade_amounts: Vec, pub successful_trade_timestamps: Vec, - /// PR 6 (001 FR-008): the `f` tag's value for each qualifying successful order, - /// omitting any order whose `f` value is empty. Excluded entirely (from both the - /// fiat breakdown's numerator and denominator), not counted as an "(empty)" bucket. + /// The `f` tag's value for each qualifying successful order, omitting any order + /// whose `f` value is empty. Excluded entirely (from both the fiat breakdown's + /// numerator and denominator), not counted as an "(empty)" bucket. pub fiat_values: Vec, - /// PR 6 (001 FR-009): every `pm` tag mention across qualifying successful orders, - /// flattened — one entry per tag value (`pm` is a multi-value Nostr tag, e.g. - /// `["pm", "SEPA", "Cash"]`, not a single comma-joined string), so a single order - /// can contribute zero, one, or many entries. + /// Every `pm` tag mention across qualifying successful orders, flattened — one + /// entry per tag value (`pm` is a multi-value Nostr tag, e.g. `["pm", "SEPA", + /// "Cash"]`, not a single comma-joined string), so a single order can contribute + /// zero, one, or many entries. pub payment_method_mentions: Vec, - /// PR 6 (001 FR-011): the `premium` tag parsed as `i64` for each qualifying - /// successful order, only when it parses successfully. A missing, empty, or - /// unparseable value is excluded rather than treated as `0`. + /// The `premium` tag parsed as `i64` for each qualifying successful order, only when + /// it parses successfully. A missing, empty, or unparseable value is excluded + /// rather than treated as `0`. pub premium_values: Vec, - /// PR 7d (002 FR-004): one entry per qualifying successful order, pairing its - /// `created_at` with its parsed `amt` (or `None` when `amt` did not parse) — the - /// activity grid's own input shape (`stats::grid::GridOrder`). `trade_amounts` and + /// One entry per qualifying successful order, pairing its `created_at` with its + /// parsed `amt` (or `None` when `amt` did not parse) — the activity grid's own + /// input shape (`stats::grid::GridOrder`). `trade_amounts` and /// `successful_trade_timestamps` above are two separately filtered lists (only /// amt-parseable orders vs. every successful order) and cannot be zipped together; /// this field is populated in the same loop as both, so the grid never needs a @@ -50,9 +49,9 @@ pub struct OrderAggregate { pub qualifying_orders: Vec<(i64, Option)>, } -/// PR 3 (T084/T085): FR-002's full qualifying-order procedure — dedup by `d` tag to the -/// highest `created_at` (ties broken by the greatest event id), then filter to only the -/// deduplicated events whose selected state carries `s=success`. +/// The full qualifying-order procedure — dedup by `d` tag to the highest `created_at` +/// (ties broken by the greatest event id), then filter to only the deduplicated events +/// whose selected state carries `s=success`. pub fn aggregate_order_events(order_events: Vec) -> OrderAggregate { let total_order_count = order_events.len(); let mut first_order_ts = i64::MAX; @@ -61,7 +60,6 @@ pub fn aggregate_order_events(order_events: Vec) -> OrderAggregate { let mut pending_dedup_events: Vec = Vec::new(); for event in order_events { - // Track order time range if (event.created_at.as_u64() as i64) < first_order_ts { first_order_ts = event.created_at.as_u64() as i64; } @@ -69,7 +67,6 @@ pub fn aggregate_order_events(order_events: Vec) -> OrderAggregate { last_order_ts = event.created_at.as_u64() as i64; } - // Track status distribution for all fetched events (all are orders now) let s_value = s_tag(&event).map(|s| s.to_string()); match &s_value { Some(val) => { @@ -98,9 +95,7 @@ pub fn aggregate_order_events(order_events: Vec) -> OrderAggregate { let mut premium_values: Vec = Vec::new(); let mut qualifying_orders: Vec<(i64, Option)> = Vec::new(); - // Process the final state of unique orders for (_order_id, event) in orders_map { - // Check Status 's' let status_str = s_tag(&event).unwrap_or("unknown"); let parsed_status = OrderStatus::from_str(status_str); @@ -121,25 +116,21 @@ pub fn aggregate_order_events(order_events: Vec) -> OrderAggregate { if let Some(amount) = amount_sats { // `amt` comes from an untrusted relay event; saturating_add avoids a // panic (debug) or silent wraparound (release) on a crafted extreme - // value, per Principle VI (no panics on user-facing paths). No - // observable difference for any realistic sat amount (bounded by the - // 21M BTC supply, far below u64::MAX). + // value. No observable difference for any realistic sat amount + // (bounded by the 21M BTC supply, far below u64::MAX). total_volume_sats = total_volume_sats.saturating_add(amount); trade_amounts.push(amount); } qualifying_orders.push((event_ts, amount_sats)); - // Get fiat currency 'f' (FR-008): excluded entirely when empty. if let Some(fiat) = f_tag(&event) { if !fiat.is_empty() { fiat_values.push(fiat.to_string()); } } - // Get payment methods 'pm' (FR-009): flattened, one entry per mention. payment_method_mentions.extend(pm_tag_values(&event)); - // Get premium 'premium' (FR-011): excluded when missing or unparseable. if let Some(premium_str) = premium_tag(&event) { if let Ok(premium) = premium_str.parse::() { premium_values.push(premium); @@ -171,9 +162,9 @@ mod tests { use super::*; use crate::models::test_support::make_event; - /// FR-002's full procedure is dedup-then-filter, in that order: whichever event wins - /// the `d`-tag tie-break (highest `created_at`, ties broken by greatest event id) is - /// the one whose `s` value decides whether the order counts as successful — never the + /// The full procedure is dedup-then-filter, in that order: whichever event wins the + /// `d`-tag tie-break (highest `created_at`, ties broken by greatest event id) is the + /// one whose `s` value decides whether the order counts as successful — never the /// other candidate's status, and never both. #[test] fn aggregate_order_events_qualifying_selection_follows_the_tie_break_winner() { @@ -196,9 +187,9 @@ mod tests { assert_eq!(aggregate.successful_orders, usize::from(winner_is_success)); } - /// FR-013: a malformed or missing `amt` value is unusable data on that order - /// specifically, not evidence the trade did not happen — the order still counts - /// toward `successful_orders`, but is safely excluded from `total_volume_sats` and + /// A malformed or missing `amt` value is unusable data on that order specifically, + /// not evidence the trade did not happen — the order still counts toward + /// `successful_orders`, but is safely excluded from `total_volume_sats` and /// `trade_amounts` rather than panicking on the unparseable value. #[test] fn aggregate_order_events_excludes_a_malformed_amt_from_volume_but_still_counts_the_trade() { @@ -226,7 +217,7 @@ mod tests { assert_eq!(aggregate.successful_orders, 0); } - /// FR-008: only successful orders carrying a non-empty `f` value contribute to + /// Only successful orders carrying a non-empty `f` value contribute to /// `fiat_values`; an empty value is excluded entirely. #[test] fn aggregate_order_events_collects_fiat_values_from_successful_orders_excluding_empty() { @@ -252,11 +243,9 @@ mod tests { } /// Builds an order event with a multi-value `pm` tag alongside ordinary - /// single-value tags — matching the actual Nostr wire format (`mostro/src/nip33.rs` - /// passes the already-comma-split `Vec` directly as the tag's content, so - /// each method is its own array element, e.g. `["pm", "SEPA", "Cash"]`, not one - /// comma-joined string). The shared `test_support::make_event` helper only builds - /// single-value `(name, value)` tags, which cannot represent this. + /// single-value tags — matching the actual Nostr wire format, e.g. `["pm", "SEPA", + /// "Cash"]`. The shared `test_support::make_event` helper only builds single-value + /// `(name, value)` tags, which cannot represent this. fn make_order_event_with_pm_values( created_at: u64, d: &str, @@ -279,8 +268,8 @@ mod tests { .expect("event signs") } - /// FR-009: `pm` mentions are flattened across every successful order, one entry per - /// tag value. Different orders are keyed by distinct `d` tags and processed from a + /// `pm` mentions are flattened across every successful order, one entry per tag + /// value. Different orders are keyed by distinct `d` tags and processed from a /// `HashMap` with no guaranteed iteration order, so this compares the flattened /// mentions as a sorted multiset rather than asserting a specific cross-order /// sequence; the within-order value order is exercised separately below. @@ -308,7 +297,7 @@ mod tests { ); } - /// FR-009: within a single order, `pm` mentions preserve the tag's value order. + /// Within a single order, `pm` mentions preserve the tag's value order. #[test] fn aggregate_order_events_preserves_the_tag_value_order_within_one_order() { let multi = make_order_event_with_pm_values( @@ -326,10 +315,10 @@ mod tests { ); } - /// PR 7d (002 FR-004): `qualifying_orders` pairs each qualifying successful order's - /// timestamp with its parsed `amt` (or `None` when unparseable), one entry per - /// successful order — the same set `successful_trade_timestamps` covers, not the - /// smaller `trade_amounts`-only set. + /// `qualifying_orders` pairs each qualifying successful order's timestamp with its + /// parsed `amt` (or `None` when unparseable), one entry per successful order — the + /// same set `successful_trade_timestamps` covers, not the smaller + /// `trade_amounts`-only set. #[test] fn aggregate_order_events_pairs_each_successful_orders_timestamp_with_its_parsed_amount() { let with_amount = make_event( @@ -365,8 +354,8 @@ mod tests { ); } - /// FR-011: a `premium` tag that fails to parse as `i64`, or is missing, is excluded - /// from `premium_values` without panicking. + /// A `premium` tag that fails to parse as `i64`, or is missing, is excluded from + /// `premium_values` without panicking. #[test] fn aggregate_order_events_excludes_a_malformed_premium_but_keeps_a_valid_one() { let valid = make_event(