Skip to content

fix(storage): restore SQLite and Parquet with verified beta conversion - #1248

Merged
frahlg merged 4 commits into
masterfrom
fix/sqlite-parquet-recovery-20260913
Sep 14, 2026
Merged

frahlg merged 4 commits into
masterfrom
fix/sqlite-parquet-recovery-20260913

Conversation

@frahlg

@frahlg frahlg commented Sep 13, 2026

Copy link
Copy Markdown
Member

Core's DuckDB history path can fail during full backup and adds a native runtime/build dependency to every installation. This change restores SQLite history plus verified Parquet archives, with state.db retaining goals, identities and models separately from history.db.

Beta candidate. Backup can still delay durable goal saves on Raspberry Pi (#1246), and a new safety fault can still wait behind an already blocked control tick (#1247). These issues carry release-blocker and prevent stable promotion. No production history has been converted and this storage candidate is not installed on the live site.

Requires merged #1240 at 67439f4 and the paired driver pin from srcfl/device-drivers#116. All EV hardenings from those changes remain included.

The normal Core and backup builds use CGO_ENABLED=0 and have no DuckDB dependency. Fresh and older SQLite/Parquet installations start directly. Only DuckDB beta installations use the separate offline converter under go/tools/history-migrate. It fingerprints frozen sources, resumes bounded verified copies, reconciles catalog IDs by name, preserves older summaries and energy counters, and publishes only after readback, sync and a recovery receipt. It retains original history files. Schema 4 blocks image-only rollback across the history format.

History writes keep bounded queues and durable commit receipts. Archive writes use bounded staging, verified Parquet publication and value-matched pruning. Raw reads, chart buckets and JSON output have explicit limits. Hourly series summaries and older daily energy totals survive retention. Full backup uses verified read snapshots and paced writes. Known EV safety stops now precede session IO for all chargers in a tick; this does not yet cover a new fault arriving while an earlier tick is already blocked.

Full backups now read retained Parquet pages and compare row counts, so matching hashes cannot hide a damaged source. The first SQLite migration records its intended generation before copying and can finish a binding interrupted by power loss, while still refusing unrelated files.

Closes #1250. Closes #1251.

Validation:

  • New regressions reproduce both review findings before the fixes and pass after them: four first-binding restart cases, truncated Parquet, and damaged pages under an intact footer. Backup tests cover both creation and independent archive verification. The new binding, corruption and full backup/restore tests also pass on the ARM64 Pi using isolated synthetic data; local make verify passes on f69110e.

  • Full master-target CI found that Windows cannot sync a read-only file handle. The converter now opens only its new destination read/write before the final sync. The existing interruption/resume tests cover this path; conversion sources remain read-only.

  • Local make verify, state/backup/loadpoint/MPC race suites, migration interruption/source-change/receipt tests, corruption/pruning tests, process-kill recovery and full backup/restore tests pass. Final exact-commit logs are linked in the local delivery report.

  • The new blocked-session test fails before the safety-ordering fix and passes after it, for stale site-meter permission and unavailable charger power with two chargers.

  • A real DuckDB fixture with one million interleaved samples passes locally and on ARM64 Pi (204 s, 98.5 MiB measured peak process RSS). The same fixture passes locally inside an enforced 256 MiB container limit. The Pi kernel does not enable its memory controller, so its RSS result is not an enforced-memory test.

  • The 48 MiB backup fixture restores all rows/goals locally with concurrent durable saves and commits. On Pi the backup still produces multi-second goal-save delays; a normal-priority run reached 14.94 s. An idle-priority run reached 45.18 s, but also deprioritized goal saves. These failures remain visible and block stable promotion; the 2 s acceptance limit has not been relaxed.

  • The standalone converter needs a compatible glibc/C++ runtime; an isolated compatible container works on the older Pi host. The normal pure-Go Core does not inherit that requirement.

Before converting a live DuckDB site: estimate disk space, verify a full backup and separate restore on the box, then validate migration, restart, EV identity/SoC/goal, forecasts and recent/old history on the exact candidate. Stable promotion requires corrections and passing target evidence for #1246 and #1247; both findings remain open. No household records are included in this PR.

Related: #1234, #1246, #1247. Operator steps are in docs/history-conversion.md.

Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
@frahlg
frahlg changed the base branch from fix/backup-ev-followup-20260913 to master September 14, 2026 03:52
@frahlg
frahlg marked this pull request as ready for review September 14, 2026 03:52
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 14, 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-14T03:58:44.492760Z 76bf6c4 Draft marked ready
ℹ️ 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: 76bf6c4103

ℹ️ 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 on lines +298 to +299
func (s *Store) ImportedHistoryFiles(ctx context.Context) (map[string]bool, error) {
return map[string]bool{}, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Validate retained Parquet before verifying a backup

When an existing Parquet day is truncated or otherwise unreadable, returning an empty imported-file set causes the backup to include that file, but backup.Verify checks only its self-recorded hash and never calls walkParquetRows. The archive is therefore reported as a verified full backup even though restoring it leaves that portion of history unreadable; validate every retained Parquet file before publishing the backup.

AGENTS.md reference: AGENTS.md:L24-L25

Useful? React with 👍 / 👎.

Comment thread go/internal/state/history_sqlite.go Outdated
Comment on lines +103 to +104
} else if active == "" && restore == "" {
return errors.New("unbound SQLite history; complete its migration before starting")

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 Recover an interrupted initial history binding

If power is lost after migrateSQLiteHistory commits the generation: marker but before SaveConfig writes history_sqlite_generation, the next boot enters this branch and permanently refuses to start. This can happen during either a fresh installation or the first legacy SQLite migration, and there is no converter or automatic retry path for it, so the binding protocol needs a recoverable pending marker or another way to distinguish its own completed migration from an unrelated unbound database.

AGENTS.md reference: AGENTS.md:L24-L25

Useful? React with 👍 / 👎.

Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
@frahlg
frahlg merged commit 41bcbb5 into master Sep 14, 2026
17 checks passed
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.

Interrupted first SQLite history binding prevents restart Full backup verification accepts unreadable Parquet with matching hashes

1 participant