A type assertion is not a parser. An external response starts as bytes and says nothing about its variant until something checks it.
Text decoding and JSON parsing come first, each with its own failure path. The parsed value should reach validation as unknown.
The supported response is either a ready tag with an items array of strings or a failed tag with a message string. The runtime check has to confirm three things. The value is an object, its tag is one we know, and the payload for that tag has the required shape. An object with ready and no items is incomplete. One with waiting carries a tag we never declared. Writing as with the union fixes neither.
Once the check passes, the decoder returns a discriminated union. Testing for ready narrows it, and the items become usable. An exhaustive check with never points out an unhandled alternative in the declared model. Good maintenance information, but it never looks at response bytes.
Rejected input still needs a decision. Report a decoding failure, or model unknown responses as a variant on purpose. Casting quietly into the accepted union skips that choice.
Keep the runtime evidence right next to where the typed value gets built. Review each handler's calculation too. A covered branch can still compute the wrong answer.