Loading…
Loading…
Technical documentation for the Update Channel Service, which manages software update status, channels, and operator-approved updates via a release
The UpdateChannelService is a core component responsible for managing the platform's software update lifecycle. It determines the current update status, manages update channels, handles operator-initiated updates, and reconciles update history for automatic updates performed by other system components.
This service acts as a presentation and control layer for an in-VPC release-controller. Historically, this service performed its own network polls to an external update manifest endpoint. This model has been retired. The release-controller now owns the entire update process: polling for new releases, verifying their signatures, and applying them. The UpdateChannelService provides a stable API for operators by scraping the release-controller's /status endpoint, making it the single source of truth for update status presented in the administration interface.
This architectural change makes the update status reporting more robust and eliminates a class of false-positive errors, such as a "signature verification failed" status that could previously occur if a public key was not provisioned. This service performs no direct network calls to the external update platform and no signature verification.
Key responsibilities include:
s2r_update_history log of all updates, whether operator-initiated or automatic.The service's functionality is divided into three main areas: status resolution, update application, and background reconciliation.
The effective update status, visible to operators, is derived from the release-controller.
GET /admin/v1/updates endpoint, invokes the effective() method.CACHE_TTL) to reduce load.resolveUncached() is called to compute a fresh status.ReleaseControllerStatusClient. This client call is fail-open with a short timeout, preventing it from blocking or failing the status request.ok, degraded) and its own product check results are translated into a calm, operator-facing status:
success: The controller is healthy and its last check was successful.degraded: The controller is in a degraded state, or its last check for a product update failed.unreachable: The release-controller could not be reached.operator_approved mode and the controller has observed a new version that has not yet been applied, the service exposes this version as latestAvailableVersion. In auto mode, this is always null because the controller applies updates automatically, so there is no "available-but-not-installed" state.in_progress operator-initiated updates by correlating them with terminal status reports from the controller (see Attempt-Based Correlation).EffectiveStatus object is cached and returned.When the system's update_mode is set to operator_approved, an operator must manually trigger the application of an available update. This is handled by the applyUpdate method, exposed via POST /admin/v1/updates/apply.
s2r_update_history table with a status of in_progress. This ensures a durable record of the attempt exists before any external side effects are initiated.ReleaseControllerStatusClient.triggerApply(), sending a signed request to the release-controller to begin the update.202 Accepted-style response quickly.UpdateChannelService handles the controller's response:
TRIGGERED: The request was accepted. The service updates the in_progress history row with the unique applyAttemptId returned by the controller. The row remains in_progress; its final state will be determined later via reconciliation.CONFLICT, UNAVAILABLE, etc.): The controller did not start the update. The service updates the pre-recorded in_progress row to a failed status (not_applied) with a reason based on the controller's feedback. This prevents dangling in_progress records for rollouts that never launched.The release-controller can apply updates automatically and out-of-band from the UpdateChannelService. The service includes reconciliation logic to ensure these updates are recorded and operators are notified.
On application startup, reconcileAutoInstallOnStartup() runs to synchronize the history table with the current reality.
currentVersion) against the to_version of the latest record in s2r_update_history.initiated_by set to release-controller and status as success. This new record is flagged as an "unseen notification" for administrators.A critical reconciliation task is closing out operator-initiated updates that were left in_progress. The service uses an attempt-based correlation mechanism.
success, rolled_back, failed).UpdateChannelService fetches these terminal attempts.in_progress history row with a matching apply_attempt_id.success, rolled_back, and other failures.The following diagram illustrates the modern, controller-driven update flow. The UpdateChannelService no longer polls the external source directly; it queries the in-VPC Release Controller, which owns the update process.
flowchart TD
subgraph "API Platform"
A[Operator via Admin UI]
B[UpdateChannelService]
C[Release Controller]
end
D[External Update Source]
A -- "1. Views update status" --> B
B -- "2. Scrapes status from" --> C
C -- "3. Periodically polls" --> D
D -- "4. Provides release manifest" --> C
C -- "5. Reports status to" --> B
B -- "6. Displays status to" --> A
A -- "7. Approves update" --> B
B -- "8. Triggers apply on" --> C
C -- "9. Fetches and applies update from" --> DThe service exposes its functionality through several methods, which are typically surfaced via the administrative API.
Provides a comprehensive snapshot of the current update status, configuration, and any available updates. This is the primary data source for the "Settings -> Updates" UI panel.
The response is a JSON object with the following fields:
| Field | Type | Description |
|---|---|---|
channel | String | The currently configured update channel (e.g., release, beta). |
mode | String | The update mode (auto or operator_approved). |
currentVersion | String | The currently installed version of the software. |
currentReleaseNotesUrl | String | A URL to the release notes for the currentVersion. Null if the version is not a standard semantic version. |
lastCheckAt | OffsetDateTime | The timestamp of the last status derivation from the release-controller. |
lastCheckStatus | String | The status of the last check: success, degraded, or unreachable. |
lastCheckError | String | A descriptive error message if lastCheckStatus is not success. |
lastCheckErrorClass | String | An error code corresponding to lastCheckError. |
latestAvailableVersion | String | The version string of the next available update. Null if the system is up-to-date or in auto mode. |
latestAvailableSeverity | String | The severity of the available update (e.g., critical, high), sourced from the signed release manifest. |
latestAvailableSummary | String | A one-line summary of the available update, sourced from the signed release manifest. |
latestAvailableReleaseNotesUrl | String | A URL to the release notes for the latestAvailableVersion. |
checkIntervalHours | Integer | The configured interval for scheduled update checks. |
windowStartHour | Integer | The configured UTC start hour for the update maintenance window. |
windowDurationHours | Integer | The configured duration of the update maintenance window. |
stubMode | Boolean | Always false. The network poll is retired, not a stub. |
placeholderKey | Boolean | Always false. Manifest verification is no longer on this service's path. |
entitledChannels | Array<String> | The complete list of valid channels that can be configured: internal, beta, early-access, release. |
updateProcessActive | Boolean | true if the release-controller is present and reporting status. |
POST /admin/v1/updates/apply
Triggers the application of a specific software version. This is only used when the update_mode is operator_approved.
A JSON object containing the target version. If the body is empty, the service will attempt to apply the latestAvailableVersion from the current status.
{
"requestedVersion": "1.2.4"
}
The response is a JSON object indicating the outcome of the trigger request.
On success (TRIGGERED):
{
"fromVersion": "1.2.3",
"toVersion": "1.2.4",
"outcome": "TRIGGERED",
"historyId": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"status": "in_progress",
"note": "Apply requested — the release controller is applying 1.2.4 in the background. Watch history for the result."
}
On rejection (CONFLICT):
{
"fromVersion": "1.2.3",
"toVersion": "1.2.5",
"outcome": "CONFLICT",
"historyId": "b2c3d4e5-f6a7-8901-2345-67890abcdef1",
"status": "not_applied",
"note": "The release controller could not apply 1.2.5 — it is not the currently observed available version, or automatic apply is not enabled on this install."
}
POST /admin/v1/updates/check
Triggers an immediate, asynchronous refresh of the update status. This invalidates the in-memory cache and forces a fresh scrape of the release-controller's status. It does not perform any direct network poll to the external update platform.
Retrieves a list of recent update events from the s2r_update_history table for the current installation. Each history row provides details about a specific version change.
The response is a list of HistoryRow objects.
| Field | Type | Description |
|---|---|---|
id | UUID | The unique identifier for this history record. |
fromVersion | String | The version before the update. |
toVersion | String | The version after the update. |
initiatedBy | String | Who or what initiated the update (e.g., operator, release-controller, baseline). |
status | String | The final status of the update (success, failed, in_progress, rolled_back). |
statusReason | String | A reason for a non-success status. |
createdAt | OffsetDateTime | The timestamp when the update event was recorded. |
summary | String | A one-line summary of the release, if available. |
releaseNotesUrl | String | A URL to the release notes for the toVersion, if available. |
These operations manage the per-administrator notifications that appear after an automatic update. An "unseen notification" is a history row initiated by the release-controller that a specific administrator has not yet acknowledged.
notifications(adminSubject): Returns a count of unseen notifications and the most recent unseen HistoryRow for the given administrator.acknowledgeNotifications(adminSubject): Marks all unseen notifications for the given administrator as seen. This is an idempotent action that does not affect other administrators.The UpdateChannelService is configured through system settings and Spring application properties (or environment variables).
| Name | Type | Default | Description |
|---|---|---|---|
update_channel | String | release | The update channel to follow. Valid values are internal, beta, early-access, release. |
update_mode | String | operator_approved | The update application mode. Valid values are operator_approved (requires manual apply) and auto (controller applies updates automatically). |
update_check_interval_hours | Integer | 24 | The interval in hours for the scheduled status refresh job. |
update_window_utc_start_hour | Integer | 2 | The UTC hour when the maintenance window for automatic updates starts. |
update_window_utc_duration_hours | Integer | 2 | The duration in hours of the maintenance window. |
installation_id | String | — | The unique UUID for the installation, used to scope history and notifications. |
s2r.updates.check-cron | String | 0 0 3 * * * | The cron expression for the scheduled status refresh. Overrides update_check_interval_hours. |
s2r.deploy.version | String | dev | The current running version of the software. Can be set via env vars S2R_DEPLOY_VERSION or S2R_DEPLOY_COMMIT_SHORT. |
s2r.updates.release-notes-base-url | String | SpecariaEndpoints.PUBLIC_API_BASE | The base URL for constructing links to release notes. Can be set via env var S2R_UPDATES_RELEASE_NOTES_BASE_URL. |
CONFLICT | POST /admin/v1/updates/apply | The release controller could not apply the requested version. This may be because it is not the currently observed available version, or because automatic apply is not enabled.
| ALREADY_IN_PROGRESS | POST /admin/v1/updates/apply | An update is already being applied. The operator should monitor the update history for the result of the ongoing process.
| NOT_OBSERVED | POST /admin/v1/updates/apply | No update is available to apply. The operator should trigger a status refresh (POST /admin/v1/updates/check) first.
| UNAUTHORIZED | POST /admin/v1/updates/apply | The release controller rejected the apply request. This typically indicates a misconfiguration of the internal signing secret used for communication between services.
| UNAVAILABLE | POST /admin/v1/updates/apply | Apply is not available. The in-VPC release controller is not reachable, or the internal signing secret is not set.| FAILED | POST /admin/v1/updates/apply | The release controller could not verify the release to apply. The operator can try again.
| --- | --- |
| lastCheckStatus: degraded | GET /admin/v1/updates | The release-controller is reporting a non-healthy state, or it failed to check for a product update. The accompanying error message provides more detail. This is not a critical failure of the admin service itself.
| lastCheckStatus: unreachable | GET /admin/v1/updates | The service could not communicate with the in-VPC release-controller. This is expected in deployments without a release-controller but indicates a problem otherwise.
NOTE
The retirement of the direct network poll means that this service can no longer produce a false "signature verification failed" error. All signature verification is now owned by the release-controller. Any degraded status reported by this service reflects an issue with the controller or its ability to poll upstream, not a failure within the UpdateChannelService itself.
ReleaseControllerStatusClient: The client used to communicate with the in-VPC release-controller.SystemSettingRepository: The repository for retrieving system-wide configuration like update_mode and update_channel.UpdateChannelRepository: The repository for managing the s2r_update_history table.ProductIdentity: A component that provides the product identifier (e.g., s2r) used in constructing release notes URLs.