fix(history): keep fresh polls received during dispatch - #1091
Conversation
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b263e0c9a0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| now := time.Now() | ||
| balance := tel.ForecastMeasurement(now, ctrl.SiteMeterDriver, opts) |
There was a problem hiding this comment.
Capture freshness time under the telemetry lock
When a driver Update already holds the telemetry mutex, this captures now and then blocks in ForecastMeasurement; Update subsequently assigns a later UpdatedAt before releasing the mutex, so the snapshot still rejects that poll as future: and drops both the history row and observed-consumer ledger entry. Capture the freshness time while holding the same store lock as the snapshot, or have ForecastMeasurement obtain its own snapshot time, to fully close the race this change targets.
Useful? React with 👍 / 👎.
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>


Accepted text proposal
Maintainer-requested follow-up to #1089 before publishing the new primary forecast beta. This fixes the verified freshness finding in that PR.
What changed
A driver poll can arrive after a control tick starts but before Core saves history. Core now captures the freshness cutoff under the telemetry read lock and retains the original tick timestamp for the history row. An in-flight writer finishes before Core reads the clock and measurements together. This keeps the fresh history point and household ledger observation.
Why
The old cutoff used the start of the tick. A new poll during dispatch therefore appeared to come from the future. The new regression reproduces that loss before the fix and passes after it.
Boundaries and safety
Stale and truly future-dated readings still produce no household history or ledger entry. Forecasts with an explicit origin still reject readings published after that origin. Dispatch and model state are unchanged. Other open
main.gowork does not touch this history cutoff.Verification
8d17c362ac003b2d8bd7e56e7f8818ed682f2acbon merged 2.17.0 commitba017f91e71c4ecc242e6fcdc6cf09dda8c0e859.TestPersistTelemetryTickUsesPersistenceFreshnessfails on the original code and passes with the fix. It covers a poll after tick start, stale data, future data, the history timestamp, household watts and the persisted consumer ledger identity.-race -count=20; a temporary Go overlay that moved the clock before the lock made the concurrency test fail as expected.-race.make verify-allpasses, including native Energyplan 0.2.1 integrations and Linux ARM64, Linux AMD64 and Windows AMD64 cross-builds.Checklist
Note
Medium Risk
Changes how history and energy-ledger snapshots qualify meter freshness during dispatch; behavior for truly stale or future-dated readings is unchanged, but timing-sensitive persistence paths are affected.
Overview
Fixes a race where driver polls arriving mid–control tick were judged against the tick start as the freshness cutoff, so new readings looked “from the future” and were dropped from history and the household energy ledger even when they were valid.
buildHistoryPointnow callsForecastMeasurementNow, which takes the snapshot after the telemetry read lock (so an in-flight poll can finish first) while history rows still use the original tickTsMs. Freshness checks usebalance.Atfrom that snapshot, not the tick timestamp.Telemetry adds
ForecastMeasurementNowand refactorsForecastMeasurementto shareforecastMeasurementLocked; explicit-origin forecasts still useForecastMeasurement(now)and reject readings published after that origin.Regression coverage includes
TestPersistTelemetryTickUsesPersistenceFreshness(poll after tick start, stale/future samples, ledger identity) and concurrency tests for lock ordering.Reviewed by Cursor Bugbot for commit 8d17c36. Bugbot is set up for automated code reviews on this repo. Configure here.