mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
fix(mobile): keep the two advisory task probes total so a nullish payload cannot unhydrate the screen
`preflight.check` and `linear.status` are read under `success-result-or-skip` and documented as advisory. That policy accepts an envelope whose `result` is absent or null, then asks the reader to decode it; a `looseObject` refuses, the throw leaves the reader, and the caller's catch discards the entire hydration. The corpus records the difference: on the `result-absent` and `result-null` partitions main hydrates the Tasks screen and lists one provider, and the checked readers left it unhydrated with no providers. `.catch` restores main's answer exactly. Every consumer guards to the leaf and compares to `true`, so absence, null and a garbage payload have always meant "not installed" and "not connected". Four cases pin it, and removing either catch fails all four. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
This commit is contained in:
@@ -87,6 +87,24 @@ describe('the two advisory probes', () => {
|
||||
gh: { installed: true, authenticated: true }
|
||||
})
|
||||
})
|
||||
|
||||
// `success-result-or-skip` accepts an envelope whose `result` is absent or null and then asks
|
||||
// the reader to decode it, so a refusal here throws out of the caller's whole hydration. Main
|
||||
// hydrated on both of these replies; the recorded settings.task-hydration and
|
||||
// settings.workspace-context partitions are what caught the difference.
|
||||
it.each([
|
||||
['absent', undefined],
|
||||
['null', null],
|
||||
['a bare string', 'preflight'],
|
||||
['an array', []]
|
||||
])('reads a %s payload as "nothing advertised" rather than refusing it', (_label, reply) => {
|
||||
const preflight = taskPreflightSchema.safeParse(reply)
|
||||
expect(preflight.success).toBe(true)
|
||||
expect(preflight.success && preflight.data.glab?.installed).toBeUndefined()
|
||||
const linear = taskLinearStatusSchema.safeParse(reply)
|
||||
expect(linear.success).toBe(true)
|
||||
expect(linear.success && linear.data.connected).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('the three preference writes', () => {
|
||||
|
||||
@@ -64,13 +64,21 @@ export const taskUiStateSchema = z
|
||||
* (use-mobile-tasks-runtime-hydration.tsx:295, mobile-home-host-requests.ts:95) and the
|
||||
* `readProbeMember` pair in use-new-workspace-runtime-context.ts:92. `git` and `gh` are declared
|
||||
* non-optional by PreflightStatus but no mobile consumer reads them, so they pass through.
|
||||
*
|
||||
* Total on purpose. This read is advertised as advisory, but `success-result-or-skip` accepts an
|
||||
* envelope whose `result` is absent or null and then asks the reader to decode it, and a refusal
|
||||
* there throws out of the whole hydration -- the corpus records main hydrating the Tasks screen on
|
||||
* that reply and this reader leaving it unhydrated. `.catch` restores main's answer exactly,
|
||||
* because every consumer guards to the leaf and reads absence as "not installed".
|
||||
*/
|
||||
export const taskPreflightSchema = z.looseObject({
|
||||
glab: salvagedOptional(
|
||||
'glab',
|
||||
z.looseObject({ installed: salvagedOptional('installed', z.boolean()) })
|
||||
)
|
||||
})
|
||||
export const taskPreflightSchema = z
|
||||
.looseObject({
|
||||
glab: salvagedOptional(
|
||||
'glab',
|
||||
z.looseObject({ installed: salvagedOptional('installed', z.boolean()) })
|
||||
)
|
||||
})
|
||||
.catch({ glab: undefined })
|
||||
|
||||
/**
|
||||
* Whether Linear is connected.
|
||||
@@ -80,10 +88,15 @@ export const taskPreflightSchema = z.looseObject({
|
||||
* `false` already mean the same thing and nothing is required. `workspaces` is not declared: the
|
||||
* picker reads it through a different operation on this same method, and listing a member ahead of
|
||||
* a reader is how a schema starts refusing replies no consumer here would have noticed.
|
||||
*
|
||||
* Total for the same reason as the preflight read above: absence and `false` already mean the same
|
||||
* thing here, so a nullish payload must read as "not connected" rather than throw.
|
||||
*/
|
||||
export const taskLinearStatusSchema = z.looseObject({
|
||||
connected: salvagedOptional('connected', z.boolean())
|
||||
})
|
||||
export const taskLinearStatusSchema = z
|
||||
.looseObject({
|
||||
connected: salvagedOptional('connected', z.boolean())
|
||||
})
|
||||
.catch({ connected: undefined })
|
||||
|
||||
/**
|
||||
* The three writes whose reply body no call site reads.
|
||||
|
||||
Reference in New Issue
Block a user