Compatibility
Published
Health checks are written once, reviewed lightly and trusted absolutely. They also read almost nothing of their own: memory figures, CPU times and resource counters all arrive in objects the runtime constructs, with property names fixed by the platform. That combination -- a contract with the runtime, in code nobody exercises -- is what this page measures.
What was measured
One sample that reads the objects a service actually reports on: the memory sample, the per-core CPU times, the resource usage counters, the version block and the uptime and load figures. It derives a heap percentage, compares it against an alert threshold, builds the envelope it would ship to a metrics backend, and prints a health verdict of the kind a load balancer acts on.
Every number in the sample is printed as a shape rather than a value -- finite number, NaN, undefined, string -- because memory and CPU figures differ between runs. Printing the raw numbers would make every comparison report a difference for reasons that have nothing to do with protection. This is a small discipline that decides whether a measurement means anything.
The base column is clean. All five protection profiles reproduced the unprotected output exactly. None of this is a risk from protection alone; all of it is a risk from a member pattern that reaches platform-authored names such as the heap fields, the CPU time fields or the resource counters.
A memory alert that can never fire, and a health check that always fails
The heap arm produces both failure directions at once, which is what makes it the most useful result on the page. With a pattern matching the two heap fields, both reads returned undefined, the derived percentage became NaN, and the health verdict flipped from healthy to degraded. In the same run, the alert threshold went on reporting false.
The two behaviours have the same cause and opposite consequences. The health verdict is guarded by a test that the value is a finite number, so an unusable reading correctly fails that test and the endpoint reports degraded. The alert is a greater-than comparison, and every relational comparison with NaN is false, so the memory alert is not merely wrong -- it can no longer fire for any input at all. A threshold that has quietly stopped existing is worse than one that fires spuriously, because nothing about it looks unusual.
That pairing is worth keeping as a rule. A guard written as a validity test fails closed and tells you something is wrong. A guard written as a comparison fails open and tells you nothing. The same renamed read produces both, in the same file, four lines apart.
Renaming a container stops the process before it serves anything
Two arms fail loudly and immediately. Renaming the CPU times container threw while enumerating it -- a type error about converting undefined to an object -- and renaming the version block threw on the first read out of it. In both cases nothing after that point in the file ran.
This is the container-versus-leaf split again, and in monitoring code it is the outcome to prefer. A metrics module that fails at startup is fixed before it ships. A metrics module that reports plausible numbers is trusted for months. The loud failures on this page are the two that a smoke test catches; the quiet ones are the two that page nobody.
The individual CPU time fields are leaves and behave accordingly: renaming them left the idle reading undefined and the derived busy figure NaN, with no error, and the health verdict flipped to degraded through the same validity test as the heap arm.
The envelope you ship carries the failure outward
The sample builds the object it would send to a metrics backend and prints it. Under the heap pattern that envelope went out with the heap field reporting undefined and the derived percentage reporting NaN, while the fields sourced from other objects stayed correct. A backend receiving that will show one series with gaps and its neighbours intact, which is the signature of a partial collection failure rather than a build change.
An arm matching the envelope's own field names shows the output side on its own. There the values stayed correct and the keys became generated names, because both halves of those reads live in the file. Every assertion inside the process passes. The series simply stops arriving under the name the dashboard queries, and a series that stops arriving is indistinguishable from a service that stopped reporting.
Resource counters behave the same way as the heap fields: renaming them left both readings undefined while everything around them stayed correct. A partially correct metrics payload is the hardest kind to diagnose, because the parts that work are evidence that collection is fine.
A presence check that passes while the read fails
The resource-usage arm contains the clearest single line on this page. With a pattern matching two counters, both reads returned undefined -- and the test for whether the object has one of those counters at all went on reporting true, in the same run, two lines apart.
That is not a contradiction. The presence test asks the object for a property by a string, and the object is the one the runtime built, so the string matches the name that is genuinely there. The read beside it was rewritten to a generated identifier, so it finds nothing. One line reports the field exists; the next reports it has no value.
Any diagnostic built on presence rather than value inherits that blind spot. A capability probe that checks whether the platform exposes a counter before reading it will pass and then read undefined. A schema check over a runtime object will report every field accounted for. The same shape appeared three times in this pass's measurements -- in a database column guard, in a request-body validator, and here -- which is enough to treat it as a rule rather than a coincidence: a check that compares strings to keys cannot see a renamed read.
The core description fields behave the same way. A pattern matching the processor model and speed fields left the test that the model is a string reporting false, because the read returned nothing. Nothing threw, and a host inventory would simply record a machine with no processor name.
Why monitoring code is the worst place for this
Three properties combine badly here. Monitoring code is rarely covered by tests, because asserting on a memory figure is awkward. It is written to be defensive, so it swallows problems by design. And it is the instrument you reach for when something else is wrong, which means a fault in it is discovered during an incident, while it is actively misleading you.
The names involved are also unusually easy to match by accident. The platform's field names for memory, CPU time and resource usage are ordinary English words, and several of them -- the idle, user and system time fields, the model and speed fields on a core -- are exactly the kind of short name a broad member pattern picks up while aiming at something else in the application.
The mitigation is the one this site keeps arriving at. Anchor the pattern to names you own on both ends, ideally through a prefix convention, so a platform field can never match. If you want protection specifically around your metrics code, protect the values it computes rather than the names the runtime hands you.
How to check a protected build in one minute
Print the key list of the memory object beside the value you read out of it. The key list is built by the runtime and never changes; your read either agrees with it or does not, and a single line of output settles it.
Then check the two guards separately. Print the derived percentage and print the alert verdict. A percentage of NaN with an alert verdict of false is the exact signature described above: the alert is not calm, it is disabled. If your health check reports degraded at the same time, the two guards are doing what the measurement predicted, and neither of them is telling you about memory.
Finally, print the envelope you ship. If the keys are generated names, the failure is on the output side and nothing inside the process will ever notice it. If the values are undefined or NaN, the failure is on the input side and the runtime is writing names your code no longer asks for.
Frequently asked questions
Does obfuscation break health checks and metrics collection?
Not in the default configuration. A sample reading the memory sample, CPU times, resource usage, the version block and uptime produced identical output on all five protection profiles measured. Metrics become a surface only when member renaming matches property names that the runtime writes.
Why does my health endpoint report degraded after protecting the build?
Because a validity test is doing its job on an unusable value. Renaming the heap fields makes both reads undefined and the derived percentage NaN, and a test that the value is a finite number correctly rejects it. The service is healthy; the reading is not, and the endpoint cannot tell the difference.
Can member renaming disable a memory alert entirely?
Yes, and this was measured in the same run as the degraded verdict. The alert is a greater-than comparison, and every relational comparison with NaN evaluates to false, so the alert can no longer fire for any input. A threshold that has stopped existing looks exactly like a threshold that is never crossed.
Why does my metrics module throw on startup after obfuscation?
Because a container was renamed rather than a leaf. Renaming the CPU times object or the version block makes the first access read undefined, which throws immediately. In monitoring code this is the outcome to prefer: a module that fails at startup gets fixed, and a module that reports plausible numbers gets trusted.
Why is one metric series missing while the others look fine?
Because only the reads matching your pattern were affected. The measured envelope carried undefined for the renamed heap field while the CPU and resource figures beside it stayed correct. A backend shows that as one series with gaps and its neighbours intact, which reads as a partial collection failure rather than as a change in the build.
Do my own metric field names get renamed too?
Yes, and the effect is invisible inside the process. When your source writes and reads a name, both halves are renamed together and every assertion passes. Only the emitted document changes, so the series stops arriving under the name your dashboard queries, and a series that stops arriving looks like a service that stopped reporting.
How do I check metrics code in a protected build?
Print the runtime's key list beside the value you read out of it, then print the derived percentage and the alert verdict separately. A NaN percentage with an alert verdict of false is the signature that the alert has been disabled rather than satisfied. Finally print the envelope you ship, to see whether the failure is on the input side or the output side.
Related reading