From 4c76840733e7a5a02fd43bf541a4dfce0c6b81e7 Mon Sep 17 00:00:00 2001 From: Suppaseth Charoenkarnka Date: Sun, 12 Jul 2026 22:46:39 +0700 Subject: [PATCH 1/2] fix(token): epoch fallback for out-of-range rollup ts; drop informal comment Post-merge Copilot findings on v3.4.12: - rollup day/month/year_key fell back to Utc::now() for an out-of-range timestamp, which would bucket a corrupt event under TODAY and pollute the current period's rollup. Fall back to the Unix EPOCH instead (obviously wrong, self-flagging) via a shared utc_or_epoch helper. Add a test. - Drop the informal '(Copilot)' attribution from the fake-daemon read-loop comments in token_check.rs / token_gain.rs test code. --- .../onebrain-cli/src/commands/token_check.rs | 2 +- .../onebrain-cli/src/commands/token_gain.rs | 2 +- crates/onebrain-token/src/gain/rollup.rs | 26 ++++++++++++++++--- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/crates/onebrain-cli/src/commands/token_check.rs b/crates/onebrain-cli/src/commands/token_check.rs index e6e058e..5910639 100644 --- a/crates/onebrain-cli/src/commands/token_check.rs +++ b/crates/onebrain-cli/src/commands/token_check.rs @@ -613,7 +613,7 @@ mod tests { std::thread::spawn(move || { // Accumulate until the end of the HTTP headers (`\r\n\r\n`) so // a request split across TCP segments isn't misread from a - // single partial `read` (Copilot). + // single partial `read`. let mut req_bytes = Vec::with_capacity(8192); let mut chunk = [0u8; 1024]; while req_bytes.len() < 8192 { diff --git a/crates/onebrain-cli/src/commands/token_gain.rs b/crates/onebrain-cli/src/commands/token_gain.rs index 209cc5c..ca0951a 100644 --- a/crates/onebrain-cli/src/commands/token_gain.rs +++ b/crates/onebrain-cli/src/commands/token_gain.rs @@ -1200,7 +1200,7 @@ mod tests { std::thread::spawn(move || { // Accumulate until the end of the HTTP headers (`\r\n\r\n`) so // a request split across TCP segments isn't misread from a - // single partial `read` (Copilot). + // single partial `read`. let mut req_bytes = Vec::with_capacity(8192); let mut chunk = [0u8; 1024]; while req_bytes.len() < 8192 { diff --git a/crates/onebrain-token/src/gain/rollup.rs b/crates/onebrain-token/src/gain/rollup.rs index a3e11c5..f63fcfe 100644 --- a/crates/onebrain-token/src/gain/rollup.rs +++ b/crates/onebrain-token/src/gain/rollup.rs @@ -68,21 +68,31 @@ impl RollupValue { } } +/// UTC datetime for a unix timestamp, falling back to the Unix EPOCH (not the +/// current time) for an out-of-range `ts`. An out-of-range value can't happen +/// for real gain events (they carry `now_ts()` seconds), but bucketing a corrupt +/// one under 1970 makes it obviously wrong and self-flagging, rather than +/// silently recording it under TODAY and polluting the current period's rollup. +fn utc_or_epoch(ts: i64) -> DateTime { + DateTime::from_timestamp(ts, 0) + .unwrap_or_else(|| DateTime::from_timestamp(0, 0).expect("unix epoch is a valid timestamp")) +} + /// `YYYY-MM-DD` (UTC) for a unix timestamp — the daily rollup key's period /// segment. `pub(crate)` so [`super::pivot::query_events`] buckets raw /// events on the exact same day boundary the rollup does. pub(crate) fn day_key(ts: i64) -> String { - let dt = DateTime::from_timestamp(ts, 0).unwrap_or_else(Utc::now); + let dt = utc_or_epoch(ts); format!("{:04}-{:02}-{:02}", dt.year(), dt.month(), dt.day()) } fn month_key(ts: i64) -> String { - let dt = DateTime::from_timestamp(ts, 0).unwrap_or_else(Utc::now); + let dt = utc_or_epoch(ts); format!("{:04}-{:02}", dt.year(), dt.month()) } fn year_key(ts: i64) -> String { - let dt = DateTime::from_timestamp(ts, 0).unwrap_or_else(Utc::now); + let dt = utc_or_epoch(ts); format!("{:04}", dt.year()) } @@ -256,6 +266,16 @@ mod tests { use crate::level::OptLevel; use tempfile::tempdir; + #[test] + fn utc_or_epoch_falls_back_to_1970_not_now_for_out_of_range_ts() { + // An out-of-range ts (`from_timestamp` → None) must bucket under the + // Unix epoch (obviously wrong, self-flagging), never silently under + // today's date where it would pollute the current period's rollup. + assert_eq!(utc_or_epoch(i64::MAX).year(), 1970); + assert_eq!(day_key(i64::MAX), "1970-01-01"); + assert_eq!(year_key(i64::MIN), "1970"); + } + fn event(ts: i64, surface: Surface, transform: &str, before: u64, after: u64) -> GainEvent { GainEvent { ts, From 7d47bf740b263f353a39e5ecf65f7a750becb7b2 Mon Sep 17 00:00:00 2001 From: Suppaseth Charoenkarnka Date: Sun, 12 Jul 2026 22:48:29 +0700 Subject: [PATCH 2/2] =?UTF-8?q?docs(readme):=20bump=20Quickstart=20--versi?= =?UTF-8?q?on=20example=203.4.9=20=E2=86=92=203.4.12?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4dff6f2..d3b2ed7 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ From zero to a working OneBrain vault in three steps: ```bash # 1. Verify the install onebrain --version -# → onebrain 3.4.9 +# → onebrain 3.4.12 # 2. Scaffold a vault and let init pull the OneBrain plugin mkdir my-vault && cd my-vault