mirror of
https://github.com/stablyai/orca.git
synced 2026-09-28 16:02:45 +00:00
fix(runtime): show blocked hosts honestly and remove obsolete status options
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import type { PairingOffer } from '../../shared/pairing'
|
||||
import {
|
||||
advanceRuntimeEnvironmentCapabilityIncarnation,
|
||||
@@ -17,7 +17,6 @@ describe('runtime environment capability evidence', () => {
|
||||
it('accepts evidence by dispatch order instead of completion order', () => {
|
||||
const older = captureRuntimeEnvironmentCapabilityEvidence('env', pairing())
|
||||
const newer = captureRuntimeEnvironmentCapabilityEvidence('env', pairing())
|
||||
const pause = vi.fn()
|
||||
|
||||
expect(
|
||||
applyRuntimeEnvironmentCapabilityVerdict({
|
||||
@@ -30,12 +29,10 @@ describe('runtime environment capability evidence', () => {
|
||||
applyRuntimeEnvironmentCapabilityVerdict({
|
||||
evidence: older,
|
||||
verdict: 'absent',
|
||||
runtimeId: 'runtime-old',
|
||||
onAbsent: pause
|
||||
runtimeId: 'runtime-old'
|
||||
})
|
||||
).toBe(false)
|
||||
|
||||
expect(pause).not.toHaveBeenCalled()
|
||||
expect(isRuntimeEnvironmentCapabilityPaused('env')).toBe(false)
|
||||
})
|
||||
|
||||
|
||||
@@ -68,8 +68,6 @@ export function applyRuntimeEnvironmentCapabilityVerdict(args: {
|
||||
evidence: RuntimeEnvironmentCapabilityEvidence
|
||||
verdict: RuntimeEnvironmentCapabilityVerdict
|
||||
runtimeId: string
|
||||
onCapable?: () => void
|
||||
onAbsent?: () => void
|
||||
}): boolean {
|
||||
const state = stateFor(args.evidence.environmentId)
|
||||
if (
|
||||
@@ -83,11 +81,6 @@ export function applyRuntimeEnvironmentCapabilityVerdict(args: {
|
||||
verdict: args.verdict,
|
||||
runtimeId: args.runtimeId
|
||||
}
|
||||
if (args.verdict === 'capable') {
|
||||
args.onCapable?.()
|
||||
} else {
|
||||
args.onAbsent?.()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -47,9 +47,9 @@ describe('runtime environment shared-control connection cache', () => {
|
||||
applyRuntimeEnvironmentCapabilityVerdict({
|
||||
evidence: absent,
|
||||
verdict: 'absent',
|
||||
runtimeId: 'runtime-test',
|
||||
onAbsent: () => pauseRemoteRuntimeSharedControlRetry(ENVIRONMENT_ID)
|
||||
runtimeId: 'runtime-test'
|
||||
})
|
||||
pauseRemoteRuntimeSharedControlRetry(ENVIRONMENT_ID)
|
||||
expect(getRemoteRuntimeSharedControlDiagnostics(ENVIRONMENT_ID)?.state).toBe('closed')
|
||||
await delay(400)
|
||||
expect(server.connectionCount()).toBe(1)
|
||||
@@ -58,12 +58,10 @@ describe('runtime environment shared-control connection cache', () => {
|
||||
applyRuntimeEnvironmentCapabilityVerdict({
|
||||
evidence: capable,
|
||||
verdict: 'capable',
|
||||
runtimeId: 'runtime-test',
|
||||
onCapable: () => {
|
||||
ensureRemoteRuntimeSharedControlConnection(ENVIRONMENT_ID, server.pairing)
|
||||
reconnectRemoteRuntimeSharedControlConnection(ENVIRONMENT_ID)
|
||||
}
|
||||
runtimeId: 'runtime-test'
|
||||
})
|
||||
ensureRemoteRuntimeSharedControlConnection(ENVIRONMENT_ID, server.pairing)
|
||||
reconnectRemoteRuntimeSharedControlConnection(ENVIRONMENT_ID)
|
||||
await waitFor(() => server.connectionCount() === 2)
|
||||
})
|
||||
|
||||
@@ -119,9 +117,9 @@ describe('runtime environment shared-control connection cache', () => {
|
||||
applyRuntimeEnvironmentCapabilityVerdict({
|
||||
evidence,
|
||||
verdict: 'absent',
|
||||
runtimeId: 'runtime-test',
|
||||
onAbsent: () => pauseRemoteRuntimeSharedControlRetry(ENVIRONMENT_ID)
|
||||
runtimeId: 'runtime-test'
|
||||
})
|
||||
pauseRemoteRuntimeSharedControlRetry(ENVIRONMENT_ID)
|
||||
|
||||
expect(getRemoteRuntimeSharedControlDiagnostics(ENVIRONMENT_ID)?.state).toBe('reconnecting')
|
||||
await waitFor(() => server.connectionCount() === 2)
|
||||
|
||||
@@ -231,3 +231,20 @@ function remoteControl(
|
||||
lastError: null
|
||||
}
|
||||
}
|
||||
|
||||
it('does not report reconnecting after verification is terminally blocked', () => {
|
||||
expect(
|
||||
runtimeHostConnectionStateForEntry({
|
||||
status: null,
|
||||
snapshot: {
|
||||
environmentId: 'browser',
|
||||
pairingRevision: 1,
|
||||
sequence: 1,
|
||||
checkedAt: 1,
|
||||
status: null,
|
||||
verification: 'blocked',
|
||||
transport: 'disconnected'
|
||||
}
|
||||
})
|
||||
).toBe('disconnected')
|
||||
})
|
||||
|
||||
@@ -120,7 +120,7 @@ export function runtimeHostConnectionStateForEntry(
|
||||
): RuntimeHostConnectionState {
|
||||
if (entry?.snapshot) {
|
||||
const snapshot = entry.snapshot
|
||||
if (snapshot.retired) {
|
||||
if (snapshot.retired || snapshot.verification === 'blocked') {
|
||||
return 'disconnected'
|
||||
}
|
||||
if (snapshot.transport === 'disconnected') {
|
||||
|
||||
@@ -73,14 +73,10 @@ describe('restored client-hosted browser host attach on reachability', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// The reconnect policy suppresses the *failure* publish only. A probe that answered still owes
|
||||
// both recovery follow-ups, or a restored client-hosted page never comes back after the gap.
|
||||
it('runs both recovery follow-ups on a success when the caller opted out of publishing failures', async () => {
|
||||
it('runs both recovery follow-ups after a successful refresh', async () => {
|
||||
stubApi(vi.fn().mockResolvedValue(createCompatibleRuntimeStatusResponse('runtime-a')))
|
||||
|
||||
await storeWithRestoredHandles(true)
|
||||
.getState()
|
||||
.refreshRuntimeEnvironmentStatus('env-a', undefined, { publishUnreachable: false })
|
||||
await storeWithRestoredHandles(true).getState().refreshRuntimeEnvironmentStatus('env-a')
|
||||
|
||||
expect(prepareBrowserClientHostPlacement).toHaveBeenCalledWith({
|
||||
selector: 'env-a',
|
||||
@@ -89,24 +85,12 @@ describe('restored client-hosted browser host attach on reachability', () => {
|
||||
expect(replayClientHostedBrowserCloseIntents).toHaveBeenCalledWith('env-a', expect.anything())
|
||||
})
|
||||
|
||||
// Under either policy a failed probe owes *no* follow-ups: it verified nothing, so there is no
|
||||
// recovered host to reattach restored pages to and no one to replay closes at.
|
||||
it.each([
|
||||
{ name: 'the default policy', options: undefined },
|
||||
{ name: 'a caller that opted out of publishing', options: { publishUnreachable: false } }
|
||||
])(
|
||||
'starts no browser client host when the environment is unreachable: $name',
|
||||
async (scenario) => {
|
||||
stubApi(vi.fn().mockRejectedValue(new Error('unreachable')))
|
||||
|
||||
await storeWithRestoredHandles(true)
|
||||
.getState()
|
||||
.refreshRuntimeEnvironmentStatus('env-a', undefined, scenario.options)
|
||||
|
||||
expect(prepareBrowserClientHostPlacement).not.toHaveBeenCalled()
|
||||
expect(replayClientHostedBrowserCloseIntents).not.toHaveBeenCalled()
|
||||
}
|
||||
)
|
||||
it('runs no recovery follow-ups when the environment is unreachable', async () => {
|
||||
stubApi(vi.fn().mockRejectedValue(new Error('unreachable')))
|
||||
await storeWithRestoredHandles(true).getState().refreshRuntimeEnvironmentStatus('env-a')
|
||||
expect(prepareBrowserClientHostPlacement).not.toHaveBeenCalled()
|
||||
expect(replayClientHostedBrowserCloseIntents).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('starts no browser client host for restored pages the server hosts', async () => {
|
||||
stubApi(vi.fn().mockResolvedValue(createCompatibleRuntimeStatusResponse('runtime-a')))
|
||||
|
||||
@@ -11,10 +11,6 @@ export type RuntimeEnvironmentStatus = {
|
||||
connectionGeneration?: number
|
||||
}
|
||||
|
||||
export type RuntimeStatusRefreshOptions = {
|
||||
publishUnreachable?: boolean
|
||||
}
|
||||
|
||||
export type RuntimeStatusSlice = {
|
||||
readRuntimeHostStatusSnapshots: () => Promise<void>
|
||||
applyRuntimeHostStatusSnapshot: (snapshot: RuntimeHostStatusSnapshot) => void
|
||||
@@ -31,10 +27,6 @@ export type RuntimeStatusSlice = {
|
||||
) => void
|
||||
clearRuntimeEnvironmentStatus: (environmentId: string) => void
|
||||
retainRuntimeEnvironmentStatuses: (environmentIds: Iterable<string>) => void
|
||||
refreshRuntimeEnvironmentStatus: (
|
||||
environmentId: string,
|
||||
timeoutMs?: number,
|
||||
options?: RuntimeStatusRefreshOptions
|
||||
) => Promise<boolean>
|
||||
refreshRuntimeEnvironmentStatus: (environmentId: string, timeoutMs?: number) => Promise<boolean>
|
||||
hydrateRuntimeEnvironmentStatuses: () => Promise<void>
|
||||
}
|
||||
|
||||
@@ -710,33 +710,17 @@ describe('runtime-status slice', () => {
|
||||
clearRuntimeCompatibilityCacheForTests()
|
||||
})
|
||||
|
||||
// Both directions of the failure-publication policy, from one failing probe. A user-initiated
|
||||
// check publishes the outage it just observed; a caller holding live transport evidence must
|
||||
// not, because status.get dials its own socket and its failure is unverifiable, not exited.
|
||||
it.each([
|
||||
{ name: 'a user-initiated check', options: undefined, publishes: true },
|
||||
{ name: 'publishUnreachable defaulted', options: {}, publishes: true },
|
||||
{
|
||||
name: 'a caller that opted out of publishing',
|
||||
options: { publishUnreachable: false },
|
||||
publishes: false
|
||||
}
|
||||
])('records null and returns false when a runtime refresh fails: $name', async (scenario) => {
|
||||
it('records null and returns false when a runtime refresh fails', async () => {
|
||||
const getStatus = vi.fn().mockRejectedValue(new Error('closed'))
|
||||
stubRuntimeEnvironmentApi({ getStatus })
|
||||
const store = createSliceStore()
|
||||
const cached = makeStatus()
|
||||
store.getState().setRuntimeEnvironmentStatus('env-a', { status: cached, checkedAt: 1 })
|
||||
|
||||
const reachable = await store
|
||||
.getState()
|
||||
.refreshRuntimeEnvironmentStatus('env-a', undefined, scenario.options)
|
||||
const reachable = await store.getState().refreshRuntimeEnvironmentStatus('env-a')
|
||||
|
||||
// The dial-answered contract the bridge's bounded retry chain reads is policy-independent.
|
||||
expect(reachable).toBe(false)
|
||||
expect(store.getState().runtimeStatusByEnvironmentId.get('env-a')?.status).toBe(
|
||||
scenario.publishes ? null : cached
|
||||
)
|
||||
expect(store.getState().runtimeStatusByEnvironmentId.get('env-a')?.status).toBe(null)
|
||||
})
|
||||
|
||||
it('hydrates saved environments through the single-environment refresh path', async () => {
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import type { StateCreator } from 'zustand'
|
||||
import type { AppState } from '../types'
|
||||
import type { RuntimeStatusSlice } from './runtime-status-types'
|
||||
export type {
|
||||
RuntimeEnvironmentStatus,
|
||||
RuntimeStatusRefreshOptions,
|
||||
RuntimeStatusSlice
|
||||
} from './runtime-status-types'
|
||||
export type { RuntimeEnvironmentStatus, RuntimeStatusSlice } from './runtime-status-types'
|
||||
import { runtimeEnvironmentStatusesEqual } from './runtime-environment-status-equality'
|
||||
import {
|
||||
clearRecentRuntimeCompatibilityFailure,
|
||||
@@ -290,16 +286,12 @@ export const createRuntimeStatusSlice: StateCreator<AppState, [], [], RuntimeSta
|
||||
})
|
||||
},
|
||||
|
||||
refreshRuntimeEnvironmentStatus: (environmentId, timeoutMs = 10_000, options) =>
|
||||
refreshRuntimeEnvironmentStatus: (environmentId, timeoutMs = 10_000) =>
|
||||
refreshRuntimeEnvironmentStatus(environmentId, timeoutMs, (entry) => {
|
||||
if (entry.snapshot) {
|
||||
get().applyRuntimeHostStatusSnapshot(entry.snapshot)
|
||||
return
|
||||
}
|
||||
if (entry.status === null && options?.publishUnreachable === false) {
|
||||
// Unverifiable, not exited: leave the cached verdict for the caller's retry to settle.
|
||||
return
|
||||
}
|
||||
// Why: setRuntimeEnvironmentStatus drops any stale compat failure on a non-null
|
||||
// (reachable) status, so a recovered host's reuse-flagged refetches re-probe.
|
||||
get().setRuntimeEnvironmentStatus(environmentId, entry)
|
||||
|
||||
Reference in New Issue
Block a user