Skip to content

[Board] Boards are tombstoned and telemetry keys on the Board id - #437

Merged
KacperKozak merged 5 commits into
devfrom
board-id-tombstones
Aug 31, 2026
Merged

[Board] Boards are tombstoned and telemetry keys on the Board id#437
KacperKozak merged 5 commits into
devfrom
board-id-tombstones

Conversation

@KacperKozak

@KacperKozak KacperKozak commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Deleting a Board now leaves a tombstone instead of removing its row, and every telemetry table keys on board_id rather 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 onto board_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_name existed 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, while getBoard(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_id and drop both device_id and device_name. Names resolve from boards on read. The bucket primary key moves to (bucket_start_ms, board_id). The boardId -> bleId translation 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 historical device_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, which dev has since spent on VESC fault evidence), stripped of the backup-only sync cursor machinery, and extended to cover RideHistoryRepository, which landed on dev after that branch forked. Every code change here comes from #276, including the board_warnings cascade 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.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

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.
@KacperKozak KacperKozak added area:board Board profiles, board table, and board settings area:history Ride history, sessions, buckets, graphs area:telemetry Live telemetry ingest and display area:db Touches database / persistent storage area:native Touches native side (modules/vesc-ble, Swift/Kotlin) complexity:medium Needs care, moderate integration surface. Use sonnet. labels Aug 31, 2026
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
KacperKozak merged commit 7c577ec into dev Aug 31, 2026
5 checks passed
@KacperKozak
KacperKozak deleted the board-id-tombstones branch August 31, 2026 17:24
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:board Board profiles, board table, and board settings area:db Touches database / persistent storage area:history Ride history, sessions, buckets, graphs area:native Touches native side (modules/vesc-ble, Swift/Kotlin) area:telemetry Live telemetry ingest and display complexity:medium Needs care, moderate integration surface. Use sonnet.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant