Status: implemented
Implemented — this change has shipped.
RFC 0058: Expense recoveries and the CAM true-up
Summary
A commercial tenant record can say its lease is nnn and that its CAM is capped at some percent. It cannot say what share of what pool that tenant pays, what the cap is measured against, or what happened at last year's reconciliation. Recovery income is the second-largest line in most commercial deals and the document currently carries a lease type and one orphan number.
This contract types the recovery terms on the tenant record and adds an annual, dated, stated-and-verified true-up record. Everything is optional, verified where a figure is stated, and never projected. No future recovery is forecast, no pool is allocated across tenants, and no monthly ledger is introduced — RFC 0054 placed per-lease periodic economics out of scope until a consumer and a calc-grammar answer exist, and this RFC respects that boundary by staying annual and by using §4.26 as the dated sink.
It registers one validator family: REC-NN on format §4.9.
Motivation
Today's tenant record (format §4.9) carries:
"lease_type": "nnn | gross | modified_gross | absolute_nnn | null",
"cam_cap_pct": nullThat is the whole recovery surface. What it cannot express:
- The share.
nnnsays the tenant reimburses operating costs. It does not say whether this tenant pays 4.1% of the pool or 38%. Pro-rata share is the single number every recovery calculation multiplies by, and there is no field for it. - What the cap is measured against.
cam_cap_pct: 0.05is meaningless without knowing whether it caps growth over the base year or over the prior year, and whether unused headroom carries forward. Cumulative and non-cumulative caps on the same lease diverge by material amounts inside three years. Two readers of the same document reach different numbers. - The stop. A modified-gross lease recovers only expenses above a base year or a fixed per-square-foot stop. Neither has a home, so a modified-gross tenant is indistinguishable from a gross one.
- Which expenses are recoverable. The operating statement (§4.4) itemizes
real_estate_taxes,insurance,utilities,repairs_maintenance,contract_services,administrativeand the rest. Nothing connects a tenant's recovery to those keys, so the pool is an assumption the reader supplies. - What actually happened. Recoveries are billed on an estimate and reconciled after year end. The document has no place to record that a tenant was billed $84,000, owed $91,300, and therefore carries a $7,300 receivable — which is a real, already-settled, auditable fact, exactly the kind of thing this format exists to carry.
The roadmap has listed "CAM true-up" as blocked behind the ledger decision. RFC 0054 made that decision, and it unblocks this: a true-up is an annual reconciliation of a past period, not a forward monthly projection. It needs no collection iteration and no second period dimension.
Proposed change
Format §4.9 — recovery_terms on the tenant record
Add an optional recovery_terms object beside the existing lease_type:
"recovery_terms": {
"method": "net | base_year_stop | fixed_stop | fixed_amount | none",
"pro_rata_share": 0.0412,
"share_basis": "nra | gla | stated",
"base_year": null,
"expense_stop_per_sqft": null,
"fixed_recovery_annual": null,
"admin_fee_pct": null,
"gross_up_pct": null,
"recoverable_pool": ["real_estate_taxes", "insurance", "contract_services"],
"cap": {
"pct": 0.05,
"over": "base_year | prior_year",
"accumulation": "cumulative | non_cumulative | compounding"
}
}Normative rules:
methodis a closed vocabulary. A producer that means something else statesnoneand carries the amount as other income rather than inventing a member.pro_rata_shareMUST be a fraction in(0, 1]— not a percent (repo-wide convention;0.0412is 4.12%).- Each entry in
recoverable_poolMUST name a key that exists underoperating_statement.expenses(§4.4), includingutilitiesas a whole. A name that matches nothing isREC-03, because a pool that points at a non-existent expense silently recovers zero. cap.accumulationMUST be stated whenevercap.pctis stated. There is no default, because the three treatments disagree and picking one silently is how a reader gets a different number than the producer.base_yearis REQUIRED whenmethodisbase_year_stop;expense_stop_per_sqftwhenfixed_stop;fixed_recovery_annualwhenfixed_amount. A method without its input isREC-02.gross_up_pct, when stated, is the occupancy the pool was grossed up to, as a fraction. It is recorded, not applied: this RFC does not gross up a pool.
Format §4.9 — recovery_true_up, an annual reconciliation
Add an optional array of dated reconciliation records:
"recovery_true_up": [
{
"period_start": "2025-01-01",
"period_end": "2025-12-31",
"pool_actual": 2216000.0,
"tenant_share_uncapped": 91299.2,
"tenant_share_capped": 91299.2,
"estimated_billed": 84000.0,
"true_up_amount": 7299.2,
"settlement": "billed | credited | disputed | unsettled",
"cash_flow_ref": null
}
]Normative rules, all stated-and-verified, none derived:
period_endMUST be on or afterperiod_start(REC-04).- The period MUST be closed —
period_endstrictly before the document'sas_ofdate (REC-05). A true-up over a period that has not ended is a forecast, and this RFC does not carry forecasts. - Where
pool_actualandpro_rata_shareare both present,tenant_share_uncappedMUST equal their product at the currency quantum (REC-06). tenant_share_cappedMUST NOT exceedtenant_share_uncapped(REC-07). The cap arithmetic itself is stated, not recomputed — a cumulative cap depends on a base-year history the document does not carry, and inventing it is exactly the kind of silent assumption this project refuses.REC-07is the honest, checkable half.true_up_amountMUST equaltenant_share_capped − estimated_billedat the currency quantum (REC-08). A positive amount is owed by the tenant.cash_flow_ref, when stated, MUST resolve to a §4.26cash_flow_series(REC-09), which is where the settled amount becomes a dated cash line the assembler and receipt coverage can already verify. This is the cross-cutting requirement that §4.26 is the addressable sink for new dated cash.
Validator family REC-NN (protocol §XI code table)
| Code | Severity | Refuses |
|---|---|---|
REC-01 | error | pro_rata_share outside (0, 1], or stated as a percent (> 1) |
REC-02 | error | A method whose required input is absent |
REC-03 | error | A recoverable_pool entry naming no §4.4 expense key |
REC-04 | error | period_end before period_start |
REC-05 | error | A true-up period that has not closed as of the document date |
REC-06 | error | tenant_share_uncapped disagreeing with pool_actual × pro_rata_share |
REC-07 | error | tenant_share_capped exceeding tenant_share_uncapped |
REC-08 | error | true_up_amount disagreeing with capped share less billed |
REC-09 | error | cash_flow_ref that does not resolve |
REC-10 | warning | cam_cap_pct stated alongside recovery_terms.cap — the legacy field is superseded and the two can disagree |
Library surface
Additive exports from @uwmd/core:
verifyRecoveryTrueUp(terms, rows, context)→RecoveryVerification, following theverifyWaterfall/verifyCashFlowSeriesposture: no claims meansverified, an unresolvable reference meansunverifiable.- Types
RecoveryTerms,RecoveryTrueUpRow,RecoveryVerification.
Compatibility analysis
- Existing
.uw.mdfiles — unaffected. Both fields are optional; a document stating neither behaves byte-identically and validates exactly as before. One conformance fixture proves that. cam_cap_pct— retained, not removed. It becomes a legacy alias thatREC-10warns about when contradicted. Removing it would break files in the wild for no gain.- Tier-1 Reader / Tier-2 Editor — unaffected; new optional object.
- Tier-3 Calc Host — unaffected. No pack formula reads these structures. Consistent with the RFC 0054 finding, calc paths are flat identifiers and these are collections; they are read by verifiers and host code.
- Tier-4 Agent Host — an agent may extract and state these figures. It MUST NOT compute them, per invariant 1.
- Modules — no manifest change.
- Protocol — additive: one new code family. Minor bump.
Conformance
Per the cross-cutting requirement, at minimum:
- one engine-produced fixture with a full net-lease recovery and a settled true-up that verifies;
- one absent-case fixture proving a document with no
recovery_termsis byte-identical and verifies as before; - one fixture per refusal code
REC-01–REC-09; - one
REC-10warning fixture with bothcam_cap_pctand acapobject; - one base-year-stop and one fixed-stop fixture, since the two methods are the ones most often conflated.
Alternatives considered
- Recompute the cap. Rejected. Cumulative and compounding caps need a base-year history across prior reconciliations that the document does not carry. Recomputing from one year would produce a confident wrong number.
REC-07checks the direction, which is the part that is actually knowable. - Allocate the pool across tenants. Rejected. That requires every tenant's share to sum correctly and a policy for vacant space; it is a modeling decision, not a recorded fact. Each tenant states its own share.
- Put recoveries in the deferred per-lease periodic series. Rejected by RFC 0054's own reasoning: an annual reconciliation of a closed period is not a periodic ledger and does not need one.
- Leave it to
other_income.utility_reimbursements. That is where recovery income lands today, and it is why a reader cannot tell a capped modified-gross recovery from a flat reimbursement.
Errata (implementation)
Two things the draft got wrong, corrected in the implementation rather than carried forward:
- The section is §4.3, not §4.9. The commercial tenant record lives in the Rent Roll (§4.3) — which is where RFC 0055 put the lease clauses these sit beside. §4.9 is the DCF section. Every rule is registered against §4.3.
- No
verifyRecoveryTrueUpexport. The draft proposed a verifier surface likeverifyWaterfall. That was the wrong shape: a verifier surface exists where an engine recomputes a whole allocation (§4.24–§4.27), and a CAM true-up is three subtractions. It is implemented in the validator, matching its actual siblings —TAX-NN(RFC 0053),HDG-NN/ESC-NN(RFC 0056) andCAPX-NN(RFC 0057) all do arithmetic there. Adding a verifier export would have introduced a second place to look for one contract's rules.
A third refinement: REC-05 anchors on the rent roll's own as_of_date and is skipped when that is absent, rather than falling back to file metadata as "the document's as_of date" loosely implied. File metadata is an edit timestamp; using it would refuse a legitimately re-saved document.
Unresolved questions
- Whether
REC-05should be a warning for a fragment. A.uwpart.mdlease abstract may legitimately carry a true-up for a period that has not closed relative to its own thin metadata. The LU-04 precedent suggests a downgrade; proposed here as staying an error, matching theWF-02posture. - Whether the recoverable pool should support a label-bearing
other, as RFC 0052'ssale_deductionsand RFC 0056's escrows do. Proposed: no, on the grounds that the pool names §4.4 keys and §4.4 already hasother_expenses. Worth a reviewer's second look. - Admin fee base.
admin_fee_pctis stated but this RFC does not say whether it applies to the pool before or after the cap. Both conventions exist. Proposed: state the resulting figures and do not take a position, consistent with RFC 0055's decision to state resulting rent rather than the increment.