feat(rollout): release channel schema, queries and store - #1015
Draft
rl-block wants to merge 1 commit into
Draft
Conversation
This was referenced Sep 4, 2026
🔐 Codex Security Review
Review SummaryOverall Risk: HIGH Findings[HIGH] Automated review incomplete
NotesHuman review is required because the bounded automated review was incomplete. Generated by Codex Security Review | |
This was referenced Sep 4, 2026
rl-block
force-pushed
the
rollout/01b-release-channel-schema
branch
from
September 4, 2026 09:39
dc276d2 to
532e3cb
Compare
rl-block
force-pushed
the
rollout/01b-release-channel-schema
branch
from
September 4, 2026 10:10
532e3cb to
f471d11
Compare
rl-block
force-pushed
the
rollout/01b-release-channel-schema
branch
from
September 4, 2026 10:15
f471d11 to
354b39c
Compare
rl-block
force-pushed
the
rollout/01b-release-channel-schema
branch
2 times, most recently
from
September 4, 2026 12:06
144f58a to
d8499dd
Compare
rl-block
force-pushed
the
rollout/01b-release-channel-schema
branch
2 times, most recently
from
September 8, 2026 02:53
c5e95b0 to
fe0c808
Compare
Storage for release channels and the rollouts that enforce them, aligned with the merged RolloutService contract (#1014): assignments and rollouts are keyed by canonical (manufacturer, model) pairs compared with an ASCII-only fold, firmware is identified by payload checksum with the file's target metadata snapshotted onto the assignment, every pair carries an assignment generation that survives clearing, rollouts carry a revision that a trigger advances on every change to the row or its devices, plus actor attribution and delegated-control columns. Membership views resolve ties to no channel and expose every conflicting relation; managed-deployment provenance is recorded per device. The files service gains the two lookups the domain needs for checksum identity.
rl-block
force-pushed
the
rollout/01b-release-channel-schema
branch
from
September 8, 2026 03:17
fe0c808 to
8d62462
Compare
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.
Reviewable diff: +1226/-0 across 5 files (excludes generated, test, story and fixture files).
Summary
Adds the storage layer for release channels: tables for channels, their scope selectors, per-pair firmware assignments identified by payload checksum, rollouts (with generation, revision, actors and lineage), per-miner rollout progress and per-device deployment provenance, plus the views that answer "which channel does this miner belong to right now" and "how is each overlap resolved" from live fleet placement. The sqlc queries the domain layer uses (scope preview with overlap detection, rollout snapshots, per-miner halts and retries, telemetry evidence) ship with their generated Go. Nothing reads these tables yet.
Stack. #1014 (merged: API contract) -> #1015 (this PR) -> #1016 (domain: channels and scopes) -> #1017 (rollout engine) -> #1018 (API wiring) -> #1019 -> #1020 -> #1021 -> #1022 -> #1023. Diff is relative to
main. Upstream context: the proto in #1014 defines the vocabulary the columns mirror (method, order, stage, status, cancel reason, halt reason, thresholds). Out of scope: the transactional rules that use these queries (advisory-locked overlap rejection in #1016, stage transitions and dispatch in #1017) and the activity-label migration for rollout events (#1022).How it works
Channel definition.
release_channelholds the name, description, behaviour columns (method,order_by,batch_size,pilot_size,wait_between_batches_seconds,review_after_each_batch,auto_continue,stabilization_seconds, the four nullable thresholds,max_concurrent_offline); the name is unique per org (UNIQUE (org_id, name)).release_channel_targetstores one row per scope selector (target_typein site/building/rack/group/miner plustarget_id).release_channel_firmwarestores one row per(channel, manufacturer, model)pair (unique under an ASCII fold): the assigned payload checksum, version and snapshotted target keys, and anassignment_generationthat advances on every change including clearing, which empties the checksum but keeps the row.Membership.
release_channel_matchunions five queries, one per selector kind, each joining the selector to the devices it currently covers through the existing placement tables (device.site_id,device.building_id,device_set_membershipfor racks and groups,device_set_rackfor a rack's building) and tagging the row with a specificity (miner 1, group 2, rack 3, building 4, site 5).release_channel_membercollapses that to one row per miner withDISTINCT ON (device_id)ordered by specificity then channel id, and setsconflictedwhen more than one channel matched. Every read of "who is in this channel" goes through this view, so moving a miner changes its channel immediately.Rollouts.
firmware_rolloutrecords one run per pair: the target checksum and version, the lineage for rollback, the assignment generation, a trigger-maintainedrevision/updated_at, the starting and last-acting actor, a copy of the behaviour it started with,status,cancel_reason,stage,batch_count/current_batch,stage_changed_at,paused_at,finished_at, and a partial unique index guaranteeing at most one active rollout per(channel, model).firmware_rollout_deviceholds the snapshot of miners in a run:batch_index(NULL for the rest stage),position,attempts,first_sent_at/last_sent_at,last_error,halted_at/halt_reason(failedorcanceled),excluded_at,added_at, and the baseline captured when the miner was snapshotted (baseline_status,baseline_hash_rate_hs,baseline_power_w,baseline_efficiency_jh,baseline_temp_c,baseline_open_errors,baseline_at).Queries (
release_channel.sql): channel CRUD and target replacement;ListDeviceIDsByIdentifiersto turn operator-supplied identifiers into ids;ResolveReleaseChannelScope, which evaluates an unsaved scope with the same joins as the view and reports, per miner, the model and the owning channel if another one already covers it;LockReleaseChannelScopes, a per-org transaction-scoped advisory lock;ListReleaseChannelMismatchedMembers(members whose reported version differs from the assignment);ListFirmwareRolloutswith optional channel and status filters and keyset paging on(created_at, id)(the caller passes the last row's key and a limit);GetFirmwareRolloutWithChannel;ListReleaseChannelMinersPage(one channel's members, optional model, keyset paging on(device_identifier, device_id));SnapshotFirmwareRolloutDevices,ListFirmwareRolloutDevicesjoined to current status and latest telemetry;RequeueFirmwareRolloutDevicesandReleaseFirmwareRolloutDeviceHaltsfor retry; and the stage/status update statements.Diagrams
erDiagram release_channel ||--o{ release_channel_target : "scope selectors" release_channel ||--o{ release_channel_firmware : "one per model" release_channel ||--o{ firmware_rollout : "runs" firmware_rollout ||--o{ firmware_rollout_device : "snapshot" release_channel_target }o--o{ device : "resolved via release_channel_match"flowchart LR T["release_channel_target (site / building / rack / group / miner)"] --> M["release_channel_match: one row per (channel, miner) with specificity"] P["device placement: device.site_id, building_id, device_set_membership, device_set_rack"] --> M M --> R["release_channel_member: DISTINCT ON miner, lowest specificity wins, conflicted flag"] R --> Q["ListReleaseChannelMembers / ListReleaseChannelMismatchedMembers"]Areas of the code involved
server/migrations/000148_release_channels.up.sql(new)release_channel_matchjoins (a wrong join silently changes who gets firmware) and theDISTINCT ONordering inrelease_channel_memberserver/migrations/000148_release_channels.down.sql(new)server/sqlc/queries/release_channel.sql(new)ResolveReleaseChannelScopemust agree with the view;LockReleaseChannelScopesis what #1016 relies on for exclusivityserver/internal/domain/stores/sqlstores/release_channel.go(new)SQLReleaseChannelStore(transaction-awareQueries(ctx)) andIsUniqueViolationserver/internal/infrastructure/db/migration_bridges_test.gomainmoved from 145 to 147 while this stack was open)server/migrations/release_channels_test.go(new)server/generated/sqlc/**release_channel.sql.go,models.go,querier.go,retrying_querier.gen.go,db.go)Key technical decisions & trade-offs
fleet_device_placement, so neither view blocks schema changes to the other.(created_at, id)for rollouts,(device_identifier, device_id)for members), compared as a row value against the sameORDER BY, so pages stay stable while rollouts start or miners move.Contract alignment (after the #1014 merge)
#1014 merged on 2026-09-08 as
74454ed9. This stack was first cut against the contract of 2026-09-04; on 2026-09-08 every PR was rebased ontomainand reworked to the merged contract, so each one is again one commit above its parent and green on its own. Head8d62462ec.This PR implements: Pair-keyed assignments (
manufacturer,model, ASCII-folded unique index) whose row outlives clearing and carriesassignment_generation; checksum artifact identity with the file's target metadata snapshotted; rollouts withmanufacturer, checksum lineage, generation,revision/updated_atadvanced by triggers on the row and on its devices, started-by/last-action-by actors,controller_timeout_seconds,min_sample_coverage_percent;skippedhalts withskip_note; managed-deployment provenance per device;release_channel_memberresolving specificity ties to no channel andrelease_channel_conflictexposing winner/loser/excluded-tie relations; paged model groups, conflicts and suppressed-member queries; files-service lookups for checksum identity.Implementation notes:
CancelRollout's contract text makes canceled-remaining miners retryable, so cancellation suppresses too.Deferred to follow-up slices (not in this stack): delegated control (
ROLLOUT_METHOD_DELEGATED,AdvanceRollout,SkipRolloutDevices,CompleteRollout,WAITING_FOR_CONTROLLER,controller_timeout_seconds) and the events feed (ListRolloutEvents,RolloutEvent,RolloutActorhistory). The schema, behavior, error reasons, permission entries and enum vocabulary for both are already in place; the handler answers those RPCs withUnimplementedand refuses the DELEGATED method until the slices land.Testing & validation
sqlc diffclean (generated code matches the queries).DB_PASSWORD=fleet go test ./migrations/ ./internal/infrastructure/db/: 148 applies, rolls back and re-applies; the bridge test passes with the new latest version.go build ./...with the generated code.