Status: accepted
Accepted — merged with intent to implement.
RFC 0098: Declare the point in time of the capital stack and senior loans, and reconcile CC-03 per point
Accepted by the UWMD maintainers, 2026-10-19, as drafted (owner: "RFC 0098 (#402) is accepted as drafted; implement it"). The Decisions table D1–D9 stands as written. This is a breaking format change (a newly required field); it opened alone as a draft, and the synchronized implementation follows in its own PR.
Summary
capital_stack (§ 4.24) gains a required point_in_time, either closing or stabilization. A debt_structure block (§ 4.7) MAY state the same field. When a block omits it, the loan is outstanding at both points: one loan held through the deal, which covers every single-loan record. § 4.8 states that sources_uses is the closing statement. CC-03 then compares only like-dated figures. For each point it selects one senior debt_structure from the blocks outstanding at that point, using the unchanged RFC 0040 order. It compares sources_uses with the closing senior, and the stack's senior_debt tranche with the senior outstanding at the stack's point. A leg with no figure for its point is not_applicable in coverage, not an error. A construction-to-permanent record whose figures are all correct passes CC-03. Arithmetic and the $100 tolerance do not change.
Motivation
Issue #403, filed by an adopter, reports a correct construction-to-permanent record that fails CC-03 with an error:
capital_stackis one point in time. RFC 0033 makes it the stabilized stack for a deal underwritten to stabilization, so itssenior_debttranche is the permanent takeout.sources_usesstates the closing. At closing the senior source is the construction facility, on its own line, andsources.loan_amount(the takeout) is 0.- The record carries two senior
debt_structureblocks, the construction facility and the takeout, both declaring_role: senior. RFC 0040 role selection finds a role collision, recordsvariant_unresolvableand emitsCC-16, soCC-03selects nodebt_structure. - With no
debt_structureselected, the stack leg falls back to the closing figure (validator.ts,reference = dsLoan ?? suLoan). It compares the 18,000,000 takeout with 0 and raisesCC-03(error).
Every figure is right. No section says which date its senior figure describes, so the check compares a stabilization amount with a closing amount. The adopter refuses to sign such records today, and before RFC 0033 its predecessor suppressed capital_stack on development deals altogether.
RFC 0033 anticipated this. It rejected a temporal marker in 2026-09 because per-phase reconciliation had no defined semantics ("which phase does CC-03 check against debt_structure?"). It left open "whether sources_uses needs a matching statement". This RFC answers both questions for the two points the standard already reasons about, and only those two.
Proposed change
1. Format § 4.24 capital_stack: a required point_in_time
Add to the section's top-level fields and normative rules:
point_in_time— REQUIRED.closingorstabilization. Non-extensible. It names the one point in time the stack describes (RFC 0033):closingfor the capitalization funded at closing (including a deal underwritten during its bridge period), andstabilizationfor the stabilized (permanent) capitalization. A stack that omits it, or states any other value, MUST be rejected (CS-04).
RFC 0033's rule is unchanged: the stack states one point, and facilities that do not coexist at that point MUST NOT be concurrent tranches. RFC 0033 could not check this mechanically. The declared point now lets CC-03 read the right senior.
A mixed-use component stack (§ 4.23) has the same shape and carries the same required field, with the same CS-04 rule. CC-03 still does not apply to it.
2. Format § 4.7 debt_structure: an optional point_in_time
point_in_time— OPTIONAL.closingorstabilization. Non-extensible. It names the only point at which this loan is outstanding:closingfor a facility retired before stabilization (a construction loan, typically), andstabilizationfor a facility first funded after closing (a permanent takeout). A block that omits it states a loan outstanding at both points, one loan for the hold. A stated value outside the vocabulary MUST be rejected (DS-01).
The field describes when the loan exists. It does not change any debt_structure figure, and loan_purpose/loan_type keep their meaning. A producer MUST NOT infer point_in_time from loan_purpose: construction. A construction loan that converts to permanent is one loan outstanding at both points and states no point_in_time.
3. Format § 4.8 sources_uses: the closing statement
sources_usesstates the closing: the sources that fund the deal and the uses they pay for, at closing. Its senior bucket (sources.loan_amount, orsources.debt_proceedswhen that is absent) is a closing figure.
No field is added. A facility funded at closing appears in sources however it is retired later. This answers RFC 0033's second unresolved question.
4. Format § 5.3 CC-03: reconcile per point
Replace the CC-03 description in the § 5.3 table and the § 4.24 bullet ("same amount, same rate") with:
CC-03— senior loan reconciles at each point in time. For a point P (closingorstabilization), the senior candidates at P are the currentdebt_structureblocks whosepoint_in_timeis P or absent.CC-03selects at most one block per point by the unchanged § 5.3 resolution order (CC-03's registered preferencesenior, then the RFC 0040 sequence itsviavalues name), applied to the candidates at that point only. A standalonedebt_structureis the single candidate at every point it is outstanding. Two legs:
- Closing leg. The
sources_usessenior bucket against theloan_amountof the senior selected atclosing.- Stack leg. When a top-level
capital_stackis present, itssenior_debttrancheamountagainst theloan_amountof the senior selected at the stack'spoint_in_time. When the stack is atclosingand that senior states noloan_amount, the reference is thesources_usessenior bucket, as today. Astabilizationstack never falls back tosources_uses, which is a closing figure.Each leg compares within the existing $100 tolerance and reports
CC-03(error) at the existing field. A leg that has no selected senior at its point, or no figure on either side, does not apply. The rule isevaluatedwhen either leg compared. Otherwise it isskippedunder the existing reasons:variant_unresolvablewhen the candidates at a point collide,section_absent,field_absent, ornot_applicablewith a detail naming the point (for exampleno stabilization senior debt_structure).
A role collision is now judged within one point's candidates. In #403's record, the construction facility (closing) and the takeout (stabilization) are each the sole senior candidate at their point, so neither collides. Two senior blocks at the same point (or two that omit the field) still collide, exactly as today.
This RFC preserves the bullet's "same rate" clause per point and does not change which figures CC-03 compares; see Unresolved questions.
5. Protocol: role preference and coverage evidence
CROSS_CHECK_ROLE_PREFERENCE['CC-03']is unchanged (debt_structure: senior). The Protocol's § 5.3 resolution text gains one sentence: a check that resolves per point (CC-03only, in this RFC) applies the order to each point's candidates separately.- Selection evidence (RFC 0040). Coverage
resolutionstoday maps a section id to one{ variant?, via }.CC-03can make twodebt_structureselections. It records them under the keysdebt_structure@closinganddebt_structure@stabilization, one per point that resolved. No other check's keys change. The coverage schema admits the@closing/@stabilizationsuffix ondebt_structureforCC-03only. - Two diagnostics are added to the closed registry:
CS-04(error, § 4.24:capital_stack.point_in_timeabsent or outside the vocabulary) andDS-01(error, § 4.7: a stateddebt_structure.point_in_timeoutside the vocabulary).
6. Schemas
section-capital-stack.schema.json: addpoint_in_time(enum: ["closing", "stabilization"]) topropertiesand torequired. The schema stays closed.debt_structurehas no section schema today. The field is validated in code (DS-01) and documented in § 4.7. No new schema file is added.- The coverage
resolutionskey pattern widens as in § 5 above.
Compatibility analysis
This is a breaking format change, deliberately: point_in_time is required on every capital_stack.
- Existing
.uw.mdfiles. Every document that carries acapital_stack(top-level or a mixed-use component's) failsCS-04until it statespoint_in_time. The corpus and examples hold 10 such documents. Under RFC 0033 their stacks are already one point in time, so each gainspoint_in_time: stabilization(orclosingwhere it states a bridge-period stack) with no other change. Adebt_structurewithout the field keeps its exact current meaning. A record with nocapital_stackis unaffected. - Implementers. A Reader is unaffected. A Verifier must implement
CS-04,DS-01, per-point candidates and the widenedresolutionskeys. The independent Python implementation (Issue #354) follows in the same synchronized implementation, as RFCs 0090 to 0097 did. - Adopters. Adopters state the field on emitted stacks. The reporting adopter can sign construction-to-permanent records once it lands.
- Transition. None, by the prelaunch rule: no grandfathering, default or deprecation period for the required field. Absence on
debt_structureis not a compatibility default. It is the defined meaning "outstanding at both points" (see Alternatives). - Versions. Following RFC 0091's D6, acceptance allocates no Format, Protocol or package version. Prelaunch work stays 4.x, and the coordinated 5.0 launch is the owner's.
Conformance impact
Existing inputs updated (expected findings unchanged unless noted):
- the seven
conformance/documents that state acapital_stackgainpoint_in_time: stabilization:capital-stack/reject-waterfall-in-v1/deal.uwx.md,capital-stack/senior-reconciles-debt-structure/{agree,mismatch}.uwx.md,capital-stack/split-coupon-bad-fields/deal.uwx.md,documents/fixtures/17-exit-proceeds-junior-capital.uwx.md, anddocuments/malformed/{46-payload-sizing,48-payload-lawful}.uwx.md. The component stack in46-payload-sizinggets the field too. Any receipt or digest expectation over a changed input is regenerated from the unchanged procedure, and the PR names each one. examples/Agave-Court-Apts-Scottsdale-AZ.uwx.mdandexamples/verification/*: the same edit.
New cases, under conformance/capital-stack/, all synthetic with round amounts:
construction-takeout-reconciles: #403's example. Astabilizationstack (senior 18,000,000, mezzanine 2,000,000), aclosingconstruction block with noloan_amount, astabilizationtakeout at 18,000,000, andsources_usessources.loan_amount0. Expect noCC-03,CC-03evaluated, noCC-16forCC-03, and bothresolutionskeys.construction-takeout-disagrees: the same record with the takeout at 18,500,000. ExpectCC-03ontranches.senior_debt.amount.closing-leg-disagrees: the construction block states 17,000,000 andsources_usesstates 16,000,000. ExpectCC-03onsources.loan_amount.single-loan-both-points: onedebt_structurewith nopoint_in_timeand astabilizationstack. Both legs compare the one loan, matching today's results.same-point-collision: twoseniorblocks both atstabilization. Expectvariant_unresolvableandCC-16as today.stabilization-no-fallback: astabilizationstack, aclosing-only senior andsources_uses0. The stack leg isnot_applicable, not compared withsources_uses.stack-point-in-time-absentandstack-point-in-time-unknown:CS-04.debt-point-in-time-unknown:DS-01.
Each case runs through the RFC 0004 runner, so the Python implementation is held to it by the same corpus.
Reference implementation
@uwmd/verifyvalidator.ts:CC-03builds candidates per point and resolves each with the existingresolveCrossCheckSectionmachinery restricted to those blocks. It addsCS-04besideCS-01/CS-02incheckStackContent(CS-03stays inpayload-admission.ts) and addsDS-01.protocol.tsregisters the two codes and the § 5.3 sentence and widens the coverageresolutionstype.index.tsgains no new export.- Schemas:
section-capital-stack.schema.json, and the coverage pattern. - Python (
implementations/python): the same per-point candidates, codes and coverage keys in its validation modules. It is held by the corpus above. - Starter tools: the web editor has no capital-stack surface, so nothing changes there; it shows
CS-04/DS-01through its existing per-section issue list. Excel output is unchanged. - Tests: a
validator.capital-stack.test.tsblock per new case, and a negative control showing that removing the per-point filter reproduces #403's false error.
Alternatives considered
- Additive and optional on both sections (#403's own shape). Absent on the stack would mean "behave as today". It is equally sound for
CC-03, but it keeps RFC 0033's one-point rule uncheckable for every stack that omits the field, and it adds a second, undated way to state a stack. With compatibility waived, requiring the field on the one section whose meaning is "one point in time" is cleaner. - Required on
debt_structuretoo. A single-valued required field cannot say "one loan held through both points", the most common record. That needs a third value (both) or an array ([closing, stabilization]). Both add a token to every single-loan record to restate what absence already says, and they invite the illegal pair of abothblock beside a dated one. The rejection is recorded so the owner can overrule it at acceptance. - Infer the point from
loan_purpose: construction/loan_type: construction. This is inference, which the standard refuses: a construction loan that converts to permanent is outstanding at both points, and a bridge loan can be either. The producer knows the point and states it. - Field name
stage(the issue's word). The Format already uses "stage" for the pipeline (deal_stage, and "Required for pipeline stage" on every section), anddeal_stageitself takes the valueclosing.capital_stack.stage: closingwould read as a pipeline milestone.point_in_timeis RFC 0033's own term for exactly this property. - Skip the
sources_usesleg when the stack is stabilized.CC-03would stop comparing instead of comparing like with like (rejected in #403). - Per-tranche dating (
active_from/retired_by) or one stack per phase. This is RFC 0033's deferred Phase 2 design. It needs the multi-period spine to verify sizing per phase. This RFC dates whole sections at two named points, which needs no spine and leaves that design open.
Unresolved questions
- CC-03's rate leg. § 4.24 says the senior tranche reconciles in "same amount, same rate", but the TypeScript
CC-03at16cf18c3compares amounts only. The independent Python ledger records a trancherateagainstdebt_structure.interest_ratecomparison. That divergence predates this RFC and is filed separately as #406. Whatever resolves it applies per point under this RFC. - Other senior-preferring checks.
CC-02,CC-05,CC-09,HDG-01–HDG-10andESC-04keep their single selection. In a two-senior record they still meet the role collision, recordvariant_unresolvableand emitCC-16(info), which does not fail the record. Which point each should read (for exampleCC-05's DSCR against stabilized NOI) is a separate question for its own RFC, together with #382 / RFC 0092's construction-and-takeout LTV basis. - Points beyond two. As-is / as-stabilized dual sizing and draw-period points stay with the Phase 2 multi-period spine (RFC 0026 § D; RFC 0033).
Decisions (accepted)
| # | Question | Proposal | Why (precedent) |
|---|---|---|---|
| D1 | Field name | point_in_time | RFC 0033's term; stage/closing collide with deal_stage |
| D2 | Required where | Required on capital_stack; optional on debt_structure with absence = both points | RFC 0033 makes every stack one point; absence is a defined meaning, not a default |
| D3 | Vocabulary | Closed: closing, stabilization | The two points sources_uses and RFC 0033 already name; closed vocabularies refuse unknown values |
| D4 | sources_uses | Defined as the closing statement; no field | Answers RFC 0033's unresolved question without a shape change |
| D5 | Selection | RFC 0040 order applied within each point's candidates; collisions judged per point | No new selection order; same-point collisions unchanged |
| D6 | Fallback | Stack leg falls back to sources_uses only for a closing stack | Keeps today's fallback where it is like-dated |
| D7 | Coverage | Existing four skip reasons; resolutions keys debt_structure@<point> for CC-03 | No new reason; two selections need two keys |
| D8 | Diagnostics | CS-04 (stack field), DS-01 (debt field), both errors | Admission codes separate from reconciliation (RFC 0096 precedent) |
| D9 | Versions | None allocated; prelaunch 4.x | RFC 0091 D6 |
Prior art
- Lender practice dates each facility. A construction loan agreement and a permanent loan commitment are separate instruments with separate funding dates, and a takeout commitment is underwritten to stabilization. The closing statement (settlement statement) lists only what funds at closing. This RFC states those two documents' dates on the sections that mirror them.
- RFC 0033 and RFC 0026 § D of this project, which fixed the stack to one point and deferred phased stacks.