mirror of
https://github.com/stablyai/orca.git
synced 2026-09-25 00:02:35 +00:00
fix(mobile): keep main's malformed-reply message and re-anchor two mutants
Three findings the reply matrices surfaced that the pilot scenarios could not.
The clipboard upload's `uploadId` read goes back to the raw result. A success carrying no result
throws a destructuring TypeError there, and V8 puts the destructured expression's source text in
the message — which the composer shows. Reading the slot off the interpreted payload rewrote that
sentence for every user who hits a malformed reply, on three families' `result-absent` and
`result-null` partitions. The cast is the one main made, kept for the message alone.
`race` and `new-tab-refusal-order` both anchored in text the migration rewrote, so each matched
zero sites. Re-anchored at their new homes; the defect each injects is unchanged, and
`probe-hole-witness.test.ts` still shows the probe killing the reorder while every pre-probe
scenario survives it.
`native-chat-send-delivery-unknown` is added as this domain's own mutant: dropping the
delivery-unknown arm of a chat send makes an ack lost after the frame was written read as a
definite rejection, which invites the user to send the same message twice. A second candidate —
swapping `terminal.list` from skip to throw — survives every golden and is not registered: the
inventory hook wraps its whole read in `catch { return false }`, so a refusal and a throw leave
the same strip.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
This commit is contained in:
@@ -146,7 +146,12 @@ async function uploadMobileClipboardImageTransaction(
|
||||
throw new Error(startResponse.error.message)
|
||||
}
|
||||
|
||||
const { uploadId } = clipboardImageUploadStart.interpret(startResponse) as { uploadId: string }
|
||||
// Why the raw result rather than the interpretation: a success carrying no result throws a
|
||||
// TypeError here, and V8 puts the destructured expression's source text in its message — which
|
||||
// the composer then shows. Reading the slot off the accepted payload would rewrite that sentence
|
||||
// for every user who hits a malformed reply, which is the one change this migration must not make.
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the same cast main made, kept so the thrown message is the same one.
|
||||
const { uploadId } = startResponse.result as { uploadId: string }
|
||||
try {
|
||||
for (
|
||||
let offset = 0;
|
||||
|
||||
@@ -7,10 +7,21 @@ import type { OperationMutation } from '../operation-module-loader'
|
||||
*/
|
||||
export const OPERATION_MUTATIONS = {
|
||||
// Loses the generation comparison, so a stale workspace response poisons the search cache.
|
||||
// Drops the delivery-unknown arm of a native-chat send, so an ack lost after the frame was
|
||||
// written reads as a definite rejection and invites the user to send the same message twice.
|
||||
'native-chat-send-delivery-unknown': {
|
||||
file: 'mobile-native-chat-send.ts',
|
||||
before: ` return isRpcDeliveryUnknown(error) || isLogicalClientCutoverError(error)
|
||||
? 'unknown'
|
||||
: 'rejected'`,
|
||||
after: ` return isLogicalClientCutoverError(error) ? 'unknown' : 'rejected'`
|
||||
},
|
||||
// Re-anchored where the operation migration moved the acceptance read; the defect it injects —
|
||||
// a stale workspace response poisoning the search cache — is unchanged.
|
||||
race: {
|
||||
file: 'use-mobile-native-chat-file-search.ts',
|
||||
before: '!response.ok || generationRef.current !== generation',
|
||||
after: '!response.ok'
|
||||
before: '!accepted.accepted || generationRef.current !== generation',
|
||||
after: '!accepted.accepted'
|
||||
},
|
||||
// Accepts a null result envelope instead of rejecting it. The guard is repeated for three
|
||||
// mutations in this file; the anchor carries the message so only the recorded one is edited.
|
||||
@@ -108,15 +119,13 @@ export const OPERATION_MUTATIONS = {
|
||||
},
|
||||
// Checks the sibling's refusal before the operation's own, so a correlated refusal reports the
|
||||
// sibling. Invisible to every scenario whose sibling succeeds or rejects at the transport.
|
||||
// Re-anchored where the operation migration moved both reads; the reorder it injects — the
|
||||
// detection refusal deciding the error before the settings read is interpreted — is unchanged.
|
||||
'new-tab-refusal-order': {
|
||||
file: 'mobile-new-tab-agent-loader.ts',
|
||||
before: ` const readSettings = newTabSettingsRead.interpret(settingsResponse)
|
||||
if (!detectedResponse.ok) {
|
||||
throw new Error((detectedResponse as RpcFailure).error.message)
|
||||
}`,
|
||||
after: ` if (!detectedResponse.ok) {
|
||||
throw new Error((detectedResponse as RpcFailure).error.message)
|
||||
}
|
||||
before: ` const readSettings = newTabSettingsRead.interpret(settingsResponse)`,
|
||||
after: ` const detected0 = detectedAgents.interpret(detectedAgents.reply)
|
||||
void detected0
|
||||
const readSettings = newTabSettingsRead.interpret(settingsResponse)`
|
||||
},
|
||||
// Publishes an unaccepted read, blanking settings a refusal should have left alone. Invisible
|
||||
|
||||
@@ -28,7 +28,8 @@ const mutants: Record<string, Mutation> = {
|
||||
'settings-task-hydration-fulfilled': 'task-hydration-envelope',
|
||||
'settings-task-write': 'task-preferences-optimistic',
|
||||
'settings-workspace-submit-fulfilled': 'workspace-submit-envelope',
|
||||
'settings-task-workspace-fulfilled': 'task-workspace-envelope'
|
||||
'settings-task-workspace-fulfilled': 'task-workspace-envelope',
|
||||
'native-chat-write-delivery-unknown': 'native-chat-send-delivery-unknown'
|
||||
}
|
||||
/**
|
||||
* The archived tree's visible state, pinned per seed: b1 serves the poisoned empty inventory, b2
|
||||
|
||||
Reference in New Issue
Block a user