Status: implemented
Implemented — this change has shipped.
RFC 0037: Cross-check resolution over variant maps, and a validation coverage channel
Status note. Accepted and implemented 2026-09-09 under owner-led governance, in the same change as this document. Raised by underwriter.cc as UPSTREAM-005 (2026-09-02) after a $15.2M senior-loan disagreement validated clean for weeks because every cross-check that would have caught it had silently no-opped on a variant map.
Summary
Two related defects in the reference validator's cross-section consistency pass (checkCrossSectionConsistency, format spec §5.3) are fixed by two normative additions. First, a resolution rule: when a section a CC-NN rule reads is present as a variant map, the validator resolves it to one block by a stated preference order (default, then base, then a rule-specific variant such as t12 for operating_statement or appraisal for due_diligence), takes a lone variant when there is exactly one, and otherwise declares the section unresolvable for cross-checking and says so with a new info code, CC-16. Second, a coverage channel: a conforming validator reports, for every CC-NN rule it registers, whether the rule was evaluated or skipped and why, so "checked and clean" is distinguishable from "never looked". Neither addition changes any rule's threshold or severity.
Motivation
- Variant maps silently disable rules.
getSectionreturnsnullfor a multi-variant section — correct, and documented as "usegetSectionVariant" — but ten of the fifteen cross-checks resolve their sections with a baregetSection. Splitting a section into variants is a protocol feature, not a producer error: format §2.8 names six multi-variant sections, and the UW JSON envelope (RFC 0014,uw-document-envelope.schema.jsonsections→oneOf: [block, variantMap]) admits a variant map on any section, so an envelope carrying a senior facility and a mezz sleeve as twodebt_structurevariants is conforming and losesCC-02,CC-03,CC-05andCC-09with no diagnostic. On underwriter.cc's exportsdebt_structureis a variant map on 100% of documents. - There is no coverage channel.
ValidationResultdistinguishes error / warning / info, but not "checked and clean" from "never evaluated". A consumer cannot tell a reconciled document from one whose reconciliation rules all no-opped.verifyCapitalStackalready models the right posture withCS-SIZING-UNEVALUABLE; the cross-check pass had no equivalent. - The precedent exists and is inconsistent.
CC-15already reads "the base variant" oflease_up_schedule; theoperating_statementlookup already preferst12thendefault;stress_testsprefersdefault. Three rules had three private conventions and twelve had none.
Proposed change
Format spec §5.3 — resolution over variant maps
Add, after the check table:
Resolution over variant maps (RFC 0037). A cross-section check reads each section it names as one block. When that section is present as a variant map (§2.8, or any section of a UW JSON envelope carrying more than one block), a conforming validator MUST resolve it by this order and take the first variant present: (1) the check's registered preference, if any —
CC-01preferst12onoperating_statement;CC-08prefersappraisalondue_diligence; (2)default; (3)base; (4) the sole variant, when the map holds exactly one. A check whose own rule exempts every variant but a named one (CC-15reads the lease-up base variant only, RFC 0008) stops after that name and reportsnot_applicablerather than unresolvable. If none applies the section is unresolvable for cross-checking: every check that reads it is skipped, the skip is recorded in coverage (below), and the validator emitsCC-16once per unresolvable section asinfo, naming the variants found and the checks skipped. A validator MUST NOT pick an arbitrary variant, average variants, or check variants pairwise — a document that states two senior facilities has not stated which one reconciles withsources_uses.
Coverage (RFC 0037). A conforming validator MUST report, alongside its issues, a coverage record for every
CC-NNcheck it registers:evaluatedwhen the comparison was actually performed (whether or not it produced an issue), orskippedwith one of four reasons —section_absent,variant_unresolvable,field_absent,not_applicable— and a short detail naming the section, field or precondition. A check with no coverage entry is a check the validator does not implement. Coverage is reporting, not a verdict: it never changesoverall_status.
Add to the check table:
| Check ID | Description | Sections |
|---|---|---|
CC-16 | A section a cross-check reads is present as multiple variants and none resolves under the §5.3 preference order; the checks reading it were skipped (info) (RFC 0037) | any |
Protocol spec §III.6a
No new family: CC-16 sits in the registered CC-NN sequence. A sentence under the taxonomy table points at the coverage record as the mechanism a host uses to display "not evaluated" beside "clean".
@uwmd/core
ValidationResultgainscoverage: Record<string, CrossCheckCoverage>, keyedCC-01…CC-15;CrossCheckCoverageis{ status: 'evaluated' | 'skipped'; reason?: CrossCheckSkipReason; detail?: string }. Both types are exported.CC-16itself is an issue, not a coverage key.CROSS_CHECK_VARIANT_PREFERENCE(['default', 'base']) andCROSS_CHECK_RULE_IDSare exported fromprotocol.ts;BUILTIN_REMEDIATIONSgainsCC-16.uwmd validateprints a one-line coverage summary in its human output (--jsoncarries the full record).
Compatibility
- Format: additive. No document that validated before validates differently except that a document with an unresolvable variant map now carries an
infoissue it did not have.overall_statusis unchanged in every case (infonever moves it). - Protocol: additive — a new registered code in an existing family and a new field on the validation result. Candidate for the next protocol minor; this RFC does not cut it.
- Documents whose variant map resolves under the new order (a
defaultor lone variant) now have checks evaluated that were previously skipped and may gain realCC-NNwarnings or errors. That is the point: those disagreements existed and were invisible. - Consumers constructing
ValidationResultby hand must addcoverage. The tier-1 baselines compareoverall_statusand issue(code, severity)pairs only, so no existing baseline moves.
Conformance
tier-1-reader/fixtures/08-variant-cross-checks.uwx.md:due_diligenceas two variants (appraisal,environmental) besidevaluation.appraised_value— resolves by preference andCC-08fires;operating_statementast3+budgetandstress_testsasdownside+upside— neither resolves, soCC-01,CC-07andCC-09are skipped andCC-16is emitted once per section. The frozen verdict pins all of it.- Unit coverage in
validator.coverage.test.ts: every reason, the preference order, the lone-variant rule,CC-16de-duplication, and that coverage keys are exactly the registered rule ids.
Implementation
Landed with this RFC: validator.ts (resolveCrossCheckSection, the coverage ledger threaded through the five functions that emit CC codes), types.ts, protocol.ts, cli.ts, index.ts, the fixture and its baselines, and the two spec edits above.
Alternatives considered
- Check every variant pairwise. Rejected: a document with two
debt_structurevariants (senior, mezz) is not two candidate seniors; a pairwiseCC-03would report the mezz sleeve as a failed senior reconciliation, which is a false positive with the same cost as the false negative it replaces. - A declared aggregate variant (
variant=aggregate) the producer must emit. Rejected for now: it moves the burden to every producer and invents a block that restates other blocks' sums, which is exactly the kind of restatementCC-NNexists to police.defaultalready serves as the producer's declared answer, and a producer that wants a check evaluated can name one. - Coverage as
infoissues only. Rejected: fifteeninfolines on every clean document would bury the real ones. Coverage is a record beside the issues;CC-16is the one case that deserves an issue because the producer can act on it.
Unresolved questions
- Whether the envelope should restrict variant maps to the §2.8 multi-variant sections. This RFC deliberately does not: the envelope grammar has been public since RFC 0014 and producers rely on it. If a future RFC narrows it, the resolution rule here still applies to the sections that remain.