Skip to content

fix(history): keep fresh polls received during dispatch - #1091

Merged
frahlg merged 2 commits into
masterfrom
fix/history-freshness-cutoff
Sep 7, 2026
Merged

fix(history): keep fresh polls received during dispatch#1091
frahlg merged 2 commits into
masterfrom
fix/history-freshness-cutoff

Conversation

@frahlg

@frahlg frahlg commented Sep 7, 2026

Copy link
Copy Markdown
Member

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.go work does not touch this history cutoff.

Verification

  • Head 8d17c362ac003b2d8bd7e56e7f8818ed682f2acb on merged 2.17.0 commit ba017f91e71c4ecc242e6fcdc6cf09dda8c0e859.
  • TestPersistTelemetryTickUsesPersistenceFreshness fails 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.
  • A synchronized writer/reader regression proves the snapshot waits for an in-flight poll before capturing time. A second regression retains explicit-origin future rejection. Both pass with -race -count=20; a temporary Go overlay that moved the clock before the lock made the concurrency test fail as expected.
  • Focused telemetry, history, stale-meter and persistence tests pass with -race.
  • make verify-all passes, including native Energyplan 0.2.1 integrations and Linux ARM64, Linux AMD64 and Windows AMD64 cross-builds.

Checklist

  • The diff implements one accepted scope and does not add follow-on work.
  • I checked open pull requests that touch the same files.
  • Tests cover the changed behaviour and its failure path.
  • No UI changed.
  • A Changeset is included.
  • Every commit has a DCO sign-off.

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.

buildHistoryPoint now calls ForecastMeasurementNow, which takes the snapshot after the telemetry read lock (so an in-flight poll can finish first) while history rows still use the original tick TsMs. Freshness checks use balance.At from that snapshot, not the tick timestamp.

Telemetry adds ForecastMeasurementNow and refactors ForecastMeasurement to share forecastMeasurementLocked; explicit-origin forecasts still use ForecastMeasurement(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.

Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-07T11:56:40.486876Z b263e0c PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread go/cmd/ftw/main.go Outdated
Comment on lines 4093 to 4094
now := time.Now()
balance := tel.ForecastMeasurement(now, ctrl.SiteMeterDriver, opts)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a non-blocking comment; no approval and no reviewers assigned. Cursor Bugbot completed with no findings, but APPROVAL_POLICY.md does not authorize auto-approval, and no CODEOWNERS path matches the changed files.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
@frahlg
frahlg merged commit 336969a into master Sep 7, 2026
17 checks passed

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a non-blocking comment; no approval and no reviewers assigned. Cursor Bugbot completed with no findings, but APPROVAL_POLICY.md does not authorize auto-approval, and no CODEOWNERS path matches the changed files.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant