镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

feat(update): stable/beta update channel; stable follows release tags - #684

Merged
ChuckBuilds merged 4 commits into
mainfrom
claude/update-channel
Sep 30, 2026
Merged

ChuckBuilds merged 4 commits into
mainfrom
claude/update-channel

Conversation

@ChuckBuilds

@ChuckBuilds ChuckBuilds commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

Why

Devices update by pulling main's HEAD (Update Code, and the weekly automatic update, both through perform_core_update), so every merge to main reached devices before it was released. This adds the owner-approved stable/beta update channel.

Design

auto_update.channel in config.json:

channel what an update installs
stable the newest vX.Y.Z tag by semver, checked out with a detached HEAD. Pre-releases (v3.8.0-rc1), build metadata, leading zeros and other tags (nightly, v3.7) are ignored.
beta main, exactly as before (git pull --rebase --autostash). From a detached release it first moves onto main.

No downgrade, ever. stable only checks out a tag that contains HEAD (git merge-base --is-ancestor HEAD <tag>). checkout_release() checks this again and refuses to move backwards. A device newer than the newest release (anything that pulled main since that release) keeps following main ("waiting") until a release contains its commit, then moves to it. On a detached HEAD with no branch to follow it stays where it is.

Migration. A config with no key behaves as beta until the newest tag contains HEAD. At that point it moves to stable and writes channel: "stable" (Update Code, the weekly preflight, and the case where it is already on the release commit). Because "stable, but newer than the newest release" also follows main, a missing key and stable behave the same, so nobody is downgraded either way.

  • Heads-up: ConfigManager._migrate_config copies new template keys into existing configs. Most existing devices will therefore get channel: "stable" written on first load of this version, not at migration time. Behaviour is still what the owner specified: they keep following main until a release contains their commit. It just means the key is persisted earlier than the migration rule describes.

New installs default to stable: config/config.template.json has it, and so does the installer's fallback config for when the template is missing. The installer diff is 4 lines inside that heredoc.

  • A fresh install clones main's tip, which is usually newer than the newest release. So it follows main until the next release, then moves to stable. Checking out the newest tag at install time would mean touching the one-shot/installer flow, which other branches are changing. I left that as a follow-up.

Where it lives

  • web_interface/update_channel.py (new, stdlib only):
    • tag parsing, channel resolution (resolve() returns the action: none / checkout_tag / pull / switch_to_beta) and fetch (--tags --force origin)
    • checkout() carries uncommitted edits across a checkout the way pull --autostash does (git stash create → reset → checkout → stash apply). If the edits no longer apply, they are stored in the stash list rather than left half-merged, as git's own autostash does.
    • Leaving a release for beta goes straight to origin/main (checkout -B main origin/main) when local main has no commits of its own. Otherwise the edits would be applied to an older commit where plugin files may not exist yet.
  • perform_core_update (api_v3/system.py) fetches branches and tags, resolves the channel, then pulls or checks out. The existing explicit stash for Update Code, the no-stash refusal for the automatic updater, dependency sync, restart detection and the uninstalled-plugin purge are unchanged.
  • AutoUpdater.preflight is channel-aware:
    • The upstream and ahead checks now apply only to the pull path.
    • A detached checkout with commits not on origin/main is blocked.
    • rolled_back_head is compared against the tag commit, so a release that failed its health check is not retried.
    • It records old_ref (branch name, or "" when detached) and release in the pending file. Messages name the release: "Updated LEDMatrix from 6508b43 to v1.4.0 (7720d14)".
  • Verifier accepts the new state: auto_update_verify.py rollback first returns to old_ref:
    • checkout --force <branch>, or --detach <old> when the old ref was a detached release, then reset --hard <old> as before.
    • Without this, rolling back a main→tag move would leave the device detached, and rolling back a tag→main move would drag main back to the release commit.
    • Pending files without old_ref (written by older updaters) roll back exactly as before.
  • Displays:
    • /system/check-update on stable compares release tags, not main. It adds channel, waiting, newest_release, current_release and target_version, and fetches only main and tags, since it runs on page loads with a 10 s timeout.
    • The banner says "LEDMatrix v1.2.0 is available".
    • The Tools git panel shows the release version and explains the detached HEAD instead of "unknown / Pull Latest cannot run".
    • The version display (git describe --tags) already reads v1.0.0 on a detached tag, as the WSL proof shows.
  • UI/API:
    • The General tab has an Update Channel select under Automatic Updates, with a status line (amber while stable is waiting on main).
    • GET /api/v3/system/update-channel (?fetch=1 fetches first) and POST {"channel": "stable"|"beta"}. Anything else is a 400. The response message says when stable keeps following main.
    • The form field auto_update_channel is also accepted on /config/main.
    • New routes have no local except; the blueprint handler supplies describe_exception.
  • Docs: CHANGELOG (Unreleased), GETTING_STARTED ("Keep LEDMatrix Up to Date"), TROUBLESHOOTING (updates/channel entry, plus emergency rollback on a tag), CONFIG_REFERENCE, REST_API_REFERENCE, ARCHITECTURE, WEB_INTERFACE_GUIDE.

Rig result (ledpi, Pi 4) and the fix it led to

Tested on ledpi in a scratch clone against the real GitHub tags:

  • Passed. The update paths:
    • legacy detached v3.6.2 → v3.7.0, channel saved as stable, local edit carried;
    • stable on main ahead of v3.7.0 keeps pulling main;
    • beta detached on v3.7.0 → main tracking origin/main;
    • stable on v3.7.0 is a no-op.
  • Fetch took 0.43–0.45 s (the timeout is 10 s). GET /system/update-channel and the General tab status line were correct.
  • Bug found. On a detached HEAD newer than the newest release (ledpi's real state), the channel's action is none, so Update Code leaves the device where it is. But /system/check-update fell through to the origin/main comparison. It returned update_available: true, and the Overview banner offered "A new LEDMatrix update is available (12 commits)" with Update Now, which then answered "already up to date".

Fix (commit 6c99803):

  • check-update reports update_available: false whenever the channel's action is none. channel_message carries the General tab's own wording ("...it stays on it and moves to the first release that includes it").
  • An on-branch device behind origin/main (beta, or stable waiting on a branch) still gets update_available: true.
  • The Tools git panel said "On a release" for every detached HEAD. git-info now returns current_release and channel_message, and the panel either names the release or explains what the channel does with a checkout that is not on one.
  • The banner JS needed no change: it only shows when update_available is true.
  • New tests check each case against what Update Code actually does:
    • detached-ahead, for both a stable config and a legacy config;
    • on-branch-behind, for both beta and stable-waiting;
    • git-info on a release.
  • Mutation-checked. Each of these fails a test: dropping the action-none rule, widening it to every waiting device, and never naming the release.

Tests

test/test_update_channel.py (62 tests; real git against a throwaway bare origin and device clone):

  • tag parsing: semver order (v3.10.0 > v3.9.0); pre-releases, junk and leading zeros ignored; annotated tags resolve to their commit
  • channel selection
  • the migration rule: moves when contained, not while ahead, and migrates without moving when HEAD is exactly on the release commit
  • no-downgrade in both resolve() and checkout_release()
  • autostash carry-over, and conflicting edits kept in the stash
  • the real perform_core_update for stable, beta, and stable→beta→stable round trips
  • the weekly preflight, and verifier rollback for main→tag and tag→main (main is not dragged back)
  • the API, check-update and the General form

Four mutations were each caught by the suite: dropping the ancestry check, accepting pre-release tags, never migrating, and ignoring old_ref in the rollback.

Full suite vs baseline (Windows, Python 3.14, Pillow 12.3.0 before and after both runs). The baseline was a separate temporary worktree at origin/main (c3a7a11), the commit this branch is rebased on.

  • Baseline: 61 failed, 6156 passed, 136 skipped, 6 errors
  • Branch: 61 failed, 6218 passed (+62, the new tests), 136 skipped, 6 errors
  • The sorted FAILED/ERROR test IDs (73 lines) are identical: no regressions. The failures are the known pre-existing Windows set (config atomic writes, starlark ownership, and so on).

Merge with main (64c7289). Merge commit 27fe9c9.

After the squash merge (7804ea8): main's tree is identical to the tested merge tree 27fe9c9 (tree 032f886a). A full suite run on final main gave 61 failed, 6808 passed, 143 skipped, 6 errors. The FAILED/ERROR IDs are identical to the origin/main baseline (Pillow 12.3.0 before and after).

WSL proof (Ubuntu 24.04, git 2.43, Python 3.12)

Setup: a local bare origin with v1.0.0, v1.1.0-rc1 (pre-release), nightly (junk) and an unreleased main tip, plus a device clone. The real perform_core_update, AutoUpdater and Verifier ran against it. Only sudo and systemd were faked. All checks passed:

  1. Legacy config, device behind v1.0.0. It moved to v1.0.0 with a detached HEAD, not to rc1, nightly or main, and the config was saved as stable. A plugin-repos edit and the installer's mode-only change were carried across, and nothing was left in the stash. get_git_version() reads v1.0.0-dirty.
  2. → beta. The device left the tag for main, tracking origin/main, and the plugin edit was kept.
  3. → stable while ahead of v1.0.0. It did not check out v1.0.0. It pulled main forward and reported "newer than the newest release ... keeps following main".
  4. Release v1.2.0 containing the device commit. It moved forward to it, detached.
  5. Update Code with a core edit. It moved to v1.3.0, and the edit was kept in the stash, as Update Code always has done.
  6. Weekly update to v1.4.0 whose health check fails. The verifier rolled back to v1.3.0, still detached. The next preflight does not retry v1.4.0.
  7. Weekly update on beta from a detached release. It moved to main. Rollback returned to v1.3.0 detached, and main was not dragged back.
  8. Weekly update on stable from main (old_ref=main). Rollback returned to branch main at the old commit.

Also run under Linux:

  • test_update_channel, test_auto_update, test_auto_update_verify, test_update_prompts_restart, test_update_check_reports_failure and test_git_pull_resolution: 181 passed. One test was deselected because it runs git ls-files on the project, and the /tmp copy is not a checkout.
  • Installer: bash -n first_time_install.sh passes. The config section, sliced out and sourced, was run as the user and as root with SUDO_USER:
    • template missing: the heredoc config has channel: "stable" with auto-update on or off
    • template copied: stable
    • existing config re-run: channel left alone
    • owner is the user in every case
  • The existing installer tests (test_install_*, test_web_autostart_default): 77 passed.

Still needs checking on a Pi

Not yet tested on hardware (needs root): ledmatrix-update-verify and its rollback, end to end. The rest of this list is from before the rig run.

  • On a real install that pulled main after 3.7.0: the General tab shows "waiting", and Update Code keeps pulling main.
  • Once the next release is tagged: Update Code moves to it detached, and the web and display services restart onto it.
  • ledmatrix-update-verify end to end on a detached tag: a weekly update to a newer release passes. Also force a failure (e.g. stop the web service during the check) and confirm the rollback lands back on the previous tag and the services come back.
  • Switching stable→beta on a device whose local main is stale, over a real (slow) network.
  • Check-update timing on a Pi Zero: fetch --tags origin main must stay inside the 10 s timeout.
  • The Tools tab and banner wording on a detached checkout; re-running one-shot-install.sh on a stable device (it pulls main into the detached HEAD; afterwards the device waits for the next release).

Not changed:

  • Tools → Force reset still resets to origin/main.
  • The one-shot installer still clones main.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added Stable and Beta update channels, selectable in General settings. Stable follows tested releases; Beta follows the latest development code.
    • Added channel-aware update checks and status messages, including guidance when a device is ahead of the latest Stable release.
    • Updates preserve local edits, and failed automatic updates restore the previous version.
  • Documentation
    • Updated setup, configuration, API, and troubleshooting guides with channel behavior and update instructions.

Devices updated by pulling main's HEAD, so every merge reached them
unreleased. auto_update.channel now picks what Update Code and the weekly
automatic update install:

- stable: the newest vX.Y.Z tag by semver (pre-releases and other tags
  ignored), checked out with a detached HEAD.
- beta: main, exactly as before.

Nothing moves a device backwards. stable only checks out a tag that
contains HEAD; a device newer than the newest release keeps following main
until a release contains its commit, then moves to it. A config without the
key behaves the same way and is written as stable at that point. New
installs get stable from the config template and the installer's fallback
config.

- web_interface/update_channel.py: tag parsing, channel resolution,
  checkouts that carry edits across like pull --autostash.
- perform_core_update, the auto-updater preflight and check-update are
  channel-aware; the Overview banner names the release on stable.
- The health check's rollback returns to the previous ref (branch or
  detached) via old_ref in the pending file; files without it roll back as
  before.
- General tab: Update Channel select; GET/POST /api/v3/system/update-channel.
- Docs and CHANGELOG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 2c6cc94c-75ec-4e15-bf41-f97bb77aab75

📥 Commits

Reviewing files that changed from the base of the PR and between 64c7289 and 27fe9c9.

📒 Files selected for processing (21)
  • CHANGELOG.md
  • config/config.template.json
  • docs/ARCHITECTURE.md
  • docs/CONFIG_REFERENCE.md
  • docs/GETTING_STARTED.md
  • docs/REST_API_REFERENCE.md
  • docs/TROUBLESHOOTING.md
  • docs/WEB_INTERFACE_GUIDE.md
  • first_time_install.sh
  • scripts/utils/auto_update_verify.py
  • test/fixtures/api_v3_url_map.json
  • test/test_update_channel.py
  • test/test_web_auth.py
  • web_interface/auto_update.py
  • web_interface/blueprints/api_v3/config.py
  • web_interface/blueprints/api_v3/system.py
  • web_interface/blueprints/pages_v3.py
  • web_interface/templates/v3/base.html
  • web_interface/templates/v3/partials/general.html
  • web_interface/templates/v3/partials/tools.html
  • web_interface/update_channel.py

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Adds stable and beta update channels. Stable selects a release tag when it contains the current commit; beta follows main. The change adds channel APIs and interface settings, updates automatic-update rollback to restore the prior Git reference, and documents and tests the channel behavior.

Changes

Update Channels

Layer / File(s) Summary
Channel resolution and Git operations
web_interface/update_channel.py, config/config.template.json, first_time_install.sh, docs/ARCHITECTURE.md, docs/CONFIG_REFERENCE.md, test/test_update_channel.py
Adds stable and beta channel resolution, release-tag selection, and edit-preserving checkout operations. New and fallback configurations default to stable. Tests cover channel selection, release validation, and local-edit preservation.
Channel-aware updates and rollback
web_interface/auto_update.py, web_interface/blueprints/api_v3/system.py, scripts/utils/auto_update_verify.py, test/test_update_channel.py, docs/ARCHITECTURE.md
Manual and automatic updates use the resolved channel plan. Pending verification data records the prior Git reference, which rollback restores before resetting the commit. Tests cover channel updates and rollback.
Channel API and update status
web_interface/blueprints/api_v3/system.py, web_interface/blueprints/api_v3/config.py, test/fixtures/api_v3_url_map.json, test/test_update_channel.py, test/test_web_auth.py, docs/REST_API_REFERENCE.md
Adds channel retrieval and setting endpoints, validates General-section channel updates, and returns channel-aware update and Git status. Tests cover API behavior, authentication, and update checks.
Interface and update guidance
web_interface/blueprints/pages_v3.py, web_interface/templates/v3/*, test/test_update_channel.py, CHANGELOG.md, docs/GETTING_STARTED.md, docs/TROUBLESHOOTING.md, docs/WEB_INTERFACE_GUIDE.md
Adds the General settings channel selector and status, updates release-aware banners and detached-checkout Git information, and documents channel behavior.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant UpdateAPI
  participant ChannelResolver
  participant Git
  participant Config
  User->>UpdateAPI: Request a core update
  UpdateAPI->>ChannelResolver: Resolve configured channel
  ChannelResolver->>Git: Inspect tags, branches, and commit ancestry
  Git-->>ChannelResolver: Return refs and ancestry results
  ChannelResolver-->>UpdateAPI: Return update action
  UpdateAPI->>Git: Check out release or update branch
  UpdateAPI->>Config: Persist stable channel when migration applies
  UpdateAPI-->>User: Return update result and channel information
Loading

Merge Risk: ⚪ Minimal · up to 27fe9

This change adds Stable and Beta update channels, channel-aware updates, and rollback to the prior branch or release. The reported hardware and scratch-clone checks match the intended behavior, and no confirmed defect remains. The change appears ready to merge.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 27fe9

Channel choices are constrained and reuse the existing update permissions. However, an interrupted channel switch can discard plugin edits before their recovery information is durably recorded, weakening update failure containment.

Retained concerns

  • Medium · reliability · observed: The new channel-switch transaction resets tracked edits before establishing a durable recovery reference. Its stash SHA exists only in memory; interruption or a subprocess timeout after reset can leave edits absent from both the worktree and the advertised stash list. Failed conflict-stash storage is also ignored. This weakens failure containment for separately owned plugin edits, which can enter this path even when automatic updates refuse core edits. Normal reapplication and conflict handling preserve edits when all steps complete.
Security review details

Security Blast Radius

  • inferred — The directly affected scope is an installation's checkout, tracked plugin state, dependencies, and restarted services. Update content can reach the existing privileged dependency installer. The inspected paths do not establish a new cross-tenant boundary; deployment-wide exposure remains unspecified.

Trust Boundaries and Controls

  • observed — Request-controlled channel input is reduced to a two-value allowlist and validated again before persistence. It does not supply arbitrary Git refs, paths, or commands. The new routes inherit the existing optional-authentication boundary; privileged update actions already existed under that boundary before this PR.

Resilience and Maintainability Implications

  • observed — The automatic updater copies the verifier before changing code, preserving a known-good recovery implementation. Dependency-installation failures prevent restart onto the new code and lead to verification rollback. These controls protect deployment recovery, but do not durably preserve the transient stash used inside the new channel checkout.

Hardening Proposals

  • proposed — Establish a durable recovery reference before resetting edits, retain it until reapplication is confirmed, and make recovery resumable after interruption. Check stash-storage results so the reported recovery state matches what is actually recoverable.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 105 functions across 9 files. (12 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding stable and beta update channels, with stable updates following release tags.
Full details: Docstring Coverage

Explanation

Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 105 functions across 9 files. (12 skipped: 12 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codacy-production

codacy-production Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 130 complexity

Metric Results
Complexity 130

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

…ng them as SQL

"Update channel set to ..." in an f-string matches Bandit B608's
UPDATE ... SET pattern. The messages now say "Switched to the beta/stable
channel." No SQL is involved; the text is only shown in the web UI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ChuckBuilds

Copy link
Copy Markdown
Owner Author

Hardware result (ledpi, Pi 4, 2026-09-30, branch at c51f2c6): the update paths pass, but the check-update banner is wrong on a detached checkout that is newer than the newest release. I did not run a real update on ledpi's checkout. The flows ran in a scratch clone and were then deleted.

Fetch time on the Pi. git fetch --quiet --tags --force origin main (the check-update fetch) took 0.43–0.45 s over 5 runs on ledpi's real checkout, against the 10 s timeout. A full fetch --tags origin took 0.59 s, and a scratch clone's first main+tags fetch 0.46 s. The whole /system/check-update request took 0.55 s.

Live, read-only, on ledpi (detached at the branch head, config channel unset):

  • On first start the web process wrote auto_update.channel: "stable" into config.json. This is the _migrate_config behaviour noted in the PR.
  • GET /system/update-channel returned configured: stable, channel: beta, waiting: true, action: none, newest_release: v3.7.0, message "…newer than the newest release (v3.7.0); it stays on it…". ?fetch=1 returned the same in 0.49 s.
  • The General tab shows "Update Channel: Stable (releases)" with the amber status line carrying the same message.

Real perform_core_update / check_for_update / update-channel code against a scratch clone (origin = GitHub; release tags v3.6.2 and v3.7.0; a local plugin-repos edit present in every scenario; pip stubbed and never needed):

start channel check-update Update Code after
detached v3.6.2 none (legacy) available, target v3.7.0 "Now on release v3.7.0 (stable channel)." detached v3.7.0; config set to stable; edit carried; no stash
branch main @ c3a7a11 stable available (13), waiting pulled; "keeps following main" main @ origin/main, tracking; edit carried
detached v3.7.0 beta available (16) "Now following main (beta channel)." main @ origin/main, tracking; edit carried
detached v3.7.0 stable not available "already up to date … on the newest release" unchanged
detached @ c3a7a11 stable available (13) "already up to date", nothing moved unchanged

Issue: the last row, which is ledpi's real state. On a detached checkout newer than the newest release, check-update falls through to the beta comparison. It reports update_available: true against origin/main, so the banner reads "A new LEDMatrix update is available (12 commits)" with Update Now. But the channel's action is none, and Update Code answers "LEDMatrix is already up to date". Seen live in the UI on ledpi, and reproduced in the scratch clone.

Suggested fix: in check_for_update, when channel.action == ACTION_NONE (waiting, detached), report update_available: false and let the status line explain.

Not tested: ledmatrix-update-verify end to end and rollback on a device (needs root and a real update), switching over a slow network, and a Pi Zero.

ledpi was restored to main with config byte-identical to its backup.

🤖 Generated with Claude Code

ChuckBuilds and others added 2 commits September 30, 2026 15:05
Found on the ledpi rig: a detached checkout newer than the newest release
(it pulled main, then was detached) has channel action "none" -- Update
Code leaves it where it is until a release includes it -- but
/system/check-update fell through to the origin/main comparison, reported
update_available: true, and the Overview banner offered "A new LEDMatrix
update is available (12 commits)" with an Update Now that then answered
"already up to date".

check-update now reports update_available: false whenever the channel's
action is none, with channel_message in the General tab's words. An
on-branch device behind origin/main (beta, or stable waiting on a branch)
still gets update_available: true.

The Tools git panel said "On a release" for every detached HEAD; git-info
now carries current_release and channel_message, and the panel names the
release or says what the channel does with a checkout that is not on one.

Tests cover both directions against Update Code's actual behaviour;
mutation-checked (dropping the action-none rule, widening it to every
waiting device, and never naming the release each fail a test).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts:
- CHANGELOG.md: kept both; "Update channels" first, then main's sections.
- docs/ARCHITECTURE.md: the verifier paragraph now says both things --
  the heartbeat check from #687 and the return to old_ref from this branch.
- web_interface/blueprints/pages_v3.py: the General partial gets both
  update_channel_status (this branch) and web_login (#683).

Follow-ups for main's changes:
- #688: the channel API test fixture stubs api_v3.plugin_catalog, not the
  retired plugin_manager.
- #683: test_web_auth pins that GET/POST /system/update-channel need login
  from the LAN when login is on (the default rule; mutation-checked by
  exempting the endpoints).
- #685: scripts/build_css.py regenerates identical CSS; --check passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ChuckBuilds
ChuckBuilds merged commit 7804ea8 into main Sep 30, 2026
13 checks passed
@ChuckBuilds
ChuckBuilds deleted the claude/update-channel branch September 30, 2026 19:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant