Status: implemented
Implemented — this change has shipped.
RFC 0029: Make stage requirements class-aware
Accepted and implemented 2026-08-26, as proposed. The two deferred questions (land-specific
dcf/stress_teststreatment; an author-stated consolidated mixed-use operating statement) remain open for future evidence. The conformance fixtures landed in the Tier-1 valid set as this RFC preferred, pinning the exemption, the substitution, and the office control through the frozen validation verdicts.
Summary
Format spec §5.1's per-stage required-section lists are class-agnostic, so a land deal is required to produce a rent_roll — a section raw land cannot honestly have — before it may declare full_underwrite, and a mixed-use deal is required to produce a property-level rent_roll that would wrongly duplicate the per-component income detail its components section already carries. RFC 0028 made these requirements visible (DQ-06, stage_readiness); this RFC makes them true.
The proposal is one small normative overlay table in §5.1, mirrored by an executable registry in core (the RFC 0027 pattern: declare per-class variation once): land is exempt from rent_roll and operating_statement at every stage, and mixed_use substitutescomponents for both. Every other class keeps the base lists unchanged — including hospitality, whose rent_roll section already carries keys/ADR/segmentation rather than unit rents, the existing precedent that a section id is a class-neutral container.
Motivation
Found by doing, twice:
The RFC 0028 example cleanup could not make the land and mixed-use examples honest at their real depth.
Sundance-Ranch-Land-Buckeye-AZcarries a NOI carry model, valuation, debt, sources & uses, and a DCF — real underwriting depth — but had to restage toscreening, because no stage abovescreeningis declarable without arent_rolland raw land has no tenants.Roosevelt-Row-MixedUse-Phoenix-AZrestaged for the same reason while itscomponentssection holds exactly the income detail the requirement wants, per use, as RFC 0019 designed. The workaround is recorded indocs/wiki/13-status.mdwith this RFC named as the fix.The requirement contradicts decisions the project already made. RFC 0019 ruled that mixed-use income lives per-component in
components(CC-12foots the property NOI from component NOIs), and theLAND_PACKdeliberately computes no income-tenancy metrics at all — the format already knows these classes diverge; §5.1 is the one table that was never told.The scan evidence. Every class but two reuses the base sections with class-appropriate payloads (the hotel example's
rent_rollis keys/ADR/segmentation; senior housing's is unit/acuity mix) — reuse works. Onlyland(a section that cannot exist) andmixed_use(a section that must not exist at property level, per MU-design) genuinely diverge. The overlay is therefore two rows, not a matrix.
Proposed change
1. §5.1 gains a normative overlay table
After the base per-stage table, add:
Class overlays. The base lists assume an income property with direct tenancy. Two asset classes diverge structurally, and for them the requirements are adjusted as follows — exhaustively; an asset class not listed here (including an unrecognized one) takes the base lists verbatim:
asset_classAdjustment landrent_rollandoperating_statementare exempt at every stage: raw land has no tenancy and no operating statement — its carry lives innoi_modelanddcf, and nothing stands in for the exempted sections.mixed_usecomponentssubstitutes forrent_rollandoperating_statement: wherever a stage's base list requires either, the requirement is satisfied by (and only by) acomponentssection — which RFC 0019 §4.23 already makes the home of per-component income and operating detail.A substitution is checkable — the substitute section is required at the stage where the replaced section would have been — where an exemption simply removes the requirement. Section ids remain class-neutral containers otherwise: hospitality's
rent_rollcarries keys/ADR/segmentation, senior housing's carries unit/acuity mix, and no overlay entry exists for them because reuse is the design, not a gap.
2. Core mirrors it once (validator.ts)
export const STAGE_SECTION_OVERLAYS: Partial<Record<AssetClass, {
exempt?: readonly string[];
substitute?: Readonly<Record<string, string>>;
}>> = {
land: { exempt: ['rent_roll', 'operating_statement'] },
mixed_use: { substitute: { rent_roll: 'components', operating_statement: 'components' } },
};One resolution function (requiredSectionsFor(stage, asset_class)) applies the overlay to the base list, deduplicating a substitute that replaces two sections. Both consumers use it: computeStageReadiness and the RFC 0028 DQ-06 check — the booleans and the issues stream must never disagree about what a stage requires. CC-14 is untouched: property is never exempt for any class. An unrecognized asset_class (or none) takes the base lists.
- Files affected:
packages/uwmd-core/src/validator.ts(the registry, the resolver, both consumers);validator.section-readiness.test.ts(new cases); spec §5.1; conformance fixtures below. - API surface:
STAGE_SECTION_OVERLAYSandrequiredSectionsForexported fromvalidator.ts; whether they joinindex.tsfollows the existing precedent ofSTAGE_REQUIREMENTS(export where it is exported, not otherwise). - Additive; no deprecation.
Compatibility analysis
- Existing files: pure relaxation — no document that validates today stops validating, and no code fires that did not fire. For
landandmixed_usedocuments, someDQ-06info notes disappear andstage_readinessbooleans can flipfalse → true. Neither is pinned by any receipt, digest, or Tier-1 baseline (stage_readinessis not part of the frozen validation verdict, which records codes and severities only — a mixed-use or land Tier-1 fixture with aDQ-06baseline entry would regenerate, and none exists today). - Tier-1 Readers: must consult the overlay when computing readiness or emitting
DQ-06; a reader that ignores it over-reports info-severity gaps, which is the pre-RFC behavior — nonconforming but not dangerous. - Tiers 2–4, modules, receipts, Excel: untouched; no number moves.
- The mixed-use substitution tightens nothing today: a
mixed_usedocument withoutcomponentsalready refuses (MU-01machinery requires ≥2 components), so requiringcomponentsatfull_underwritecannot newly reject any currently-valid document.
Conformance impact
New fixtures (a stage-overlay addition to the tier-1 valid set or a small named group — implementation's choice, with the tier-1 valid set preferred because its validation-verdict baselines freeze exactly the assertion wanted):
- A land deal at
full_underwritewith norent_roll/operating_statementbut the rest of the list: zeroDQ-06, andstage_readiness.full_underwritetrue. - A mixed-use deal at
full_underwritewhosecomponentssatisfies the substitution: zeroDQ-06for the substituted sections. - A control: an office deal missing
rent_rollatfull_underwritestill gets itsDQ-06— the overlay is two rows, not a loophole.
Existing fixtures: none change — the capital-stack agree/mismatch pair and the RFC 0028 malformed fixture use multifamily/office shapes the overlay does not touch.
Follow-up (not gating): Sundance-Ranch-Land-Buckeye-AZ and Roosevelt-Row-MixedUse-Phoenix-AZ may restage upward once the overlay lands — to the stage their contents then satisfy, which for both still means first adding borrower_sponsor / preliminary_sizing / market_analysis for anything above term_sheet's neighborhood. Restaging is example authoring, not protocol work.
Reference implementation
Registry + resolver + two consumer edits + tests, sized like RFC 0027's core change. Test plan: the resolver against all seven stages × {land, mixed_use, office, unrecognized}; the substitution-dedup (two replaced sections, one components requirement); DQ-06 and stage_readiness agreement pinned by a test that diffs their verdicts over the same documents; CC-14 still fires on a property-less land deal.
Alternatives considered
- Full per-class requirement matrices (each class declares its own seven lists). Ten near-identical copies of the base table that can drift independently — the exact shape RFC 0027 §XIII was built to avoid. The overlay expresses the two real divergences in two rows.
- Drop
rent_rollfrom the base lists entirely. Under-requires the eight classes where direct tenancy is the business; the info-severity gap reports would stop naming the most load-bearing missing section. - A
not_applicablemarker in the document (author writesrent_roll: nullwith a reason). Moves a class-structural fact into every instance document, invites boilerplate, and leaves the requirement wrong by default. Class structure belongs to the class, not the file. - Leave it:
screeningis good enough for land/mixed-use. Caps two classes' declarable stage below their actual underwriting depth forever, and makesdeal_stagemean different things per class — the opposite of what RFC 0028 established.
Unresolved questions
- Whether
dcf/stress_testsatcredit_approvalwant land-specific treatment (a land carry stresses differently). No evidence yet; the overlay table is the natural home if one emerges. - Whether
operating_statement's mixed-use substitution should also accept a consolidated property-level statement when an author states one. Deferred: CC-12 foots NOI from components, and a second statement is a second thing to reconcile.
Prior art
- RFC 0027 — the declare-per-class-variation-once registry pattern and its "no silent per-consumer re-derivation" rationale, reused directly.
- RFC 0019 §4.23 — establishes
componentsas the mixed-use income home, which is what makes the substitution well-defined. - JSON Schema's conditional
required(if/then) solves the same shape — requirements that depend on a discriminating field — and is why the overlay is keyed onasset_classrather than free-form predicates.