Compatibility
Published
Plenty of builds emit a machine-readable description of their API: an OpenAPI document, a schema dump, a manifest for a gateway. It leaves your process as a file and is read by tools nobody on your team runs, sometimes by customers. The key names in that document are not yours to choose, and when you write the document as an object literal, every one of those names is a property name in your source.
What was measured
One file that assembles an OpenAPI document as an object literal, writes it to disk, reads it back, and hands the path to a client generator. The generator is a separate module that is copied into the output directory unprotected, exactly like a third-party plugin: it is somebody else's tool, it reads the document with its own key names, and it cannot be rebuilt with yours. It reports the specification version it found, the operations it could generate, the models it could build and the required fields it saw.
The base column is clean. Across the default target, the modern target, both gate profiles and the string-transform profile the protected build produced the same document, the same generator output and the same publishability verdict as the unprotected one.
The member column matches the names the specification fixes. Each result below is what a downstream consumer sees, which is the only place several of them are visible at all.
A document that publishes with a generated key
The first arm renamed the single key that identifies the specification version. The document still wrote successfully, still parsed as valid JSON, and the list of its top-level keys came back with a generated identifier sitting alongside the real ones. The generator reported the version as missing.
That is the whole failure in one line: a syntactically valid document, published, carrying a key that no reader on earth recognises. Validation that only checks the file parses passes it. Anything that checks a required field reports that field missing, and points at the build that wrote the file rather than at the transform that renamed it.
Renaming the block that holds the routes was worse in effect and quieter in appearance. The document still had a routes block under a generated name, so it still looked structurally reasonable, and the generator produced no operations at all. A client library generated from that file compiles and contains nothing.
A client whose method names are error messages
The sharpest result came from renaming the operation identifier, which is the field a generator uses to name the method it emits for each route.
The generator did not fail. It fell back, as generators do, and named all three operations after the placeholder it uses when an operation identifier is absent. The emitted client therefore has three methods whose names are built from a verb, a path and the words no operation identifier. That client compiles. It publishes. Somebody installs it and calls a method whose name is a complaint about your document.
This is a build-time cause with an install-time symptom, and the audience for the symptom is the least equipped to diagnose it. The document is valid. The generator is working correctly. The only wrong thing happened in a transform that ran in a repository the consumer has never seen.
A related arm renamed the schema container, its properties block and its required list together. The generator produced no models, and the in-process code that reads the required list back threw a TypeError. Two failure modes from one pattern: silence on the far side, a crash on the near side.
Reading the generator's answer is a rename site too
Both directions of this contract are in one file. The document your build writes is one; the object the generator hands back is the other, and it belongs to the generator.
Renaming the fields on that returned object produced a TypeError on the first read of it, because a list that came back undefined has no length. A pattern matching the version fields made the generator's reported version read undefined instead. The generator was correct throughout; the reader was not.
One arm was completely inert, and it is the honest one to record. A descriptive field that neither this build nor the generator ever reads back was renamed in the published document, and nothing observable changed. The document still carries a generated key where a human-readable summary belongs. An arm that changes no behaviour is not the same as an arm that changed nothing, and in a published artifact the difference eventually shows up in somebody's documentation site.
Worth noting the one check that did catch a failure in process: the publishability test compares the number of generated operations and models against the numbers the build expects. It went from true to false. A count compared against a literal is the only kind of self-check in this measurement that saw anything at all, which is consistent with everything else in this series about presence checks being blind.
What to do about it
The specification key names are an external contract, so keep them out of the member pattern: the version key, the routes block, operation identifiers, the components and schemas containers, properties, required, and the informational block. That is a long list, which is the argument for anchoring to a private prefix instead of enumerating them.
Better still, do not build the document as an object literal in protected source. Load it from a JSON file at build time, or assemble it in a small unprotected script that runs alongside the protected bundle. Keys parsed from text are never rename sites, which turns the whole problem off rather than managing it.
Then verify the artifact rather than the code. Emit the document, list its top-level keys, and compare that list against one you typed. Run whatever generator your consumers run and count the operations and models it produces. Both checks are cheap, both belong in the same pipeline that produces the document, and both would have caught every arm measured here except the one that changes nothing anyone reads.
Frequently asked questions
Does obfuscation break an OpenAPI document my build emits?
Not in the default configuration. A build that assembles a document as an object literal, writes it, reads it back and hands it to a separate client generator produced identical results on all five protection profiles measured. The document becomes a surface only when member renaming is on and the pattern matches names the specification fixes.
What does a consumer see when a specification key is renamed?
A valid file with an unrecognised key. In the measured run the version key was published as a generated identifier, the document still parsed, and the generator reported the version as missing. Validation that only checks the file parses will pass it through.
Why are my generated client methods named after an error string?
Because the operation identifier field was renamed, so the generator did not find it and fell back to its placeholder. The measured run produced three methods whose names are built from a verb, a path and the words that say no operation identifier was present. The client still compiles and still publishes.
Can this break my build rather than only the consumer?
Yes, in one direction. Renaming the schema containers made the in-process read of the required list throw a TypeError, and renaming the fields on the object the generator returns threw on the first read of it. The far side stays silent; the near side is where the exception appears.
Is there a check that catches this before publishing?
Counting. The measured build compares the number of generated operations and models against the numbers it expects, and that check went from true to false. Presence checks do not work here, because comparing key names against strings is blind to renaming on both sides.
What about fields nothing reads?
They are still wrong in the published document. One arm renamed a descriptive field that neither the build nor the generator reads back, and no behaviour changed at all, but the document ships with a generated key where the human-readable text belongs.
What is the cleanest way to avoid all of this?
Do not write the document as an object literal inside protected source. Load it from a JSON file, or emit it from a small unprotected script that runs beside the protected bundle. Keys built from parsed text are not rename sites, which removes the contract from the transform's reach entirely.
Related reading