feat(dashboard): OSA version upgrade flow in archive settings - #223
Conversation
Archive owners see their OSA version, what's newer, and what changed —
and upgrade from the settings panel instead of a raw API call.
Platform mode (the full flow):
- UpgradeSection above Redeploy: pin + availability chip (Up to date /
Update available → vX.Y.Z), blocked-with-reason while deploying, a
deprecated-pin nudge, a withdrawn-pin 'upgrade required' alert (the
cloud refuses redeploys of withdrawn versions — surfaced proactively),
and the honest failed-upgrade state: the pin moves when the upgrade
starts, so a failed deployment says 'retrying deploys {pin}', never
implying rollback.
- UpgradeDialog: from → to with a picker when several supported targets
exist (default = newest, 'recommended' on the registry default),
release notes rendered inline and stacked oldest-first for
multi-version jumps + a GitHub compare link, a plain-language
migrations/pause warning, and a forward-only acknowledgment checkbox
(new ui/Checkbox primitive) gating the confirm.
- useUpgradeArchive mirrors useCreateArchive's handoff: pre-seed the
status cache from the 202, then route into /deploying/{id} — the
existing screen owns progress. invalid_state maps to 'A deployment is
already in progress.'; validation_error shows the server's precise
forward-only message verbatim.
- GET /api/releases: cached (10 min) server-side GitHub notes proxy,
session-guarded; range (from, to], drafts/prereleases skipped; 502
degrades the dialog to the registry notes_url link-out.
Self-host mode (awareness, deliberately no button — server and
dashboard share one OSA_IMAGE_VERSION in compose, so the dashboard
cannot upgrade the server without restarting itself):
- Version row in 'Manage this node' + a registry check (ghcr tags list,
cached ~1h, riding /api/node; null on failure degrades silently) and,
when behind, the exact copyable command:
osa start --osa-version vX.Y.Z.
- compareOsaVersions normalizes the health endpoint's bare 0.0.11
against v-prefixed registry tags.
Wire layer: osa_version_pin joins wireArchive (always present, per the
cloud DTO); wireOsaVersion + decoder with strict status validation; both
service impls (real + mock) extended — the mock registry seeds
supported/deprecated versions and reproduces the cloud's 400/422 error
contract for tests.
15 new tests (7 section/dialog, 3 self-host, 5 releases route); full
suite 222 green; lint + production build clean.
Greptile SummaryThe PR adds platform archive-version upgrades and self-host update awareness, including registry decoding, release-note retrieval, upgrade handoff, and supporting UI primitives.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| apps/dashboard/src/features/archive-upgrade/UpgradeSection.tsx | Adds registry-backed archive version status, lifecycle notices, and the upgrade-dialog entry point. |
| apps/dashboard/src/features/archive-upgrade/UpgradeDialog.tsx | Adds target selection, release notes, forward-only acknowledgment, and upgrade submission. |
| apps/dashboard/src/server/releases.ts | Adds cached, paginated retrieval of published GitHub release notes. |
| apps/dashboard/src/server/ghcr.ts | Adds a cached and time-bounded GHCR tag lookup for self-host update awareness. |
| apps/dashboard/src/api/amacrin/wire/decode.ts | Extends archive decoding and introduces strict OSA registry status decoding. |
Sequence Diagram
sequenceDiagram
participant User
participant Dashboard
participant Registry
participant Cloud
participant DeployingScreen
User->>Dashboard: Open archive settings
Dashboard->>Registry: List supported OSA versions
Registry-->>Dashboard: Version registry
User->>Dashboard: Confirm target version
Dashboard->>Cloud: POST archive upgrade
Cloud-->>Dashboard: 202 + deployment
Dashboard->>DeployingScreen: Seed status cache and navigate
Reviews (2): Last reviewed commit: "fix(dashboard): address Greptile review ..." | Re-trigger Greptile
| const [[discRes, readyRes, statsRes], latest] = await Promise.all([ | ||
| Promise.allSettled([ | ||
| fetch(`${base}/`, { headers: { accept: "application/json" } }), | ||
| fetch(`${base}/api/v1/ready`, { headers: { accept: "application/json" } }), | ||
| fetch(`${base}/api/v1/stats`, { | ||
| headers: { ...auth, accept: "application/json" }, | ||
| }), | ||
| ]), | ||
| latestOsaVersion(), | ||
| ]); |
There was a problem hiding this comment.
Optional registry lookup blocks overview
When GHCR responds slowly or stalls, /api/node waits for latestOsaVersion() before returning completed local node data, causing the self-host overview to remain on its loading skeleton and the settings version row to stay absent. Bound this optional lookup or decouple it from the core response so registry availability can degrade independently.
Knowledge Base Used: Management Dashboard (apps/dashboard)
There was a problem hiding this comment.
Fixed in 5f50cb7 — the whole ghcr lookup now runs under a single 4s AbortSignal.timeout deadline (token fetch + every tags page), so a stalled registry resolves to null and /api/node returns the local node data without waiting.
| ) : newest ? ( | ||
| <Badge tone="info" withDot> | ||
| Update available → {newest.version} | ||
| </Badge> | ||
| ) : ( | ||
| <Badge tone="success">Up to date</Badge> | ||
| )} |
There was a problem hiding this comment.
Registry errors become up-to-date state
When the version-registry request fails, data remains undefined but this branch renders “Up to date,” disables upgrading as though the archive were already newest, and suppresses deprecated or withdrawn-pin warnings. Handle versions.isError separately so unavailable registry data is not presented as a successful availability check.
Knowledge Base Used: Management Dashboard (apps/dashboard)
There was a problem hiding this comment.
Fixed in 5f50cb7 — versions.isError now renders a neutral "Version check unavailable" badge (with a matching button tooltip) instead of "Up to date", so a down registry is never presented as a successful availability check. Regression test added.
| `https://api.github.com/repos/${REPO}/releases?per_page=50`, | ||
| { headers: { accept: "application/vnd.github+json" } }, | ||
| ); | ||
| if (!res.ok) throw new Error(`github releases: ${res.status}`); |
There was a problem hiding this comment.
Release history stops at page one
If an upgrade range starts before GitHub’s newest 50 releases, this single-page request silently omits older migration notes while the dialog treats the returned subset as complete. Follow GitHub’s pagination links or otherwise detect that the requested lower bound was not reached.
Knowledge Base Used: Management Dashboard (apps/dashboard)
There was a problem hiding this comment.
Fixed in 5f50cb7 — publishedReleases() now follows the GitHub Link header (rel="next", shared parser in server/link-header.ts, 10-page cap) so ranges reaching past the newest 50 releases keep their older notes. Pagination regression test added.
- Bound the ghcr latest-version lookup with a 4s AbortSignal deadline so a slow registry degrades to null instead of holding /api/node (and the self-host overview skeleton) hostage. - Distinguish registry errors from up-to-date in UpgradeSection: a failed versions query now shows 'Version check unavailable' instead of falsely claiming the pin is newest and suppressing deprecation warnings. - Paginate GitHub releases via the Link header (shared parser in server/link-header.ts, also adopted by ghcr.ts) so upgrade ranges reaching past the newest 50 releases keep their older notes. 4 new regression tests; suite 226 green.
Closes #222.
Builds the dashboard side of the upgrade contract shipped in cloud #95 (version registry +
POST /archives/{id}/upgrade), so archive owners upgrade from settings instead of a raw API call.Platform mode
Up to date/Update available → vX.Y.Z), disabled-with-reason while a deployment is in flight, a deprecated-pin nudge, and a withdrawn-pin alert (the cloud refuses redeploys of withdrawn versions — surfaced before the user hits that wall).deployment.started, so a failed upgrade shows "Retrying deploys {pin}" — the UI never implies rollback, matching the cloud's forward-only semantics./deploying/{id}— the existing DeployingScreen owns progress.invalid_statemaps to "A deployment is already in progress.";validation_errorshows the server's precise message verbatim.GET /api/releases: session-guarded BFF proxy for GitHub release notes (10-min cache; range(from, to]; drafts/prereleases skipped). On GitHub failure the dialog degrades to the registrynotes_urllink-out.Self-host mode
Awareness only, deliberately no button: compose shares one
OSA_IMAGE_VERSIONbetween server and dashboard, so the dashboard cannot upgrade the server without restarting itself. Instead, "Manage this node" gains a version row with a registry check (ghcr tags list riding/api/node, ~1h cache, silent degrade on failure) and — when behind — the exact copyable command:osa start --osa-version vX.Y.Z.Wire/service layer
osa_version_pinjoinswireArchive(always present per the cloud DTO); newwireOsaVersiondecoder with strict status narrowing;listOsaVersions+upgradeArchiveimplemented in both the real client and the mock (which seeds a supported/deprecated registry and reproduces the cloud's 400/422 error contract). Newui/Checkboxprimitive.Tests
15 new (7 UpgradeSection/dialog E2E, 3 SelfHostVersion, 5 releases route); full suite 222 green;
pnpm build+ lint clean.Out of scope
Toast system (none exists — the
/deployingredirect carries feedback), org-wide bulk upgrades, anosa upgradeCLI command (SDK repo follow-up), post-upgrade tenant health ping.