mirror of
https://github.com/stablyai/orca.git
synced 2026-10-24 00:04:01 +00:00
Report the keyring gap inline in headless serve
Deferring the probe to the first window is right for the desktop app, but serve never opens one, so the fallback timer became its only path. That moved the stall to after `printServeReady`: the runtime advertises itself, a relay or mobile client pairs, and only then does the main thread freeze on the keyring — stalling pings and PTY pumps, which a client reads as a dead host. Serve now reports inline, which is the timing it already had, and blocks before anything is advertised rather than under a live client. STA-5765
This commit is contained in:
@@ -73,7 +73,11 @@ describe('scheduleSecretProtectionGapReport', () => {
|
||||
})
|
||||
|
||||
const schedule = (): void =>
|
||||
scheduleSecretProtectionGapReport({ dataFile, log: (m) => void logged.push(m) })
|
||||
scheduleSecretProtectionGapReport({
|
||||
dataFile,
|
||||
log: (m) => void logged.push(m),
|
||||
deferUntilFirstWindow: true
|
||||
})
|
||||
|
||||
/** Runs an already-queued setImmediate; the 1ms is slack, not a delay under test. */
|
||||
const drain = (): void => void vi.advanceTimersByTime(1)
|
||||
@@ -127,4 +131,27 @@ describe('scheduleSecretProtectionGapReport', () => {
|
||||
vi.runAllTimers()
|
||||
expect(probes).toBe(1)
|
||||
})
|
||||
|
||||
it('reports inline in headless serve, before the runtime is advertised as ready', () => {
|
||||
// Why not deferred: serve opens no window, so the fallback would fire only after
|
||||
// clients could already have paired, and a frozen main thread reads as a dead host.
|
||||
scheduleSecretProtectionGapReport({
|
||||
dataFile,
|
||||
log: (m) => void logged.push(m),
|
||||
deferUntilFirstWindow: false
|
||||
})
|
||||
expect(probes).toBe(1)
|
||||
expect(logged).toEqual(['[secrets] The OS keyring is unavailable.'])
|
||||
})
|
||||
|
||||
it('leaves no timer armed in headless serve', () => {
|
||||
scheduleSecretProtectionGapReport({
|
||||
dataFile,
|
||||
log: (m) => void logged.push(m),
|
||||
deferUntilFirstWindow: false
|
||||
})
|
||||
expect(vi.getTimerCount()).toBe(0)
|
||||
vi.runAllTimers()
|
||||
expect(probes).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -19,11 +19,27 @@ import { reportSecretProtectionGap } from './secret-protection-report'
|
||||
// serve has no window to wait on.
|
||||
const REPORT_FALLBACK_MS = 15_000
|
||||
|
||||
export function scheduleSecretProtectionGapReport(options: {
|
||||
export function scheduleSecretProtectionGapReport({
|
||||
deferUntilFirstWindow,
|
||||
...options
|
||||
}: {
|
||||
dataFile: string
|
||||
force?: boolean
|
||||
log?: (message: string) => void
|
||||
/**
|
||||
* Why headless serve reports inline instead: it never opens a window, so the fallback
|
||||
* timer is the only path — and by then the runtime has been advertised as ready and
|
||||
* clients may have paired. Freezing the main thread under a live client stalls pings
|
||||
* and PTY pumps, which reads as a dead host. Blocking before anything is advertised is
|
||||
* the timing serve already had, and the safer of the two.
|
||||
*/
|
||||
deferUntilFirstWindow: boolean
|
||||
}): void {
|
||||
if (!deferUntilFirstWindow) {
|
||||
reportSecretProtectionGap(options)
|
||||
return
|
||||
}
|
||||
|
||||
let ran = false
|
||||
const run = (): void => {
|
||||
if (ran) {
|
||||
|
||||
+2
-1
@@ -2352,7 +2352,8 @@ void app.whenReady().then(async () => {
|
||||
// and must not gate the first window (STA-5765).
|
||||
scheduleSecretProtectionGapReport({
|
||||
dataFile: activeOrcaProfile.dataFile,
|
||||
force: process.env.ORCA_ALWAYS_REPORT_SECRET_PROTECTION === '1'
|
||||
force: process.env.ORCA_ALWAYS_REPORT_SECRET_PROTECTION === '1',
|
||||
deferUntilFirstWindow: !isServeMode
|
||||
})
|
||||
// Why here: the host key store is a sidecar of the same profile, and every SSH connect consults
|
||||
// it. Left unbound it reports nothing trusted, which is safe but silently discards our own
|
||||
|
||||
Reference in New Issue
Block a user