mirror of
https://github.com/stablyai/orca.git
synced 2026-10-08 08:02:32 +00:00
chore(mobile): App Store prep — privacy manifest, debug-log cleanup, and protocol-version compat block (#1440)
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { evaluateCompat } from './protocol-compat'
|
||||
|
||||
const MOBILE_V = 1
|
||||
|
||||
describe('evaluateCompat', () => {
|
||||
it('returns ok when both desktop fields are undefined and constants are wide-open', () => {
|
||||
const verdict = evaluateCompat({
|
||||
mobileProtocolVersion: MOBILE_V,
|
||||
minCompatibleDesktopVersion: 0,
|
||||
desktopProtocolVersion: undefined,
|
||||
desktopMinCompatibleMobileVersion: undefined
|
||||
})
|
||||
expect(verdict).toEqual({ kind: 'ok' })
|
||||
})
|
||||
|
||||
it('returns ok when desktop reports version equal to mobile', () => {
|
||||
const verdict = evaluateCompat({
|
||||
mobileProtocolVersion: MOBILE_V,
|
||||
minCompatibleDesktopVersion: 0,
|
||||
desktopProtocolVersion: MOBILE_V,
|
||||
desktopMinCompatibleMobileVersion: 0
|
||||
})
|
||||
expect(verdict).toEqual({ kind: 'ok' })
|
||||
})
|
||||
|
||||
it('returns ok when desktop reports a newer version (additive changes assumed safe)', () => {
|
||||
const verdict = evaluateCompat({
|
||||
mobileProtocolVersion: MOBILE_V,
|
||||
minCompatibleDesktopVersion: 0,
|
||||
desktopProtocolVersion: MOBILE_V + 5,
|
||||
desktopMinCompatibleMobileVersion: 0
|
||||
})
|
||||
expect(verdict).toEqual({ kind: 'ok' })
|
||||
})
|
||||
|
||||
it('blocks with mobile-too-old when desktop requires a newer mobile', () => {
|
||||
const verdict = evaluateCompat({
|
||||
mobileProtocolVersion: MOBILE_V,
|
||||
minCompatibleDesktopVersion: 0,
|
||||
desktopProtocolVersion: 5,
|
||||
desktopMinCompatibleMobileVersion: MOBILE_V + 1
|
||||
})
|
||||
expect(verdict).toEqual({
|
||||
kind: 'blocked',
|
||||
reason: 'mobile-too-old',
|
||||
desktopVersion: 5,
|
||||
requiredMobileVersion: MOBILE_V + 1
|
||||
})
|
||||
})
|
||||
|
||||
it('coerces undefined desktopVersion to 0 in the verdict payload', () => {
|
||||
const verdict = evaluateCompat({
|
||||
mobileProtocolVersion: MOBILE_V,
|
||||
minCompatibleDesktopVersion: 0,
|
||||
desktopProtocolVersion: undefined,
|
||||
desktopMinCompatibleMobileVersion: MOBILE_V + 1
|
||||
})
|
||||
expect(verdict).toMatchObject({
|
||||
kind: 'blocked',
|
||||
reason: 'mobile-too-old',
|
||||
desktopVersion: 0
|
||||
})
|
||||
})
|
||||
|
||||
it('blocks with desktop-too-old when desktop reports below the local minimum', () => {
|
||||
const verdict = evaluateCompat({
|
||||
mobileProtocolVersion: MOBILE_V,
|
||||
minCompatibleDesktopVersion: 5,
|
||||
desktopProtocolVersion: 3,
|
||||
desktopMinCompatibleMobileVersion: 0
|
||||
})
|
||||
expect(verdict).toEqual({
|
||||
kind: 'blocked',
|
||||
reason: 'desktop-too-old',
|
||||
desktopVersion: 3,
|
||||
requiredDesktopVersion: 5
|
||||
})
|
||||
})
|
||||
|
||||
it('mobile-too-old wins precedence when both constraints would fire', () => {
|
||||
// Why: documents the intended kill-switch precedence — desktop's
|
||||
// refusal of a too-old mobile takes priority over mobile's local
|
||||
// refusal of a too-old desktop.
|
||||
const verdict = evaluateCompat({
|
||||
mobileProtocolVersion: MOBILE_V,
|
||||
minCompatibleDesktopVersion: 99,
|
||||
desktopProtocolVersion: -1,
|
||||
desktopMinCompatibleMobileVersion: MOBILE_V + 1
|
||||
})
|
||||
expect(verdict.kind).toBe('blocked')
|
||||
expect((verdict as { reason: string }).reason).toBe('mobile-too-old')
|
||||
})
|
||||
|
||||
it('with minCompatibleDesktopVersion = 0 every reported desktop passes', () => {
|
||||
for (const v of [0, 1, 2, 99]) {
|
||||
expect(
|
||||
evaluateCompat({
|
||||
mobileProtocolVersion: MOBILE_V,
|
||||
minCompatibleDesktopVersion: 0,
|
||||
desktopProtocolVersion: v,
|
||||
desktopMinCompatibleMobileVersion: 0
|
||||
})
|
||||
).toEqual({ kind: 'ok' })
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,50 @@
|
||||
// Why: pure compat evaluator shared between desktop tests and mobile
|
||||
// runtime. Mobile imports a thin wrapper (`mobile/src/transport/protocol-compat.ts`)
|
||||
// that injects the mobile-side constants; desktop tests import this
|
||||
// directly so the function is covered by the root vitest suite.
|
||||
// All four numbers are passed in to keep the function dependency-free.
|
||||
|
||||
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' }
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Why: declares the desktop's mobile-pairing protocol version so mobile
|
||||
// builds can detect declared-incompatible combos and hard-block at pair
|
||||
// time. Today's values are wide-open (mobile=any, desktop=any), so
|
||||
// nothing actually blocks; the wire format is ready for the day we
|
||||
// ship a genuinely-breaking change.
|
||||
//
|
||||
// Bump DESKTOP_PROTOCOL_VERSION when:
|
||||
// - You remove an RPC method or required parameter that mobile uses.
|
||||
// - You change the meaning (units, nullability) of an existing field
|
||||
// mobile reads.
|
||||
// - You change encryption, framing, or the auth handshake.
|
||||
// Do NOT bump for:
|
||||
// - Adding new RPC methods.
|
||||
// - Adding new optional fields on existing methods.
|
||||
// - Adding new event types in `terminal.subscribe`.
|
||||
//
|
||||
// Bump MIN_COMPATIBLE_MOBILE_VERSION when desktop ships a change that
|
||||
// requires a minimum mobile version to function safely. This is the
|
||||
// "kill switch": desktop can refuse old mobile builds without needing
|
||||
// a desktop release of mobile.
|
||||
|
||||
export const DESKTOP_PROTOCOL_VERSION = 1
|
||||
export const MIN_COMPATIBLE_MOBILE_VERSION = 0
|
||||
@@ -11,6 +11,11 @@ export type RuntimeStatus = {
|
||||
authoritativeWindowId: number | null
|
||||
liveTabCount: number
|
||||
liveLeafCount: number
|
||||
// Why: optional so mobile builds can read both new and pre-PR desktops.
|
||||
// Absence is treated as 0 by mobile's compat evaluator. See
|
||||
// src/shared/protocol-version.ts for bump discipline.
|
||||
protocolVersion?: number
|
||||
minCompatibleMobileVersion?: number
|
||||
}
|
||||
|
||||
export type CliRuntimeState =
|
||||
|
||||
Reference in New Issue
Block a user