When two programs consume the same data and behave differently, the instinct is that something wasn’t declared or typed strongly enough. That diagnosis is right for a specific class of defect and misleading outside it, and the two look identical from the bug report.
Declaring a vocabulary once catches drift within a value: a state name spelled four ways in four files, an enum whose consumers each write their own open match so a new member silently takes the default arm, a wire format parsed by three ad-hoc string expansions that stop agreeing the moment a field is added. These are real and they are cheap to eliminate: one definition, generated or parsed into every consumer, and the match becomes exhaustive by construction. The tell is that the consumer written in the language with an actual sum type is usually the one that got it right.
What no amount of typing catches is two internally consistent programs disagreeing about which things they are describing. A status count and a picker list can both be type-correct, both parse the same declared vocabulary, and still select different sets, because no type expresses “the set A counts is the set B lists.” Nor does typing catch a state machine whose transition relation is legal at every edge but has no edge for one real-world exit: every value valid, every transition valid, and a state you can enter and never leave. The first needs a single shared selection predicate or a test that can see both consumers at once; the second needs the transition relation itself declared and checked for whether every state can be left by an event guaranteed to fire.
The practical split: if the bug is “these two spellings disagree,” declare it once. If the bug is “these two answers disagree,” you need a definition of the question, and something that exercises both answers against it. Related: Contract Tests Beat Endpoint Hunting, Agent Waiting State Splits Into Blocked and Idle.