Skip to content

feat(update): channel-aware guided update (Spec 079 US2)#818

Open
Dumbris wants to merge 11 commits into
mainfrom
079-upgrade-nudge-us2
Open

feat(update): channel-aware guided update (Spec 079 US2)#818
Dumbris wants to merge 11 commits into
mainfrom
079-upgrade-nudge-us2

Conversation

@Dumbris

@Dumbris Dumbris commented Jul 7, 2026

Copy link
Copy Markdown
Member

What / Why

Spec 079 User Story 2: a user told an update exists now sees the exact one-line command for their install channel — or safe channel-appropriate guidance when no command can be trusted. Builds on the US1 foundation (#798/#805): internal/updatecheck stays the single source of truth; everything here is additive (FR-021).

Spec: specs/079-upgrade-nudge/spec.md (US2, FR-008..FR-011, FR-021, edge cases: prerelease-on-stable, channel mis-identification).

Locked design decisions (implemented as specified)

  • A — Channel detection (internal/updatecheck/channel.go): build marker -X github.com/smart-mcp-proxy/mcpproxy-go/internal/updatecheck.buildChannel=<v> (full module path — documented in-code why) wins; heuristics in confidence order: Homebrew paths (symlink-resolved incl. /Cellar/), /.dockerenv, deb/rpm (require BOTH /usr/bin/mcpproxy AND the owning package DB; AUR/manual copies and dual-DB ambiguity → unknown), .app/Contents/MacOS → dmg, go-install via debug.ReadBuildInfo() Main.Version + no ldflags version, else unknown. Traced reality for go-install: the checker receives the "development" default (internal/server/server.gohttpapi.GetBuildVersion()), so the heuristic requires a non-semver ldflags version plus a valid non-(devel) module version. All probes injected via func fields for table tests.
  • B — Guidance (guidance.go): commands only for homebrew / deb / rpm / go-install (exact strings per spec); dmg / windows-installer / docker / tarball / unknown get guidance text (docker guidance names no registry — no official image is published today). Release URL deep link preserved on every path (FR-010).
  • C — Additive API fields: install_channel (always set once detected) + update_command (only when update available AND a command exists) on VersionInfo / InfoResponseUpdate / contracts.UpdateInfo / frontend UpdateInfo. Contract tests pin all six pre-existing update fields (FR-021).
  • D — Build stamping in exactly two sites: Dockerfile (docker) and scripts/build-windows-installer.ps1 (windows-installer, core + tray). release.yml matrix builds intentionally unstamped (one binary feeds tarball+homebrew+dmg) with an explanatory comment at the ldflags site; dev builds rely on heuristics.
  • E — Surfaces: mcpproxy status appends — Run: <cmd> or the guidance line (legacy format kept for older daemons); mcpproxy doctor prints Run:/Update: after its Download line; Web UI banner shows the command in a copyable code block with a copy button (dismissal logic untouched); system.ts gains installChannel/updateCommand getters.
  • F — Tray FR-011: kept self-update suppression and fixed its gap — Intel-mac installs (/usr/local/Cellar/ behind a /usr/local/bin symlink) were missed; now symlink-resolved + Cellar prefix. No other tray changes.
  • G — Prerelease-on-stable: pure-semver comparison retained; pinned tests: v0.48.0-rc.1 vs stable v0.47.0 → no nudge/no command; v0.47.0-rc.4 vs v0.47.0 → nudge to stable (documented reading of spec edge case L82).
  • H — Docs: OpenAPI regenerated via make swagger (fields documented from Go comments); docs/api/rest-api.md + docs/features/version-updates.md ("How the Install Channel Is Detected" + command table).

Also updated cmd/generate-types/main.go (frontend contracts.ts is auto-generated; a hand edit is overwritten on every build).

Testing

  • New table tests: channel_test.go (marker-wins, each heuristic isolated, deb/rpm both-signals + ambiguity→unknown, go-install traced-version cases), guidance_test.go (exact commands, no-command channels, no-ghcr.io pin), checker_channel_test.go (channel always set, command gating, prerelease pins, FR-021 field-survival), status/doctor rendering tests, tray Homebrew-path tests (incl. real symlink resolution).
  • go build ./cmd/mcpproxy + go build -tags server ./cmd/mcpproxy ✅; go test -race for updatecheck / httpapi / contracts / cmd/mcpproxy / tray ✅; golangci-lint run --config .github/.golangci.yml ./... → 0 issues.
  • Live E2E on an isolated instance (port 18099, scratch data dir, binary stamped buildChannel=homebrew + v0.40.0 — also proves the full -X path works): /api/v1/info returns install_channel=homebrew + update_command="brew upgrade mcpproxy"; mcpproxy status shows … — Run: brew upgrade mcpproxy; doctor shows Run: brew upgrade mcpproxy after Download.
  • Playwright sweep (3/3 green) against the embedded Web UI: banner shows version/release link/command block, copy button writes the command to the clipboard and confirms, per-version dismissal still persists across reload. Evidence kept in worktree tmp dir per verification-doc policy.

Dumbris added 6 commits July 7, 2026 14:54
…Spec 079 US2)

Add install-channel detection (internal/updatecheck/channel.go) with a
build-time marker that wins over runtime heuristics:

- marker: -X github.com/smart-mcp-proxy/mcpproxy-go/internal/updatecheck.buildChannel
  (full module path; Go silently ignores -X with a wrong path)
- heuristics in confidence order: Homebrew paths (symlink-resolved, incl.
  Intel-mac Cellar), /.dockerenv, deb/rpm (require BOTH /usr/bin/mcpproxy
  AND the owning package db — AUR/manual copies fall to unknown),
  .app/Contents/MacOS bundle -> dmg, go-install via debug.ReadBuildInfo
  Main.Version with no ldflags version, else unknown. Ambiguity never
  guesses (FR-008/FR-009).

Add guidance (internal/updatecheck/guidance.go): exact one-line
update_command only for homebrew/deb/rpm/go-install; dmg/windows-installer/
docker/tarball/unknown get channel-appropriate guidance text instead
(docker guidance names no registry — no official image is published).

Surface both as additive fields install_channel (always set once detected)
and update_command (only when an update is available and a command exists)
on VersionInfo/InfoResponseUpdate/contracts.UpdateInfo; contract test pins
the six pre-existing update fields (FR-021).

Pin prerelease-on-stable semantics (spec L82): v0.48.0-rc.1 vs stable
v0.47.0 -> no nudge; v0.47.0-rc.4 vs v0.47.0 -> nudge to stable.
…pec 079 US2)

mcpproxy status appends 'Run: <update_command>' — or the channel guidance
line when no command is safe — to the Version update annotation; older
daemons without install_channel keep the legacy format. mcpproxy doctor
prints the same action line after its Download line. Guidance passes an
empty release URL because both surfaces already print it (no duplication).
…sion (Spec 079 FR-011)

isHomebrewInstallation missed /usr/local/Cellar/ and never resolved the
/usr/local/bin symlink Homebrew actually exposes on Intel macs, so those
installs could be offered a conflicting in-app self-update. Resolve
symlinks first and add the Cellar prefix. No other tray changes
(minimal-fix policy); the tray check convergence is FR-001a, out of this
slice.
…Spec 079 US2)

UpdateBanner.vue shows the channel's one-line update command in a
copyable code block with a copy button when update_command is present;
dismissal logic is untouched (per-version, FR-005). system.ts exposes
installChannel/updateCommand getters. contracts.ts gains the additive
UpdateInfo fields via the cmd/generate-types generator (the file is
auto-generated). OpenAPI artifacts regenerated (make swagger).
…ec 079 US2)

Dockerfile stamps buildChannel=docker; build-windows-installer.ps1 stamps
buildChannel=windows-installer on both core and tray ldflags. The
release.yml matrix builds intentionally stay unstamped (one binary feeds
tarball + Homebrew + DMG, so any single value would be wrong for some
consumers) — a comment at the ldflags site documents this. Makefile/
build.sh/build.ps1/pr-build.yml stay unstamped: dev builds use heuristics.
… 079 US2)

rest-api.md documents the additive update.install_channel/update_command
fields; version-updates.md gains 'How the Install Channel Is Detected'
plus the per-channel command/guidance table.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 7, 2026

Copy link
Copy Markdown

Deploying mcpproxy-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 0e9b2f6
Status: ✅  Deploy successful!
Preview URL: https://e80ce283.mcpproxy-docs.pages.dev
Branch Preview URL: https://079-upgrade-nudge-us2.mcpproxy-docs.pages.dev

View logs

@codecov-commenter

codecov-commenter commented Jul 7, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 90.75145% with 16 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/updatecheck/channel.go 91.56% 7 Missing ⚠️
internal/httpapi/server.go 37.50% 2 Missing and 3 partials ⚠️
cmd/mcpproxy/doctor_cmd.go 72.72% 3 Missing ⚠️
internal/updatecheck/checker.go 94.73% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

📦 Build Artifacts

Workflow Run: View Run
Branch: 079-upgrade-nudge-us2

Available Artifacts

  • archive-darwin-amd64 (28 MB)
  • archive-darwin-arm64 (25 MB)
  • archive-linux-amd64 (16 MB)
  • archive-linux-arm64 (15 MB)
  • archive-windows-amd64 (28 MB)
  • archive-windows-arm64 (25 MB)
  • frontend-dist-pr (0 MB)
  • installer-dmg-darwin-amd64 (22 MB)
  • installer-dmg-darwin-arm64 (19 MB)

How to Download

Option 1: GitHub Web UI (easiest)

  1. Go to the workflow run page linked above
  2. Scroll to the bottom "Artifacts" section
  3. Click on the artifact you want to download

Option 2: GitHub CLI

gh run download 28876786527 --repo smart-mcp-proxy/mcpproxy-go

Note: Artifacts expire in 14 days.

Dumbris added 5 commits July 7, 2026 15:04
…e (Spec 079 US2)

Cross-model review round 1 fixes:

- rpm channel: the RPM database existing only proves the host is
  RPM-based; a manual tarball copy to /usr/bin/mcpproxy on Fedora was
  misclassified as rpm and shown a wrong 'sudo dnf upgrade' command.
  Ownership is now confirmed with a one-shot 'rpm -qf /usr/bin/mcpproxy'
  probe (3s timeout, fails closed to unknown per FR-009).

- dmg channel: DMG installs run the core from the tray-staged
  ~/Library/Application Support/mcpproxy/bin/mcpproxy (and the bundled
  source lives under Contents/Resources/bin), so the .app/Contents/MacOS
  check never matched the process serving /api/v1/info and DMG installs
  reported unknown. Both paths now classify as dmg; that staged directory
  is written only by the tray's bundle-staging, so it uniquely implies a
  DMG install.
…; banner guidance fallback (Spec 079 US2)

Cross-model review round 2 fixes:

- Prerelease targets (rc channel / MCPPROXY_ALLOW_PRERELEASE_UPDATES):
  prereleases are published only to the GitHub pre-release channel, so
  the generic package-manager commands would not deliver them — brew/
  apt/dnf serve stable artifacts and Go's @latest resolves to the newest
  stable. When the offered version is a prerelease only go-install now
  gets a command, pinned to the exact version; every other channel
  omits the command and falls back to guidance (FR-009).

- Web UI banner: channels that intentionally have no safe command
  (dmg/windows-installer/docker/tarball/unknown) now render the same
  guidance line as status/doctor instead of nothing, mirroring
  internal/updatecheck.GuidanceLine (FR-009/FR-010).
…ack for prerelease command channels (Spec 079 US2)

Cross-model review round 3 fixes:

- go-install builds carry no ldflags version (the checker received the
  non-semver 'development' default), so Start/CheckNow skipped checks
  and the go-install channel command was unreachable. The Go toolchain
  records the exact module version (tagged or pseudo-version) in build
  info — promote it into the checker's current version for that channel.

- Prerelease targets on command channels (homebrew/deb/rpm/go-install
  without a pinnable version) had their command suppressed AND
  GuidanceLine returned empty for those channels, leaving status/doctor/
  Web UI with no action line at all. GuidanceLine (and its Web UI
  mirror) now falls back to the generic release-page line; callers only
  invoke it when no command accompanies the update, so nothing
  double-renders.
…ce promoted go-install version (Spec 079 US2)

Cross-model review round 4 fixes:

- deb/rpm detection additionally requires the MCPProxy repository config
  (/etc/apt/sources.list.d/mcpproxy.list resp.
  /etc/yum.repos.d/mcpproxy.repo, as written by the documented setup).
  A standalone .deb/.rpm downloaded from a GitHub release is dpkg/rpm-
  owned but has no apt/dnf upgrade candidate, so the channel commands
  would be no-ops; those installs now degrade to unknown and get the
  release-page guidance (FR-009).

- /api/v1/info top-level version now prefers the update checker's
  current version: identical for packaged builds, but for go-install
  builds the checker promotes the build-info module version while the
  ldflags default would render 'development' on status/Web UI next to a
  real go-install update command.
isHomebrewPath matches Unix Homebrew prefixes with forward slashes, but
TestIsHomebrewPath_ResolvesSymlinks builds Cellar paths via filepath.Join,
which yields backslash paths on Windows that can never match. Homebrew is
macOS/Linux only, so the symlink scenario is inapplicable on Windows; skip
it there while keeping full darwin/linux coverage.
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.

2 participants