cloud aui/v0 decode forwards malformed status and metadata.steps values to the runtime
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 aMessageStatustype, andfromThreadMessageLikeforwards an assistant status without checking it.metadata.steps: "bad"survivesmetadata?.steps ?? []and reaches whatever iterates it, including the telemetry extraction inAssistantCloudThreadHistoryAdapter.
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.
Source: assistant-ui/assistant-ui