orca/src/shared/protocol-compat.ts

110 lines
3.9 KiB
TypeScript

// Why: pure compat evaluators shared between desktop tests, renderer runtime
// switching, and the mobile mirror. All version numbers are passed in to keep
// the logic dependency-free and easy to duplicate in Expo.
export type RuntimeCompatVerdict =
| {
kind: 'ok'
clientProtocolVersion: number
serverProtocolVersion: number
}
| {
kind: 'blocked'
reason: 'client-too-old' | 'server-too-old'
clientProtocolVersion: number
serverProtocolVersion: number
requiredClientProtocolVersion?: number
requiredServerProtocolVersion?: number
}
export function evaluateRuntimeCompat(input: {
clientProtocolVersion: number
minCompatibleServerProtocolVersion: number
serverProtocolVersion: number | undefined
serverMinCompatibleClientProtocolVersion: number | undefined
}): RuntimeCompatVerdict {
// Why: absent fields are protocol 0. New clients can give old servers a
// clear "update server" error instead of attempting partially-supported RPCs.
const serverProtocolVersion = input.serverProtocolVersion ?? 0
const requiredClientProtocolVersion = input.serverMinCompatibleClientProtocolVersion ?? 0
if (input.clientProtocolVersion < requiredClientProtocolVersion) {
return {
kind: 'blocked',
reason: 'client-too-old',
clientProtocolVersion: input.clientProtocolVersion,
serverProtocolVersion,
requiredClientProtocolVersion
}
}
if (serverProtocolVersion < input.minCompatibleServerProtocolVersion) {
return {
kind: 'blocked',
reason: 'server-too-old',
clientProtocolVersion: input.clientProtocolVersion,
serverProtocolVersion,
requiredServerProtocolVersion: input.minCompatibleServerProtocolVersion
}
}
return {
kind: 'ok',
clientProtocolVersion: input.clientProtocolVersion,
serverProtocolVersion
}
}
export function describeRuntimeCompatBlock(verdict: RuntimeCompatVerdict): string {
if (verdict.kind === 'ok') {
return 'Runtime client and server are compatible.'
}
if (verdict.reason === 'client-too-old') {
return `This Orca client is too old for the selected server. Update Orca on this machine. Client protocol ${verdict.clientProtocolVersion}, server requires client protocol ${verdict.requiredClientProtocolVersion}.`
}
return `The selected Orca server is too old for this client. Update Orca on the server. Server protocol ${verdict.serverProtocolVersion}, client requires server protocol ${verdict.requiredServerProtocolVersion}.`
}
export type CompatVerdict =
| { kind: 'ok' }
| {
kind: 'blocked'
reason: 'mobile-too-old' | 'desktop-too-old'
desktopVersion: number
requiredMobileVersion?: number
requiredDesktopVersion?: number
}
export function evaluateCompat(input: {
mobileProtocolVersion: number
minCompatibleDesktopVersion: number
desktopProtocolVersion: number | undefined
desktopMinCompatibleMobileVersion: number | undefined
}): CompatVerdict {
// Why: absent fields → 0 lets mobile keep talking to pre-PR desktops.
// Bumping minCompatibleDesktopVersion above 0 will fence those older
// desktops alongside any explicitly-version-0 desktop, which is the
// intended kill-switch behavior.
const desktopVersion = input.desktopProtocolVersion ?? 0
const requiredMobile = input.desktopMinCompatibleMobileVersion ?? 0
// Why: mobile-too-old precedence — if desktop says "I refuse this
// mobile build" (kill switch), that wins over any local mobile
// judgment about desktop's age.
if (input.mobileProtocolVersion < requiredMobile) {
return {
kind: 'blocked',
reason: 'mobile-too-old',
desktopVersion,
requiredMobileVersion: requiredMobile
}
}
if (desktopVersion < input.minCompatibleDesktopVersion) {
return {
kind: 'blocked',
reason: 'desktop-too-old',
desktopVersion,
requiredDesktopVersion: input.minCompatibleDesktopVersion
}
}
return { kind: 'ok' }
}