cloud aui/v0 decode forwards malformed status and metadata.steps values to the runtime

Author: okisdevCreated Sep 18, 2026Updated Sep 18, 2026
Labelsbug

Problem

auiV0DecodeSafely (added in #7700) makes a malformed stored row survive: an unreadable part, attachment, nested message, date, or role-incompatible field is dropped on its own instead of rejecting the thread. It deliberately stops at the shape of a field a row legitimately carries.

So on an assistant row, a corrupt status and a corrupt metadata.steps still reach the runtime as-is:

  • status: { type: "cancelled" } is not a MessageStatus type, and fromThreadMessageLike forwards an assistant status without checking it.
  • metadata.steps: "bad" survives metadata?.steps ?? [] and reaches whatever iterates it, including the telemetry extraction in AssistantCloudThreadHistoryAdapter.

Neither costs the row today, which is why #7700 left them: the thread loads and the rest of the message is intact.

Why it was left out of #7700

Validating the MessageStatus union at the decode boundary is what broke #7624. Its isAssistantStatus rejected the valid requires-action and admitted idle and cancelled, so a cloud thread paused on a tool approval reloaded as { type: "complete", reason: "unknown" } and lost its pending-approval state. Every status in storage was a valid MessageStatus when auiV0Encode wrote it, so a sanitizer here can only ever preserve or destroy, never repair, and getting the union wrong destroys silently.

#7444 declined the same class for local storage, on the rule that the guard stays no stricter than what the runtime itself produces.

What a fix would need

Not a hand-written union. Either the shape is derived from one declaration the type checker keeps honest (the way #7700's role tables use satisfies Record<ThreadUserMessagePart["type"], true>), or both boundaries share a single validator with a round-trip test that runs auiV0Encode output back through it, so a validator that grows stricter than the encoder fails CI instead of deleting valid state. #7700 added that round-trip test for parts and attachments; a status and metadata validator should ride the same harness.

Worth deciding whether this is wanted at all: forwarding a corrupt field is the pre-existing behavior on both persistence paths, and the failure it causes is a wrong badge rather than a lost thread.

Track in Rupic

Source: assistant-ui/assistant-ui