[Board] Boards are tombstoned and telemetry keys on the Board id - #437
Merged
Conversation
Deleting a Board now stamps boards.deleted_at instead of removing the row, so Ride History keeps a resolvable Board identity (ADR 0027). Board-owned configuration is still hard-deleted; telemetry and Tune Profiles are not. getBoards() filters tombstones, getBoard(id) deliberately resolves them, and connect paths refuse a tombstoned Board. An ordinary upsert carries an existing tombstone forward, so deletion is terminal. Ported from #276 (#279) and the closed #435 (#428), renumbered onto schema 40 -> 41.
Every telemetry table — frames, minute buckets, markers, diagnostic events and metric exclusion ranges — now keys on board_id and drops both device_id (the mutable BLE identifier) and device_name (the Board name denormalized at capture time). Ride History resolves Board names by lookup, so a rename relabels history (ADR 0028, closes #274). Migration 41 -> 42 rebuilds all five tables, resolving each BLE identifier to a Board exactly once through a shared scratch map so no two tables can pick different claimants of a duplicated identifier. Rows that resolve to no Board mint a tombstoned Board named from their historical device_name, so orphaned history keeps a label and stays joinable. The boardId -> bleId translation used by markers, events and ranges is deleted. RideHistoryRepository (added on dev after #276 branched) is rekeyed the same way. Ported from #276 (#280) with the follow-up dedup and marker/event/range rekey.
The iOS backup stamp still claimed schema 41, so a current backup left the board-id migration unstamped and replayed it on restore — caught by TelemetryMigrationTests. Also rekeys the native API and history docs, adds both ADRs to the docs index, and records the Board Tombstone rules in CONTEXT.md.
Every durable Board-owned table now keys on board_id; boards.ble_id is the only BLE identifier left and is a Board Link attribute, not a join key.
boardNamesById() opens its own pool read; calling it inside getPage's read tripped GRDBPrecondition and killed the reader queue. Hoist it out, like every other call site already does.
KacperKozak
added a commit
that referenced
this pull request
Sep 2, 2026
#279 and #280 landed on dev as #437, so the branch's own tombstone and board-id migrations are dropped and dev's ladder is the one that ships. The sync migrations move to the tail of it: 42→43 Change Timestamps, 43→44 sync_seq, 44→45 the six remaining tables, 45→46 sync_actions, 46→47 sync_binding. Schema 47 on both platforms. The branch had taken schema 32 (Room 31→32, GRDB v32) for its first sync migration and pushed the shipped alert-repeat migration to the tail. Dev has since spent 32 through 42, so the slot goes back to alert-repeat and every sync migration is renumbered above dev's. Rebuilding the buckets on board_id now happens before the sync columns exist, so the rebuild no longer has to carry them and the assertions that it does are gone.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Deleting a Board now leaves a tombstone instead of removing its row, and every telemetry table keys on
board_idrather than the mutable BLE identifier. Ride History keeps a stable, resolvable Board identity, and renaming a Board relabels its whole history.Note
Risk: Medium — changes deletion semantics and rewrites five telemetry tables on upgrade.
Complexity: Medium — small runtime surface, but the read/write split and the identifier resolution have to hold identically in Room and GRDB.
DB: Schema + data — additive
boards.deleted_at(41), then a rebuild of all five telemetry tables ontoboard_id(42).Tasks
Description
The BLE identifier was never an identity. It is nullable, it moves when a Board is re-linked to a different peripheral, and one address can be claimed by two Boards, so two readers resolving the same address were free to disagree — which is how a ride could end up with its frames under one Board and its buckets under another. The denormalized
device_nameexisted to paper over that and over Board deletion, but it froze the label at capture time and could never answer "which rides came from this Board".Two decisions fix it together, which is why they land in one PR: a Board's row has to survive deletion before telemetry can safely point at it.
Boards are tombstoned (ADR 0027). Deleting a Board stamps
boards.deleted_at. Board-owned configuration — settings, warnings, Alert Rules, Last Known Board Config Values — is still hard-deleted; telemetry and Tune Profiles are untouched, as before.getBoards()filters tombstones so they leave every Rider-facing list, whilegetBoard(id)deliberately resolves them, because history still has to name a deleted Board. Paths that act on a Board rather than describe one refuse a tombstone. Deletion is terminal: an ordinary upsert carries an existing tombstone forward instead of clearing it.Telemetry keys on the Board id (ADR 0028). Frames, minute buckets, markers, diagnostic events and metric exclusion ranges all carry
board_idand drop bothdevice_idanddevice_name. Names resolve fromboardson read. The bucket primary key moves to(bucket_start_ms, board_id). TheboardId -> bleIdtranslation that markers, events and ranges needed is deleted, along with the session-boundary bug it caused.The migration resolves each BLE identifier to a Board exactly once, into a shared scratch map every rebuild reads, so no two tables can pick different claimants of a duplicated address. Where an address is ambiguous the pick is arbitrary but stable (lowest
boards.id) — for rows predating the migration no evidence of the real Board exists. Rows that resolve to no Board at all mint one tombstoned Board per unresolved identifier, named from that row's historicaldevice_name, so orphaned history keeps a label and stays joinable rather than becoming unowned.Both slices are extracted from the long-running Ride History backup branch (#276), where they were implemented and tested first, and rebased onto current
dev: renumbered onto schema 41 and 42 (the branch used 37-39, whichdevhas since spent on VESC fault evidence), stripped of the backup-only sync cursor machinery, and extended to coverRideHistoryRepository, which landed ondevafter that branch forked. Every code change here comes from #276, including theboard_warningscascade that the re-implementation in the closed #435 was missing.ADR 0027 is the exception, and deliberately so. #276 argues the decision through the server's Board-owned foreign keys, Sync Batches and Delete Actions — none of which exists on
dev— so this PR carries the app-scoped wording, which states the same decision and defers the server half to #276.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.