Skip to content

feat(rollout): firmware rollout engine - #1017

Merged
rl-block merged 19 commits into
mainfrom
rollout/01d-rollout-engine
Sep 16, 2026
Merged

rl-block merged 19 commits into
mainfrom
rollout/01d-rollout-engine

Conversation

@rl-block

@rl-block rl-block commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Reviewable diff: +3097/-58 across 17 files (excludes generated, test, and story files).

Summary

This PR turns release-channel firmware assignments into paced updates and corrects drift. It adds assignment workflows, a persistent rollout engine, operator controls, health evidence and progress reads. Dispatch coordinates with operator actions and commits queued commands together with their rollout bookkeeping; a durable channel-wide budget limits concurrent disruption.

Stack and scope. This is the first remaining PR, with its diff against main. Merged #1015 supplies storage, verified delivery and concurrency primitives; merged #1016 supplies channel configuration, membership views and the cached availability API. The full chain is:

#1014 contract, merged#1015 storage and dispatch, merged#1016 channel operations, merged#1017 engine#1018 API/runtime#1019 client data#1020 channel UI#1021 active updates#1022 header/activity#1023 E2E.

Domain decisions live here. #1018 exposes/schedules them, #1019#1022 add client workflows, and #1023 adds E2E. Delegated execution/controller lifecycle and the rollout events feed remain deferred beyond this stack.

How it works

Assign firmware and snapshot work

Preview reports affected miners and batches, including no-op clears for never-assigned or already-cleared pairs. Target and on-target counts share one database snapshot; queued commands for another artifact prevent a miner from counting as on-target, while generation suppression applies only to proposed targets. Apply locks the channel before reading assignments, then saves artifact checksum and target/version metadata per manufacturer/model pair. Changed assignments advance their generation and atomically persist their predecessor on the assignment, including when every member already matches and no rollout is needed. They supersede active work and snapshot behavior, target order, batch/health baselines and rollback lineage. Later reconciliation inherits that generation’s persisted predecessor. Unchanged assignments preserve active work; clearing stops enforcement.

Apply, rollback, reconciliation and retry use the same channel-before-rollout lock order. Reconciliation treats its initial scan as candidate discovery, then reloads the current assignment and checks for active work under the lock. A finished retry checks the current generation after waiting for that lock. Concurrent clears or replacements therefore cannot create obsolete work. Reconciliation start events include the current channel name. Rollback restores the preceding artifact or clears a first assignment; rollback and drift correction run all at once.

Reconcile membership and dispatch atomically

Before starting new work, each tick adopts eligible successful commands sent by finished rollouts. This updates device provenance without resuming the rollout, rewriting target phases or clearing suppression. New evidence may advance its revision for polling; retry also performs this check before creating replacement work. Active preparation locks the channel and rollout, reloads active state, then reconciles membership, provenance and verification. A canceled or superseded rollout is skipped without changing its targets or retained history. Returners retain their batch/baseline but verify again; late joiners have no baseline and must hash. Enrolled targets remain tied to their paired device even if firmware changes its reported hardware identity. Live compatibility still gates new dispatches.

Dispatch uses the current channel budget. With the channel and rollout locked, the engine reloads active/unpaused state and targets and excludes miners that left since preparation. It holds those locks through command enqueue and attempt recording, so pause, cancel and assignment changes serialize with dispatch. The command batch, queue messages, attempts and command reservations commit together. Workers only see commands whose attempts and reservations committed; rollback removes the batch, queue entries and bookkeeping together. Command activity and batch tracking start only after commit. The dispatch transaction does not retry its callback, and nested dispatch transactions are rejected.

FirmwareUpdateArtifact uses the saved artifact with strict payload checks. Command batches retain the assignment's owning user independently of system/API audit actors; reconciliation and retry preserve that owner, while rollback creates a new assignment. Command activity identifies background enforcement as a system actor without a synthetic user identity. Any PENDING or PROCESSING firmware command makes the miner wait before another send or attempt exhaustion, even after the resend interval. Queued checksums take precedence over legacy file IDs. Terminal firmware-command results atomically clear trusted provenance, including failures that may occur after installation. The retained empty row advances the provenance timestamp and records the completed batch UUID, so stale observations cannot restore an older artifact. This per-device identity follows database writes rather than worker clocks and remains present after provenance is adopted.

Share the offline budget across rollout history

Migration 149 adds durable assignment predecessors, a per-device completion witness and firmware_rollout_reservation, keyed by channel, device and dispatched batch. The budget counts each device once across all hardware groups: historical rollout targets that are currently offline, plus outstanding dispatched commands whose targets have not yet been observed offline. Exclusion, cancellation, retry, completion and elapsed retry time do not silently free a slot.

The engine records offline observations every tick, including when rollouts are paused, gated or terminal. An observed offline-to-online recovery releases the command reservation; command termination also removes it, but an offline historical target still consumes capacity until recovery. Fleet deletion releases the device. Existing dispatches are backfilled conservatively; assignment predecessors are recovered from same-generation history where available. Each newly created rollout snapshots the current channel limit, including reconciliation, retry and rollback. The merged migration 148 stays unchanged.

Verify progress and advance stages

Verification requires the target version, matching provenance, no foreign firmware command, online status and baseline-appropriate hashing. Guarded writes and a reload before DONE protect concurrent deployments. Missing provenance, changed hardware identity or replacing provenance with another artifact reporting the same version additionally requires a durable successful result for the exact batch, device and checksum. The retained current command must resolve to a successful result for the same immutable checksum; a different, failed, unknown or missing result blocks older evidence. Historical legacy file IDs cannot establish immutable artifact identity. Queued/failed commands and preflight skips cannot establish successful evidence; incompatible targets fail without another incompatible send.

Batches and pilots stop at review gates or waits. Auto-continue evaluates stabilization, paired health samples, coverage and new errors. Timers subtract paused time and reject stale advancement decisions. Stage advancement and completion lock the rollout and reload targets before checking settlement. A committed pause or newly retried target therefore prevents a stale tick from advancing or completing work. Drift reopens updating and stabilization; later health changes alone do not rewrite persisted DONE or finished history.

Evidence reports neutral excluded counts without using their health samples or blocking readiness. Its scope is the current batch, every target for an unbatched rollout, or unbatched rest targets after batching; operational completion ignores departed targets.

Control and read work

Actions check optional expected_revision and preserve actors/notes. Continue requires resume while paused. Cancel preserves completed work and suppresses unfinished targets for the generation, including targets that have left the channel; retry resets/appends active targets or starts eligible replacement work. Missing organization-scoped resources return NOT_FOUND; database failures retain their causes instead of becoming business-state errors.

List/detail and mutation responses include counts, evidence, paged devices and cached advisory file availability. Each response shares checksum lookups without rehashing payloads; assignment, enforcement and delivery keep strict checks. Incremental polling captures a transaction boundary, drains all pages, then inclusively rechecks that boundary on the next cycle so late commits remain visible. Callers merge replayed rows by ID/revision; status must remain unset to observe every status transition. The UI continues full-list polling.

Diagrams

flowchart TD
    A["Apply, rollback or retry"] --> B["Lock channel and read current assignment"]
    C["Enforcement tick"] --> P["Adopt eligible completed dispatches without resuming history"]
    P --> B
    B --> D["Create eligible rollout and snapshot targets"]
    C --> O["Observe offline and recovered reservations"]
    D --> E["Lock channel then rollout; recheck active state and reconcile"]
    E --> F["Lock channel then rollout; refresh budget and dispatch state"]
    F --> G["Wait for pending commands; check artifact and compatibility"]
    G --> H["One commit: command batch, queue, attempts and reservations"]
    H --> I["Start tracking; workers execute firmware update"]
    I --> J["Terminal command results and independently arriving miner telemetry"]
    J --> C
    O --> F
    E --> K["Lock rollout; reload targets and evaluate progress"]
    K --> L["Rollout list and detail views"]
    M["Cached advisory file availability"] --> L
Loading
sequenceDiagram
    participant C as Polling caller
    participant D as Rollout domain
    participant S as SQL store
    C->>D: First page with previous polling cursor
    D->>S: Capture next-cycle transaction boundary
    loop Drain pages with unchanged filters
        D->>S: Read eligible rows
        D-->>C: Rows and same next-cycle polling cursor
        opt Another page remains
            C->>D: Request next page
        end
    end
    C->>D: Start next cycle with returned polling cursor
    Note over C,S: Late changes remain eligible and rows may repeat
Loading

Areas of the code involved

Area / package / file What changed Why it matters for review
Rollout service.go; behavior.go Preview, apply, rollback and behavior snapshots Desired state, no-op/clear semantics, lineage and validation
Rollout rollout.go Targets, controls, retry, evidence, views and cursors Recovery and reporting boundaries
Rollout enforce.go; terminal provenance reconciliation Reconciliation, dispatch, budget, verification and gates Lock order, command pacing, late command results and lifecycle decisions
Session/command services; permission middleware Enforcement actor, assignment user ownership and transactional dispatch Valid command ownership, atomic queue writes and deferred finalization
SQL transactor; db/commit_hooks.go; queue service Ambient transactions and post-commit work Rollback/retry isolation; workers see durable bookkeeping
Migration 149; release-channel and command SQL queries Assignment predecessors, reservations, command evidence and provenance invalidation Safety state survives lifecycle changes; command results atomically invalidate stale provenance; migration 148 is unchanged
proto/rollout/v1/rollout.proto Evidence scope description Counts match the stage being reported
Rollout, command and SQL-transactor tests Lifecycle, database races and failure injection Test-only; inspect concurrency and recovery coverage
server/generated/**; client generated protobuf bindings Regenerated bindings generated — skip

Key technical decisions & trade-offs

  • Assignment predecessor storage: retain rollback lineage even when no rollout is created; rollout history is no longer required to reconstruct the transition.
  • Assignment and behavior snapshots: preserve lineage and pacing independently of later channel/upload edits; the channel's offline limit remains live.
  • Channel-before-rollout locks: serialize assignment creation, retry and dispatch across hardware groups in one channel to avoid stale work.
  • Atomic queue and bookkeeping: workers can see commands only after attempts/reservations are durable; activity/tracking move after commit.
  • Durable reservation table: retain channel safety observations across exclusion and terminal history without changing completed rollout revisions; historical offline targets may continue holding capacity.
  • Queue state before retry age: long-running commands delay further attempts instead of being duplicated or prematurely failed.
  • Exact successful evidence for ambiguous provenance: absent provenance, same-version reports and hardware renames require current command-result evidence. Later commands with unknown or different bytes invalidate older proof.
  • Retained invalidation marker: clearing identity while advancing its timestamp closes stale insert/update races; retaining the current batch UUID avoids ordering by worker clocks or inferring identity from pruned history.
  • Late-result adoption: finished rollouts may update device provenance, while their lifecycle and suppression remain intact.
  • Separate evidence and completion scopes: excluded targets remain visible without contributing health samples or blocking completion.
  • Cached read availability, strict execution: polling cost stays independent of payload size; execution independently verifies bytes.
  • Inclusive polling boundary: replay avoids missed late commits; callers deduplicate and may see more replay during long transactions.
  • Periodic engine: production scheduling remains single-instance; database locks also serialize overlapping dispatch passes.

Testing & validation

This review round is validated with canonical cd server && just gen, full rollout, command, SQL-store and migration suites, and server lint. Focused provenance, terminal-adoption and completion-retry cases pass under -race. The top of the stack also passes rollout/API handler and migration tests plus lint. New cases cover later failed/foreign commands, backward/equal worker clocks, missing audit witnesses, stale insert/update races, real database deadlocks, and retry without repeating device execution. Migration tests exercise downgrade/upgrade and preserve existing provenance. Client and protobuf sources are unchanged in this round.

Coverage also includes assignment lineage and no-op clears; foreign-command preview counts; scope changes between preparation and dispatch; cancellation, exclusion and reentry; pause/cancel and overlapping dispatches; exact provenance and excluded evidence; long-running queues, historical offline targets and paused recovery; atomic queue/bookkeeping rollback and deferred-commit failures. Migration tests cover ordered downgrade/upgrade and reservation backfill from existing dispatches.

Most domain tests substitute miner dispatch; command transaction tests use the production command/queue path with Postgres. Hardware delivery integrity remains covered upstream, and browser workflows land in #1023. Final CI status is in the PR checks.

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

🔐 Codex Security Review

Note: This is an automated security-focused code review generated by Codex.
It should be used as a supplementary check alongside human review.
False positives are possible - use your judgment.

Scope summary

  • Reviewed pull request diff only (f5981b40caed401d7d4fefe9d213ac6b11c9b472...5e6581255b3a56f92056fd551746a154c02d09ad, exact PR three-dot diff)
  • Model: gpt-6-astra

💡 Click "edited" above to see previous reviews for this PR.


Review Summary

Overall Risk: HIGH

Findings

[HIGH] Automated review incomplete

  • Category: Other
  • Description: The automated review produced no usable result for f5981b40caed401d7d4fefe9d213ac6b11c9b472...5e6581255b3a56f92056fd551746a154c02d09ad (workflow run 35041583060; reason: codex-job-timeout, elapsed: unknown, budget: 30 minutes).
  • Impact: The pull request has not received complete automated security, correctness, and reliability analysis.
  • Recommendation: Require human review before merging. Do not treat this result as approval-free or low risk.

Notes

Human review is required because the bounded automated review was incomplete.


Generated by Codex Security Review |
Triggered by: @rl-block |
Review workflow run

@rl-block
rl-block force-pushed the rollout/01d-rollout-engine branch 2 times, most recently from 1fd4704 to c1976f5 Compare September 14, 2026 14:57

@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: 3ded33a343

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread server/internal/domain/rollout/enforce.go Outdated
Comment thread server/internal/domain/rollout/service.go Outdated
Comment thread server/internal/domain/rollout/rollout.go
Comment thread server/migrations/000148_release_channels.up.sql Outdated
Apply, rollback, cancel, retry, pause, resume and continue, batching and
pilots with review gates, auto-continue, and the enforcement tick, aligned
with the merged RolloutService contract (#1014):

- assignments are keyed by canonical (manufacturer, model) pairs and
  identified by payload checksum; a file is resolved to its artifact at
  apply time, the file's target metadata is snapshotted, and dispatch waits
  while no uploaded file carries the checksum;
- every assignment change opens a generation; rollback and retry of a
  finished rollout require the rollout to be current, and reconciliation
  rollouts inherit the generation's lineage;
- members halted in the most recent rollout of the generation that holds
  them are suppressed until retried, and the retry set is exactly that
  suppression set, whichever rollout produced it;
- managed-deployment provenance is recorded when a dispatched target reports
  the version, and DONE, on-target counts and the mismatch rule all require
  it;
- every mutation carries an actor, an optional expected revision checked
  against the row under lock (STALE_REVISION carries the current value) and a
  note; failures carry machine-readable reasons;
- the offline budget is the channel's live value shared across its active
  rollouts, counting offline targets and outstanding commands as slots;
- telemetry gates fail closed under the coverage rule, with aggregates over
  verified miners carrying their sample counts and change fields absent when
  not derivable;
- PreviewFirmware plans an apply, with an optional behavior override that
  cannot carry an offline budget.
A member with a pending or processing FirmwareUpdate for another artifact is
mismatched under the contract's mismatch rule: the engine targets it, holds
verification and provenance until the command drains, and tells a missing
file (ARTIFACT_MISSING) from one whose metadata cannot back an assignment
(ARTIFACT_MISMATCH).

@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: 0049e0143a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread server/internal/domain/rollout/enforce.go Outdated
Comment thread server/internal/domain/rollout/enforce.go Outdated

@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: c800370ef7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread server/internal/domain/rollout/enforce.go Outdated
Comment thread server/internal/domain/rollout/enforce.go
Comment thread server/internal/domain/rollout/enforce.go Outdated
Comment thread server/internal/domain/rollout/enforce.go Outdated
Comment thread server/internal/domain/rollout/rollout.go
Comment thread server/internal/domain/rollout/service.go

@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: 792d74a848

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread server/internal/domain/rollout/enforce.go
Comment thread server/internal/domain/rollout/rollout.go
Comment thread server/internal/domain/rollout/service.go Outdated

@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: 7336fe2991

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread server/internal/domain/rollout/enforce.go Outdated
Comment thread server/internal/domain/rollout/service.go
Comment thread server/internal/domain/rollout/enforce.go
Comment thread server/internal/domain/command/service.go Outdated

@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: f6e638db87

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread server/internal/domain/rollout/enforce.go Outdated
Comment thread server/internal/domain/rollout/enforce.go Outdated
Comment thread server/internal/domain/rollout/enforce.go Outdated
Comment thread server/internal/domain/rollout/enforce.go Outdated

@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: 6d722bf91a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread server/internal/domain/rollout/enforce.go Outdated
Comment thread server/internal/domain/rollout/enforce.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

client javascript Pull requests that update javascript code review-policy: human-approved Managed by the Review Policy workflow. server shared

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants