validateMessage

fun validateMessage(message: JsonElement, direction: MessageDirection = MessageDirection.AGENT_TO_RENDERER, catalogId: String?): SchemaValidation

Whether a whole message, still in its wire form, matches the schema for its direction.

This is the entry the conformance harness uses, and it reaches what the per-element checks cannot: an envelope key no message defines, a createSurface whose components array is empty, a callRendererFunction that omits the catalogId its own definition requires. Each of those is a constraint on the message, not on anything inside it.

catalogId binds the catalog.json placeholder for the whole message. It is passed rather than read from the payload because a message may carry a catalogId per component, while the placeholder resolves once — the specification's own harness binds it per test suite for exactly this reason. For a live surface, pass the id the surface was created with; the per-element checks are what apply a component's own override.

It has no default. Passing null is meaningful -- it says "check this against nothing but the protocol", which is what a userAction or an error needs -- but it is not a sensible thing to fall into: with the placeholder unbound, every message carrying a component or a call fails on an unresolvable $ref and is reported invalid for a reason that is about the renderer rather than the payload. A caller must say which it means.

Takes the raw element rather than a decoded message on purpose. A decoded one has already been through the model's own strict parse, which refuses much of what this is meant to report on — a payload that fails to decode never reaches a checker at all.