• v1.7.0 0cb1f6bf52

    Ghost released this 2026-04-25 11:59:28 -04:00 | 140 commits to main since this release

    Summary

    Fixes #1081. When the version flagged is_latest was soft-deleted, no
    other version was promoted, and because GetCurrentLatestVersion didn't
    filter deleted rows, subsequent publishes with a lower semver never
    became latest — leaving /v0/servers/{name}/versions/latest permanently
    404.

    This PR has two parts:

    1. Code fix — recalculate is_latest on every status change

    • Recalculate is_latest after every status mutation
      (UpdateServerStatus, UpdateAllVersionsStatus, and the inline status
      change in UpdateServer), in the same transaction that already holds
      the per-server advisory lock.
    • Winner selection reuses the existing CompareVersions logic: highest
      non-deleted version gets the flag; if every version is deleted, the
      highest deleted version keeps it so admin lookups via
      includeDeleted=true still resolve the server (important for restore
      flows).
    • New SetLatestVersion DB primitive issues two statements (clear old,
      set new) — idx_unique_latest_per_server is a non-deferrable unique
      partial index, so a single UPDATE that flips two rows trips the
      constraint.

    2. One-shot migration — heal servers already affected (no manual SQL

    needed)

    014_heal_is_latest.sql promotes the highest non-deleted version on
    every server whose is_latest flag is on a deleted row (or absent
    entirely). Picker parses major.minor.patch numerically and falls back
    to published_at for non-semver or as a tiebreaker. Heals
    io.github.containers/kubernetes-mcp-server to 0.0.61 automatically
    and catches any other servers silently affected by the original bug,
    without hardcoded version numbers.

    Picker behavior vs. CompareVersions:

    • Standard M.m.p semver: matches exactly, including the
      backport-after-delete case (e.g. published 1.0.1 then 1.0.0 hotfix after
      deleting 2.0.0 → picks 1.0.1, not 1.0.0).
    • Non-semver: falls through to published_at DESC. Reasonable.
    • Prereleases (1.0.0-alpha vs 1.0.0): both parse to (1,0,0),
      tiebreak by published_at. Diverges from CompareVersions (which
      always says 1.0.0 > 1.0.0-alpha). Edge case requires (a) server hit by
      this bug AND (b) prereleases at the top of the version list. Acceptable;
      the publisher can flip it with one mcp-publisher status call (which
      now triggers proper recalc thanks to part 1).

    Verification

    End-to-end curl repro of the exact scenario from the issue, run against
    both main and this branch:

    Before (main):

    publish 1.0.0 → 200
    publish 0.0.59 → 200
    PATCH .../1.0.0/status {deleted} → 200
    GET /versions/latest → 404            ← bug
    publish 0.0.60 → 200
    GET /versions/latest → 404            ← bug persists, 0.0.60 has isLatest=False
    

    After (this branch):

    publish 1.0.0 → 200
    publish 0.0.59 → 200
    PATCH .../1.0.0/status {deleted} → 200
    GET /versions/latest → 200 (0.0.59, isLatest=true)
    publish 0.0.60 → 200
    GET /versions/latest → 200 (0.0.60, isLatest=true)
    

    Test plan

    • Service tests covering: soft-delete latest → next highest active
      promoted; the exact #1081 repro end-to-end through the service;
      all-versions-deleted still keeps highest addressable for admin;
      restore-to-higher-version reclaims latest.
    • Migration tests covering: kubernetes-mcp-server scenario (heals to
      highest semver); backport scenario (semver beats most-recent-published);
      no-is_latest-row defensive case; all-deleted server left untouched;
      healthy server left untouched; non-semver versions fall back to
      published_at.
    • Full unit test suite (./internal/... ./cmd/...) passes against a
      real Postgres.
    • make lint (0 issues) and make validate pass.
    • Manual curl repro against a running registry confirms the fix
      resolves the failure mode and main still reproduces it.

    🤖 Generated with Claude Code


    Co-authored-by: Claude Opus 4.7 (1M context) noreply@anthropic.com

    Downloads