A defect class, not an outage
This is a reconstruction of a recurring defect, not the report of a single dated event. The pattern has appeared independently in more than one codebase that splits a product across two frontends in one repository, and the account below is assembled from that shape rather than from one timeline. The particulars — which language went blank, which build ran — vary; the mechanism does not. Where the reconstruction infers sequence it cannot directly observe, that is marked. The aim is to name the conditions under which the defect recurs, so a reader can recognize the seam in their own build before it drops anything.
What happened
At build time, two frontends each emit a file of locale strings — key-value pairs, one per translatable label — for the same locale and the same namespace. A merge step folds those files into one object so the application can load a single bundle per language. The merge is shallow: it assigns the entries of the second object over the entries of the first, so that for any key the two objects share, the second writer’s value lands and the first writer’s value is discarded. The output is a single, well-formed object. It is valid; it is just smaller than the union of its inputs by exactly the count of the keys they had in common.
The discarded entries do not announce themselves. The merge does not fail, the build does not warn, and the emitted bundle parses cleanly. RFC 8259 is explicit that JSON imposes no requirement on how duplicate names within an object are handled — implementations may take the first, take the last, or report an error, and behavior is unpredictable across them (RFC 8259 §4). A last-writer-wins object merge is one lawful resolution of that latitude. Nothing in the format treats the collision as exceptional, so nothing downstream has cause to.
Why it hid
The defect has no signal. A crash has a stack trace; a malformed file has a parse error; a missing file has a path that does not resolve. This has none of these. The merge succeeds, the bundle is structurally complete, and every key present resolves to a string that renders. The only evidence that anything was lost is the absence of a key that no longer exists to be looked up — and a lookup for a missing key returns a fallback or an empty label, not an error. The internationalization layer that consumes the bundle is specified to resolve and format strings for a requested locale (ECMA-402); it is not specified to know which keys ought to have been there. Completeness of the catalogue is the build’s responsibility, and the build asserted none.
So the detector is a person. The loss surfaces when someone fluent in the affected language opens the affected screen and sees a blank where a label belongs, or sees the fallback language bleed through. That detector is slow, partial, and lucky — it finds only the strings someone happens to look at, in the locales someone happens to read. (A neighboring hazard travels the same silent path: where keys or values are normalized inconsistently, two strings that look identical can compare as distinct, and a merge or lookup can lose one of them without a collision ever being visible — the normalization forms exist precisely to make such comparisons well-defined (Unicode Standard Annex #15).)
The contributing layers
Read from the surface down, the defect is layered, and each layer is individually reasonable. The proximate layer is the merge: a shallow assign written for the common case in which two inputs have disjoint keys, and correct exactly while that assumption holds. It never asserted the assumption it depended on, so the day the assumption broke, it broke quietly rather than loudly.
Beneath that is the data layer: two frontends own overlapping slices of one locale namespace, so the inputs are not in fact disjoint. And beneath that is the visibility layer: there is no point before the merge where the two inputs are diffed against each other, so the overlap is invisible until the merge resolves it — by which time the resolution has already discarded one side. The merge is both the first place the two inputs meet and the place that loses the conflict.
The deepest layer is structural, and it is where this analysis stops. No role was assigned the seam between the two frontends’ locale ownership. Each team owned its own strings and tested its own surface; neither owned the union, the namespace they shared, or the merge that joined them. The build inherited its shape from the org chart — two teams, two frontends, one accidental shared namespace and no owner for it — which is the observation Conway made in 1968: a system designed by an organization is constrained to mirror that organization’s communication structure (Conway, 1968). Here the boundary between two teams’ communication became a boundary in the data, and because it was a boundary no one was responsible for, it became a silent data path. The fix that holds is not a better merge but an owned seam: a step that treats a shared key as a conflict to be reported rather than a value to be overwritten, owned by someone accountable for the union. The five whys below trace the same descent in order.