Skip to content

feat(dashboard): OSA version upgrade flow in archive settings - #223

Merged
rorybyrne merged 2 commits into
mainfrom
feat/222-upgrade-flow
Aug 16, 2026
Merged

feat(dashboard): OSA version upgrade flow in archive settings#223
rorybyrne merged 2 commits into
mainfrom
feat/222-upgrade-flow

Conversation

@rorybyrne

Copy link
Copy Markdown
Contributor

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

  • UpgradeSection (top of archive settings): current pin + availability chip (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).
  • Failure honesty: the pin moves in the same transaction as deployment.started, so a failed upgrade shows "Retrying deploys {pin}" — the UI never implies rollback, matching the cloud's forward-only semantics.
  • UpgradeDialog: from → to with a picker when several supported targets exist (registry default marked "recommended"), release notes rendered inline (stacked oldest-first for multi-version jumps, plus a GitHub compare link), a plain-language migration/pause warning, and a forward-only acknowledgment checkbox gating the confirm button.
  • Handoff: on 202 the mutation seeds the status cache from the returned deployment and routes to /deploying/{id} — the existing DeployingScreen owns progress. invalid_state maps to "A deployment is already in progress."; validation_error shows 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 registry notes_url link-out.

Self-host mode

Awareness only, deliberately no button: compose shares one OSA_IMAGE_VERSION between 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_pin joins wireArchive (always present per the cloud DTO); new wireOsaVersion decoder with strict status narrowing; listOsaVersions + upgradeArchive implemented in both the real client and the mock (which seeds a supported/deprecated registry and reproduces the cloud's 400/422 error contract). New ui/Checkbox primitive.

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 /deploying redirect carries feedback), org-wide bulk upgrades, an osa upgrade CLI command (SDK repo follow-up), post-upgrade tenant health ping.

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-apps

greptile-apps Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds platform archive-version upgrades and self-host update awareness, including registry decoding, release-note retrieval, upgrade handoff, and supporting UI primitives.

  • Adds the archive upgrade section, confirmation dialog, and deployment-status handoff.
  • Adds cached GHCR and GitHub release lookups for self-host awareness and migration notes.
  • Extends service, wire, domain, mock, and fixture contracts with OSA version data.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

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
Loading

Reviews (2): Last reviewed commit: "fix(dashboard): address Greptile review ..." | Re-trigger Greptile

Comment on lines +44 to 53
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(),
]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +50 to +56
) : newest ? (
<Badge tone="info" withDot>
Update available → {newest.version}
</Badge>
) : (
<Badge tone="success">Up to date</Badge>
)}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5f50cb7versions.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.

Comment thread apps/dashboard/src/server/releases.ts Outdated
Comment on lines +31 to +34
`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}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5f50cb7publishedReleases() 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.
@rorybyrne
rorybyrne merged commit 228b75e into main Aug 16, 2026
15 checks passed
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.

feat(dashboard): OSA version upgrade flow in archive settings

1 participant