mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 08:03:22 +00:00
docs: add remote server update advisor design doc
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -0,0 +1,486 @@
|
||||
# Remote Server Update Advisor
|
||||
|
||||
## Decision
|
||||
|
||||
Build a remote server update advisor before building any remote self-update
|
||||
executor.
|
||||
|
||||
When the protocol compatibility gate blocks a paired Orca server, Orca should
|
||||
tell the user what is wrong, identify the server version and likely install
|
||||
shape when it can, and provide copyable platform-specific update guidance. The
|
||||
client must not download, install, restart, or run privileged commands on the
|
||||
remote machine in this design — not even over an SSH connection Orca already
|
||||
holds.
|
||||
|
||||
The advisor covers **both block directions** reported by
|
||||
`evaluateRuntimeCompat` (`src/shared/protocol-compat.ts`):
|
||||
|
||||
- `server-too-old`: show the remote update guidance described in this doc.
|
||||
- `client-too-old`: do not show server guidance. Route to the local update
|
||||
flow — the desktop updater (`src/main/updater.ts`) on desktop, the app-store
|
||||
update message on mobile.
|
||||
|
||||
The first implementation adds optional server-side metadata now, but treats it
|
||||
as advisory and untrusted:
|
||||
|
||||
```ts
|
||||
type RuntimeUpdateInfo = {
|
||||
currentVersion?: string | null
|
||||
latestVersion?: string | null
|
||||
updateAvailable?: boolean | null
|
||||
installKind?: RuntimeInstallKind
|
||||
restartKind?: RuntimeRestartKind
|
||||
serviceName?: string | null
|
||||
installPath?: string | null
|
||||
hostArch?: string | null // process.arch, e.g. 'x64' | 'arm64'
|
||||
docsUrl?: string
|
||||
}
|
||||
|
||||
type RuntimeInstallKind =
|
||||
| 'mac-app'
|
||||
| 'mac-homebrew'
|
||||
| 'windows-installer'
|
||||
| 'linux-appimage'
|
||||
| 'linux-deb'
|
||||
| 'linux-rpm'
|
||||
| 'source'
|
||||
| 'unknown'
|
||||
|
||||
type RuntimeRestartKind = 'desktop' | 'foreground-serve' | 'systemd' | 'unknown'
|
||||
```
|
||||
|
||||
The protocol compatibility gate remains the sole source of truth for whether a
|
||||
client can use the server. `latestVersion` and `updateAvailable` improve the
|
||||
prompt, but the app must render useful guidance when every field is absent.
|
||||
|
||||
## Problem
|
||||
|
||||
Remote Orca servers can be desktop apps, headless Linux AppImages, systemd
|
||||
services, Windows machines, deb/rpm package installs, or source builds. A
|
||||
client that only says "update the server" leaves users stranded at the exact
|
||||
moment the app is blocked. A client that attempts to update the server
|
||||
automatically risks running the wrong package manager, requiring `sudo`,
|
||||
breaking a service, or mutating a machine the user did not intend to touch.
|
||||
|
||||
The right first step is to make the blocked state actionable without taking
|
||||
control of the remote host.
|
||||
|
||||
### The cold-start constraint
|
||||
|
||||
`updateInfo` only exists on servers new enough to ship it — and the servers
|
||||
being blocked are, by definition, old. The first generation of blocked servers
|
||||
will send **no** `updateInfo` at all. The advisor must therefore be fully
|
||||
functional from client-side data alone: the compat verdict, the already-shipped
|
||||
`hostPlatform` field on `RuntimeStatus` (`src/shared/runtime-types.ts` — itself
|
||||
optional; servers older than it land in the unknown-install guide), the
|
||||
paired endpoint's port, and client-side release metadata. `updateInfo` is an
|
||||
investment that pays off the *next* time a server falls behind. This also
|
||||
means steps 1–2 of the rollout should reach a release as early as possible,
|
||||
independent of the UI.
|
||||
|
||||
## Goals
|
||||
|
||||
- Show a clear "Update server required" prompt when protocol compatibility
|
||||
blocks a paired server, reusing the existing entry points
|
||||
(`HostSectionHeaderMenu.tsx`, `RuntimeEnvironmentsPane.tsx`).
|
||||
- Handle `client-too-old` by routing to the local update flow instead.
|
||||
- Show running and required protocol versions from the compat verdict.
|
||||
- Add optional update metadata to runtime status so future blocked states can
|
||||
display server version, latest known version, install kind, and restart kind.
|
||||
- Provide copyable update commands or manual steps for the detected
|
||||
platform/arch/install kind, and a useful fallback when unknown.
|
||||
- Support the SSH use case: the guidance is written to be pasted into the
|
||||
user's own SSH session on the server. Orca does not run it.
|
||||
- Never block the compat gate or the status path on a network call.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Do not run update commands on the remote server, via any transport —
|
||||
including SSH connections Orca already holds for SSH workspaces.
|
||||
- Do not invoke `sudo`, package managers, installers, or service restarts.
|
||||
- Do not silently replace AppImages or desktop apps.
|
||||
- Do not require exact app-version equality between client and server
|
||||
(protocol versions decide compatibility; app versions are display/advice).
|
||||
- Do not build a new stateful update service for the MVP.
|
||||
- Do not cover the SSH relay (`src/main/ssh/ssh-relay-deploy.ts`). The relay
|
||||
is client-deployed into content-hash-versioned install dirs and self-heals on
|
||||
version mismatch; it never needs this advisor. This doc is about paired
|
||||
`orca serve` runtime servers.
|
||||
|
||||
## Design
|
||||
|
||||
### Trust model
|
||||
|
||||
Every `updateInfo` field crosses a trust boundary: it is produced by a remote,
|
||||
possibly compromised, server and some of it is rendered into commands the user
|
||||
is invited to paste into a root shell. Rules:
|
||||
|
||||
- Command blocks are rendered **only from client-owned templates**. Server
|
||||
data may select a template or fill a validated placeholder; it is never
|
||||
concatenated into a command as free text.
|
||||
- `serviceName`: must match `^orca[A-Za-z0-9@:._-]{0,75}\.service$` — the
|
||||
mandatory `orca` prefix mirrors the trust rule cgroup-based systemd
|
||||
detection already applies ("trusted only when the unit name starts with
|
||||
`orca`"), so a compromised server cannot aim the rendered
|
||||
`sudo systemctl restart` at an unrelated unit such as `sshd.service`, and
|
||||
the leading alphanumeric means the value can never parse as a `systemctl`
|
||||
flag. Otherwise it is discarded and the default `orca-serve.service` is
|
||||
used.
|
||||
- `installPath`: must be absolute, end in `.AppImage`, and match
|
||||
`^[A-Za-z0-9/._~+-]{1,256}$` (no spaces, quotes, or shell metacharacters) or
|
||||
it is discarded and the documented default `/opt/orca/orca-linux.AppImage`
|
||||
is used. The `.AppImage` suffix is load-bearing: the template `sudo mv`s
|
||||
onto this path, so without it a malicious server could aim the overwrite at
|
||||
an arbitrary root-owned file. Only the Linux AppImage template renders
|
||||
`<install-path>`; typical Windows paths (drive letters, backslashes) fail
|
||||
the regex anyway, and the Windows and macOS guides never embed a path in a
|
||||
command.
|
||||
- `currentVersion` / `latestVersion`: must match
|
||||
`^\d+\.\d+\.\d+(-[A-Za-z0-9.-]{1,40})?$` or they are not displayed.
|
||||
- `docsUrl`: parsed with `new URL()` and rendered only if the origin is
|
||||
`https://github.com` and the **parsed** pathname is `/stablyai/orca` or
|
||||
starts with `/stablyai/orca/`; otherwise the client's built-in links are
|
||||
used. Checking the parsed pathname defeats `…/orca/../other-repo`, which a
|
||||
raw string-prefix check passes and browsers normalize to another repo; the
|
||||
trailing slash rules out sibling names like `stablyai/orca-foo`.
|
||||
- Port: taken from the client's own paired endpoint (an integer the client
|
||||
already validated), never from server metadata. Behind a tunnel or proxy it
|
||||
can differ from the server's listen port, so guides treat it as a hint.
|
||||
- Enum fields (`installKind`, `restartKind`): unrecognized values are treated
|
||||
as `unknown`. `hostArch` is a free-form `process.arch` string: anything
|
||||
other than a recognized arch (`x64`, `arm64`) is treated as absent.
|
||||
|
||||
Values that fail validation are treated as absent — no error, no partial
|
||||
rendering.
|
||||
|
||||
### Runtime status metadata
|
||||
|
||||
Add optional `updateInfo` to `RuntimeStatus`, alongside the existing
|
||||
`runtimeProtocolVersion` / `capabilities` / `hostPlatform` fields, emitted from
|
||||
the same place status is stamped today (`src/main/runtime/orca-runtime.ts`).
|
||||
Older servers omit it; all client handling must be defensive.
|
||||
|
||||
Detection is computed **once at server startup and cached** — never on the
|
||||
status hot path, which is polled. No package-manager subprocess may run per
|
||||
status request. Signals, in order of reliability:
|
||||
|
||||
- `source`: `app.isPackaged` is false.
|
||||
- `linux-appimage`: `process.env.APPIMAGE` is set (the AppImage runtime sets
|
||||
it); its value is `installPath`.
|
||||
- `windows-installer`: win32 and `process.execPath` under the NSIS install
|
||||
location.
|
||||
- `mac-app`: darwin and `process.execPath` inside a `.app` bundle. Homebrew
|
||||
cask installs copy the app into `/Applications`, so `mac-homebrew` is not
|
||||
reliably distinguishable at runtime; when ambiguous, report `mac-app` (the
|
||||
mac guide includes a Homebrew alternative). `mac-homebrew` stays in the enum
|
||||
for explicit future configuration.
|
||||
- `linux-deb` / `linux-rpm`: non-AppImage packaged Linux install (execPath
|
||||
under the package install dir); the deb-vs-rpm distinction may use a single
|
||||
package-ownership check at startup. If the check is skipped or fails, report
|
||||
`unknown`.
|
||||
- `restartKind: systemd`: `/proc/self/cgroup` names a `.service` unit under
|
||||
`/system.slice/` — which also yields `serviceName`. A user-slice `.service`
|
||||
cgroup is trusted only when the unit name starts with `orca`: children share
|
||||
their parent's cgroup, so a manual `orca serve` inside a terminal or
|
||||
multiplexer that itself runs as a user service (e.g.
|
||||
`gnome-terminal-server.service`, a tmux user unit) would otherwise
|
||||
false-positive. `INVOCATION_ID` alone is not trusted for the same
|
||||
inheritance reason.
|
||||
- `restartKind: desktop` when serving from the full desktop app;
|
||||
`foreground-serve` for `orca serve` with no systemd markers.
|
||||
|
||||
Detection must prefer `unknown` over guessing, and prefer the broader kind
|
||||
over an unverified narrower one.
|
||||
|
||||
`hostArch` is `process.arch`, needed because Linux assets differ by arch
|
||||
(`orca-linux.AppImage` vs `orca-linux-arm64.AppImage`). When absent (old
|
||||
server), the guide shows the x64 command with an arm64 note rather than
|
||||
guessing.
|
||||
|
||||
### Client prompt surfaces
|
||||
|
||||
Use the same advisor content everywhere a blocked remote server surfaces:
|
||||
|
||||
- Runtime environment settings row (`RuntimeEnvironmentsPane.tsx`): the
|
||||
strongest detail view — versions, detected install/restart kind, copyable
|
||||
commands, docs links, and "Check again" (re-fetches status and re-evaluates
|
||||
compatibility). "Check again" disables with a spinner while the recheck is
|
||||
in flight; on success the blocked state clears back to the normal server
|
||||
view, and on continued incompatibility an inline note confirms the server
|
||||
is still out of date — never a click with no visible response.
|
||||
- Sidebar host menu (`HostSectionHeaderMenu.tsx`): keep the existing
|
||||
"Update server required" action; deep-link to the settings row.
|
||||
- Blocked action dialog or toast: the compat gate already throws a
|
||||
`RUNTIME_COMPAT_BLOCK_CODE`-tagged error
|
||||
(`src/renderer/src/runtime/runtime-protocol-compat.ts`), so surfaces can
|
||||
distinguish a version block from a transport failure and offer the same
|
||||
deep-link.
|
||||
|
||||
The advisor needs the blocked server's `RuntimeStatus` (`hostPlatform`,
|
||||
`updateInfo`) and the compat verdict even though the gate throws. Today the
|
||||
`RUNTIME_COMPAT_BLOCK_CODE` error carries only a message and code, discarding
|
||||
both: attach the verdict and `RuntimeStatus` to that error (e.g.
|
||||
`error.verdict`, `error.status`), and have "Check again" read status through
|
||||
the raw non-asserting RPC (`status.get` + `unwrapRuntimeRpcResult`) rather
|
||||
than `getRuntimeEnvironmentStatus`, so detection and versions can render
|
||||
while the environment stays blocked.
|
||||
|
||||
Primary copy for `server-too-old`:
|
||||
|
||||
> This Orca server needs an update before this client can use it.
|
||||
|
||||
For `client-too-old`, keep the existing `describeRuntimeCompatBlock` message
|
||||
and offer the local updater action instead of any server guidance.
|
||||
|
||||
When detection is available, include a line such as:
|
||||
|
||||
> Detected: Linux AppImage, systemd service.
|
||||
|
||||
The line comma-joins only the fields that resolved — e.g. "Detected: Linux
|
||||
AppImage" when `restartKind` is `unknown` — and is omitted entirely when both
|
||||
are unknown.
|
||||
|
||||
Command blocks are copyable one block at a time. A command block is a
|
||||
suggestion for the user to run on the server (typically over their own SSH
|
||||
session), never a command Orca executes.
|
||||
|
||||
The mobile client duplicates the compat evaluators (see the header comment in
|
||||
`protocol-compat.ts`); the guide matrix and copy should live in shared,
|
||||
dependency-free modules so mobile can mirror them.
|
||||
|
||||
### Update guide matrix
|
||||
|
||||
The client owns a guide matrix keyed by `hostPlatform`, `hostArch`,
|
||||
`installKind`, and `restartKind`. It ships with the client and needs no server
|
||||
deploy to change. Placeholders below (`<install-path>`, `<service-name>`,
|
||||
`<port>`) are filled from validated metadata or documented defaults
|
||||
(`/opt/orca/orca-linux.AppImage`, `orca-serve.service`, `6768` — matching
|
||||
`docs/reference/headless-linux-server.md`).
|
||||
|
||||
All Linux download commands write to a sibling temp file and `mv` into place.
|
||||
Overwriting a running binary in place fails with `ETXTBSY`; `mv` within the
|
||||
same directory is an atomic rename that works while the old version runs. Do
|
||||
not stage in `/tmp` — a cross-filesystem `mv` degrades to a non-atomic copy
|
||||
into the destination path, which reintroduces the same failure.
|
||||
|
||||
Linux AppImage (x64; use `orca-linux-arm64.AppImage` on arm64), download and
|
||||
swap:
|
||||
|
||||
```bash
|
||||
sudo curl -fL https://github.com/stablyai/orca/releases/latest/download/orca-linux.AppImage \
|
||||
-o <install-path>.new
|
||||
sudo chmod +x <install-path>.new
|
||||
sudo mv <install-path>.new <install-path>
|
||||
```
|
||||
|
||||
Then restart:
|
||||
|
||||
- systemd service: `sudo systemctl restart <service-name>`
|
||||
- foreground server: stop the running `orca serve` (Ctrl+C) and re-run your
|
||||
usual serve command, e.g.
|
||||
`LIBGL_ALWAYS_SOFTWARE=1 <install-path> serve --port <port>` plus any flags
|
||||
you normally pass (such as `--pairing-address`). The advisor cannot know the
|
||||
original command line, so it must not present an invented one as complete.
|
||||
|
||||
Debian or Ubuntu package (assets are versioned: `orca-ide_<version>_amd64.deb`,
|
||||
or `_arm64.deb` on arm64; when release metadata supplies the exact asset URL
|
||||
the advisor prepends a matching `curl -fLO <asset-url>` line — here and for
|
||||
rpm — otherwise it links the releases page and the command uses the downloaded
|
||||
filename):
|
||||
|
||||
```bash
|
||||
curl -fLO <asset-url> # included when release metadata supplies the exact asset URL
|
||||
sudo apt install ./orca-ide_<version>_amd64.deb
|
||||
sudo systemctl restart <service-name> # if running as a service
|
||||
```
|
||||
|
||||
Fedora, RHEL, or compatible RPM package (`orca-ide-<version>.x86_64.rpm` /
|
||||
`.aarch64.rpm`):
|
||||
|
||||
```bash
|
||||
curl -fLO <asset-url> # included when release metadata supplies the exact asset URL
|
||||
sudo dnf install ./orca-ide-<version>.x86_64.rpm
|
||||
sudo systemctl restart <service-name> # if running as a service
|
||||
```
|
||||
|
||||
macOS desktop app:
|
||||
|
||||
- Open Orca on that Mac and use the in-app update flow, or download the latest
|
||||
`orca-macos-<arch>.dmg` from the releases page and replace the app.
|
||||
- If it was installed with Homebrew: `brew update && brew upgrade --cask
|
||||
stablyai/orca/orca --greedy` (the cask is marked `auto_updates`, so plain
|
||||
`brew upgrade` skips it; `--greedy` is required).
|
||||
- Restart the paired server after the app updates.
|
||||
|
||||
Windows installer:
|
||||
|
||||
- Open Orca on that Windows machine and use the in-app update flow, or
|
||||
download and run
|
||||
`https://github.com/stablyai/orca/releases/latest/download/orca-windows-setup.exe`.
|
||||
- Restart Orca (or the `orca serve` process) after installation.
|
||||
|
||||
Source build: pull and rebuild; link the repo README.
|
||||
|
||||
Unknown install:
|
||||
|
||||
- Show the server platform and version if known.
|
||||
- Link `docs/reference/headless-linux-server.md` (on GitHub) and the releases
|
||||
page.
|
||||
- Tell the user to update Orca on the server machine, restart the server, and
|
||||
click "Check again".
|
||||
|
||||
Exact deb/rpm filenames are versioned and must come from release metadata. If
|
||||
the client does not know the exact asset URL, it shows manual download
|
||||
guidance — it never invents a versioned filename. The unversioned
|
||||
`releases/latest/download/` URLs are stable only for the artifacts with
|
||||
unversioned names in `config/electron-builder.config.cjs`:
|
||||
`orca-linux.AppImage`, `orca-linux-arm64.AppImage`, `orca-macos-<arch>.dmg`,
|
||||
and `orca-windows-setup.exe`.
|
||||
|
||||
### Release metadata
|
||||
|
||||
No new update service. The desktop updater already publishes electron-updater
|
||||
manifests at a stable generic feed
|
||||
(`https://github.com/stablyai/orca/releases/latest/download` — see
|
||||
`src/main/updater.ts`): `latest-linux.yml`, `latest-mac.yml`, and `latest.yml`
|
||||
carry the latest version and asset filenames (the arm64 Linux release
|
||||
publishes its own `latest-linux-arm64.yml` variant). The advisor picks the
|
||||
manifest matching the *server's* platform/arch — not the client's — and reuses
|
||||
it for `latestVersion` / `updateAvailable` / exact asset names.
|
||||
|
||||
Constraints, all inherited from known updater behavior:
|
||||
|
||||
- The fetch is best-effort with a short timeout, done lazily when the advisor
|
||||
is shown — never on the compat gate or status path. Failure means
|
||||
`updateAvailable: null` and version-less guidance, not an error.
|
||||
- During release transitions, GitHub can expose a release before its assets
|
||||
are reachable (see the walk-back logic in `updater.ts`). A fetched manifest
|
||||
that names unreachable assets must degrade to the manual releases-page link,
|
||||
not a broken copyable URL.
|
||||
- Results may be cached briefly on the client; staleness only affects advice
|
||||
text, never compatibility.
|
||||
- If a server ever populates `latestVersion` / `updateAvailable` itself (e.g.
|
||||
from its own updater check at startup), the client-fetched manifest wins:
|
||||
server values are startup-stale hints and are never refreshed on the status
|
||||
path.
|
||||
|
||||
A stateful update service is only needed later for staged rollouts, kill
|
||||
switches, signed org policy, private channels, or fleet controls.
|
||||
|
||||
## Rollout
|
||||
|
||||
Ordered so each piece is independently safe; one PR works, but steps 1–2 may
|
||||
land earlier on their own (see the cold-start constraint):
|
||||
|
||||
1. `RuntimeUpdateInfo` type in `src/shared/runtime-types.ts`; startup-cached
|
||||
detection in main; emit `updateInfo` in runtime status
|
||||
(`src/main/runtime/orca-runtime.ts`).
|
||||
2. Validation module for server-supplied fields (shared, dependency-free, unit
|
||||
tested against injection inputs).
|
||||
3. Client-side guide matrix keyed by platform/arch/installKind/restartKind
|
||||
with the fallback chain above (shared module for mobile reuse).
|
||||
4. Advisor UI in `RuntimeEnvironmentsPane.tsx` with both verdict directions:
|
||||
`server-too-old` → guidance; `client-too-old` → local updater action.
|
||||
"Check again" re-fetches status.
|
||||
5. Deep-links from `HostSectionHeaderMenu.tsx` ("Update server required") and
|
||||
from `RUNTIME_COMPAT_BLOCK_CODE` error surfaces.
|
||||
6. Lazy release-metadata lookup (`latest-*.yml`) for `latestVersion`,
|
||||
`updateAvailable`, and exact deb/rpm asset names.
|
||||
|
||||
## Testing
|
||||
|
||||
- Unit: detection mapping per platform fixture (APPIMAGE env, systemd cgroup
|
||||
line — including a user-slice terminal-service cgroup that must not map to
|
||||
`systemd`, `app.isPackaged`); every validation regex rejects
|
||||
shell-metacharacter payloads (`; curl … | sh`, backticks, `$()`, newlines) and accepts the
|
||||
documented defaults.
|
||||
- Unit: guide matrix snapshot per (platform, arch, installKind, restartKind)
|
||||
key plus the all-fields-absent cold-start case — the doc's own commands are
|
||||
the expected output.
|
||||
- Unit: verdict branching — `client-too-old` never renders server commands.
|
||||
- Extend `runtime-compatibility-test-fixture.ts` (today it only builds
|
||||
compatible-status responses) with blocked-status fixtures with and without
|
||||
`updateInfo`.
|
||||
- Manifest fetch: timeout and 404 paths yield `updateAvailable: null` and the
|
||||
fallback guide; no path throws into the compat gate.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
### Full remote self-update
|
||||
|
||||
The client could download an asset on the remote machine, install it, and
|
||||
restart the server — including over an SSH connection Orca already holds for
|
||||
SSH workspaces.
|
||||
|
||||
Rejected for the first version. Remote hosts vary too much: AppImages, systemd
|
||||
services, desktop apps, Windows installers, package managers, and source
|
||||
builds all have different permission and restart models. Running install
|
||||
commands remotely — even over Orca-owned SSH — creates a trust and audit
|
||||
problem, especially when `sudo` or service restarts are involved. It can be
|
||||
revisited as an explicit, capability-gated flow that asks for confirmation and
|
||||
shows exactly what will run.
|
||||
|
||||
### Protocol-only error message
|
||||
|
||||
Keep the current compatibility block and only say the server is too old.
|
||||
|
||||
Rejected. Technically safe but not user-safe: the user still has to infer
|
||||
which machine is stale, how Orca was installed there, which asset to download,
|
||||
and whether a service restart is needed.
|
||||
|
||||
### Client-only latest-version check
|
||||
|
||||
Compare the remote protocol against the client's own app version or update
|
||||
feed, with no server metadata.
|
||||
|
||||
Rejected as the only mechanism. The client's platform and install channel need
|
||||
not match the server's — a macOS client may pair with a Linux AppImage server.
|
||||
Client-side release data feeds the guides, but cannot describe the server's
|
||||
install shape.
|
||||
|
||||
### Require a new Orca update server
|
||||
|
||||
Rejected for the MVP. The advisor needs best-effort latest-version and asset
|
||||
metadata, which the existing electron-updater manifests already provide.
|
||||
|
||||
### Exact version lockstep
|
||||
|
||||
Require the server to run the exact client app version.
|
||||
|
||||
Rejected. The protocol contract in `protocol-compat.ts` already defines
|
||||
compatibility, and exact equality would fail compatible pairs. App versions
|
||||
are for display and advice only.
|
||||
|
||||
## Deferred / Open Questions
|
||||
|
||||
### From 2026-07-06 review
|
||||
|
||||
- **Blocked surface left undecided between dialog and toast** — Client prompt surfaces (P1, design-lens, confidence 75)
|
||||
|
||||
Dialog and toast are not interchangeable: a dialog is modal, traps focus,
|
||||
and requires explicit dismissal, while a toast is transient, non-blocking,
|
||||
and typically announced as a polite live region. The doc says "Blocked
|
||||
action dialog or toast" without choosing, leaving the implementer to pick
|
||||
focus-management and dismissal behavior on their own, risking inconsistent
|
||||
UX if this surface and future blocked-action surfaces land differently.
|
||||
|
||||
- **Advisor assumes the blocked user can run sudo on the server** — Goals / Client prompt surfaces (P2, product-lens, adversarial, confidence 75)
|
||||
|
||||
Every actionable path the advisor produces is a sudo command pasted into
|
||||
the user's own SSH session, which assumes the blocked user is also the
|
||||
server's administrator. On shared or team-administered servers the
|
||||
copyable commands are un-runnable and the block stays exactly as
|
||||
unactionable as the plain "update the server" message. A copyable "send
|
||||
these steps to whoever administers this server" handoff would keep the
|
||||
goal met for non-admin users.
|
||||
|
||||
- **Server-metadata pipeline is sequenced first but delivers no value to currently-blocked servers** — Rollout / cold-start constraint (P2, product-lens, adversarial, confidence 75)
|
||||
|
||||
By the doc's own logic, rollout steps 1–2 (updateInfo schema, cached
|
||||
detection, trust model) help no server blocked today — they pay off only
|
||||
the next time a server falls behind, and that window's population is
|
||||
unvalidated. The client-only advisor (steps 3–5) already delivers the full
|
||||
user-facing unblock; consider shipping it as v1 and treating steps 1–2 and
|
||||
6 as a fast-follow once advisor engagement is confirmed, or naming the
|
||||
expected frequency of the payoff window to justify front-loading a
|
||||
permanent wire contract.
|
||||
Reference in New Issue
Block a user