Measured, not asserted
Published
An escalation clause is a recurrence, and a recurrence has a property that single-shot calculations do not: an error in it does not stay the size it started at. Losing the flag that makes an escalation compound does not make one year wrong by a percent. It makes year two wrong by one step, year three by two, and year five by four -- while every individual year remains a correct, in-bounds application of a correctly-read published index. This test measured that against a real obfuscation engine, and then measured something sharper: which of the tenant's own protections is what turns the lost flag into money.
Two escalation modes that are the same until something clamps them
The fixture is a five-year term on a published index with one period per lease year, a base rent of 189,000.00, a base period named in the lease, and a negotiated collar of 2% minimum and 5% maximum per year. Every index period is present and every lookup succeeds in every arm below.
Correct, compounding: five in-bounds steps, floored=0 capped=0, YEAR-5-RENT=221233.66, TERM-TOTAL=1039996.99.
Rename the compounding flag and the library's default takes over -- each year measured against the original base rent instead of the rent last payable. The result is mode=simple, capped=4, YEAR-5-RENT=198450.00, TERM-TOTAL=989615.93. That is 50,381.06 of rent over the term, and the tenant's own escalation rule still prints escalation-check(5 of 5 year(s) priced within the collar...) and still passes.
Now the part that is not obvious, and that was measured rather than assumed. Rename the compounding flag and the annual ceiling in one pattern and the term total goes back to 1039996.99 exactly, with capped=0. Losing two names produced the correct answer where losing one lost 50,381.06.
The tenant's own protection is what makes the lost flag expensive
The reason is arithmetic and it is worth stating plainly, because it inverts the intuition. Compounding measures each year's step against the previous period and multiplies; simple measures the whole movement from the base period and applies it to the original rent. On an unconstrained index those two produce the identical number in every year, because the product of the annual ratios is the cumulative ratio. The modes are algebraically the same schedule.
The 5% annual ceiling is what separates them. In compounding mode each annual step is around 3% and the ceiling never fires. In simple mode the figure being tested against the ceiling is the cumulative movement -- 6.39%, 9.84%, 13.37%, 17.05% -- so the ceiling fires in four years out of five and the rent flattens at 198,450.00 from year two onward.
So the collar the tenant negotiated for protection is the mechanism by which a lost mode flag costs the landlord 50,381.06. Remove the collar and the loss vanishes. Renaming the ceiling on its own, renaming the floor on its own, and renaming both together were all completely inert -- the compounding steps sit comfortably inside 2% and 5%, so no clamp ever fires in the correct run.
The general form: a control can be inert on its own, inert in combination, and still be the thing that converts an unrelated lost name into money. Testing controls in isolation for whether they change the answer will report all three of those arms as clean.
The silent arm has no tell at all
The compounding arm does leave one: the capped count moves from zero to four, and a reviewer watching that field would see it. The base-period arm leaves nothing.
Rename the option naming the base period and the library falls back to the earliest period it holds, which here is a year earlier than the one the parties agreed. The mode is unchanged, every period is real, every lookup succeeds, floored=0 capped=0 exactly as in the correct run, five of five years priced, and the check passes identically. TERM-TOTAL goes from 1,039,996.99 to 1,044,651.87 and YEAR-5-RENT from 221,233.66 to 222,119.16.
There is no field on the report that moves except the base period display itself. The schedule is internally consistent, in-bounds, complete and 4,654.88 wrong.
Renaming the quoted index period keys was semi-loud: two periods vanish from the series, the sorted list shifts, and both the floor and the ceiling fire once each -- floored=1 capped=1 against a correct 0 and 0. Quoted keys in an object literal are renamed exactly like dotted property names, which this series has now measured in three consecutive passes.
A schedule of nothing that passes its own check
Renaming the original base rent produced YEAR-5-RENT=0.00 and TERM-TOTAL=0.00, and the escalation rule passed: five of five years priced within the collar. Zero is a number, zero times any escalation is zero, and a rule defined over coverage and bounds has nothing to object to.
Renaming the whole index series is the loud direction: no base period, no periods to walk, a non-numeric total, and the schedule refused outright.
That is the same contrast again. The arm that destroys the input loudly refuses. The arms that change what the input means -- which period is the base, whether the steps stack, which rent each step is measured against -- produce a complete schedule that satisfies every assertion in the file. If you are shipping lease logic into a partner-branded portal, our white-label protection notes cover where the client-side half sits.
What to change, whether or not you obfuscate
Pin the schedule, not the steps. Every check in this fixture is defined over years priced and bounds respected, and all of them pass in every arm above. The assertion that catches all of it is a stored expected schedule: five rents, computed once against a known index and a known base period, compared year by year. It is five numbers and it is the only thing here that would have failed.
Make the base period and the escalation mode required inputs with no defaults. A library default of non-compounding is a reasonable guess about a lease it has never seen and should never be the silent answer when the caller intended to specify one.
Test the collar against both modes. The ceiling in this fixture is inert in the correct configuration and load-bearing in the failing one, so a test that only exercises the correct mode will report it as dead code.
And keep member-renaming patterns narrow and explicit, excluding any options object carrying index series, base periods, modes or collars. Renaming identifiers and control flow is unaffected by all of this.
How this was measured
Five commercial-real-estate areas were driven through the real obfuscation engine on five presets -- the ES5 default, the modern target, the emit gate's option set on both targets, and the string transforms. All five produced output byte-identical to the unprotected run on all five presets, twenty-five for twenty-five.
The member-renaming column is sixty-nine patterns on two presets, one hundred and thirty-eight verdicts: one hundred and twenty-eight moved the output, ten did not, and all ten are the pinned control arms measured on both presets.
Before any of that, every option key and every record field was deleted in the unprotected world to confirm it changes the answer at all -- ninety arms plus a union arm, zero structurally blind on the first run.
Every figure quoted above came from executing the protected artifact and reading the fields that decide the outcome, not from a diff summary.
Frequently asked questions
Did obfuscation on its own break rent escalation?
No. All five presets produced output byte-identical to the unprotected run across all five areas measured this pass.
What actually broke it?
Member renaming reaching the compounding flag, the base period option, the index series, or the quoted period keys inside it.
Why did losing the compounding flag cost 50,381.06?
Because the library's default measures each year against the original base rent rather than the rent last payable. Every step stays in bounds and every period is read correctly; the schedule simply stops stacking.
Why did losing the ceiling as well put the total back?
Because the two escalation modes are algebraically identical on an unconstrained index. The 5% annual ceiling is what separates them: in simple mode the figure tested against it is the cumulative movement, so it fires four years out of five.
Which arm left no tell?
The base period. Mode unchanged, every period real, floored and capped both zero exactly as in the correct run, five of five years priced, check passing -- and the term total 4,654.88 wrong.
Are quoted index period keys renamed?
Yes. Renaming the quoted period keys removed two periods from the series and shifted the whole schedule. A table keyed by quoted strings is reachable by a member pattern exactly like a dotted property name.
What is the safest way to use member renaming here?
Keep the pattern narrow and explicit and exclude any options object carrying index series, base periods, modes or collars. Renaming identifiers and control flow is unaffected.
Related reading