Changelog
All notable changes to UW Markdown — the format spec, the protocol spec, the reference library @uwmd/core, the conformance corpus, and starter tools — are documented here. The format is based on Keep a Changelog, and the project follows semantic versioning per surface (the format, the protocol, and each package each carry an independent semver).
Unreleased
Accepted normative errata — Protocol 2.17.1 (RFC 0062, unreleased)
- Same-day
cash_flow_series.seriesrows remain distinct and no longer emit PS-02 merely for sharing a valid date. Ordinary date selection still refuses CALC-PERIOD-002 for a repeated requested date; a unique date resolves even when another date repeats. Other registered series and whole-column Excel duplicate guards retain their existing rules. - Jared accepted RFC 0062 on 2026-09-24, retaining 2.17.1 as normative errata. Status is accepted, not implemented: the reference implementation and tests are prepared but have not shipped. Format stays 2.0; package versions, dependencies and tags do not change. Published core/CLI 2.13.0 continue to pair with Protocol 2.17.0.
2.13.0 - 2026-09-21
Core/CLI 2.13.0 add the already-specified period-indexed navigation capability and ship implementation, security, and representation fixes against Protocol 2.17.0 and Format 2.0. Signing 0.2.17 and batch 0.8.12 repin core. Standalone packages remain unpublished.
Released
The v2.13.0 tag published core/CLI 2.13.0, signing 0.2.17 and batch 0.8.12 to npm with SLSA provenance through trusted publishing (OIDC).
Fixed
- Empty-frontmatter-array fidelity. Model-fidelity exports now serialize an empty frontmatter array as
[]instead of a bare YAML key. The bare key reparsed asnulland changed the semantic digest on seven private golden-deal/control cases under published 2.12.0; the corrected serializer preserves[]and all model-fidelity round-trips pass. See the de-identified acceptance record. - Protocol version-label integrity (RFC 0061). Corrected the protocol document's stale
2.15.0status line and2.8.0§0.3 label to the authoritative2.17.0, and extendedverify-versionsto fail if either checked mirror drifts fromVERSIONS.mdandPROTOCOL_VERSIONagain.
Added
- Period-indexed path navigation in
calc/evaluator.ts, resolving a registered series by its stated period identity (dcf.annual_cash_flows@Y3) per §VIII.2a. Selection is by identity, never by row position. - Release readiness check.
scripts/check-release-readiness.mjs, wired asnpm run release:checkand into.github/workflows/release.yml, verifies that npm Trusted Publishers OIDC is configured before av*tag triggers a publish — the tag is the trigger, so a missing trusted publisher otherwise fails after the point of no return.
Validation
- The frozen private golden corpus accounts for 532 Artifact B assertions: 526 pass and six remain documented source/baseline defects. All 20 refusal assertions and all eight runnable cases pass in this release. GD04 and GD07 remain evidence-blocked. This broad real-deal acceptance does not claim RFC 0045 real-deal property-cash-flow assembly; the required explicit periodic inputs and coverage remain unavailable.
- Release preparation passed 2,433 workspace tests across 138 files, 589 default conformance checks, all three RFC 0030 capability profiles, 46 JSON Schema compilations, 221 emitted-code checks, test typechecking, lint over 1,047 files, package-content verification, documentation build, version/index/release/lockfile guards, and OIDC release readiness.
Decided
RFC 0060 — the four tranche-class candidates, and why none opens the enum. The roadmap carried
ground_lease,pace,tax_credit_equityandsoft_debtas proposedcapital_stackclasses, to be settled in one bounded RFC. They are settled:TrancheClassis unchanged, and no schema, classification set, sizing function, validator rule, Excel formula or conformance fixture moves.ground_lease— wrong layer. ATranchehas no field for tenure, and no honestamountexists for one. Where the underwriting includes ground rent in OpEx, adding it again as a debt tranche would double-count it.pace— no enum;other_debtis honest where the underwriting treats the obligation as a debt-service-bearing tranche whose terms the existing fields express.soft_debt— no enum;other_debtcovers the subset reducible to amount, rate, accrual, position, amortization, IO and term.tax_credit_equity— deferred with its mechanics.sources_uses.sources.tax_credit_equityalready records the amount, and the credit investor's return is not a rate, so the class has nothing a sizing verb consumes until a LIHTC profile specifies pay-in and delivery.
The RFC closes the enum-opening question only. It does not claim these domains are modelled, and says so explicitly. Ground leases in particular are not represented: §4.4 has no
ground_rentkey, §4.5noi_model.expenseshas no ground-rent line and no generic bucket at all, and no section types the leasehold as an object. Likewise unmodelled: PACE assessment servicing and lien behaviour; residual-receipts, contingent payment and forgiveness for soft debt; tax-credit pay-in, delivery, compliance and recapture. Each survives as demand-gated mechanics, profile or tenure work needing its own RFC and a demonstrated consumer — not as tranche-class debt.decidedis terminal; nothing ships from it.
Removed
The unreleased SQL export surface, withdrawn before release.
src/sql.tsexposedexportSql,exportSqlStatements,UWSqlErrorandExportSqlOptionsfrom@uwmd/coreand its browser entry, addeduwmd export --format sqlwith--schema/--no-ddl/--no-views, and emitted PostgreSQL and Snowflake DDL for six tables plus four reporting views. It is removed. Nothing consumed it outside its own tests, and no released version ever contained it.RFC 0049 settles the relational boundary, and this crossed it. The RFC's Non-goals name "warehouse-specific SQL" outright, and it assigns the integration to
@uwmd/lake;docs/DATA_LAKE.mdopens by saying UWMD "is the backbone of a CRE data lake, not the lake itself." The CLI tests labelled the feature "RFC 0049 database lake export", which the RFC does not authorize.It was also a live defect. Both
@uwmd/lakeand the exporter defineuw_documentswith the samesemantic_digestprimary key and the same three index names but different columns — the exporter omitspath,currency_code,format_version,protocol_version,document_profile,valid,error_countandwarning_count— and both default to schemapublic, so they resolve to one relation. Core-then-lake makes the lake'sINSERTfail on a missing column; lake-then-core writes catalog rows with a null validation verdict.Withdrawn rather than moved into
@uwmd/lakeor a new package: a second, BI-shaped relational projection is an architectural decision, and no adopter requirement exists to justify settling its package and schema now. If one appears it returns through an RFC. JSON export is unchanged —uwmd exportandstringifyUWEnvelopebehave exactly as before, and the--formatflag returns to not existing.The unspecified Tier-3 collection surface, withdrawn before release. Two commits on this branch added sixteen builtins —
sum_by,avg_by,min_by,max_by,count_by,count_where,filter,filter_by,find,find_by,map_by,pluck,values,to_array,get,prop— plus numeric bracket indexing in the grammar. All of it is now removed. Nothing consumed it: no pack, no fixture, no tool.It was out of spec on four counts. §VIII.3's table is the enumerated builtin set and §II.3 clause 2 requires a host to implement that set; none of the sixteen were in it. §VIII.1 defines
member ::= "." identifier | "[" string "]"and §VIII.2a says in terms that "numeric bracket indices are not added to that grammar", yet the parser accepteda[0]. §VIII.1 also requires a host to reject what does not parse against the grammar, so this engine accepted documents every other conforming engine must refuse. Andfilter/map_byreturned arrays intoCalcResult.value, whichspec/schemas/calc-result.schema.json— the normative schema declared at the head of Part VIII — types asnumber | string | boolean | null.filterrendered as the display string"[object Object]".RFC 0019 had already considered this exact primitive and rejected it (Alternatives §1), choosing static component slots precisely so that "named ranges stay static and the existing emitter is sufficient". Reintroducing it reopened the Excel↔calc parity hole that design closed: the emitter targets Excel 2016, where
FILTERandLAMBDAdo not exist and an array-valued cell has no representation, sofilterandmap_byhad no workbook counterpart at all.The five scalar aggregators do have a plausible Excel story (
SUMIF,COUNTIF,AVERAGE,MIN/MAX). If a concrete use case appears, they should return through an RFC that pins the grammar delta, the §VIII.3 table rows, and the emission contract — including the empty-collection, null-versus- blank and truthiness semantics, each of which diverges between the calc engine and Excel and so decides parity rather than following from it.A regression test now pins
Object.keys(BUILTINS)to the §VIII.3 set and asserts the grammar rejects numeric indices, so the surface cannot drift back by accretion.
Security
CalcResult.valueis type-checked instead of cast.evaluateCalccast the evaluator's result straight intonumber | string | boolean | null, so a non-scalar reached receipts and the CLI unchecked. It is now refused withCALC-TYPE-001. The removal above eliminates the only builtins that could produce one, but the guard is the invariant, not the absence of a caller — receipts digest this field.The calc evaluator refuses prototype-polluting path segments. A Tier-3 path could previously name
__proto__,constructororprototypeand walk into the prototype chain of the evaluation context. The evaluator now refuses such a segment with the newCALC-FORBIDDEN-PROPcode, alongside the existingMAX_NODEStraversal bound. This matters more now that RFC 0019 lets a formula traverse collections rather than only fixed paths.
Fixed
irrbisects again, as protocol §VIII.3 requires. An earlier commit on this branch replaced the bisection loop with a Newton-Raphson pass seeded at0.1with a1e-7epsilon — the exact procedure §VIII.3 step 5 says an implementation MUST NOT use, and the one RFC 0024 was written to remove. The bisection loop, the1e-9/1e-12stopping conditions and the endpoint- root check are restored;IRR_INITIAL_SEEDandIRR_CONVERGENCE_EPSILONare gone rather than left as dead exports.Two consequences, both user-visible:
- Cross-engine reproducibility is restored. Newton's iterates depend on a derivative evaluation order no document pins, so the regression silently gave up the bit-identical-root guarantee that is RFC 0024's entire purpose.
- Some bracketed cash flows stopped refusing.
irr(-100, -200, 110)brackets a real root at ≈ -0.5509 but Newton stepped outside[-0.999, 10]and raisedCALC-IRR-DIVERGE; it now returns the root. Conversely, roots are tighter:irr(-5, 1, 15)returned an NPV residual of1.9e-8under the loose epsilon and now meets1e-9.
Every conformance fixture passed throughout, in both directions — Newton and bisection agree within the §VIII.5 six-decimal quantum on all of them, exactly as RFC 0024 predicts. The defect surfaced only through an unseeded property test drawing a seed that hit
[-5, 1, 15].The calc property suite is seeded by default.
calc.property.test.tsdrew a fresh seed on every run, so a real defect appeared as an intermittent CI red on whichever push was unlucky and could not be reproduced from the failure alone. It now pins a seed;UWMD_FUZZ=1restores random exploration, and counterexamples it finds should be pinned as examples incalc.test.ts—[-5, 1, 15]now is.@uwmd/lake0.1.2 → 0.2.0, lake schema 0.1 → 0.2. The RFC 0049 adapter was exercised against a live PostgreSQL server for the first time — the whole conformance corpus, 382 documents and 24,380 facts, in one transaction — and three defects fell out that an in-memory double could not have caught.uw_facts.value_jsonwasjsonb NOT NULL, but UWMD's canonical fact table represents an object or an array by its flattened children and leaves the container's own value empty. An empty string is not JSON, so 21.6% of real corpus facts were unloadable and the first one aborted the load. The column is now nullable underCHECK (value_json IS NOT NULL OR json_type IN ('object', 'array')); a container is SQL NULL, and a scalar with no value is refused with the newLAKE_FACT_VALUEcode rather than silently nulled.uw_receipts.verdictprojected a field no receipt carries — a verdict is what verifying a receipt produces (UW_RECEIPT_v1§5), not something a receipt states — so the column was NULL for every real receipt anduw_receipts_verdict_idxled with a dead column. Both are replaced byvalidation_errors/validation_warningsfrompolicy.validation, and the index by(pack_id, pack_version). The unit test had missed this by inventingcomputation.verdictin its fixture.postgresLakeSchema()returns a multi-command script, which cannot be sent throughLakeClient.query(sql, params)at all: a values array, even an empty one, selects the extended query protocol. NewpostgresLakeSchemaStatements()returns the commands individually and is the source of truth; the README anddocs/DATA_LAKE.mdexamples are corrected.
Also documented, not changed:
uw_packagesis keyed by the manifest's declaredpackage_id, the one table not keyed by a digest, so two packages claiming one id collapse. See the load record for the evidence, the row counts, and what the run does not prove — no network, no concurrency, no managed service, and a GIN index the planner did not choose at corpus scale.
[2.12.0] - 2026-09-16
Added
- RFC 0058 types expense recoveries and the CAM true-up on the commercial tenant record (§4.3).
recovery_termscarries the pro-rata share, the stop, the recoverable pool and a cap whoseaccumulationmust be stated;recovery_true_uprecords a closed period's reconciliation, withREC-06checking the share of the pool andREC-08the settled amount. Registers theREC-NNfamily, includingREC-09, which requires a statedcash_flow_refto resolve — the settled amount lands in §4.26, the addressable sink where assembly and receipt coverage already verify dated cash. Nothing is projected, grossed up or allocated across tenants. - RFC 0059 implements the distribution-waterfall clawback as the terminal true-up protocol §XVI predicted, closing a gap RFC 0035 deferred and RFC 0036 deferred again. An optional
distribution_waterfall.clawbackstates a basis (lp_preferred_shortfall,lp_irr_floororlp_em_floor), an optional statednet_of_tax_rate, and acapthat must be"promote_received"— a GP cannot owe back more promote than it received.stated_outcomes.clawback_amountis verified against the recomputation at the currency quantum. RegistersWF-10–WF-13and theWF-15warning; protocol §VIII.10 gains step 5. Nothing is escrowed, crystallized per period, or projected.
Changed
- Protocol 2.15.0 → 2.17.0 for §VIII.10 step 5 and the clawback codes (RFC 0059) and the
REC-NNfamily (RFC 0058). No wire format, formula or precision change; the two receipt fixtures that embedprotocol_versionare repinned.
Fixed
verify-codesis a new CI guard: every code an implemented RFC's table or a format-spec rule bullet promises must be one@uwmd/coreactually emits, and every emitted validation family must be registered in protocol §XI. It exists because RFC 0058 shipped nine of the ten codes it specified and nothing went red — schemas validated, conformance passed (no fixture exercised the missing rule, because the fixtures came from the same incomplete pass), and the RFC read as delivered. A missing refusal looks exactly like a document with nothing to refuse.
Notes
- Every clawback basis is closed-form. The IRR floor reuses RFC 0036's hurdle balance verbatim rather than iterating on
xirr— the calc engine has no iteration, and a design needing a nested solve would have been unreachable. A test pins the boundary property: at a floor equal to the IRR the LP actually achieved, the true-up is zero. - RFC 0058 leaves two figures stated, not recomputed, and says why in the spec: the capped amount, because a cumulative or compounding cap depends on a base-year history no single document carries (
REC-07checks only the direction a cap can move), and the allocation of a pool across tenants, because that needs a vacant-space policy and is a modeling decision.REC-05anchors on the rent roll's ownas_of_dateand is skipped when absent — never on file metadata, which is an edit timestamp. - A waterfall with no provision reports
null; a provision that computes to nothing reports0. Absence and zero are different answers, and a stated amount with no provision behind it isunverifiable, not a disagreement.
[2.11.0] - 2026-09-16
Core/CLI 2.11.0, signing 0.2.15, batch 0.8.10, Protocol 2.15.0, Format 2.0. Five RFCs, all additive: every member of every new structure is optional, and a document stating none of them validates exactly as it did at 2.10.0.
RFCs 0055, 0056 and 0057 type structures nothing yet consumes. No rent escalates, no break is exercised, no balance amortizes, nothing is priced, no strike crossing is projected and no stated saving is applied. Typed-but-inert is the intended state: the format learns to say these things before anything acts on them, and each consumer arrives with its own contract.
Three of the new rules require a disclosure rather than defaulting one, because in each case the unstated reading is the misleading one: HDG-06 (what happens when a rate cap expires), CAPX-07 (whether a stated expense saving is already inside the NOI model) and, from 2.10.0, TAX-08 (whether the terminal tax is inside exit NOI).
Added
RFC 0049 is implemented as
@uwmd/lake0.1.0 (packages/uwmd-lake, unpublished): the reference PostgreSQL/JSONB lake adapter. It plans idempotent, parameterizedINSERT … ON CONFLICT … DO UPDATEstatements over six tables from canonical outputs — envelopes,block_valuesfacts, receipts, package manifests and source-evidence references — and executes them against an adopter-supplied{ query(sql, params) }client. Raw canonical JSON is stored injsonbalongside typed shadow columns, so unknown sections, extension keys, explicit nulls and array order survive a load. Identity is always a digest, never a file path.@uwmd/lakereaders for the three shapes adopters already hold:lakeInputFromEnvelope,readBlockValuesCSV(UW CSV bundle) andreadBatchFactJSONL(@uwmd/batchcorpus fact table).RFC 0055 types the commercial lease clauses that have been untyped stubs since Format 1.0 —
escalation_schedule,termination_optionandco_tenancy_details— and adds thelc_original/lc_outstanding_balancepair beside the tenant-improvement one, registering theLSE-NNvalidator family. Steps state the resulting rent rather than the increment. Nothing is exercised: no rent escalates, no break is taken, no remedy applies and no balance amortizes. Purely additive.RFC 0056 types
debt_structure.rate_hedgeandsources_uses.uses.escrows, registering theHDG-NNandESC-NNvalidator families. A cap now carries a strike, a notional, a term and a statedpost_expiration_assumptionrather than the lonerate_cap_pctit had; escrows carry upfront and monthly amounts under a closed vocabulary with a label-bearingother.ESC-04ties a"replace"assumption to a fundedrate_cap_replacementline, which is the budget a three-year cap on a five-year hold has never had anywhere to go.rate_swapandrate_collarare reserved and refused byHDG-02: their mark-to-market can be negative and a cap's cannot. Nothing is priced and no strike crossing is projected. Purely additive.RFC 0057 types
sources_uses.uses.renovation— budget, contingency, contingency used, a stated-and-verified remaining, and total drawn as of a date — and addsexpense_targeted, capital spend named against thenoi_model.expensesline it reduces. Registers theCAPX-NNfamily.CAPX-07requiresin_noi_model, the disclosure that keeps a stated saving from being applied twice; nothing subtracts a saving from an expense line, from EGI or from NOI.CAPX-08verifiessimple_payback_yearsand refuses it against zero savings rather than admitting an infinity. Redevelopment downtime needs no new field: §4.25natural_turnoveralready carries it. Purely additive.RFC 0054 decides where per-lease economics live and declines the roadmap's premise that lease clauses, CAM true-ups and TI/LC balances share a container. It splits by shape: clauses are attributes of a lease and are typed in place by RFC 0055, while the periodic series waits for a named consumer. The series half is unbuilt by decision, not by oversight — the Tier-3 calc grammar addresses neither collections nor two period dimensions, so a lease × period ledger is unreachable from any pack formula.
Changed
- Protocol 2.13.0 → 2.15.0, registering the
LSE-NN,HDG-NN,ESC-NNandCAPX-NNcode families. RFC 0055 addedLSE-NNto the protocol's code-prefix table without a bump, so released 2.13.0 andmainbriefly described two different tables; the bump covers all four. No wire format, formula or precision changes.
Notes
- The adapter adds no database driver to any package and does not touch the protocol, the format spec, the schemas, conformance or financial math. Source evidence stores identity and status only: a bytes-bearing payload is refused with
LAKE_SOURCE_BYTES. No live PostgreSQL instance is exercised by the tests; a real load remains an adopter integration step.
Fixed
- Format §4.25 and §4.26 claimed
lease_up_scheduleandcash_flow_seriescells stayed "addressable by ordinary path traversal", citing expressions that raiseCALC-PARSE-001. Calc paths are flat identifiers; those structures are read by verifiers and host code, not by pack formulas. verify-indexesnow fails an RFC that is empty, has no frontmatter block, or has frontmatter with no body, and checks the index table's status column against each RFC's own frontmatter. Both gaps let real drift through.- The RFC index listed 0046 (currency identity) and 0047 (property cash-flow input inventory) as
acceptedafter both shipped in 2.10.0.
[2.10.0] - 2026-09-15
Added
- Read-only
uwmd verify-cash-flows <file> [--variant <name>] [--json]over the existing cash-flow metric verifier, with strict input guards, explicit no-stated-metrics results and automation exit codes. No protocol, financial formula, precision tolerance or package-version change. - RFC 0046 adds optional document-level
frontmatter.currency_codeandCUR-01. Explicit identity is rendered as a code prefix while locale still controls numeric separators; absent identity preserves legacy symbols. No FX, mixed-currency arithmetic, or currency-specific precision is introduced. - RFC 0047 adds read-only
inspectPropertyCashFlowInputsanduwmd inspect-property-cash-flows, inventorying source variants, periods, rows and required plan dimensions without assigning economic categories or inferring expense, reserve or payment-timing inputs. - RFC 0048 adds a worked standalone document kit: lease abstract and source note profiles,
.uwpart.mdfragments, inline/externalized composition twins, a packaged.uwpkg.zipexample and a namedstandaloneconformance suite. - RFC 0050 adds
accrual: "split"on preferred-equity tranches, withcash_rateentering cash coverage,accrued_rateexcluded from it, fullratedriving weighted cost, andCS-02bvalidation. Debt PIK toggles and accrued compounding remain deferred. - RFC 0051 lifts
hurdle_mode: "any" | "both"into the normative waterfall contract. On asplittier stating bothuntil_lp_emanduntil_lp_irr,anycaps at the smaller capacity and the defaultbothkeeps the larger.WF-01rejectshurdle_modewithout both hurdles. - RFC 0052 adds an optional closed
sale_deductionsvocabulary naming every disposition cost-of-sale row, and an optionalnet_sale_proceedsfigure verified against gross sale less exit costs at the currency quantum.prepayment_penalty,defeasanceandloan_payoffare reserved and refused by this unlevered assembler. Purely additive: a plan stating neither member is unchanged. No financial formula or derived exit value is introduced. - RFC 0053 types the
noi_modelreassessment basis and adds an RFC 0041 period-addressed abatement schedule, registering theTAX-NNvalidator family.TAX-01-TAX-04check the basis arithmetic, with around_to_decimalsthat may be negative so a deliberate rounding is declared rather than unexplained.TAX-05-TAX-07check the schedule and its tie to the stabilized value.TAX-08requires a sale-triggered terminal basis to equalexit_value_gross, catching a going-in tax carried into terminal NOI. Stated and verified only: no tax is derived, no jurisdiction rules are inferred, and the exit-value circularity remains the author's to converge.
Changed
property-cash-flow-assembly.schema.jsonnow referencesproperty-cash-flow-plan.schema.jsoninstead of restating it. The hand-copied duplicate had already drifted once; a regression test fails if the reference is replaced by a copy again. No validation outcome changes.- The §VIII.5 half-away-from-zero quantizer is exported from
cash-flow-series.tsso a surface comparing a stated figure against a recomputation rounds where the verifier rounds, rather than keeping its own copy.
Fixed
examples/standalone/package/**is marked-text.sources/anchor-lease.txtwas CRLF in the working tree and LF in the index, so the manifest pinned a sha256 over bytes no checkout reproduces andstandalone/package/integrityfailed on every CI runner while passing locally.- RFC 0051's title is quoted. It is the first RFC title to contain
:, which made VitePress read the plain scalar as a nested mapping and fail the site build.verify-indexesnow reports an unquoted frontmatter scalar carrying:across all copied RFCs, so the break surfaces locally. VERSIONS.mdno longer claims@uwmd/exceland@uwmd/reportare unpublished. Both serve a stale0.3.0on the registry, pushed 2026-08-16 during the 1.3.0 manual release and pinned to@uwmd/core1.3.0. Both are pending deprecation.verify-versionsnever contacts the registry, which is why the claim went unchallenged.
Released
Core/CLI 2.10.0 with Protocol 2.13.0 and Format 2.0. Signing 0.2.14 and batch 0.8.9 repin core. Excel 0.9.2, report 0.8.14 and the 0.1.2 reference module packages remain unpublished.
Verification
- Build, full workspace test suite, test typechecking, 504 default plus 76 declarative conformance checks, three capability profiles, 38 JSON schemas, lint, and the lockfile, package, version, index and release guards.
- Receipt baselines regenerated for the new engine version, including the
03-result-disagreesreceipt whoseengine_versionmust be the new one orRCP-07reclassifies the scenario fromfailedtounverifiable.
[2.9.0] - 2026-09-12
Released
Core/CLI 2.9.0 package RFC 0045 with Protocol 2.12.0 and Format 2.0. The v2.9.0 trusted-publishing workflow published all four packages; clean registry installation and API/CLI behavior were verified. Signing 0.2.13 and batch 0.8.8 repin core. Excel 0.9.1, report 0.8.13 and the 0.1.1 reference module packages remain unpublished.
Added
- RFC 0045 / Protocol 2.12.0: candidate-only property cash-flow assembly, explicit unlevered/pre-tax coverage and source evidence, with a read-only CLI. The synthetic example is engineering data; real-deal validation remains separate.
Verification
- Clean installation, build, 1,990 workspace tests, 457 default plus 76 declarative conformance checks, 34 JSON schemas, test typechecking, lint, release consistency checks and documentation build passed.
- Isolated four-package tarball installation verified core/browser and CLI assembly, typed refusals, source preservation and pinned synthetic metrics. Receipt changes are engine-version labels only.
[2.8.0] - 2026-09-12
Released
Core and CLI 2.8.0 package the merged RFCs 0042–0044. Protocol remains 2.11.0 and Format remains 2.0. The v2.8.0 tag and trusted-publishing workflow published core, CLI, signing and batch; clean registry installation was verified. Signing 0.2.12 and batch 0.8.7 repin core; unpublished report 0.8.12, Excel 0.9.0 and the two 0.1.0 reference modules remain outside the publication workflow.
Added
- RFC 0042: refinement uses finite stated period inputs while ranking ordinary scalar gaps. Exact variant/override context and per-output diagnostics preserve missing, nonnumeric, malformed, duplicate and ambiguous inputs distinctly.
--calc-contextsupplies validated inputs touwmd calc,uwmd refineand explicit workbook export. Refinement refuses ordinary scalar overrides; workbook export requires selected custom-calculation IDs.- RFC 0043: contextual Excel bindings and complete period-column snapshots, with public types/schemas. The source Excel package exports selected arithmetic calculations across all five standard series. Native Excel verification covers 14 scenarios and 48 cell checks. Reverse import of additional inputs refuses.
- RFC 0044: browser-safe
projectLeaseUpCashFlowsand read-onlyuwmd project-lease-upmap a verified exact lease-up variant onto explicit dates. Candidate output preserves exact stated amounts, semantic source digest, and canonical bindings. Typed refusals retain structural/verifier evidence. The output covers rent receipts, concessions and TI/LC only; full DCF economics and cash-flow metric Excel export remain separate contracts.
Verification and documentation
- 1,931 workspace tests, 441 default plus 76 declarative conformance checks, and 31 JSON schemas pass after repinning, along with the full release gates and clean tarball/registry installation smoke checks. Receipt changes are engine labels only; financial digests, formulas and precision boundaries remain unchanged.
- Independently verified PCG64 against NumPy 1.26.4: 11 seeds, 11,264 raw draws and 176 doubles. Reconciled roadmap/status and added executable consumer guides.
[2.7.0] - 2026-09-12
Released
- Core and CLI 2.7.0 package the merged RFC 0040 and RFC 0041 features. Protocol stays 2.8.0; Format stays 2.0. Package 2.7.0 and Protocol 2.8.0 are independent version streams.
- Exact-pin updates: signing 0.2.11, batch 0.8.6; unpublished excel/report 0.8.11. The two unpublished modules retain 0.1.0. Receipt engine labels advance to 2.7.0; calculation digests are unchanged.
- Release tag:
v2.7.0. The tag invokes the existing trusted-publishing workflow for core, CLI, signing and batch.
Added
- RFC 0040: signed block
_roleannotations, deterministic role-aware cross-check selection, component exclusion and per-section coverage evidence. Trusted role assignment survives edits and representation conversion; agent writes cannot assign roles. - RFC 0041: explicit year, absolute month/quarter and date selectors on the five standard period series, with contextual variants, overrides and dependency keys. PS-01/02/03 diagnostics and typed evaluation refusals cover malformed, duplicate and unresolvable references. Financial formulas are unchanged.
- Excel emission and refinement perturbation explicitly decline period selector expressions. Relative Qn/Mn aliases, calendar conversion, module registration and speculative leasing remain separate future work.
2.6.2 - 2026-09-10
Released
@uwmd/core2.6.2,@uwmd/cli2.6.2 (lockstep),@uwmd/signing0.2.10 (repin-forced patch; core's exact optional-peer pin moves with it),@uwmd/batch0.8.5 (repin). Unpublished repins:@uwmd/excel0.8.10,@uwmd/report0.8.10;@uwmd/module-hospitalityand@uwmd/module-data-centerrepin at 0.1.0 (both unpublished). Protocol stays 2.6.0.- Why a patch, not a minor: nothing normative moves. The cut carries the one fix below — remediation copy that renderers show next to an issue — plus a prose correction in §4.25 and the schema examples. No verdict, code, severity or corpus case changes. Receipt baselines
issue/01,issue/02andverify/03engine_version to 2.6.2.
Fixed
BUILTIN_REMEDIATIONSCC-01..CC-10 now describe the rules the validator runs. The ten cross-section entries dated from the initial commit and described an earlier quick_metrics-reconciliation draft (CC-01"NOI mismatch: noi_model vs quick_metrics.noi_underwritten", severity error) that never shipped; §5.3 andvalidator.tshave always definedCC-01as rent-roll GPR vs operating-statement GPR within 3%, a warning. Because the validator attaches the registry'stitle/remediation/spec_refto every issue it emits, a realCC-05issue (DSCR NOI vs the NOI model) was surfaced to renderers titled "Cap rate mismatch". Every entry's sections, tolerance and default severity now mirror the table and the rule, pinned by a test that reads the §5.3 table from the spec. The §4.25 prose that justifiedCC-15's single seam by claimingCC-01reconcilesnoi_modeltoquick_metricsis corrected (no rule does), as are the wiki and theissue-remediationschema examples. Validation verdicts, issue codes and severities are unchanged — the conformance corpus does not move.
2.6.1 - 2026-09-10
Released
@uwmd/core2.6.1,@uwmd/cli2.6.1 (lockstep),@uwmd/signing0.2.9 (repin-forced patch; core's exact optional-peer pin moves with it),@uwmd/batch0.8.4 (repin). Unpublished repins:@uwmd/excel0.8.9,@uwmd/report0.8.9;@uwmd/module-hospitalityand@uwmd/module-data-centerrepin at 0.1.0 (both unpublished). Protocol stays 2.6.0.- Why a patch, not a minor: nothing in the format or protocol spec moves. The cut carries two bug fixes — the core module runtime scoping a declaring module to its declared class, and
uwmd validate --jsonexiting 1 on errors as §II.6a.2 always said — and one corpus case correction (tier-1/09-returns-tax-basis/validatenow expects exit 1). The data-center module is new code in a new, unpublished package, not a spec change. Receipt baselinesissue/01,issue/02andverify/03engine_version to 2.6.1.
Fixed
uwmd validate --jsonexits 1 on a document with errors, as §II.6a.2 has always said. The JSON branch returned before the exit-code line, so JSON mode exited 0 on anerrorsverdict while the human-readable mode exited 1. The tier-1 case generator had hardcodedexit_code: 0forvalidateon the assumption that every fixture validates clean; fixture 09 (RFC 0038, anRT-01error by design) was the first that does not, and it pinned the bug as the baseline. The generator now derives the exit code from the baseline (errors/blocked→ 1) andtier-1/09-returns-tax-basis/validateexpects 1. Implementations that followed the prose were failing that case; implementations that followed the case were contradicting the prose (raised app-side as underwriter.cc TASK-1121).
Added
- Data-center module (RFC 0039).
@uwmd/module-data-center0.1.0 (unpublished, like hospitality) — the first product module on a module-declared asset class. Declaresorg.uwmd.data_center(fallbackindustrial), three kW-denominated sections (dc_capacityrequired,dc_power,dc_revenue), eleven calculations in threaded order, seven validations (CC-MOD-DC-01..07, null-silent). The RFC's open question closed yes: a module formula reads the standard sections (quick_metrics.purchase_price,noi_model.net_operating_income) with no runtime change, so the per-commissioned-kW price and NOI calcs ship.examples/Mesa-Gateway-Data-Center-Mesa-AZ.uwx.mdis the corpus's first custom-class example;conformance/modules/runtime/06–11add six scenarios and the runner dispatches onexpected.module. Protocol and format versions do not move. - Fix (
@uwmd/coremodule runtime): a module that declares a custom class (declares_asset_classes) and lists no builtinasset_classeswas treated as applying to every document, so it would have raisedMOD-SECTION-MISSINGon every non-matching file.applicableModulesnow scopes a declaring module to the class it declares; a module naming neither field still applies to all. Found by RFC 0039, note 2.
2.6.0 - 2026-09-09
Released
@uwmd/core2.6.0,@uwmd/cli2.6.0 (lockstep),@uwmd/signing0.2.8 (repin-forced patch; core's exact optional-peer pin moves with it),@uwmd/batch0.8.3 (repin). Unpublished repins:@uwmd/excel0.8.8,@uwmd/report0.8.8;@uwmd/module-hospitalityrepins at 0.1.0. Protocol 2.6.0 — the IRR-hurdle minor: RFC 0036 below.- Why a minor, not a patch: §VIII.10 step 3 gains a new normative capacity rule (the closed-form LP hurdle balance behind
until_lp_irr), and the validator tightens —WF-01now refuses a non-increasing hurdle ladder of either kind, so a document 2.5.0 accepted can be refused by 2.6.0. Receipt baselinesissue/01,issue/02andverify/03engine_version to 2.6.0.
Added
- IRR-hurdled waterfall tiers (RFC 0036). Protocol 2.5.0 → 2.6.0 (a new normative capacity rule in §VIII.10 step 3); the format version does not move. A
splittier may now stateuntil_lp_irr(a fraction in (0, 1)) alongside — or instead of —until_lp_em: the tier pays until the LP's dated flows, including the payment being made, reach the hurdle rate under the series' own day count. The boundary is closed-form, not the nested bisection RFC 0035 §C sketched: the LP's hurdle balanceB = −xnpv(F, h) × (1 + h)^t_rowover every LP flow so far (earlier rows and earlier tiers of this row), capacitymax(0, B) / lp_share. The identity — not a solved "LP IRR ≥ h" — is the normative definition, so the interleaved-capital-call case is deterministic across engines; an implementation MUST NOT determine a tier boundary by iterating onxirr. Both hurdles on one tier means both must be met (the larger capacity governs); laddered promotes (12% → 15% → 18%) follow.WF-01drops the RFC 0035 reservation refusal and gains:until_lp_irrin (0, 1);lp_share > 0and the terminal-uncapped rule for a split capped by either field; successive stated hurdles of one kind strictly increasing (compared at the rate / ratio quantum). One tightening: a non-increasinguntil_lp_emladder — always a dead tier — is now refused; the corpus scan the RFC required found zero such documents, so it ships as an error rather than the fallbackWF-04warning. Schemasection-distribution-waterfallgains the field;WaterfallTierSplit. until_lp_irris the only type change and there are no new exports.conformance/waterfall/gains twelve scenarios (boundary, crossing, ladder, already-met, interleaved call, combined hurdles ×2, compound pref then IRR, and fourWF-01rejections) and losesreject-reserved-irr-hurdle— corpus 385 → 396. Every pinned number is engine-generated; the boundary case is hand-checked (LP 120,000 / GP 30,000, LPxirr0.12 at 6 dp).
2.5.0 - 2026-09-09
Released
@uwmd/core2.5.0,@uwmd/cli2.5.0 (lockstep),@uwmd/signing0.2.7 (repin-forced patch; core's exact optional-peer pin moves with it),@uwmd/batch0.8.2 (repin). Unpublished repins:@uwmd/excel0.8.7,@uwmd/report0.8.7;@uwmd/module-hospitalityrepins at 0.1.0. Protocol 2.5.0 — the validator-honesty minor: RFC 0037 and RFC 0038 below.- Why a minor, not a patch:
ValidationResultgains a requiredcoveragefield (consumers constructing one by hand must add it), a new registered code (CC-16) and a new validator family (RT-NN) are emitted, and format §4.9 gains a field. Receipt baselineverify/03engine_version to 2.5.0. Also carries #162 (docs wording), #163 (RFC 0036 draft) and #165 (continuity plan).
Added
Continuity plan (
MAINTAINERS.md"Continuity"): MIT + public repo is the plan; no successor is named; six months of maintainer unresponsiveness is the dormancy threshold after which the community should fork. Closes the launch-review bus-factor flag.Cross-check resolution over variant maps, and a validation coverage channel (RFC 0037). Ten of the fifteen §5.3 cross-checks resolved their sections with a bare
getSection, which returnsnullon a variant map — so a document carrying, say, a senior facility and a mezz sleeve as twodebt_structurevariants (conforming under the RFC 0014 envelope, which admits a variant map on any section) silently lostCC-02,CC-03,CC-05andCC-09with no diagnostic. Format §5.3 now specifies the resolution order (the check's own preference →default→base→ a lone variant), an unresolvable map is reported once per section asCC-16(info), andValidationResultgains acoveragerecord — one entry per registered check,evaluatedorskippedwith a reason — so "checked and clean" is distinguishable from "never evaluated".uwmd validateprints the summary line;--jsoncarries the record. New exports:CROSS_CHECK_RULE_IDS,CROSS_CHECK_VARIANT_PREFERENCE,CrossCheckCoverage,CrossCheckSkipReason. Tier-1 fixture08-variant-cross-checks. Raised by underwriter.cc as UPSTREAM-005.dcf.returns.tax_basis(RFC 0038). A closed, optional declaration —pre_tax(default when absent) orafter_tax— of the basis every metric indcf.returnsis stated on, so two documents' IRRs can be told apart from a spread. Unregistered values areRT-01(error) in the newRT-NNfamily. New exports:RETURN_TAX_BASES,DEFAULT_RETURN_TAX_BASIS,ReturnTaxBasis,getReturnTaxBasis(). Tier-1 fixture09-returns-tax-basis. Raised by underwriter.cc as UPSTREAM-006.
2.4.0 - 2026-09-04
Released
@uwmd/core2.4.0,@uwmd/cli2.4.0 (lockstep),@uwmd/signing0.2.6 (repin-forced patch; core's exact optional-peer pin moves with it),@uwmd/batch0.8.1 (repin). Unpublished repins:@uwmd/excel0.8.6,@uwmd/report0.8.6;@uwmd/module-hospitalityrepins at 0.1.0. Protocol 2.4.0 (three spec-conformance fixes below; no new protocol machinery).- Why a minor, not a patch:
uwmd parse --json's per-section shape changed to the §II.6a.6 projection — breaking for consumers of the old UWBlock dump. Receipt baselineverify/03engine_version to 2.4.0.
Fixed
Protocol §III.6a: the
META_*(underscore) provenance family is live, not retired. The table declared it retired on the claim that it was "specified and never emitted" and covered byDQ-NN— both halves false: the reference validator emitsMETA_MISSING,META_FIELD_MISSING_<FIELD>andMETA_LOW_CONFIDENCE_NO_REVIEW_FLAG, the tier-1 baselines pin them, and theDQsequence polices incomplete data, not incomplete provenance. Found by underwriter.cc implementing the codes the corpus demands while the spec said they did not exist. Prose-only; no validator or baseline change.uwmd parsenow emits the §II.6a.6 conformance projection forsectionsandsuperseded—{ meta, content }per block (per variant for a multi-variant section), withmetathe on-disk_metaverbatim andcontentthe block content proper. Previously it dumped in-memoryUWBlocks, so the fence object (with_metanested insidecontent), reader artifacts (line numbers, prose,rawJson) and the flattened in-memory meta all leaked into the output — and into the tier-1 baselines, which therefore required shapes §II.6a.6 says MUST NOT be required. A faithful implementation of the spec's own projection failed 6 of 6 tier-1parsecases (found by underwriter.cc's first driver run; their UPSTREAM-notes item on baseline/spec divergence). All sixtier-1-reader/expected/*.parsed.jsonbaselines are regenerated in the projection shape. Breaking for consumers ofuwmd parse --json's per-section shape (the fence object is still available in the document itself;superseded_blockskeeps its prior shape for compatibility).tieris no longer required by the implementation-manifest schema, matching §II.5's normative text, which says an implementation whose capabilities do not stack into a clean tier SHOULD "publish capabilities and omit a tier claim rather than round down". The schema'srequiredarray contradicted the spec (UPSTREAM-003, reported by underwriter.cc); the spec wins.ImplementationManifest.tieris now optional in@uwmd/coreaccordingly.
2.3.0 - 2026-09-03
Released
@uwmd/core2.3.0,@uwmd/cli2.3.0 (lockstep),@uwmd/signing0.2.5 (repin-only patch; core's exact optional-peer pin moves with it), and — first publish —@uwmd/batch0.8.0 (owner decision 2026-09-03: the corpus fact table's first consumer is the underwriter.cc screener, which shouldnpx @uwmd/batchrather than clone the repo).release.ymlnow carries a fourth publish step; batch gained therepositorymanifest field npm provenance requires. Coordinated repins for the still-unpublished surfaces:@uwmd/excel0.8.5,@uwmd/report0.8.5;@uwmd/module-hospitalityrepins to core 2.3.0 and stays 0.1.0, unpublished.- Carries the #146 data-lake pair: the
/guide/data-lakeon-ramp (#148) and the corpus fact table +.uwx.mddiscovery fix (#149). No spec, schema, format, or protocol movement — protocol stays 2.3.0 (the core 2.3.0 / protocol 2.3.0 number collision is coincidence; the surfaces version independently).
Added — corpus fact table for data-lake ingestion (#146 leg B)
@uwmd/core:flattenEnvelopeBlockValues(envelope)— the normativeblock_valuesflattening (UW CSV Bundle spec §3) exposed as data (UWBlockValueRow[]), so hosts can build fact tables without re-implementing it.encodeUWCSVBundlenow consumes the same function — one source of truth. Exported from both entries (UWJSONValueType/UWBlockValueRowtypes alongside). No behavior change to the bundle encoding.@uwmd/batch:buildUWMDFactTable/writeUWMDFactTableand the--factsCLI flag — walks a deal directory and emitsuwmd-facts.jsonl(one line per JSON fact per deal: theblock_valuesrow prefixed withpath,deal_id,asset_class,semantic_digest, and the validation verdict) plusuwmd-facts-manifest.jsonwith counts and adeals_skippedlist. Deals that parse but fail validation are included withvalid: false; a fact table never silently drops a deal. Durable fact key:(semantic_digest, block_ref, scope, pointer). First consumer: the underwriter.cc batch screener.@uwmd/batchfix:.uwx.mddiscovery — the directory walker only matched.uw.md, silently scanning zero files in a post-2.0 corpus (whereinitscaffolds.uwx.md). It now matches both extensions.- The data-lake guide (
docs/DATA_LAKE.md,/guide/data-lake) gained a "Scaling up" section showing the--factspipeline; every command and query in it was executed before being written down.
2.2.0 - 2026-09-02
Released
@uwmd/core2.2.0,@uwmd/cli2.2.0 (lockstep), and@uwmd/signing0.2.4 (repin-only: its core dependency is exact, so the core bump forces a patch; core's exact optional-peer pin moves with it). Coordinated repins for the versioned-but-unpublished surfaces:@uwmd/excel0.8.4,@uwmd/report0.8.4,@uwmd/batch0.7.4;@uwmd/module-hospitalityrepins to core 2.2.0 and stays 0.1.0, unpublished;tools/web-editoris afile:link and needs no repin.- Carries RFC 0035 (distribution waterfall, protocol 2.3.0 §VIII.10) — drafted, accepted, and implemented 2026-09-02, the same day as the 2.1.0 release it follows. Format stays 2.0 (additive section registration does not bump it).
Added — distribution waterfall (RFC 0035; protocol 2.2.0 → 2.3.0)
- Format §4.27
distribution_waterfall— the tiered LP/GP split of a deal's equity cash flows as the fourth state-and-verify structure:cash_flow_refinto a §4.26 dated series,equity_split, and an ordered closed tier ladder —return_of_capital,preferred_return(simple / compound-annual accrual under the series' §VIII.9.1 day count; unpaid pref compounds in the latter),catch_up(closed-form capacity to a target promote share), andsplittiers with optional equity-multiple hurdles and a mandatory uncapped terminal split.stated_outcomes(per-party contributions / distributions / MOIC / XIRR via §VIII.9.3,promote_total,profit_total) and an optionalstated_schedulechecked cell-for-cell (absent cells read 0). New validator familyWF-01…WF-03(ladder grammar incl. the reserved-and-refuseduntil_lp_irr; the cash reference must resolve; a waterfall needs capital). - Protocol §VIII.10 — the normative allocation walk (accrue first, pairwise year fractions, contributions by
equity_split, ladder fill with pinned per-tier capacities), so two engines produce identical allocations and agree on the promote.verifyWaterfall/computeWaterfallrecompute the entire allocation, never trusting stated splits; three-state at the §VIII.9.4 quanta.capital_stackand itsCS-WATERFALL-UNSUPPORTEDboundary are untouched — the waterfall is the equity side, in its own section. - Conformance: new
conformance/waterfall/suite, 14 scenarios — the fully hand-worked classic case (ROC → 8% pref → 100% catch-up to 20% → 80/20, every figure exact on paper; the catch-up lands the GP at exactly 20% of profit), the compound-pref twin, the EM-hurdle boundary crossing, and the six WF refusals (corpus 363 → 377; receipt baselines re-pinprotocol_version2.3.0). 30 new unit tests including a cash-conservation property. - Erratum recorded in the RFC, found by building it: the draft defined catch-up "profit" as distributions above returned capital and pref — under which a catch-up following the pref tier has capacity zero forever. Profit means distributions above returned capital; pref receipts count as profit (the industry reading);
promote_totalstill excludes the GP's own pref. - Deferred by design: IRR-hurdled tiers (
until_lp_irrreserved; the bisection-on-boundary-amount design is named in the RFC), clawback/crystallization, n-party splits, Excel emit (the §4.26 literals posture), and acapital_stackcross-check.
2.1.0 - 2026-09-02
Released
@uwmd/core2.1.0,@uwmd/cli2.1.0 (lockstep), and@uwmd/signing0.2.3 (repin-only: its core dependency is exact, so the core bump forces a patch; core's exact optional-peer pin moves with it). Coordinated repins for the versioned-but-unpublished surfaces:@uwmd/excel0.8.3,@uwmd/report0.8.3,@uwmd/batch0.7.3;@uwmd/module-hospitalityrepins to core 2.1.0 and stays 0.1.0, unpublished;tools/web-editoris afile:link and needs no repin.- The post-2.0 same-day pair: RFC 0034 (calendar-anchored cash flows, protocol 2.1.0 §VIII.9) and RFC 0015 (portfolio & relationship profiles, protocol 2.2.0 §XV) — both drafted/revised, accepted, and implemented 2026-09-02. Format stays 2.0 (additive section registration does not bump it; see the RFC 0034 erratum).
Added — portfolio & relationship profiles (RFC 0015; protocol 2.1.0 → 2.2.0)
- Protocol §XV (Future work renumbered §XVI): the optional
portfolio-relationshipscapability and the.uwportfolio.jsonsidecar — typed entities and provenance-backed edges spanning deals, the portable carrier for the entity layer of the RFC 0018 edge registry. Out-of-band by design: no_metaownership, no canonical-byte changes, no storage/query/aggregate contract (stated fund-level numbers stay with RFC 0021 composites + rollup receipts). - Registry-resolved edges: types resolve through
BUILTIN_EDGE_TYPES(lookupEdgeType) — a known member-layer type used as an entity edge refuses (PORT-010, the one-table-two-layers rule enforced from the sidecar side); builtinfrom/toentity-kind constraints refuse (PORT-011); unknown entity/edge types and fields are preserved, reportable viauninterpretedPortfolioTypes, never refused. Provenancesourceis a document/source identifier, explicitly kept apart from the RFC 0031_meta.sourceactor grammar. - Core (
portfolio.ts, browser-safe, read-only):PortfolioProfile/PortfolioEntity/PortfolioEdge(exactlyUWEntityEdge+id),validatePortfolioProfile(PORT-001…PORT-011, newportfolioerror category),getPortfolioRelationships, andentityEdgesToPortfolioEdges— the bridge that givesprojectPackageLinksToEntityEdges(which produced entity edges with no portable destination) its home. Newuw-portfolio-profile.schema.json;portfolio_versionstarts its own line at1.0. - CLI:
uwmd portfolio validate|edges(cli-portfolio.ts) — also what lets the conformance suite exist as commands. - Conformance: new
conformance/portfolio-relationships/suite, 7 scenarios — validity, missing provenance, duplicate id (one namespace), dangling endpoint, unknown-preservation (types reported + extension fields retained), wrong-layer refusal, and the package→profile projection round-trip (corpus 356 → 363; receipt baselines re-pinprotocol_version2.2.0). - Erratum recorded in the RFC: its conformance sketch asked scenario 05 to prove byte-for-byte survival through a targeted edit, but the reference surface is read-only by the RFC's own design — 05 pins preservation through validation and type reporting instead; byte-level editor conformance waits for an editor to exist.
Added — calendar-anchored cash flows (RFC 0034; protocol 2.0.0 → 2.1.0)
- Format §4.26
cash_flow_series— dated, irregular flows (ISO-8601 calendar dates, signed amounts) as the third state-and-verify structure aftercapital_stackandlease_up_schedule: multi-variant, asset-class independent, optional at every stage. Stated aggregates (total_net,moic,xnpvat a rate,xirr) are recomputed three-state byverifyCashFlowSeries, never trusted. New validator familyCF-01…CF-03(row grammar incl. real-calendar-day checking; non-decreasing dates with the first row as anchor, ties legal and unmerged; a statedxirrrequires a sign change). Newsection-cash-flow-series.schema.json; chat/summary renderers gain the dated-flow table. The §4.26 worked example's metrics were computed by the verifier and pinned verbatim inconformance/cash-flow/verify-all-metrics, so spec and implementation cannot drift apart. - Protocol §VIII.9 — the closed day-count registry (
DAY_COUNT_CONVENTIONS:actual/365fdefault,actual/360,30/360uspinned to the exact Excel DAYS360 U.S. clamps with no NASD February special-casing — the divergence documented, not discovered);xnpvclosed-form over anchor-relative year fractions, accumulated in series order;xirrby the RFC 0024 bisection procedure verbatim (same bracket, tolerances, exact high-endpoint root, no Newton polish;CALC-XIRR-DIVERGEon failure). §VIII.3's iterate sentence now reads:irrandxirrare the only builtins permitted to iterate. - Reachable only by declaration (§VIII.9.4):
CashFlowMetricDecl+evaluateCashFlowMetric(s)— the §VIII.7/§VIII.8 pattern's third instance. The §VIII.1 grammar and the §VIII.3 expression-callable table are untouched;CalcResult.valueis not widened; overrides shadow individual rows (cash_flow_series.series[7].amount) without touching the document. Unit defaults quantize per RFC 0023 (%→6,$→2,x→4). NewCALC-CF-SERIESrefuses a declaration naming a missing, malformed, or wrong-variant series; an explicit variant does not fall back todefault. New capabilitycalc-cash-flow; newCFvalidator code family. - Conformance: new
conformance/cash-flow/suite, 15 scenarios across the three kinds (payload verify, document validate+verify, declaration evaluation incl. the three-convention pinned-root trio and the override shadow), run by default and wired into CI (corpus 341 → 356; the two receipt-issuance baselines re-pinprotocol_version2.1.0). - Deferred by design (recorded in the RFC): Excel emit — Excel's XIRR is Newton-seeded, so a live formula cannot hold the bit-exact parity boundary; any future Cash Flow sheet emits literals. Also deferred: a
dcfcross-check, defaults entries, and the RFC 0026 Phase 2 waterfall (its stated precondition — a hold-period dated-series primitive — is now removed). - Errata against the RFC, found by building it: the draft claimed "format 2.0 → 2.1", but additive section registration has never bumped the format version (§4.23/§4.24/§4.25 all landed at format 1.1); §4.26 follows precedent and the format version does not move. The draft's worked-example metrics were hand-computed and wrong; the spec's are now generated by the verifier.
2.0.0 - 2026-09-02
Released
@uwmd/core2.0.0,@uwmd/cli2.0.0 (lockstep), and@uwmd/signing0.2.2 (repin-only: its core dependency is exact, so the core bump forces a patch; core's exact optional-peer pin moves with it). Coordinated repins for the versioned-but-unpublished surfaces:@uwmd/excel0.8.2,@uwmd/report0.8.2,@uwmd/batch0.7.2;@uwmd/module-hospitalityrepins to core 2.0.0 and stays 0.1.0, unpublished;tools/web-editoris afile:link and needs no repin.- The major. Everything in the 2.0 cut below is why this is 2.0.0 and not 1.11.0: retired
PROTO-EDIT-010, the removedlegacy_extensiondetection field,manualleavingSOURCE_TAGS, and the two per-file sunsets are breaking for consumers of those surfaces. A 2.0 implementation remains a full 1.x reader — the whole 1.x line stays supported input — and 1.x files keep their 1.x validation semantics per format v2 §1.3's per-file boundary.
Changed — the 2.0 cut (BREAKING; RFC 0009 / RFC 0025 / RFC 0031)
- Protocol 1.14.0 → 2.0.0; the implementation now authors format 2.0 (
FORMAT_VERSION'2.0') while reading the whole 1.x line (SUPPORTED_FORMAT_VERSIONS). Modulerequires_protocol/requires_formatranges are satisfied against the supported SETS (SUPPORTED_PROTOCOL_VERSIONS), so^1modules keep loading — a 2.0 implementation is a 1.x implementation too. - Full v2 editing —
PROTO-EDIT-010is retired. Every writer (editor replace/supersede/pipeline-append,writeAgentBlock,init) goes through one seam (stampMetaIntoBlockContent) that emits the shape the file'suw_versiondemands: nested_meta+ the_overrideslift for 2.0 files, flat for 1.x. Integrity stamping hashes under the file's own canonicalization rule.uwmd initscaffoldsuw_version: "2.0"by default (--format 1.1for the legacy flat scaffold). - The per-file 2.0 vocabulary boundary (format v2 §1.3/§4):
SRC-01/SRC-02escalate to errors inuw_version: "2.0"files (1.x files keep their warnings); newSRC-03rejects the retiredresolution: "manual";manualleftSOURCE_TAGS(actor-only — theSourceTagunion keeps it for the actor field); the §2.6 read-time interpretation no longer applies inside a 2.0 file's own blocks. - Legacy structured
.uw.mdsniffing sunset (RFC 0025, format v2 §6.1): structured UWX content under a.uw.mdname is nowSOURCE_LEGACY_STRUCTURED(error); an explicit UWX override remains the escape hatch;migrateLegacyUWMarkdownstill plans the byte-identical rename (probing fences directly).UWSourceDetection.legacy_extensionis removed (breaking). The Lite canonicalization1.0recognition obligation ends; the genericRCP-10version-mismatch degradation is retained as quality-of-implementation. - Conformance corpus renamed to spec-compliant extensions: every structured fixture moved
.uw.md→.uwx.md(86 files), with runner, case-generator, and docs updated; Lite fixtures keep.uw.mdlegitimately. Receipts baselines re-pinprotocol_version2.0.0; Lite compile-report baselines re-record the bridge's new pinned-1.1 rationale (the Lite bridge stays a 1.x-format producer until its own RFC).
Added
- RFC 0009 — the format 2.0 normative text (the 2.0 cut's spec leg):
spec/UW_FORMAT_SPEC_v2.md, a delta specification that incorporates the v1 document by reference and amends it foruw_version: "2.0"files — the nested_metashape (§2, with therevisionrename andintegrity.algorithm), the_overridesblock annotation (§3), the 2.0 vocabulary (§4:manualactor-only,SRC-01/SRC-02escalate to errors per-file, newSRC-03forresolution: "manual"), canonicalization v2 and the migration/signature policy (§5), and the two boundary sunsets (§6: legacy structured.uw.mdsniffing becomes an error; the Lite canonicalization1.0recognition obligation ends while the generic degradation may be retained). §1.3 records the per-file reading of the 2.0 boundary, reconciling RFC 0031's per-format phrasing with RFC 0009's per-release phrasing in favor of per-file — the only reading consistent with the v1 round-trip guarantee. Plusspec/schemas/uwmd-block-v2.schema.json(nestedUWMetaV2+_overrides,manual-free resolution enum) and the schemas-README row.
1.10.0 - 2026-09-01
Released
@uwmd/core1.10.0,@uwmd/cli1.10.0 (lockstep), and@uwmd/signing0.2.1 (repin-only: its core dependency is exact, so the core bump forces a patch; core's exact optional-peer pin moves with it). Coordinated repins for the versioned-but-unpublished surfaces:@uwmd/excel0.8.1,@uwmd/report0.8.1,@uwmd/batch0.7.1;@uwmd/module-hospitalityrepins to core 1.10.0 and stays 0.1.0, unpublished;tools/web-editoris afile:link and needs no repin.- This is the RFC 0009 timeline's on-ramp minor:
uwmd migrate --to-v2and dual-shape reading go live so operators can convert files to the v2 nested_metashape ahead of the 2.0 release. Protocol 1.14.0; format stays 1.1.
Added
RFC 0009 leg 2 — the
STAGE_CONTRACTmerge.STAGE_REQUIREMENTS,STAGE_SECTION_OVERLAYS(RFC 0029), andBUILTIN_INCOMPLETE_DATA_POLICIESmerge into one registry,STAGE_CONTRACT— rows keyed(stage, section, field_path?, asset_class?)carryingrequired,on_provisional, and the scope stage'sone_ofgroups; the class overlays becomeasset_class-qualified rows rather than a side table. The registry is derived mechanically from the compact authoring tables at module init (the tables moved fromvalidator.tstoprotocol.ts; validator re-exports them), and validators resolve presence through it. No behavior change — pinned by equivalence tests across the full stage × asset-class × policy cross-product (stage-contract.test.ts), and no protocol version move (no new codes, no observable contract change). New public API:STAGE_CONTRACT,StageContractEntry,lookupStageContract, plus the previously internal stage tables. Refinements against the RFC sketch, recorded in the RFC on flip:required/on_provisionalare optional per row, andone_ofexists because the sketch's singlefield_pathcould not express the scope stage's either-or groups.RFC 0009 (accepted 2026-09-01 at the sprint's Phase 4 gate) — the 1.10.0 on-ramp to format 2.0. Protocol 1.13.0 → 1.14.0; format stays 1.1 (the nested shape is reserved, not admitted, at 1.x):
- The shim (
meta-shape.ts):UWMetaV2and the structural reshape in both directions. The parser accepts a nested_metain auw_version: "2.0"file and exposes the flat in-memory view every consumer already reads (block.meta_shaperecords the on-disk shape);content._metastays bytes-derived and untouched. The RFC 0031 legacy-tag rule survives the shape change: resolution set,provenance.sourceabsent, never invented. - Versioned canonicalization: the v1 digest rule is frozen forever for
uw_version: "1.x"files;canonicalizeV2is normalize-then-hash, so both accepted shapes digest identically. A defaultedintegrity.algorithm('sha256') is excluded from the digest; a future non-default value is hashed and cannot be stripped undetected.computeBlockHash/verifyChainpick the rule from the file's own frontmatter. META-*validator family (protocol §III.6a):META-V2-IN-V1andMETA-V1-IN-V2enforce one shape per file, in both directions.uwmd migrate --to-v2(enginemigrateToV2, browser-safe): whole-file conversion — frontmatter to"2.0", every block nested,field_overrideslifted to the top-level_overridesannotation,resolution: "manual"rewritten touser_inputwith a provenance note, hashes re-stamped chain-aware (a parent link that was broken before migration is carried over broken — migration never repairs tamper evidence). Signed blocks refuse by default;--resign(via the optional@uwmd/signing, structurally typed to preserve layering) or--strip-signatures(recorded inprovenance.notes) is the key holder's explicit choice.--emit-v2-shapeis accepted as a synonym.- Editor refusal
PROTO-EDIT-010: core 1.x refuses edits againstuw_version: "2.0"files rather than corrupting them with flat blocks; full v2 editing ships with the 2.0 cut. - Conformance corpus 331 → 341: the seven-scenario
meta-v2suite (tier-1-reader/v2-fixtures/, including the recorded shim-roundtrip baseline and both digest-insensitivity pins) and the three-scenariomigratesuite. Receipts baselines re-pinprotocol_version1.14.0.
- The shim (
1.9.0 - 2026-09-01
Released
@uwmd/core1.9.0,@uwmd/cli1.9.0 (lockstep), and@uwmd/signing0.2.0 — signing's first tag-triggered OIDC publish, after its 0.1.0 was manually published earlier the same day from thev1.8.0tag (its historically paired state) to bootstrap the trusted publisher. Coordinated@uwmd/corerepins:@uwmd/excel0.8.0,@uwmd/report0.8.0,@uwmd/batch0.7.0,tools/web-editor0.8.0;@uwmd/module-hospitalityrepins to core 1.9.0 and stays 0.1.0, unpublished.- The single-day sprint batch: the owner-ordered Phase 2 trio — RFC 0008 (lease-up schedule, §4.25), RFC 0011 (capability tokens, §XIV), and RFC 0001 (display locales, §III.1a) — each accepted and implemented the same day, plus the RFC 0032/0033 adopter-scope clarifications.
- Format stays 1.1; protocol goes 1.10.0 → 1.13.0 across the span: 1.11.0 added §4.25 + the
LU-NNfamily +CC-15(RFC 0008), 1.12.0 added §XIV +POL-03+capability-verify(RFC 0011, future work renumbered §XV), and 1.13.0 added §III.1a + theLOCfamily (RFC 0001). Every format change is additive at 1.x. - Conformance corpus 306 → 331 (the
lease-up,capability, andlocalenamed suites). Receipts baselines regenerated — the diff is exactly theengine_versionfields — and the 03-result-disagrees frozen receipt re-pinned to the new engine so its disagreement still reads as same-enginefailedrather than degrading to RCP-07 unverifiable.
Added — RFC 0001 implemented: display-locale negotiation (corpus 323 → 331)
RFC 0001 (accepted and implemented 2026-09-01). Protocol goes to 1.13.0, adding §III.1a — Display locales. A file declares the locale it was authored in (locale frontmatter, default en-US); an implementation declares what it renders (ImplementationManifest.supported_locales, absent = ['en-US']); a display render of an unsupported locale is refused (LOC-01, new LOC family; UnsupportedLocaleError from the renderer) — never silently produced in a different locale.
- Display-only by construction: locale touches chat/summary/report renders only. Canonical JSON content, CSV renders, UW Lite canonical form, digests/receipts, and calc evaluation are locale-free —
CalcEvaluationContext.localeis pinned locale-invariant by conformance. - The registry, not ICU: non-
en-USformatting comes from the new curatedBUILTIN_FORMAT_RULEStable (format-rules.ts), stated verbatim in §III.1a — never runtimeIntl, whose output varies across runtimes.en-USkeeps its historical code path byte-identical; every existing baseline is untouched. - First wave:
en-US,en-GB,de-DE,fr-FR,ja-JP,zh-CN(NBSP conventions included, self-defended by a charCode test).formatCurrency/formatPercent/formatRatio/formatDategain alocaleoption; the renderers threadfrontmatter.localethrough. - New
conformance/locale/suite (8 scenarios: five per-locale rendering pins, the LOC-01/display refusal, calc invariance across all six locales, CSV byte identity). Also in this change: the implementation-manifest schema's capability enum caught up with the type (integrity,refinement,capability-verifywere missing) and the reference manifest now claimscapability-verify(an RFC 0011 omission). - Deferred, per the RFC: currency-code disambiguation (own future RFC), cross-locale conversion, localized message text.
Added — RFC 0011 implemented: capability tokens for write authorization (corpus 315 → 323)
RFC 0011 (accepted and implemented 2026-09-01). Protocol goes to 1.12.0, adding §XIV — Capability tokens (optional) (future work renumbers to §XV). An opt-in second gate on writes for orchestrator-bound deployments: a short-lived, scope-limited JWT a coordinator signs — "this actor may write these sections at these stages for this deal, until this time" — verified by the editor before the write.
- Tokens narrow, never widen. The static §V.3 policy check runs regardless; a token cannot override a
POL-01refusal, andinstitution/*keepssystem_only— the question RFC 0031's catch-all surfaced, now resolved and pinned by a conformance case. subbinds_meta.sourceunder the RFC 0031 actor grammar (instance identity fits the id charset:agent/L2.instance-abc-123); free-text_meta.actortakes no part in authorization.- Core stays crypto-free:
CapabilityVerifieris injected (EditOptions.capabilityVerifier, the RFC 0016 precedent), honored byapplyEditAsynconly — the sync path with a verifier configured refuses (PROTO-EDIT-008) rather than silently skipping. Missing or rejected tokens failPOL-03with a typed reason; an accepted token'sjtilands in the new block's notes ascapability:<jti>. @uwmd/signingships the reference verifier (createCapabilityVerifierover the existing KeyStore, JOSE alg names mapped onto the §V.11 shortlist) plussignCapabilityTokenfor coordinators and fixtures.- New
capability-verifyviewer capability (RFC 0030 mechanism);uwmd editgains--capability-token/--coord-key(dynamic-import optional peer, like--signing); new generatedconformance/capability/suite (8 scenarios,npm run gen-capability-fixtures).
Added — RFC 0008 implemented: the lease-up schedule section (corpus 306 → 315)
RFC 0008 (accepted and implemented 2026-09-01). Protocol goes to 1.11.0. The format jumped straight from rent_roll (a snapshot) to noi_model (a stabilized projection) with no structured representation of the path between them — the entire thesis of a value-add or ground-up deal lived in _notes. Format spec §4.25 now registers lease_up_schedule: a model_type (natural_turnover / absorption_curve), a uniform YYYY-Qn/YYYY-MM period grammar, the driving assumption set (fractions, never percents), the period array, and a stabilized_summary.
- State-and-verify, the
capital_stackprecedent. The schedule is data, not formulas — the Tier-3 calc engine is untouched (no iteration, no time axis, no new builtins), and every stated aggregate is recomputed by the new three-stateverifyLeaseUpSchedule(per-periodnet_cash_flowfrom its components; the stabilized summary against the final period, with the occupancy denominator resolved through the Protocol §XIII size-intensive registry — no denominator isunverifiable, never a guess). - New
LU-NNvalidator family (registered undervalidateper §III.6a):LU-01grammar/granularity,LU-02contiguity,LU-03empty/inverted schedule,LU-04(warning) turnover with norent_roll. PlusCC-15(warning): the base variant's stabilized NOI agrees withnoi_model.net_operating_incomewithin the named exportedLEASE_UP_STABILIZED_TOLERANCE(2%) — tolerance-checked because the trajectory endpoint and the stabilized-year projection are two independent models, and non-base variants are exempt by design. - The section is multi-variant (base / upside / downside under
variant=) and optional at every stage; new schemasection-lease-up-schedule.schema.json; view-model entry; chat/summary renderers gain the period table; newconformance/lease-up/suite (9 scenarios). Deliberately deferred, per the RFC: Excel emit,dcfcoupling, defaults-table entries, and any shared period-schedule abstraction.
Clarified — RFC 0032: _meta.provisional and signing (no version moves)
Answered the adopter question tracked upstream-side as UPSTREAM-002 (underwriter.cc): § V.7's provisional obligation stays a SHOULD, on purpose. _meta.provisional is inside canonical block JSON, so stamping it moves content_hash and any § V.11 signature — and the spec now says plainly that cross-producer agreement of hashes or signatures over provenance metadata is a non-goal (the signing input names actor, timestamp, signed_at, kid, which differ across producers by construction). Cross-implementation agreement guarantees remain where they always were: computed values (§ VIII, RFCs 0023/0024) and verification receipts (RFC 0016). Two paragraphs added, § V.7 and § V.11.2; no schema, code, corpus, or version change — the same treatment as the § V.9 wording fix.
Clarified — RFC 0033: capital_stack is one point in time (no version moves)
Answered the adopter question tracked upstream-side as UPSTREAM-003 (underwriter.cc): a ground-up deal's construction loan and its permanent takeout are one senior position in time sequence, and § 4.24 as written forced the producer to choose between double-counting them as concurrent tranches and omitting one. The section now states the scope its own verification semantics imply — the stack is the capitalization contemporaneous with the NOI the sizing verbs read (for a deal underwritten to stabilization, the stabilized stack); temporally disjoint facilities MUST NOT be stated as concurrent tranches, and the retired construction facility stays out. bridge tranches are unaffected. Phased stacks defer with the multi-period spine RFC 0026 § D already parked. One normative bullet in § 4.24; no schema, code, corpus, or version change.
1.8.0 - 2026-08-31
Released
@uwmd/core1.8.0,@uwmd/cli1.8.0 (lockstep), with coordinated@uwmd/corerepins in@uwmd/excel0.7.0,@uwmd/report0.7.0,@uwmd/batch0.6.0, andtools/web-editor0.7.0.@uwmd/signingand@uwmd/module-hospitalityrepin to core 1.8.0 but stay 0.1.0, unpublished.- The largest release since 1.0: everything below, from the 2026-08-26 unfreeze of the v2 RFC train through 2026-08-31 — the signing chain (RFCs 0010 + 0002), the language-agnostic conformance driver (0004), the module runtime and hospitality reference module (0006), module-declared asset classes (0003), sensitivity tables (0007), stochastic calculations (0005), class-aware stage requirements (0029), conformance profiles (0030), the unpoliced-write fix, and the source-vocabulary reconciliation (0031) — plus the pre-unfreeze 2026-08-25/26 batch: the two closed conformance blind spots, RFC 0028 (reportable section readiness), the stage-honest examples pass, and the CLI plumbing extraction.
- Format stays 1.1; protocol goes 1.6.0 → 1.10.0 across the span: 1.7.0 added §V.11 (block signatures), 1.8.0 added §X.2 (module asset classes), 1.9.0 was RFC 0030 (§II.6a.5/6, the §III.6a rewrite), and 1.10.0 is RFC 0031 (
_metasource/resolution split,SRC-NN, the §V.3/§V.7 rewrite). Every format change is additive at 1.x. - Conformance corpus 222 → 306 assertions across the span, plus the 44-case RFC 0004 CLI driver and the three RFC 0030 profile checks.
Added — RFC 0031 implemented: the source vocabularies reconciled (corpus 301 → 306)
RFC 0031 (accepted and implemented 2026-08-31). Protocol goes to 1.10.0. _meta.source was asked to carry two unrelated facts — who wrote the block, and how its value was resolved — and the specs answered in six vocabularies of which only one was executable; 160 blocks in our own corpus (32%) named a source no policy pattern recognized. The unpoliced-write half shipped earlier as a bug fix; this lands the vocabulary.
- The field splits.
_meta.sourceis actor-only —manualor<namespace>/<id>over the closed namespace setagent | document | system | institution(ACTOR_NAMESPACES,parseActorSource, both exported; the grammar is normative in format §2.6 and mirrored inuwmd-block.schema.json). New optional_meta.resolutionholds one canonicalSOURCE_TAGStag;field_overrides[]carries the same split at leaf granularity, and format §3.4 now states that the leaf wins for its path. - Read-time interpretation. A canonical tag found in
_meta.sourceis surfaced asmeta.resolutionby the parser — on a clone, never by rewritingcontent._meta, which feeds digests — and the actor is treated as absent. Every pre-split file keeps a correct reading with no edit. - Two new validator codes.
SRC-01(unrecognized actor source) andSRC-02(resolution tag in the actor field), both warnings through format 1.x;SRC-02becomes an error at 2.0. TheSRCfamily registers undervalidatein §III.6a. - Authority classification reads the parsed namespace, in both the editor and
verifyProvenance— never string prefixes. The colon formagent:L0-01used to be classified as a human write by the prefix test's negative space; a source outside the grammar now satisfies no authority class (it can still write undereither). The Tier-4 host was itself an offender: it stamped the bare layer id (L6-01), which resolved only the catch-all — it now writesagent/<id>, and the fencesource=mirrors the block's actual_meta.source.uwmd init's fence annotations likewise stop advertisingwizard/engine:uwmdover metas that said otherwise. - Spec repairs. Format §2.6 is rewritten (the colon-form table is retired; the broken "see Protocol §IX" cross-reference now points at §V.7); §3.1's contradictory precedence ladder becomes non-normative narrative deferring to §V.7 — §V.7 wins where they disagreed (investor profile outranks market data: a declared decision beats a scraped comp), and §3.1's rankings of
ai_extracted/agent_computed, which are not cascade steps, are removed; §4.16's private enum is replaced bySOURCE_TAGSandwizard_input— a token no other vocabulary or line of code recognized — becomesuser_input; protocol §V.3 gains the grammar, the normative catch-all, and the classification rule; §V.4 states the actual grammar where producers look first; §V.7 stampsresolutionrather thansource. uwmd migrate --source-tags(andmigrateSourceTags/mapLegacySource, browser-safe): a total, mechanical rewrite of the legacy spellings. It recovers actors from the block where possible (agent_idfor agent-method tags,manualfor user-entered ones,system/uwmdfor engine-resolved cascade tags, delimiter swaps for colon forms), refuses to guess at unknown sources, and refuses blocks carryingcontent_hashrather than silently invalidating an integrity chain. Applied to the corpus: exactly the 160 measured blocks across 42 files, zero unmapped. (The codemod is EOL-aware — a CRLF working copy would otherwise blind the fence regexes and silently skip files.)- New
conformance/source/suite (5 scenarios, corpus 301 → 306), including the data-loss regression: an edit against a catch-all-governed block supersedes, asection_replaceis refused, and a v2 block with no superseded prior reportsPOL-02. - Errata recorded in the RFC: the draft schema pattern rejected the RFC's own
agent/L6-01example (lowercase-only id charset — corrected to mixed case); fixture 03's live refusal code isPROTO-EDIT-004, withPOL-02as the post-hoc provenance verdict; and the migration's actor-assignment mapping, which the RFC left open, is now specified in the RFC's status note. The protocol document's own H1 had also read "v1.3" since protocol 1.3.0 — seven minors of rot — and now carries the major alone, with §0.3 stating the precise version. - Unblocks RFC 0009 (
_metav2 reorg), which can now type the nested provenance field without orphaning the actor vocabulary the edit engine runs on.
Added — RFC 0030 implemented: partial conformance became checkable (corpus 274 → 301)
RFC 0030 (drafted, accepted, and implemented 2026-08-30). Protocol goes to 1.9.0. The first external adopter to run the corpus end to end reported four divergences; all four were defects here, with one root cause — the corpus encoded requirements the spec never stated, and the spec offered no way to opt out of the parts that do not apply.
- Cases declare
requires_capabilitiesand the driver skips what an implementation does not claim. A skip is never counted as a pass: it is excluded frompassed, named in the TAP summary, and carried in the JSON report asskipped_by_capability. The tags are derived from the command each case runs — tier-2 reads the operation kind — so there is no table to forget. - An absent or empty
capabilitieslist means run everything. Forgetting to declare fails closed against the claimant rather than exempting it from the corpus.--no-skipturns any skip into a failure, and CI runs the reference implementation that way so the mechanism cannot erode its own coverage. - §III.6a was wrong. It said every issue code belongs to one of three families (
CC-NN,FV_*,META_*) whileBUILTIN_REMEDIATIONSshipped eighteen —INT-*andPOL-*were not legal families,FV_*had been renamedFV-NNin v1.1, andMETA_*shipped nowhere. Replaced by a registry naming the capability that owns each family, plus a test asserting every code resolves to one. The table went stale by duplicating a list that lives in code; the assertion is the part that cannot. - §II.1.6 is scoped to
validate. A Tier-1 Reader that does not claim it owes no validator codes — the requirement previously read as owingINT-NN(needs a hash chain) andPOL-NN(needs an edit engine) to any reader. - The parse baseline is a specified projection (§II.6a.6), not
@uwmd/core's in-memoryParsedUWFile. Dropped from the normative set:annotation,lineStart/lineEnd, per-block prose, and optional_metafields the document never carried. The old requirement lived in a corpus README and made a TypeScript interface a protocol surface by accident. - §II.6 no longer self-certifies by directory membership.
tier-3-calc-host/refinement/is not required of a calc host: §II.3 lists four requirements and refinement is not among them, and the RFC 0004 driver had always generated zero cases for it. Newrefinementandintegritycapabilities make that explicit rather than inferred. - New:
conformance/profiles/— three stub implementations that delegate every real subcommand to the reference CLI while claiming less than they can do, andnpm run conformance:profiles, which recomputes the skip set in JavaScript and compares it to the Python driver's. Two independent implementations of one rule; a single one checked against its own output would pass even when the rule is wrong.
Fixed — an unrecognized _meta.source no longer permitted a destructive replace
resolvePolicy returned null for a source matching no BUILTIN_EDIT_POLICIES pattern, and the editor read that null two ways: checkAuthority treated it as permitted, and dispatchEdit treated it as exempt from supersede_on_edit. A block whose source was outside the executable vocabulary could therefore be replaced in place, destroying the prior version, with POL-01 and POL-02 both unable to fire and verifyChain silent. That is a breach of the append-only provenance invariant.
Measured across all 206 corpus and example files: 613 blocks carry _meta.source, and 263 of them (43%) resolved to no policy — including 33 using canonical SOURCE_TAGS (market_data, user_input, asset_class_default, ai_extracted, system_default, market_data_accepted) and 20 using the agent: colon form that format spec §2.6 tells producers to write.
BUILTIN_EDIT_POLICIESnow ends in a terminal*catch-all withsupersede_on_edit: trueandauthority: 'either'.matchSourcescores by pattern length, so a one-character glob applies only where nothing else matched. An unrecognized source now preserves history; no write that succeeded before starts failing, which is what keeps this a fix rather than a migration.checkAuthorityrefuses when no policy matches instead of granting. That is reachable only for a caller supplying a policy list with no catch-all — an incomplete policy, which should not read as authorization.- A totality assertion pins it: no source resolves to
nullunder the builtin policies. It makes this class of bug unrepresentable rather than merely absent. generateBlankUWFilewas itself emitting unpoliced sources —wizardon every section stub andengine:uwmdon the pipeline-log entry, neither matching any pattern. Every freshly created document carried blocks no policy governed, which is why replacing them in place appeared to work; the web editor's suite caught it, since rootnpm testdoes not covertools/. Stubs now stampmanual— deliberately notsystem/init, which resolves tosystem/*and issystem_only, and these stubs exist to be filled in by a person. The append-only log entry stampssystem/init. A test asserts no generated block falls through to the catch-all: the catch-all is a safety net for documents in the wild, not a licence for our own writers to skip choosing a policy.
This is the correctness half of RFC 0031 (still in draft, and deliberately not linked here — it lands in a separate change), split out and shipped ahead of it. The vocabulary reconciliation that RFC proposes — splitting _meta.source into actor and resolution, SRC-01/SRC-02, and the corpus migration — is a design change with real migration cost and is still in draft. The unpoliced-write path is a bug, and waiting on that debate to fix it was the wrong trade.
Added — RFC 0010 implemented: signed blocks, and the receipt signing it unblocked (corpus 245 → 263)
RFC 0010 (drafted 2026-04-27, accepted and implemented 2026-08-27) is the first item off the v2 train the owner unfroze on 2026-08-26. Protocol goes to 1.7.0 for the new §V.11.
_meta.signatureis normative — protocol §V.11 (wire format, the six-field canonical signing input, verification, key distribution, capability declaration), format spec §3.4, and theuwmd-blockschema.algis a closed set at 1.x (ed25519|es256|es384); ECDSA is rawr||sper JOSE, never DER.- The cryptography lives in a new
@uwmd/signing(0.1.0), not in core.@uwmd/corekeeps its zero-crypto guarantee and owns only the crypto-free half — the wire shape andcanonicalBlockSigningInput. Two injection seams connect them:verifyChain(parsed, { signatureVerifier })and thesignatureVerifierthatverifyReceipthas always accepted. Reached from the CLI by dynamic import as an optional peer, the same arrangement@anthropic-ai/sdkgets, so the layering invariant holds. - Receipt signing now works. It was the one advertised receipt feature shipping unimplemented;
signReceipt+createReceiptSignatureVerifierclose it, and a signed receipt verifies instead of reportingRCP-08. INT-05–INT-08joinBUILTIN_REMEDIATIONS. Two distinctions are deliberate and pinned:INT-06(unknown kid) is never merged intoINT-07(bad signature) — one says load a key, the other says reject the document — and a driftedcontent_hashescalates fromINT-04 warningtoINT-07 erroron a signed block, because there it means the content in front of you is not the content anybody signed.- A verifier with no key store reports signatures as present and unchecked, never as verified:
IntegrityResultgainssignatures_presentalongsidesignatures_verified, anduwmd verify --signing --keystore=<path>opts in. - Conformance: the new
conformance/signing/suite, 6 assertions over 5 generated scenarios (valid, tampered, unknown kid, no hash, no backend) plus a backend-independence invariant asserted without a baseline. Fixtures are generated byscripts/gen-signing-fixtures.mjsfrom a checked-in test key whose private half is published — a signature over a hash of the file it lives in cannot be hand-authored. - Tests: 11 new core tests and 44 in
@uwmd/signing, including an ed25519/es256/es384 round trip, signature-swap and wrong-key rejection, key store refusals, and the full issue → sign → verify receipt path.
Added — RFC 0005 implemented: stochastic calculations (corpus 294 → 301)
RFC 0005 (drafted 2026-04-26, accepted and implemented 2026-08-27), on the override mechanism RFC 0007 added the same day. Protocol §VIII.8.
- A declaration, not built-ins. The RFC proposed
uniform(),normal(),triangular(), andmonte_carlo(expr, n)in the §VIII.1 grammar. Every builtin here is a pure function of its arguments (a sampling builtin carries PRNG state), arguments are evaluated eagerly (somonte_carlocannot take a lazy expression), and a call whose legality depends on the enclosing declaration'sdeterministicflag is a context-sensitive grammar checked by a context-free parser. Inputs are declared in JSON and each draw is an ordinary evaluation withoverrides. The grammar and the built-ins are untouched. - PCG-XSL-RR-128/64 is normative, seeded exactly as the reference
srandomdoes. Uniform doubles take the top 53 bits over 2⁵³, not 64 over 2⁶⁴, which rounds. normalis not bit-exact across platforms, and the spec says so. This is a correctness finding the RFC did not have: specifying the PRNG is necessary but not sufficient, because IEEE 754 does not specifylog,exp,sin, orcos. Box-Muller and Marsaglia polar both depend onlog, so identical seeds would still produce samples disagreeing in the last place. Every distribution is now sampled by inverse CDF:uniformandtriangularare exact,normaluses Acklam's rational approximation whose central 95% is exact and whose tails still needlog. Conformance compares the first two exactly and the third at a stated tolerance — which also settles the RFC's open question about fixture comparison, better than the proposed blanket tolerance that would have hidden the difference.- Percentiles are nearest-rank, never interpolated, so a percentile is an observed sample and is exactly reproducible whenever the samples are.
- The distribution never travels through
CalcResult. The RFC would have hadvaluecarry "the mean when the consumer requests a single number";CalcResult.valueis pinned by receipts, rendered by the CLI, and emitted from by Excel, and avaluethat silently means a distribution's mean in some rows is how a wrong number reaches a credit memo. - Input order is part of the contract, a variable drawn twice is refused, a failed draw is excluded from the summary rather than counted as zero, and
stddevis the sample (n−1) form.CALC-STOCH-001–006; capabilities gaincalc-stochasticandcalc-sensitivity. - Conformance:
conformance/stochastic/, six scenarios. Reproducibility is asserted in-process without a baseline (re-running must return the identical summary), so it binds any implementation and not only one that matches our frozen numbers; a second scenario asserts a different seed produces a different summary, since a seed that did not change the stream would make reproducibility accidental. - Tests: 34 new core tests across the PRNG, the samplers, and the engine.
Known gap at this release — the PCG test vector was self-generated
Resolved in the 2026-09-12 maintenance sprint through an independent NumPy comparison; see verification evidence. The original release-time finding below is preserved as history.
prng.ts implements PCG-XSL-RR-128/64 from the published algorithm, but the test vector was generated by that implementation. It proves self-consistency, not agreement with the reference C implementation at pcg-random.org, which nobody has diffed against. Recorded in prng.ts, in RFC 0005, and here rather than left to be assumed away; it should be closed before the RFC is accepted. The steps are in docs/handoff/HUMAN-verify-pcg64-vector.md. Until then, port the TypeScript rather than re-deriving pcg64 from the paper.
Added — RFC 0007 implemented: sensitivity tables, without touching the grammar (corpus 289 → 294)
RFC 0007 (drafted 2026-04-27, accepted and implemented 2026-08-27). Protocol §VIII.7. No version bump beyond 1.8.0, which the same day's §X.2 already carried.
- A declaration, not a builtin. The RFC proposed
sensitivity_table(expr, {…}, {…})inside the §VIII.1 grammar — which has no object literals, no array literals, and astringproduction that is a value rather than a program. That is three sandbox extensions, one of which makes a string argument executable, to reach axis data already sitting in JSON one level up. What shipped is a JSONSensitivityDeclwith an ordinary safe expression asbase_formula. The grammar is unchanged. CalcEvaluationContext.overridesis the actual primitive: values keyed by full dotted path, consulted ahead of frontmatter, sections, and prior results. Two properties are normative — overrides shadow and never write (a sweep that mutated the document would silently change the deal), and anulloverride means "treat this path as absent", distinct from having no override. Scenario sweeps and stress tests need the same mechanism.- The grid never travels through
CalcResult.value, which staysnumber | string | boolean | null. RFC 0016 receipts pin that union, the CLI renders it, and Excel emits from it; widening it would break three consumers for a feature none of them asked to carry. - A failed cell does not fail the table. A grid where one combination divides by zero is still useful, and refusing the whole thing would hide the cells that worked.
failed_cellssays how much of the table is real. CALC-SENS-001–005.CALC-SENS-004(both axes on one variable) is new beyond the RFC and is a trap rather than a redundancy: the second override silently wins for every cell, producing a grid whose rows are identical and whose reader cannot see why. Bounds are 256 cells and 64 per axis, so a 1×256 strip is no cheaper than a square.- Conformance:
conformance/sensitivity/, five scenarios with hand-checkable round numbers — including one that asserts the document reads unchanged after a sweep. - Tests: 22 new core tests covering overrides and the grid.
Fixed — RFC 0007's motivation described renderers that do not exist
The RFC states that @uwmd/excel and the web editor "both have ad-hoc grid renderers that re-derive the axis structure from the calc IDs". Neither does, and neither ever did. The rest of the motivation stands on its own; that claim is corrected in the RFC rather than repeated. Excel emit is deferred accordingly — there is no ad-hoc implementation to replace, and SensitivityResult hands an emitter the grid structure whenever one is written.
Added — RFC 0003 implemented: modules can declare asset classes (corpus 274 → 289)
RFC 0003 (drafted 2026-04-26, accepted and implemented 2026-08-27), on top of the module runtime RFC 0006 landed the same day. Protocol goes to 1.8.0 for the new §X.2; the format spec gains §2.2a. uw_version stays 1.1 — the grammar addition is additive and no 1.1 document changes meaning.
- Format spec §2.2a — the identifier grammar. The ten builtins stay a closed set; a custom class is reverse-DNS with at least three lower-snake-case segments. Ownership is fixed at the identifier, so two implementations can never disagree about whose
com.example.data_centerthis is — which is what makes opening this extension point safe where an open enum would not have been. - Protocol §X.2 — resolution, in exactly three outcomes. Resolved (builtin, or a loaded module declares it), degraded (
MOD-FALLBACK-001, rendered via the declared builtin fallback and reported as degraded, never as a full read), or unresolved (MOD-MISSING-001). Determinism holds in all three: there is no arrangement in which two conforming hosts read the same file differently. AssetClassis deliberately not widened. The RFC proposed folding custom ids into the union, which in TypeScript collapses it tostring— silently disablingASSET_CLASS_MEMBERS' exhaustiveness anchor, every pack and layout lookup's narrowing, and the RFC 0027 / 0029 class tables. A separateUWAssetClassIdis used at the boundary where a custom class is legal; anything needing a builtin still asks for one and still narrows.- Holding a declaration is not holding the module.
resolveAssetClasstakesknownDeclarationsseparately from the registry and will only ever degrade from them. A cached declaration gives a display name and a fallback; what "loaded" means is the module's calculations and validations. - Resolution is not validation.
validateUWFilechecks only what is true for every reader —INVALID-ASSET-CLASS-001/002and theMOD-DEPENDENCY-UNDECLAREDwarning. Folding resolution in would make the same file valid or invalid depending on who ran it, and §5 of the format spec now says so explicitly. MOD-ASSET-CLASS-CONFLICT-001is new beyond the RFC: two modules declaring the same identifier is squatting, and picking one silently would make resolution depend on load order. Display-name collisions stayinfo.- Custom classes get no builtin pack, layout, or size intensive (§X.2.4). Wiring one into the §XIII registry would let a third party change what
price_per_unitdivides by. - Conformance: four manifest rejections plus
conformance/modules/asset-classes/, whose first three scenarios are byte-identical documents differing only in what the host has loaded — with a cross-scenario invariant asserting they stay identical, since editing one to fix a failure would void the demonstration. - Tests: 36 new core tests (identifier grammar, the three resolution outcomes, declaration conflicts, the validator's two codes).
Added — RFC 0006 implemented: the module system gets a runtime, and a consumer (corpus 269 → 274)
RFC 0006 (drafted 2026-04-26, accepted and implemented 2026-08-27), the last item on the priority order the owner set when the v2 train unfroze.
- The gap was not the loader. The RFC's "loader changes" section was written before
modules.tsexisted; loading, version-checking, and registration have shipped and been hardened since. What building a real module surfaced is that nothing consumed a registered module:calculationswere reachable only by a host that evaluated them itself,validationswere shape-checked at load and never executed by anything, andsectionswere declared and never looked for. The module system was a registry with no runtime. module-runtime.tsis the runtime —evaluateModuleCalculations,validateAgainstModules,checkModuleSections— and introduces no new evaluation machinery. A validation rule is a safe expression in exactly the §VIII.1 grammar, run throughevaluateCalclike any other declaration. A module able to evaluate what the calc engine cannot would be a second, unsandboxed language reachable from a third-party manifest.nullis notfalse. A rule asserts what must be true and fires only onfalse. A document carrying nohotel_brandhas not violated a rule about franchise fees; it has said nothing about them, and treating absence as violation would fire every module rule on every partial file.- Failures are reported, not skipped:
MOD-CALC-ERROR,MOD-RULE-ERROR,MOD-SECTION-MISSING. The first exists because of something the tests found — an unresolved identifier evaluates tonull, so a calc depending on a broken one succeeds with no value and every rule reading it falls silent. One typo in a formula quietly disables everything downstream, and this issue is often its only trace. - Protocol §X gains the host obligations a registered module implies: declaration-order evaluation, the null rule, no second evaluation path, and report-don't-skip. Section
schemafragments stay normative JSON Schema that a host with a validator SHOULD apply —@uwmd/coredeliberately is not such a host, socheckModuleSectionschecks presence and stops rather than shipping a half-implemented subset of JSON Schema. @uwmd/module-hospitality0.1.0 — the reference module, built against the library's published surface and nothing else, because a reference module that reached inside would demonstrate nothing. Three sections, five calculations, three validations. The manifest's source of truth is TypeScript (a typo in akindorseverityis a compile error) withdist/manifest.jsonemitted at build for hosts without a TS toolchain.- Occupancy is a fraction, and
CC-MOD-HOSP-02is an error rather than a warning becauserevparstill computes from a percentage — to a number a hundred times too large. Only the rule catches it. - Conformance:
conformance/modules/runtime/, five scenarios derived from one fixture — the fixture itself (both warning branches in one file), no comp set, occupancy as a percentage, the required section removed, and the same file relabelledoffice, where nothing must run at all. - Tests: 12 new core tests against a toy manifest (core must not depend on a sibling package) and 15 in the module package.
Added — RFC 0004 implemented: a language-agnostic conformance driver
RFC 0004 (drafted 2026-04-26, accepted and implemented 2026-08-27). The corpus is the project's main lever for staying coherent as adopters arrive, and it was runnable only from TypeScript — a Python or Rust implementation could "pass the conformance tests" only by writing its own runner, and two runners drift.
- Protocol §II.6a — the conformance CLI protocol. Six subcommands, exactly one JSON document on stdout (
renderemits text), stderr free for diagnostics, and a three-value exit contract:0success,1a failure the protocol describes with stdout still parseable,2unrecoverable. Also normative: comparison is a subset test, because the baselines are projections and an implementation reporting extra fields is more informative, not wrong — while arrays compare length-sensitively, since an omitted validation issue is a disagreement rather than brevity. conformance/runner/runner.py— the reference driver. Python 3.10+, standard library only, so an air-gapped implementer can run it. TAP version 14 on stdout plus an optional JSON manifest carrying the implementation's ownImplementationManifest; TAP has no standard way to attach that, and without it two implementations' results cannot be aggregated. 44 cases across tiers 1–3, andnpm run conformance:v2.- The v1 runner is not replaced.
npm run conformanceremains the gate at 269 assertions across thirteen suites, most of which are not a single command with a single output. The v2 driver gates the protocol; rewriting receipts, composition, and packaging as CLI calls would have meant deleting coverage to satisfy a migration plan written when the corpus was four tiers. - Cases are generated and CI-checked —
npm run gen-conformance-cases, with--checkin CI. A fixture added without regenerating them would silently drop out of v2 coverage while every suite still reported green. uwmd manifest, and--jsononcalcandedit.edit --jsonreports the edit instead of performing it: a driver that rewrote fixtures as a side effect of reading them would corrupt the corpus it is testing.
Fixed — uwmd parse was dropping four fields of the parsed file
custom_calculations, custom_scenarios, extensions, and the full superseded blocks were omitted, so a caller who trusted uwmd parse to be "the parsed file" silently lost every custom calculation and every x_* extension in the document. All four are emitted now; superseded_blocks (prior blocks' content only) stays alongside superseded for compatibility, and new callers should read the latter. Surfaced by RFC 0004 making the output observable against a baseline.
Fixed — two capabilities the spec promised but the enum did not carry
ViewerCapability gained signing and module-signature-verification. Protocol §V.11.5 and §X.1.4 told implementations to declare them the day before, while the type they would declare them in had no such members.
Added — RFC 0002 implemented: module manifest signatures (corpus 263 → 269)
RFC 0002 (drafted 2026-04-26, accepted and implemented 2026-08-27), the second half of the signing chain, on RFC 0010's machinery. Protocol §X.1; no version bump beyond 1.7.0, which the same day's §V.11 already carried.
ModuleManifest.signatureis normative — protocol §X.1, the newmodule-signature.schema.json, and an additive$reffrommodule-manifest.schema.json. A module manifest is executable surface (formulas the calc engine evaluates, validations that decide whether a deal reads as blocking), and a host loading one from npm or a URL had no way to ask whether it was the manifest the author published.- The scheme is
uwmd-keystore, not Sigstore — a deliberate departure from the RFC's opening design, recorded in it. Sigstore needs a Fulcio trust root and a Rekor inclusion proof, which means either a vendored snapshot that fails closed when stale or network access inside the module loader. Neither fits a protocol whose conformance corpus is offline and deterministic.scheme: "sigstore"is reserved so adding it stays additive. - Five verdicts, kept apart:
PROTO-MOD-068missing,-069unsupported scheme,-070malformed,-071unknown key,-072invalid.missing,unknown_key, andinvalidcall for three different responses — decide a policy, load a key, reject the module — and a verifier that reports them as one makes all three indistinguishable where the operator has to act. - Three host policies:
ignore(default, what every host did before),verify-if-present(unsigned loads, broken refuses), andrequire. Both checking policies refuse onunknown_key: a host that cannot check a signature has established nothing, and treating that as success would make the policy decorative.loadModuleManifestAsync/createModuleRegistryAsyncare the async siblings; the sync loaders are untouched and pay nothing. - A malformed signature refuses even under
ignore. Declining to verify is not a licence to admit a malformed object into a frozen manifest. identityis advisory, and §X.1.5 says so normatively. A signature proves the key holder asserted an identity, never that the assertion is true; theallowedIdentitiesallow-list is worth only as much as the host's decision to bind thatkidto that identity.- Conformance:
conformance/signing/modules/, six generated scenarios each run under all three policies —04-unsignedloading underverify-if-presentand refusing underrequireis the entire policy distinction, and asserting one half would let the two collapse unnoticed. The suite reuses RFC 0010's test key: a host that trusts a signer trusts them for both artifact kinds. - Tests: 19 new core tests (crypto-free: payload, shape, taxonomy, allow-list) and 18 in
@uwmd/signing(round trip, tamper, policy matrix, registry refusal).
Fixed — §V.9's hash exclusions had never fired on a real file
Surfaced while building the RFC 0010 fixtures. The canonicalizer decided an object was _meta-shaped by requiring the key section, but every .uw.md on disk spells it section_id (format spec §3, and the block schema's own example). So looksLikeMeta returned false for every parsed block, the two exclusions §V.9 mandates silently never applied, and stamping a content_hash changed the hash it was supposed to be a digest of — INT-04 on an untouched file. It went unnoticed because the only blocks that exercised the exclusion were hand-built test objects, which do set section.
integrity-canonical.ts now accepts either spelling and §V.9's wording is corrected to match. No shipped fixture's stated hash changes.
Added — RFC 0029 implemented: stage requirements are class-aware (corpus 245 → 257)
RFC 0029 (drafted, accepted, and implemented 2026-08-26) closes the finding the RFC 0028 example cleanup surfaced: §5.1's per-stage section lists were class-agnostic, so a land deal could never honestly declare full_underwrite (a rent_roll was required and raw land has no tenants) and a mixed-use deal was asked for a property-level rent_roll its components section would wrongly duplicate.
- §5.1 gains a two-row class-overlay table, mirrored by
STAGE_SECTION_OVERLAYS+requiredSectionsFor()invalidator.ts(the RFC 0027 declare-once pattern):landis exempt fromrent_rollandoperating_statementat every stage;mixed_usesubstitutescomponentsfor both — checkable (the substitute is required where the replaced sections were, once), and tightening nothing sinceMU-01already requirescomponents. All other classes take the base lists verbatim; hospitality's keys/ADRrent_rollstays the precedent that section ids are class-neutral containers. - Both consumers resolve through the one function —
stage_readinessandDQ-06cannot disagree about what a stage requires.CC-14is untouched:propertyis never exempt. - Pure relaxation: no document that validated stops validating; some land/mixed-use
DQ-06notes disappear and readiness booleans can flip false → true — neither pinned anywhere. - Conformance: three new Tier-1 valid fixtures whose frozen validation verdicts pin the behavior — a land deal at
full_underwritewith norent_roll/operating_statementvalidates clean with zeroDQ-06; a mixed-use deal whosecomponentsfoots to the property NOI likewise; and an office control with the same section inventory as the land fixture still carries itsDQ-06— the overlay is two rows, not a loophole. - Tests: 9 new unit tests (resolver across all stages × classes, the substitution dedup, readiness/issues agreement,
CC-14on a property-less land deal).
Changed — Sundance and Roosevelt restaged to full_underwrite (RFC 0029 follow-through)
The two examples RFC 0029's overlay unblocked climbed the same day: both gained borrower_sponsor, preliminary_sizing, and market_analysis (numbers derived from each file's own sections) and now declare full_underwrite honestly — stage_readiness true, zero DQ-06, no warnings. Sundance's sizing is LTC-governed, the only test with meaning against a negative land carry; Roosevelt's is the standard three-constraint sizing on the consolidated property, with component-level debt left refused (MU-06) as designed. Agave deliberately stays at screening: it is multifamily (no overlay applies), its subject is the capital stack, and a rent roll + operating statement would be padding.
Added — cli-packages.ts test backfill (core suite → 985)
The RFC 0018 CLI command wrappers (lease abstracts, deal packages) gained a direct sibling test — they export plain functions, so unlike cli.ts no extraction was needed; the harness spies console/stdout and replaces process.exit with a thrower. 14 tests pin the command-level contracts, including the encoder's PKGZIP-002 refusal of a sha-mismatched package (which is also why verify's failed branch is unreachable through our own tooling — it is reserved for hostile archives built elsewhere) and the semantic_digest → unverifiable branch. Coverage 0% → 97.5%.
Fixed — CLI flag/positional divergence; cli.ts plumbing extracted and tested
uwmd <cmd> --flag=value <file> silently dropped the filename and died with a usage error: the positional-extraction pass skipped the token after any-- token, while parseFlags correctly treats the = form as self-contained. The two passes now share one consumption rule.
The fix came out of the cli.ts test backfill (the last item in wiki 13's coverage note): cli.ts is a top-level script that runs at import time, so it cannot carry a sibling unit test — its pure argument/path plumbing (parseFlags, extractPositionals, replaceUWExtension, hostTierFlag, defaultPartsDir, readManifestFile) now lives in cli-args.ts, imported by cli.ts and covered at 100% by cli-args.test.ts (20 tests, including a pin that the two passes mirror each other exactly and a note on the greedy space form — flags go after positionals). No public API change; the command surface remains covered by the @uwmd/cli smoke tests.
Changed — all twelve worked examples are stage-honest (RFC 0028 follow-up)
Every example now satisfies its declared deal_stage's section list — zero DQ-06 notes across the corpus, so a future RFC can revisit DQ-06's severity on honest ground.
- Seven class examples (retail, industrial, student, senior, office, hotel, self-storage) gained their five missing sections:
operating_statement(a T-12 summary reconciled to the NOI model — same revenue, expense, and NOI totals),preliminary_sizing(max loan under 75% LTV / 1.25x DSCR / 9% debt-yield tests, governing constraint named),borrower_sponsor,market_analysis, andvalidation, all with numbers derived from each file's own sections so the cross-section checks hold. - The Parkview twins gained
preliminary_sizing— truthfully recording that the proposed $5.04M loan fits the LTV test but exceeds the DSCR and debt-yield sizings, the same tension their FV-04 warning already states. - The three feature-focused examples restaged to
screening(plus avalidationsection) rather than acquiring padding: a property-levelrent_rollis wrong for land (Sundance) and mixed-use (Roosevelt — its rent data is per-component), and Agave's subject is the capital stack. This surfaced a finding worth keeping: §5.1's stage requirements are class-agnostic, and a class-aware refinement is a candidate future RFC.
No code change; examples and docs only. Full suite, conformance (245), and lint all green with zero baseline updates — the pins that read these examples (size-intensive csv, report cover, Lite round-trip) check specific facts, not whole-file shapes, so additive sections pass through them.
Added — RFC 0028 implemented: a missing required section is now a reportable defect (corpus 244 → 245)
RFC 0028 (drafted, accepted, and implemented 2026-08-26) closes the gap its own Appendix A scan measured: format spec §4.1 requires the property section at every stage and §5.1 lists per-stage required sections, yet 28 in-scope corpus documents omitted property entirely and validated clean/warnings, because stage_readiness was computed but never reported as issues.
CC-14(warning): a deal-record UWX document with nopropertysection. Preconditions mirrorCC-13's (not a compiled Lite summary, deal profile only; an externalized section is present, not missing); unconditional ondeal_stage. Never coincides withCC-13— one defect, one diagnostic.DQ-06(info): one issue per section the declareddeal_stagerequires but the file lacks — the sectional sibling ofDQ-04and the issues-stream mirror ofstage_readiness. Suppresses itspropertyentry whenCC-14fired. Info by design: the scan found all twelve worked examples fail their declared stage's list, so info reports without refusing or nagging.operating_statementre-joinsSTAGE_REQUIREMENTS.full_underwriteand above (RFC decision (a)): §5.1 always listed it, and the validator's variant-awarehasSectioncase was built for it but no stage list reached it. §5.1 also gains its missing Scope row and a presence-vs-freshness split for Monitoring.- Spec: §5.1 is now machine-checked language; §5.3 gains the
CC-14row and severity/applicability prose. Format stays 1.1 (additive; both new rules are sub-error). - Conformance: new Tier-1
malformed/10-property-section-missingfixture (CC-14+DQ-06); the Tier-1 validation-verdict and render baselines absorb the new codes (the baseline system added 2026-08-25 doing its job). Thecapital-stack/senior-reconciles-debt-structurefixtures were made stage-honest (apropertysection added, the boilerplatefull_underwriteclaim dropped) rather than freezing ten noise codes. - Tier-4 replay: the cassette's prompt-drift detector fired — agent context includes validation output, which now carries the new codes. New
scripts/rerecord-cassettes.mjsturns the replay README's inline recipe into a runnable tool: it re-records every scenario's cassette from itsscripted-completion.jsonthrough the real recorder, pinned to the replay clock. The cassette diff shows only the request side changed. - Tests: 15 new unit tests (
validator.section-readiness.test.ts) covering everyCC-14precondition, theCC-13/DQ-06mutual exclusions, and theoperating_statementrequirement through itst12variant.
Follow-up tracked in docs/wiki/13-status.md: all twelve worked examples declare full_underwrite but miss 4–5 of its required sections — bring the examples to stage-honesty (add the sections or restage them), then a future RFC may revisit DQ-06's severity.
Added — two documented conformance blind spots closed (corpus 222 → 244)
Both gaps were already written down when their features shipped; this change makes the corpus able to see them.
- Tier-1 valid fixtures now freeze their validation verdict. Each fixture gains an
expected/<id>.validation.jsonbaseline holdingoverall_statusplus every distinct(code, severity)pair fromvalidateUWFile. RFC 0027 Appendix A noted that Tier-1 valid fixtures asserted nothing about validation, so escalating a rule from warning to error would flipuwmd validateto exit 1 on fixtures with the suite still green. A severity flip and a new code are both visible diffs now. The04-scope-onlybaseline records theCC-13warning that fixture truthfully earns, plus itsDQ-05andMETA_LOW_CONFIDENCE_NO_REVIEW_FLAGinfo notes. - The RFC 0025 decimal-exactness pin the Lite suite could not carry. Every existing Lite fixture's percents (5.50 / 5.75 / 6.25 / 5.00 / −1.50) divide cleanly by 100 in binary64, so all 90 assertions passed unchanged through the RFC 0025 fix — the corpus could not distinguish decimal-point-shift normalization from naive division.
06-decimal-exact-percentsstates rates only as literals that diverge under division (5.51 / 100is0.055099999999999996, one ULP off0.0551); its twin07-decimal-exact-fractionsstates the same values as bare fractions withunit=fraction. A newequivalence.jsongroup requires both to share one digest, which holds only under the RFC 0025 rule, and the frozencanonical.jsonpins the exact fractions besides.
No library change; runner + fixtures + baselines only. Docs updated: conformance/README.md, conformance/tier-1-reader/README.md, docs/wiki/09-conformance-testing.md.
1.7.0 - 2026-08-25
Released
@uwmd/core1.7.0,@uwmd/cli1.7.0 (lockstep), with coordinated@uwmd/corerepins in@uwmd/excel0.6.0,@uwmd/report0.6.0, and@uwmd/batch0.5.0. Carries RFC 0027 (the size-intensive registry), implemented since 1.6.0.- Format stays 1.1; protocol bumps to 1.6.0. The format change is additive (nine optional null-default §4.1 fields;
CC-13is a warning), butUW_PROTOCOL_v1.mdgained a normative section — §XIII's selection registry, with a MUST on any implementation that displays, exports, or indexes a deal's size — and §0.3's own rule is that the protocol bumps on any normative change to required behavior. ("Future work" is now §XIV.) Also corrected §0.3's stale "currently 1.3.0" prose, which had drifted fromPROTOCOL_VERSIONunguarded.
Added — the size-intensive conformance group; RFC 0027 is implemented (part 4)
The seven scenarios the RFC specified, closing the arc (corpus 215 → 222; RFC 0027 flipped to implemented):
registry-covers-every-class— §XIII.1/2/3 pinned against the shipped table: nine primaries,mixed_useand unrecognized classes null, no unpinned class in the registry.pack-agreement— coverage in both directions: each primary appears in its pack's formulas, and everyproperty.*path a pack reads is registry-known.csv-exports-size-for-every-class— all ten worked examples (plus Parkview):size_basis/size_quantitynon-empty for nine classes, empty formixed_use, andtotal_unitskeeps its value everywhere — the compatibility pin.report-cover-states-size— the office cover carriesRSF 42,500, the hotel'sKeys 142, and the multifamily cover gains no size fact.lite-round-trip-non-multifamily— the office example projects to Lite with its RSF anchor and compiles back to the same value.cc-13-warns-and-does-not-refuse— CC-13 fires once as a warning and the office pack'scap_ratestill evaluates; a warning must never refuse.cc-13-silent-for-mixed-use— no CC-13 andresolveDealSizenull, by design (§XIII.2).
Also: the conformance README corpus tree gains the group, and docs/wiki/13-status.md records the arc (including the §XI → §XIII errata).
Changed — Excel layouts and the web-editor grid read the registry (RFC 0027, part 3)
The two remaining hand-maintained copies of "which field is this class's size" now derive from Protocol §XIII. Behavior is unchanged in both tools — this is the duplication-removal half of the RFC.
@uwmd/excel— a newsizeNamedInputs(assetClass, labels)helper inlayout.tsselects each layout's property-size named inputs through the registry (primary first, then the secondaries the layout labels); the nine per-class layouts drop their hard-coded rows. Labels and sheet positions are untouched, so every workbook byte and parity assertion holds.tools/web-editor— the quick-edit grid's per-class size scoping is generated fromSIZE_INTENSIVES: a path is offered to exactly the classes whose registry entry names it, with only the label and input kind remaining editor-local. Two new tests pin the derivation (71 total).
Added — the size-intensive registry lands in @uwmd/core (RFC 0027, part 2)
The executable half of the Protocol §XIII registry. New browser-safe exports: SIZE_INTENSIVES, getSizeIntensive(), resolveDealSize(), and the SizeIntensive type.
protocol.ts— the §XIII table (nine classes;mixed_usedeliberately null, unrecognized classes null, never a guess).resolveDealSize()reads the raw property section — never the cascade, because a deal's size is a fact about the asset — and applies §VIII.2's envelope-contentunwrap exactly as the calc evaluator does, so the registry and the packs can never disagree about the same document.validator.ts—CC-13(warning, never error) with the five normative applicability preconditions, plus itsBUILTIN_REMEDIATIONSentry.renderer.ts—csvappendssize_basis/size_quantity(appended, never inserted: column order is contract;total_unitskeeps its position and meaning).summaryandchatstate a non-total_unitsclass's size; multifamily and senior-housing renderings are byte-identical to before.report.ts— the Lender Package / Credit Memo cover and property table state the class's own size (RSF 42,500,Keys 142); the multifamily cover is unchanged.lite-bridge.ts—UW_LITE_FIELD_MAPPINGSgains one anchor per intensive, mirroring the Lite spec §8 rows from part 1.- Conformance —
tier-1/04-scope-onlyexpected chat/summary gain the CC-13 warning line: that fixture states"units", a field no consumer reads, so it truthfully never stated a size at the normative path. No other baseline moved; Tier-3 and Excel parity are untouched. - Tests — 33 new: the registry/pack agreement suite (both directions), CC-13's severity and every precondition, per-consumer size assertions, the Lite round-trip, and multifamily no-drift pins. Core suite 894 → 927.
Added — the size-intensive registry lands in the specs (RFC 0027, part 1)
RFC 0027 was accepted 2026-08-25; this is the spec half of its implementation.
- Format spec §4.1 — the property payload now declares every asset class's size intensive (
rentable_square_feet,gross_leasable_area,net_rentable_square_feet,rentable_units,keys,total_beds,gross_acres,usable_acres,entitled_units), all optional andnullby default, with field notes — including thegross_acresvsland_area_acresdisambiguation and the two-count note for senior/student housing. New normative language: a property block MUST state its class's primary size field and SHOULD usenull/absence, never zero, for inapplicable fields. - Format spec §5.3 — new
CC-13: the property section must state the primary size field forfrontmatter.asset_class. A warning, never an error, with five normative applicability preconditions (UWX only, deal-record profile only, recognized non-mixed-use class, property section present and not externalized). - Protocol spec §XIII (new) — the per-class size-intensive selection registry: primary field, label, unit, and secondaries for all ten classes;
mixed_usedeliberately has no property-level size (§XIII.2); the table is closed for protocol 1.x (§XIII.3). "Future work" renumbered §XIII → §XIV. The RFC text called this section §XI, which the protocol already used for the Error Taxonomy — recorded as RFC 0027 errata. - Lite spec §8 — the
deal-summary-v1bridge mapping table gains a row per intensive, so a Lite summary can state any class's size, not only multifamily's.
Spec text only; the executable mirrors (protocol.ts registry, CC-13 in the validator, the renderer/report/lite-bridge consumers) follow in the next PR.
Fixed — the web editor's quick-edit grid is asset-class aware
fieldsForSection() in tools/web-editor filtered by section_id alone, and the catalog carried only multifamily's size intensives. Two consequences: a land parcel was offered a "Total units" input, and an office, retail, industrial, self-storage, hospitality, or land deal was offered no size input at all — the denominator of every per-unit metric on the strip was reachable only through the collapsed generic all-fields editor.
- The catalog now carries the size intensive for all ten classes (
rentable_square_feet,gross_leasable_area,net_rentable_square_feet+rentable_units,keys,total_beds,gross_acres/usable_acres/entitled_units,total_units,total_nra_sqft), each scoped to the classes whose packs and worked examples use it. fieldsForSection(section_id, asset_class)narrows the grid opt-out: a field is dropped only when the class is known and the field names other classes, so an unset or unrecognized class sees everything, class-independent fields (year_built,parking_spaces) are never scoped, andmixed_useis unfiltered by design. Nothing became unreachable —GenericFieldEditorstill surfaces every scalar leaf.- Pinned by a coverage assertion, not an equality: every
property.*path a class's calc pack reads must be offered to that class. Web-editor suite 63 → 69 tests.
No published package changes; tools/web-editor is private. Format, protocol, and every schema are untouched.
Known gap — surfaced by the above
spec/UW_FORMAT_SPEC_v1.md §4.1's property payload names only total_units, total_nra_sqft, and land_area_*. The nine other classes' size intensives are read by their calc packs and carried by their worked examples but are normatively undeclared, so every tool needing them re-derives the vocabulary from the packs. Written up as RFC 0027 (since accepted), which declares the fields in §4.1 plus a normative per-class selection table in the protocol. Drafting it found three further consumers that never re-derived the mapping and are wrong today — the one-row csv read model, the §7.1 report cover, and the UW Lite bridge — all recorded in the RFC and in docs/wiki/13-status.md.
1.6.0 - 2026-08-22
Released
@uwmd/core1.6.0,@uwmd/cli1.6.0 (lockstep), with coordinated@uwmd/corerepins in@uwmd/excel0.5.0,@uwmd/report0.5.0, and@uwmd/batch0.4.0. Carries RFC 0019 (mixed-use composition) and RFC 0026 (typed capital stack), both implemented since 1.5.0.- Format stays 1.1 and protocol stays 1.5.0. Both RFCs are additive: the
components(§4.23) andcapital_stack(§4.24) sections are new and optional, no existing.uw.md/.uwx.mdfile changes meaning, andUW_PROTOCOL_v1.mdis untouched. Minor across the board for the same reason.
Added — a typed capital stack (RFC 0026, implemented 2026-08-22)
A deal can state its full financing as an ordered array of typed tranches (senior / mezzanine / preferred equity / common / bridge / seller / other) plus stack-aware sizing figures a deterministic verifier recomputes — state-and-verify (RFC 0021 §6), so an arbitrary tranche count (two mezzanine notes included) lives entirely off the iteration-free Tier-3 calc engine. The ten packs and every single-loan metric are untouched: a document without a capital_stack behaves byte-for-byte as before (pinned by a conformance fixture).
capital_stacksection (format spec §4.24 +section-capital-stack.schema.json): tranches ordered by seniority and a closed sizing vocabulary —coverage,blended_coverage,debt_yield_through,ltc_through,ltv_through,weighted_cost. Preferred equity carriesaccrual: current-pay enters cash coverage, PIK does not, and balances count toward attachment-point debt yield either way.verifyCapitalStack(@uwmd/core) — three-state (verified/failed/unverifiable), a sibling ofverifyRollup; per-tranche cash debt service (trancheAnnualDebtService) and figure recomputation at a fixed per-fn quantum (CAPITAL_STACK_SIZING_DECIMALS, exported).- Validator rules
CS-01,CS-02,CS-WATERFALL-UNSUPPORTED(the Phase-2 distribution-waterfall boundary, refused at both section and tranche level), and the generalizedCC-03— the senior tranche must reconcile withdebt_structureand thesources_usessenior bucket. - Excel: the Capital Stack sheet (
@uwmd/excel) — additive for every asset class; one row per tranche with live debt-service formulas, a native-SUM total-capitalization row, and a sizing block whose agree/verdict cells quantize at the verifier's own table.unverifiablefigures render as text, never as formulas over blank cells. - §4.23 relaxation (MU-06): a mixed-use component MAY carry its own
capital_stack, validated by the sameCS-*rules; a bare componentdebt_structurestays refused, andCC-03stays top-level-only. - Conformance: the seven-scenario
capital-stackgroup (215 total), and the worked exampleexamples/Agave-Court-Apts-Scottsdale-AZ.uwx.md.
The multi-period distribution waterfall (promote, hurdles, tiers, catch-up) is documented and deferred to a later phase (RFC 0026 §E).
Added — mixed-use composition (RFC 0019, implemented 2026-08-19)
mixed_use — the last AssetClass member without a pack — is now fully supported, closing asset-class coverage at 10 of 10. Composition lives in the document as a bounded components map keyed by each use's own class, not in the pack, so the one-pack-per-class assumption survives (the Tier-3 calc engine has no iteration to evaluate per-component packs and aggregate).
componentssection (format spec §4.23 +section-components.schema.json): per-use subtotals,allocation_pct, and operating-business intermediates.MIXED_USE_PACK(21 metrics) +MIXED_USE_DEFAULTS(mix-independent financing terms). Property cap rate / LTV / DSCR / debt yield / cash-on-cash, a NOI share per component use, allocation-gated price per unit / psf / bed, and per-component GOP / labor pass-throughs. Deliberately no property price/unit, loan/unit, or blended market cap rate.- Validator rules
CC-11(asset-class gate),CC-12(property NOI == Σ component NOI), andMU-01…MU-06, each with aBUILTIN_REMEDIATIONSentry. - Excel
MIXED_USE_LAYOUT— per-component operating statements plus a consolidation block that foots to the property NOI, with deal-aware metric emission and exact Excel↔evaluator parity.fromWorkbookrefusesmixed_use(WORKBOOK-IMPORT-UNSUPPORTED-SHAPE); export is fully supported. - Conformance: six Tier-3 calc-host fixtures and seven
tier-1-reader/malformedrejection fixtures, plus a component-scoped defaults cascade proof.
Component-level debt (a capital stack) is out of scope and tracked in RFC 0026.
1.5.0 - 2026-08-19
Released
@uwmd/core1.5.0,@uwmd/cli1.5.0 (lockstep), with coordinated@uwmd/corerepins in@uwmd/excel0.4.0,@uwmd/report0.4.0, and@uwmd/batch0.3.0. Carries everything prepared for 1.4.0 (RFC 0025's Lite percent decimal exactness, never published — see below) plus RFC 0021 and RFC 0022.- Protocol 1.4.0 → 1.5.0, the one change that makes this a minor rather than a patch: the cascade goes from seven steps to eight. Documents are unaffected; a host that hard-codes the step list is not.
- Minor rather than major across the board. Every addition is additive to the format — an externalized section is a new packaging of a document, not a new document, which is the whole point of I-1 — and no existing
.uwx.mdor.uw.mdfile needs editing to keep working.
Added — composable UWX documents (RFC 0021) ⚠️ protocol 1.4.0 → 1.5.0
A record can hold a section in separate .uwpart.md fragments, reference whole child records to form a composite, inherit assumptions along that graph, and state aggregates a verifier recomputes. One invariant carries the design and everything else follows from it:
I-1. An externalized record, resolved, has the same semantic digest as the byte-identical inline record it expands to.
That is what makes composition packaging rather than modelling: externalizing a section is not a semantic change, does not alter identity, and does not invalidate a receipt issued over the resolved form.
spec/UW_COMPOSITION_v1.md— normative fragment grammar, the externalization directive, resolution, merge order, and bounds. Three schemas:uwpart,uw-external-section,uw-rollup. Schema corpus 12 → 15.composition.ts—parseUWPart,resolveComposition,resolveComposite,externalizeSection,stringifyUWPart, bounds, and a 9-codeCOMP-*taxonomy. Browser-safe; performs no I/O.- Refusals, not degradations. A missing fragment leaves the section externalized and reports
COMP-UNRESOLVED— never a smaller collection. This is the most dangerous failure the design admits: a rent roll missing four tenants still totals, still validates, and still produces a confident DSCR.part_countis redundant withparts.lengthon purpose, so a truncated array is detectable rather than silently smaller. - Merge order is byte-wise on UTF-8, not
localeCompare— a locale-dependent order would make the canonical form, and therefore the digest, vary by machine. staleis a third state, distinct fromfailed. An ancestor whose recorded child digest no longer matches has an unadopted correction, not evidence of tampering. Checked on the edge: a recorded digest is the parent's view of the child, so the same child can be current for one parent and stale for another.- The DFS is iterative on purpose. A recursive walk would blow the JS stack before reaching the depth bound on a hostile graph, turning a clean
COMP-DEPTHrefusal into a crash. Two cycle paths are covered: one reachable from a root, caught during the walk, and one with no root, caught by a reachability check afterwards — without the second, a fully-cyclic graph would report a clean resolve over zero members. - The cascade goes from seven steps to eight (
inherited_assumption), which is why the protocol minor-bumps. Position is normative in both directions: belowuser_input, so a value entered on the deal always beats an inherited one; aboveinvestor_profile, because an assumption from a named ancestor of this deal is more specific than an institution-wide preference set. The RFC says only "betweenuser_inputandmarket_data" and its diagram omitsinvestor_profile, so it does not actually say — resolved on the merits and recorded in protocol §V.7.1. - Rollup receipts sidestep the wall RFC 0019 hit. The Tier-3 sandbox has no iteration, so a composite states its aggregates and the verifier recomputes them over named child digests using a fixed, non-extensible
fnvocabulary. No change to the calc engine. A failed child short-circuits before any arithmetic runs, so a total over an unverified child is never reported as agreeing even when the numbers happen to add up. - CLI, 20 → 25 commands —
uwmd resolve,uwmd compose --externalize(non-destructive by default;--in-placeand--dry-runavailable), and--resolvedonverifyandexport. Without--resolvedan externalized record verifies as a directive and exports a rent roll that is a list of filenames, neither of which is the document anyone means. compositionconformance suite, 20 assertions. Corpus 175 → 195. I-1 is asserted on the canonical form and the digest, never on source bytes (those differ by construction — that is the point), and again against a shuffledpartsarray. The two I-1 assertions were verified by breaking them: mutating a fragment's rent, and reordering the inline twin's rows. Each produced exactly one failure in exactly the expected fixture.
Three errata against RFC 0021 as accepted, all found by building it and all accepted into the RFC on 2026-08-18. None changes the design. (1) The directive needs collection_path: collection_key says which field identifies a row and never says which field the rows occupy, and I-1 cannot hold without it — units and rows are different documents. The alternative was a section-to-collection-field table in the library, which is precisely the hand-maintained mirror that has already drifted for section ids. (2) §7's table had a code for every semantic failure and none for a structurally invalid input: COMP-PART-MALFORMED and COMP-DIRECTIVE-MALFORMED are added. (3) The RFC gives each fragment its own _meta but never says what becomes of it when the fragment becomes a row; I-1 settles it — dropped, since the inline twin's rows are plain objects.
RFC 0021 moves to implemented. Two questions it left open stay open and are deliberately not answered here: whether a fragment may itself externalize (forbidden by omission in the RFC, now stated explicitly in spec §2.3), and whether a section may be partially externalized, which complicates I-1 considerably for unclear benefit.
Added — the Lite projection accounts for externalized sections (RFC 0021 §3)
UWLiteProjectionReport.externalized_sections— sections the record stores in.uwpart.mdfragments. Projection is UWX→Lite only and never resolves fragments, so those rows are absent from the envelope and cannot appear inomitted_paths; naming the section is the only complete account available, and §3 requires it. A record whose only loss is an externalized section is nowlossyeven though it omits no paths. A new required field on the report type — additive for anything reading the report, a compile change for anything constructing one.- The directive's own keys no longer stand in for the contents they point at.
external.parts[0],part_count, andcollection_keyhad been reported as omitted data. That is worse than silence: an externalized record listed seven omitted paths where its inline twin listed ten, so a consumer comparing the two would conclude the externalized record lost less, while it was in fact missing an entire rent roll. The keys are packaging and are now excluded. - Detection is key-presence, deliberately not validity: a projection MUST NOT throw over a malformed directive, since the report is exactly what tells a reader what the document is missing. Validity stays composition's business.
uwmd convertwarns on the two losses separately — folding them together printedomitted 0 advanced path(s)over a missing section.- New conformance scenario
composition/lite-projection/externalized/(2 assertions), including that the projected Lite document is byte-identical to its inline twin: externalization is packaging, so the report is the only place the difference may show. Corpus 193 → 195.
Added — market data as an attributable document (RFC 0022)
MarketDataLookup had been interface-only since the v1.1 train, so the top two cascade steps had never had a worked example. The deeper gap was attribution: a market-derived value recorded no trace of which observation set produced it, so two pulls a week apart were indistinguishable after the fact and a receipt over that deal could not be reproduced.
market-data-v1document profile —market-data.ts, registered as anevidenceprofile (it carries observations, not conclusions; no pack applies). Identity (document_id/as_of/provider/geo) and a per-observationbasisare refusals, not warnings: an observation set with no vintage or no named provider is not attributable, and a number with no stated basis is an assertion. Adeal_idis refused outright. 22-codeMD-*taxonomy.createDocumentMarketData— a deterministic resolver that drops into the existing cascade. Staleness runs fromas_of, not a wall-clock guess, so a stale observation falls through toasset_class_defaultrather than being used past its vintage.selectCurrentMarketDatatakes the most recentas_ofand raises on a tie rather than letting array order decide which of two disagreeing providers is authoritative.inputs_provenance, receipt format 1.0 → 1.1 — see below.- Promotion (
market_data_accepted) —promoteMarketObservationbuilds the value and provenance for accepting an observation as the underwritten number. It returns a payload rather than editing a document, because promotion is an explicit Tier-2 edit by a named actor and never a side effect of resolution. The tag is neveruser_input: a value accepted for lack of better evidence and one established by diligence are different claims, and a file that renders them identically has destroyed something a credit reviewer needs. Promotion does not upgradeconfidence. _meta.market_data_refandDQ-06— the promoted block records which set, of which vintage, with a digest. Enforced as an error: without it, the tag is an unfalsifiable claim.STANDARD_SECTION_IDS/isStandardSectionId— the canonical section list from FORMAT_SPEC Part IV, pinned by a test that parses the spec.- CLI —
uwmd market-data validate <file>, and--market-data <file>onscopeandrefine, so the top cascade steps finally have a runnable example. spec/schemas/uw-market-data.schema.json, a worked example underexamples/market-data/, and amarket-dataconformance suite (14 assertions: every attribution requirement proved to fail rather than store a blank, plus vintage selection, the tie error, staleness fall-through, and promotion).
Added — shared receipt extension section
inputs_provenance(receipt format 1.1) — artifacts beyond the subject record that a computation depended on. One section with asourcediscriminator, shared by RFC 0022 (market_data) and RFC 0021 (child_record), because a verifier handles both identically. The1.0 → 1.1bump covers both RFCs and is not repeated; 0021's rollup entries are additive here.RCP-11(unverifiable) when a referenced input is unavailable, andRCP-12(failed) when it resolves but its digest disagrees. An absent observation set is not evidence of tampering. RCP-12 is evaluated across all held references before any RCP-11, so an unrelated missing reference cannot downgrade a genuine mismatch.- A 1.0 receipt remains valid and readable; the existing verify fixtures still carrying 1.0 receipts now double as the backward-compatibility check.
Changed
MarketDataLookup.resolvemay return an optionalsource_id, surfaced asResolvedValue.resolved_from. Additive — hosts returning{ value, range }are unaffected.market_data_acceptedresolves at theuser_inputcascade step while keeping its own source tag. Without this a promoted value matched no in-file tag and fell through toasset_class_default, silently discarding the analyst's accepted number. It outranks a live market lookup deliberately: accepting one vintage must not be overwritten by a newer pull.CascadeStepis unchanged; the cascade is normatively ordered and a producer must not reorder it (protocol §IX). Extending it is a protocol change, which RFC 0021 §5 subsequently made —inherited_assumptiontakes it to eight at protocol 1.5.0 — but RFC 0022 needed no such extension.
Changed — RFC status corrections (no behaviour change)
Four RFCs were recording a status the repository had already outgrown. Nothing in the code, specs, or corpus changes; the index now matches what shipped.
- 0017 (Lite/UWX source split), 0020 (
.uwx.mdterminology alignment), 0024 (iterative-function determinism), and 0025 (Lite percent decimal exactness) move toimplemented. Each meets the bar the RFC process sets for that status — shipped in a release, with a CHANGELOG entry already present above or in 1.3.0/1.4.0. - 0020 was the misleading one. It sat at
draftwhile its entire content — the format/protocol/XML/CSV spec realignment and the ten worked examples — had already landed, so the roadmap listed it as blocking work that was in fact finished.spec/UW_FORMAT_SPEC_v1.mdnow carries 20.uwx.mdreferences. - 0014 and 0016 deliberately stay
accepted. Both are genuinely partial: 0014 has package publication outstanding, and 0016 ships unsigned-only receipts pending the RFC 0010 signing package. Marking themimplementedwould overstate them.
Added — verify-versions, a guard over the compatibility matrix
VERSIONS.md calls itself the authoritative compatibility matrix, and nothing enforced that. It drifted twice without anything going red: the 1.4.0 release bumped every manifest and left the matrix advertising 1.3.0 across six rows, and tools/vscode-uwmd sat at 0.1.0 while the extension had moved to 0.2.0.
npm run verify-versions(scripts/verify-versions.mjs) checks everyCurrent matrixrow against itspackage.json, theUW Protocoland.uw.md format specrows againstPROTOCOL_VERSION/FORMAT_VERSIONinprotocol.ts, and every "pairs with@uwmd/core1.x" note against core's actual series. It reads files as data, so it needs no install and no build, and runs as its own CI job alongside the lockfile check.- Scoped to
Current matrixonly. ThePlannedtables below it name candidate versions that are supposed to differ from what ships. - Why the gap existed.
protocol.test.tsalready asserted theUW Protocolrow matchedPROTOCOL_VERSION— which is exactly why that row never drifted while the package rows did. The guard generalizes that one check to every row. - Cutting a
@uwmd/corerelease now has five guarded steps rather than four;VERSIONS.mdwas the unguarded one.
[1.4.0] - 2026-08-16
⚠️ Read before upgrading — Lite percent digests move (RFC 0025)
UW Lite normalized a percent display by dividing by 100. That is exact for most rates and not for others:
Number('5.51') / 100is0.055099999999999996, one ULP off the0.0551the literal denotes. Because the normalized value enters the RFC 8785 canonical form, a Lite-compiled5.51%and a hand-authored UWX0.0551were different doubles with different digests, and did not compare equal under semantic equivalence or an RFC 0016 receipt — though both spell the same rate.Scaling is now exact decimal-point movement (
UW_LITE_SPEC_v1.md§4.1), and the Lite canonicalization version moves 1.0 → 1.1. The Lite grammar is unchanged:5.51%parsed before and parses now, and no document needs editing.Affected documents are narrower than it sounds. Only a Lite document containing a percent that does not divide exactly changes digest. Every fixture in
conformance/lite/uses 5.50%/5.75%/6.25%/5.00%/-1.50%, all of which divide cleanly, so all 90 assertions passed untouched — the one affected document in this repo isexamples/Parkview-Apts-Glendale-AZ.uw.md.Pre-existing receipts degrade to
unverifiable, notfailed. NewRCP-10fires when digests disagree and the canonicalization version disagrees, so a stale receipt is reported as undecidable rather than as evidence the record was tampered with. Re-issue receipts over affected documents.
Added
RCP-10(UW_RECEIPT_v1.md§5.5) — digest mismatch is no longer decisive when the canonicalization version also differs. Same carve-out shape as the §5.3 engine-identity rule, one step earlier in the precedence.UW_LITE_CANONICALIZATION_VERSIONandUWX_CANONICALIZATION_VERSIONexported from@uwmd/core(and@uwmd/core/browser).- Conformance scenario
receipts/verify/06-lite-canonicalization-superseded— a5.51%Lite deal with a receipt stamped1.0carrying the genuine pre-fix digest, expectingunverifiable+RCP-10. Corpus: 158 → 159 assertions.
Fixed
- Lite percent normalization loses decimal exactness —
lite.tsnow shifts the decimal point through the digit string rather than dividing, so the value matches the double a hand-authored UWX fraction produces. - Lite issuance conflated two version fields —
canonicalization_versionwas stamped fromUW_LITE_REPRESENTATION_VERSION. UWX already kept them separate, so Lite was the odd one out; had this not been split first, bumping the canonicalization version would have falsely claimed the Lite grammar changed.
Decided
- Lite canonicalization
1.0and legacy.uw.mdsniffing now have an expiry: both sunset at Protocol 2.0. RFC 0017 introduced legacy sniffing as a transition with no end date and RFC 0020 declined to set one; RFC 0025 would have opened a second such transition. Both are 1.x compatibility bridges, so they share one boundary rather than accumulating separately. For all of 1.x a verifier MUST keep recognizingcanonicalization_version: "1.0"and degrade toRCP-10; at Protocol 2.0 that obligation ends.
Not released — superseded by 1.5.0
1.4.0 was prepared but never published. The manifests,
CORE_VERSION, and this entry were all written for it, but thev1.4.0tag was never pushed, andrelease.ymltriggers onv*only — so the publish job never ran. npm went from 1.3.0 straight to 1.5.0 and no 1.4.0 of any package exists. Nothing was broken for installers: every published package pins@uwmd/core1.3.0, which does exist. Everything below shipped in 1.5.0 instead.The version numbers are burned rather than reused, because
@uwmd/excel0.3.0,@uwmd/report0.3.0, and@uwmd/batch0.2.0 were published — at 1.3.0 pins. Republishing those numbers carrying a 1.5.0 pin is not something npm allows, so the dependents move to 0.4.0/0.4.0/0.3.0.No guard covered this.
verify-versionscomparesVERSIONS.mdto the manifests andverify-lockfilecompares pins to declared versions; all three agreed at 1.4.0, and agreeing on a version nobody published is exactly the state they were built to permit.verify-release(below) is the missing check.
- Prepared as
@uwmd/core1.4.0,@uwmd/cli1.4.0 (lockstep), with coordinated@uwmd/corerepins in@uwmd/excel0.3.0,@uwmd/report0.3.0, and@uwmd/batch0.2.0. Minor rather than major: the change corrects an IEEE 754 artifact to match whatUW_LITE_SPEC_v1.md§4 already specified, the grammar is unchanged so no document needs editing, andRCP-10keeps legacy receipts reportingunverifiableinstead of failing.
1.3.0 - 2026-08-15
⚠️ Read before upgrading —
irrcan now refuse where it used to answer RFC 0024 makes the
irrsearch procedure normative (protocol §VIII.3), and the reference implementation changes to match. Two inputs that returned a number now raiseCALC-IRR-DIVERGE:
- A root above 1000% or below -99.9%.
irr(-1, 20)returned18.999999999994728— a 1900% return, from a search the spec documents as reaching 1000%. The old Newton pass was free to converge outside the bracket it claimed to search. There is no correct code depending on this: reading19.0out of it means relying on the spec being wrong.- A cash flow with an even number of roots in the bracket.
irr(-100, 230, -132)returned0.1, though0.2zeroes the same NPV equally well — the answer was an artifact of Newton's0.1seed. Such a cash flow has no single internal rate of return, and the engine now says so instead of picking one.Conventional cash flows are unaffected, which is the load-bearing claim here. One sign change means a unique root, and bisection agrees with the old Newton pass to ~5e-13 — orders of magnitude below the six-decimal quantum §VIII.5 reports a rate at. No pack metric uses
irr, so no built-in calculation on any asset class moves.Receipts issued before this release over a document whose IRR changes verify as
unverifiableviaRCP-07, notfailed— the same path RFC 0023 used.
Changed
irris pinned to bracket-then-bisect — protocol 1.3.0 → 1.4.0. (RFC 0024) §VIII.3 gains a normative six-step procedure: bracket over[-0.999, 10.0], return an exact endpoint root, bisect to|npv| < 1e-9or a half-interval under1e-12capped at 200 iterations, and no Newton polish. Bisection is the part that is bit-reproducible — every step is(lo + hi) / 2and a comparison of products, which IEEE 754 requires to be correctly rounded, in an order the spec now fixes. Newton's iterates depend on the association order of a derivative sum no document pins. UnderVERSIONS.mdrule 2 this is a monotonic strengthening of requirements, so a minor bump.pmt,fv,pv, andnperMUST be closed-form (§VIII.3).nperis solved iteratively in some formulations, and an implementation that does so inherits the reproducibility problem theirrprocedure exists to remove.irris now the only builtin permitted to iterate.RFC 0024 accepted and implemented (iterative-function determinism). Three checks ran before acceptance: the iteration audit the RFC asked for came back clean (
irris the only builtin that converges;nperis closed-form), and no built-in pack declares anirrmetric — which narrows the exposure to third-party modules and makes the Excel-parity question the draft called blocking unreachable today.Implementation then found two of the RFC's five stated fixtures to be impossible under its own normative procedure, and the RFC now records both as errata. Fixture 02 expected a multi-root cash flow to "return the bisection root, pinned exactly": it cannot: an even number of roots means
npvshares a sign at both endpoints, so no bracket exists and step 2 raises. Fixture 04 expected a root exactly at-0.999to be found:1.0 + (-0.999)is0.001000000000000001in binary64, so that root is not a well-defined quantity at the low endpoint. Both are now specified as they actually behave.@uwmd/core1.2.0 → 1.3.0. A minor, and the call deserves stating, becauseirrcan now throw where it returned a number — normally a major. The behavior being removed is a value returned from outside the domain the specification claims to search, so there is no correct code depending on it: reading19.0out ofirr(-1, 20)is relying on the spec being wrong. A deprecation window would have preserved the divergence between engines for the length of the window, which is the thing RFC 0024 exists to close. Callers who do depend on the old value should pin1.2.xand read §VIII.3.@uwmd/cli1.2.0 → 1.3.0.calc,receipt, andsummaryover a module declaring anirrcalculation can now report an error where they reported a number. No flag, command, or output shape changes.@uwmd/excel0.2.0 → 0.3.0,@uwmd/report0.2.0 → 0.3.0, and@uwmd/batch0.1.0 → 0.2.0 as coordinated repins only — none of their output changes. Excel mapsirr → IRRbut no pack declares anirrmetric, so no emitted workbook formula is affected;report.tsreads stored section data and never evaluates; batch digests document inputs, not calc outputs. All four dependents pin@uwmd/coreexactly, so all four had to move: a republished0.2.0carrying a different pin is not a thing npm allows, and leaving them behind would mean the repin never ships.@uwmd/batchwas held back at the 1.2.0 cut for the same reasoning that does not apply here — that release did not change its pin.Security reports now go to
[email protected](SECURITY.md), replacing the general[email protected]address. A dedicated alias keeps a vulnerability report off the same triage path as ordinary project mail. The 1.0.0 entry below still names the old address; it is a record of what was true then.
Fixed
RCP-07now compares engine identity, not the version string alone.verifyReceipttreated a receipt as same-engine whenever the version matched, so a disagreement between two different engines that happened to share a version number was reported asfailed— blaming the record for a cross-engine difference, which is exactly whatRCP-07exists to prevent. Identity is now the pair(engine, engine_version), andReceiptVerificationOptionsgains an optionalenginealongsideengine_versionso a third-party verifier can declare its own name. The issue'sexpected/actualnow carryengine@versionrather than bare versions. Receipt spec §5.3 restated to match (UW_RECEIPT_v1.md).
1.2.0 - 2026-08-15
⚠️ Read before upgrading — computed numbers change
RFC 0023 quantizes every reported calc value (protocol §VIII.5). Three consequences a caller can observe:
round(num, dec)returns different answers at exact-half boundaries.round(1.005, 2)was1.00and is now1.01. §VIII.3 always specified half-away-from-zero, so this is errata rather than a redefinition — but a module whose formulas were tuned against the old behavior will move.- Every derived metric is quantized. DSCR reports 4 decimals, dollars 2, rates 6. If you diff CLI output, receipt values, or workbook cells against stored expectations, they will differ in the tail.
- Receipts issued before this release no longer verify as
verified. They returnunverifiableviaRCP-07, notfailed— the disagreement is attributed to the engine change, not to your record. Re-issue to get a cleanverified.Protocol moves 1.2.0 → 1.3.0 accordingly: §VIII.5 adds normative
MUSTrequirements, which underVERSIONS.mdrule 2 is a minor bump.
Added
RFC 0023 implemented — a numeric model and a single quantization boundary (protocol §VIII.5). The spec now states how precise a number is. Evaluation runs in unrounded IEEE 754 binary64; a calculation's reported value is quantized half away from zero at exactly one place,
evaluateCalc(), to its effective decimal places.ModuleCalcDeclgains an optionalround_to(integer[0, 12]), defaulted fromunitby a normative table ($→2,%→6,x→4, otherwise 6) that is deliberately total — an unspecified precision is an unspecified interoperability contract.New
calc/quantize.tsexportsquantizeDecimal,resolveRoundTo,MAX_ROUND_TO,DEFAULT_ROUND_TO, andDEFAULT_ROUND_TO_BY_UNITfrom bothindex.tsandbrowser.ts.CalcResultechoes the effectiveround_to. New loader codePROTO-MOD-067refuses a malformed one. Corpus 147 → 153.
Fixed
- The §VIII.3
irrconvergence note misdescribed the implementation it documents. It said the engine brackets on[-0.999, 10.0]and refines with Newton;calc/builtins.tsruns Newton first from a seed of0.1, capped at 100 iterations, and uses the bracket only as a fallback. The difference is not academic: the bracket therefore constrains no answer, andirr(-1, 20)returns ≈19.0— a 1900% return out of a search documented as reaching 1000% — where an implementer who bracketed first, as the note described, would raiseCALC-IRR-DIVERGE. The note now describes the code and states that consequence outright, along with the seed-dependent root selection (irr(-100, 230, -132)→0.1, with0.2equally a root). Still non-normative; no engine behavior changes in this release. Pinning the algorithm is proposed in RFC 0024 (draft). - A receipt could report a clean record as corrupted.
receipts.tsran two checks over the same numbers that could not both be right:RECEIPT_RESULT_TOLERANCEcompared stated against recomputed at1e-6, whileresults_digesthashed those same raw doubles bit-exactly. A last-ULP difference passed the tolerant check and failed the exact one, surfacing asRCP-04— corruption. Quantized results leave no tail for the two to disagree about. Receipts issued before this degrade tounverifiablerather thanfailed, via the existingRCP-07engine-version rule. round(num, dec)diverged from its own documented contract. It scaled by10 ** dec, which reintroduces the artifact it exists to remove:1.005 * 100is100.49999999999999, soround(1.005, 2)returned1.00where spreadsheetROUNDreturns1.01. §VIII.3 already specified half-away-from-zero, so this is errata, not a behavior change.
Changed
- Excel↔evaluator parity is now asserted as exact equality, replacing agreement to six decimals, across all nine asset classes. Formulas are emitted through the new
emitCalcExcelFormula, which wraps them inROUND(expr, round_to)so the workbook cell quantizes exactly asevaluateCalcdoes.emitExcelFormulastill emits the bare expression; converters should prefer the new function. @uwmd/core1.1.2 → 1.2.0. The bump is load-bearing: it is what makesRCP-07classify a pre-quantization receipt as indeterminate rather than failed.@uwmd/cli1.1.3 → 1.2.0 and@uwmd/excel0.1.0 → 0.2.0 emit different bytes than before — the CLI'scalc,receipt, andsummaryoutput carries quantized values, and every workbook formula is nowROUND-wrapped — so a patch bump would have understated the change. Neither removes or renames a flag, an export, or a sheet, so neither is breaking.@uwmd/report0.1.0 → 0.2.0 as a coordinated repin only. Its output is unchanged:report.tsreads every figure out of stored section data withdeepGetand never callsevaluateCalcor resolves a pack, so quantization does not reach it. The bump keeps the workspace on one core version rather than recording a behavior change. Worth knowing for a different reason — a credit memo quotes DSCR and LTV that nothing in the pipeline recomputes, so those figures are only as good as whatever wrote them into the document.@uwmd/batchis deliberately left at 0.1.0: its semantic digests cover document inputs, not calc outputs.- The four packages that depend on
@uwmd/corepin it exactly, so all were repinned to1.2.0; nothing depends on the bumped packages themselves. VERSIONS.md's "current matrix" gained rows for@uwmd/reportand@uwmd/batch, which it had omitted while describing itself as current.
Added
RFC 0018 implemented — document profiles and deal packages. The edge registry landed first, per the ordering constraint:
BUILTIN_EDGE_TYPESinprotocol.tsis the single canonical two-layer vocabulary, withguaranteesandsupportsdeclared valid on both layers rather than duplicated. Unknown types are preserved; a known type used on the wrong layer is refused, because that is a claim the registry says cannot be true.Three document profiles are registered, and an unknown profile is preserved rather than reinterpreted as a deal.
Lease abstracts (
lease-abstract.ts) enforce the two rules the profile exists for: every asserted term carries asource_refwith a locator, and a null term must state why it is null.projectLeaseAbstractToRentRollrefuses to project anything but an executed lease, never computes a figure it was not given, and separates ambiguous conflicts from plain omissions.Deal packages (
deal-package.ts,-zip.ts,-context.ts) implement manifest validation, a deterministic.uwpkg.zipcodec, three-state verification keepingunverifiabledistinct fromfailed, and the JSON context view whose central rule is that source-evidence bytes are never inlined — only described by identity and digest.projectPackageLinksToEntityEdgessynthesizes provenance naming the package and member ids; there is deliberately no inverse.New CLI:
uwmd lease validate|project,uwmd package create|verify|list|to-context|validate-context|edges. New schema:uw-deal-package-manifest.schema.json. New conformance suite:conformance/packages/(18 assertions).npm run verify-lockfile, gating CI, asserts that every@uwmd/*entry in all three lockfiles is a workspace link rather than a registry tarball, and that each cross-package pin names the version its workspace actually declares. The failure it guards against is silent in the worst way: a lockfile that resolved@uwmd/corefrom the registry would let the whole suite pass against the last published library, so green CI would mean "the previous release still works" rather than "this commit does." The pin check catches the other half — a dependent left pinned to a version the workspace has moved past, which npm resolves locally, so nothing notices until publish.
Fixed
CI ran no jobs at all on a stacked pull request.
ci.yml'spull_request.branches: [main]filter matches the base of a PR, not its head, so a PR opened against another feature branch triggered nothing. The failure had the same shape as the one above: it did not fail, it reported nothing, and an absent check list sits next to whatever external checks do run and reads as green. The filter is removed — every pull request gets the suite, whatever it targets. Thepushtrigger stays pinned tomain, so branch pushes still do not double up with the PR run.Document-authored keys and paths could reach the JS prototype chain. Path navigation walked with a bare property lookup, so a Tier-3 formula segment or a
deepGetpath naming__proto__,constructor, orprototyperesolved onto internals the sandbox exists to keep out of reach —MAX_NODESand the grammar bound what a formula can do, but bounded nothing about what it could name. Navigation is now own-property-only and refuses those three segments, resolving them tonull/undefinedlike any missing path (getPathSegmentinparser.ts, shared bydeepGetand the Tier-3 evaluator). An array's ownlengthstill resolves; inherited members no longer do.The matching write paths are closed too: frontmatter and fence-annotation keys matching those three names are parsed and then dropped rather than assigned (dropping the key line alone would have left its indented block to be read as top-level keys); UW Lite reports
LITE_FRONTMATTER_KEY_RESERVED/LITE_ATTRIBUTE_KEY_RESERVED; and the Lite bridge rejects a mapping whosetarget_pathcontains one, asLITE_MAPPING_INVALID.XML decoding is the exception that could not simply reject:
UW_XML_MAPPING_v1§4 forbids key sanitization and requires auw:membername to be restored verbatim. Top-level members are now restored withObject.defineProperty, so a member named__proto__round-trips as an own data property instead of invoking the setter and mutating the envelope's prototype. Its duplicate check moved fromintoObject.hasOwn, which had reported inherited names as duplicates.No spec text changes: every blocked segment resolves to the value protocol §VIII.2 already assigns to a missing path.
sha256TextHexcould not correctly digest binary content. It encodes its argument withTextEncoder, so hashing bytes round-tripped through a latin1 string re-encoded every byte above 0x7F as multi-byte UTF-8 and digested different bytes than the ones on disk. Harmless while everything hashed was text; a real defect the moment a package carries a PDF. Addedsha256BytesHex, and a conformance invariant asserts a binary member survives a package round trip byte for byte.
Changed
@anthropic-ai/sdkis now an optional peer dependency of@uwmd/core, not a runtime dependency. A parser, a validator, and a calc engine should not oblige anyone to install a vendor SDK. The Tier-4 host was already provider-neutral in design —bancroft.tsreaches the default provider by dynamic import — butindex.tsre-exportscreateAnthropicProviderstatically, and that module imported the SDK statically, soimport '@uwmd/core'loaded the vendor SDK for every consumer regardless. The import is now dynamic and deferred to the first request; the SDK's types are reached by an erasedimport type.createAnthropicProvider()keeps its signature and stays synchronous — client construction moved behind the firstcomplete()/stream()call, so an invalid API key now surfaces on that call rather than at construction. A consumer who has not installed the SDK gets a typedAGENT_PROVIDER_SDK_MISSINGnaming the fix, instead of a module-resolution stack trace;AgentProviderError'scodeunion widens by that one member, and transport wrapping no longer relabels it. Hosts that supply their ownprovider, and everything Tier-1 through Tier-3, need no change.Requires a
@uwmd/coreminor bump at release: the calling shape is unchanged, but whatnpm installputs on disk is not, so a patch would understate it.Safe-ZIP inspection extracted to
zip-safety.ts. RFC 0018 §3 requires deal packages to apply the restrictions the CSV bundle already established; reimplementing them would have produced two subtly different copies of security-relevant code. Callers map a semantic violation to their own code, so the CSV bundle'sCSV_*codes are reproduced exactly and its tests pass unchanged.
Governance
RFC 0021 and RFC 0022 accepted (2026-08-13). With 0018, the arc 0018 → 0021 → 0022 is approved end to end.
0021 — composable UWX documents. Section externalization into
.uwpart.mdfragments, recursive composites, shared assumptions inherited along the composition DAG, and rollup receipts. The design rests on one invariant: an externalized record and its inline equivalent have the same semantic digest, so composition is packaging rather than modelling. Rollup receipts deliberately avoid the wall RFC 0019 hit — the Tier-3 sandbox has no iteration, so a composite states aggregates and the receipt verifier recomputes them over named child digests using a fixed, non-extensiblefnvocabulary. No calc engine change.0022 — market data as an attributable UW document.
market-data-v1with requiredas_of,provider, andbasis; receipts pin the observation set's semantic digest, with an absent set reportedunverifiablerather thanfailed; and promotion to an input of record is explicit and taggedmarket_data_acceptedrather than being rewritten touser_input, so a value accepted for lack of evidence stays distinguishable from one established by diligence.Shared receipt surface. Both amend the receipt format RFC 0016 owns — 0021 adds rollup verification, 0022 adds
inputs_provenance. The amendments are additive and do not collide, and both RFCs now carry the ownership rule 0018 §5 established for the edge registry: the amendments live inUW_RECEIPT_v1.mdanduw-receipt.schema.json, whichever is implemented first establishes the extension section, andreceipt_versionbumps once to cover both rather than twice for two simultaneously-accepted RFCs.No code ships against either yet.
RFC 0018 accepted (2026-08-13) — document profiles and deal packages. Approves
lease-abstract-v1andsource-note-v1, the UW Deal Package (.uwpkg.zipwith a manifest carrying per-membersha256andsemantic_digest), the source-file-free connector JSON context view, and the canonical two-layer edge registry. Both of its previously blocking questions were resolved in the text before acceptance; the remaining open items (legal vocabulary, amendment consolidation, package signing, OCR profile) are explicitly deferred rather than blocking. No code has shipped against it yet.Acceptance settled edge-registry ownership under 0018's own §5 rule: whichever of 0018/0015 was accepted first owns the registry, so the canonical entity-plus-member edge vocabulary now belongs to the protocol spec as 0018 defines it, and RFC 0015's edge list is superseded by that section. A later acceptance of 0015 amends that section rather than starting a second table — which was the entire point of writing §5.
This unblocks RFC 0021 (composition) and RFC 0022 (market data) architecturally; both still require their own acceptance.
Added
Module loader hardening, and a conformance suite that keeps it honest (T13).
modules.tsvalidatedcalculationsandvalidationsand stopped —sections,view_models, andagent_layerswere constrained byspec/schemas/module-manifest.schema.jsonand validated nowhere. Running identical manifests through ajv and the loader disagreed on seven of eight probes.agent_layerswas the one that mattered most: it carriesprompt_template, making it Tier-4 prompt surface, and it acceptedprompt_template: 42.The loader is now schema-complete — all three constructs validated, unknown keys refused at every level (
additionalProperties: false),depends_onshape-checked, length bounds enforced — with pointer-level error codes (PROTO-MOD-033…PROTO-MOD-066).New
conformance/modules/suite (22 assertions, runs by default, no network): every fixture is checked against both the loader and ajv, and the suite fails when the two disagree.@uwmd/corecannot depend on a JSON Schema validator under the layering invariant, so the hand-written mirror needed an external referee. Two deliberate divergences — requiringdeterministic: true, and parsing the safe-expression grammar, neither expressible in JSON Schema — are declared per fixture with a stated reason; the reverse direction has no opt-out.New
uwmd modules validate|listfor module authors, reporting the exact failing declaration rather than a bare refusal.A UW Lite worked example — the first one the repo has ever shipped (T11).
spec/UW_LITE_SPEC_v1.mdhas specified UW Lite normatively since RFC 0017 whileexamples/contained zero instances of it, which RFC 0020 identified as a plausible root cause of the Lite/UWX drift it had to go back and correct.examples/Parkview-Apts-Glendale-AZ.uw.mdcloses the gap deliberately as a twin of the existing record: same deal, same deal id, same base name, both extensions adjacent in the directory listing, so the distinction is visible before either file is opened. The numbers are the record's own — including the underwritten NOI of $396,635 rather than the $412,096 trailing-twelve actual, a distinction the example calls out because it is exactly what a bare spreadsheet cell leaves ambiguous.The example states no DSCR and no LTV. Both are derived, and the prose says so and quotes what the multifamily pack computes (DSCR 1.1091, LTV 0.7000, cap rate 0.0551, debt yield 0.0787). Those four values were evaluated through the pack, not asserted, and three CLI smoke tests now pin them — an example whose prose quotes numbers nothing checks is an example that rots. Projecting the complete record back down to Lite reports 7 projected paths against 1,215 omitted, which is the most concrete statement of the split available.
Fixed
- Duplicate module ids silently shadowed each other. Two manifests sharing an id both loaded and
byIdreturned whichever came last, so a registry lookup resolved to the wrong module. Now refused withPROTO-MOD-066. - A typo'd construct name discarded a module's work in silence.
calculationz: [...]loaded clean and contributed nothing, with no error. requires_protocol: "^1"was silently unsatisfiable. OnlyX.Ywas padded to full semver, so a bare major failed to parse and every range containing one failed to match — while the schema's own description documents^1as valid.- A malformed
depends_onmisreported itself.depends_on: ["other"]was iterated character by character and surfaced asMissing module dependency: undefined. - CI never ran the Tier-4 replay suite on pull requests.
ci.ymlpinned--tier=1,2,3,lite,receipts, so the4-replaysuite added in T10 ran only inrelease.yml. Both CI steps now callnpm run conformanceand take the runner's default list, so adding a suite to the default is enough to gate PRs.
Changed
- Errata:
module-manifest.schema.jsonwas missing thescopedeal stage.DealStageintypes.tshas carriedscopesince the v1.1 train, so a manifest the loader accepted was invalid against the normative schema. Treated as errata rather than an RFC because it aligns a stale mirror to an already-accepted decision rather than making a new one. uwmd initwrote structured UWX content to a.uw.mdfile.generateBlankUWFile()emitsuw_versionand fenceduw:section=blocks, but the default output filename wasnew-deal.uw.md(or<name>.uw.md), so the CLI produced exactly the file the format spec forbids anddetectUWSourceRepresentation()flags as legacy on the next load — every deal a newcomer scaffolded was born legacy. Now.uwx.md.uwmd exportanduwmd reportappended to a.uwx.mdname instead of replacing it.replaceUWExtension()did not list.uwx.md, so a UWX input fell through to the append branch:uwmd export deal.uwx.mdwrotedeal.uwx.md.uw.jsonrather thandeal.uw.json, andreportlikewise emitteddeal.uwx.md.report.html. (.uw.mdnever matched a.uwx.mdfilename — the suffixes genuinely differ — so it failed silently rather than mis-matching.)reportnow routes through the same helper instead of its own inline check, so every UW source extension is handled in one place. Legacy.uw.mdinputs resolve exactly as before.- These three had no test coverage at all, which is how they survived the
.uwx.mdmigration. Five CLI smoke tests now pin the default output paths, including a regression case for legacy.uw.mdinput.
Changed
- The format spec now describes
.uwx.md, the extension it has specified since RFC 0017.spec/UW_FORMAT_SPEC_v1.mdstill called itself the.uw.mdspecification and opened by declaring a.uw.mdfile "the canonical, lossless" record — the exact claim accepted RFC 0017 moved to.uwx.mdwhen it split UW Lite from UWX. It contained zero occurrences of.uwx.md. Since the docs site renders the spec directly, uwmd.org repeated the error to every visitor. Retitled, realigned throughout, and given a Naming section fixing the vocabulary: UW Markdown is the standard,.uwx.mdis the lossless record,.uw.mdis UW Lite — a current, deliberately lossy view, not a superseded predecessor, which is where the.doc/.docxanalogy stops. Same alignment applied to the protocol, XML, and CSV specs and toREADME.md. No behaviour change; the library already emitted.uwx.md. See RFC 0020. - The ten worked examples were renamed to
.uwx.md. Every file inexamples/was a structured record carrying the legacy extension, so the project's own detector flagged each one — "Structured UWX content uses the legacy .uw.md extension; migrate it to .uwx.md." The examples are the most copied artifact in the repo and were modelling deprecated usage. Content is byte-identical; 41 referencing files were updated by path only.
Added
- The Tier-4 agent host is provider-neutral (T9). Protocol §IX has always described it that way, but
agents/bancroft.tsconstructed an Anthropic client on every run and typed its internals against the SDK — so the claim could not be demonstrated, andagents/sat at 0% coverage because exercising it needed a network call and an API key. NewAgentProvidercontract (complete(), optionalstream(), neutral request/completion types) with no vendor import;agents/providers/anthropic.tsis now the only file importing@anthropic-ai/sdk, reached through a dynamic import so a host bringing its own provider never loads it.BancroftRunOptionsgainsproviderandapiKeybecomes optional — additive; existing callers are unaffected. Streaming falls back tocomplete()for providers that cannot stream. - Recorded-replay Tier-4 conformance (T10). Tier-4 was shape-and-lint only: fixtures were parsed and nothing ran, so nothing checked that a host writes what the protocol says it writes. The
l6-risk-ratingfixture was in fact un-runnable — it carries only apropertysection while L6 requiresnoi_model,valuation, anddebt_structure. NewcreateRecordingProvider/createReplayProviderplus a4-replayconformance suite that replays a cassette through the real runner and compares the document byte for byte. It runs in CI by default because it needs no network and no key. Replay is strict — a request that no longer matches its recording is a typed error naming the changed field — so a cassette doubles as a prompt-drift detector. What it does not prove, and says so in its README, is that any particular model produces the recorded answer. RunOptions.timestamp/RunOptions.logEntryIdandBancroftRunOptions.now. Agent output was previously non-reproducible:_meta.timestamp,duration_ms, and a random log-entry suffix differed every run, which is why replay could not assert on a document at all. A constant clock now freezes all three. Real runs leavenowunset and keep the random suffix that stops same-millisecond collisions.ASSET_CLASSES— theAssetClassunion as a runtime list, exported from@uwmd/coreand@uwmd/core/browser. It is derived from aRecord<AssetClass, true>exhaustiveness anchor rather than written by hand, so adding a member to the union without updating the list is a compile error (TS2741) instead of silent drift.modules.ts's private duplicate list was deleted in favour of it. Additive; no signature changes.types.test.ts— a drift guard over the asset-class registries. Holds the calc-pack registry and the defaults registry toASSET_CLASSES, checks each pack declares the class it is registered under and each defaults table stamps its own class, and pins theDEFAULT_THRESHOLDSbands (warning always fires before error).mixed_useis carried as one documented exception that fails loudly the moment its pack lands, so the allowance cannot outlive its reason. A matching guard in the web editor'scatalog.test.tskeeps its own asset-class list in step with the format.
Changed
- The
@uwmd/corecoverage floor was ratcheted to just under measured. Thresholds had sat at 70 lines / 70 statements / 90 functions / 70 branches while actual coverage was ~77 / ~77 / 96.7 / ~75.5 — five to seven points of headroom in which coverage could erode without CI noticing. Raised to 76 / 76 / 95 / 74. The gate itself was already blocking:continue-on-errorwas removed from the coverage job in13218c4, contrary to the internal status doc, which still described it as a soft floor. Verified the gate actually fails by breaching it deliberately (exit 1) rather than assuming it. The remaining ~1 point of margin is sized against real jitter —calc.property.test.tsruns fast-check with no fixed seed, so coverage varies by ~0.2 points between identical runs and a floor set flush to a measured figure would fail at random. - "No pack registered" negative tests no longer borrow a real asset class. Five tests — in
cascade.test.ts,defaults.test.ts,toWorkbook.test.ts,receipts.test.ts, and theconformance/receipts/refuse/02-no-pack-for-asset-classfixture — asserted that an unregistered class resolves to no pack, no defaults, and no Excel layout by pointing at whichever realAssetClassmember happened to still be unregistered. Every new pack therefore broke them and had to shuffle the role onward (land → hospitality → senior_housing → student_housing → mixed_use). Withmixed_usethe last one left, the next pack had nowhere to shuffle to. All five now anchor on the synthetic identifier__unregistered_test_class__, which is deliberately not a member of theAssetClassunion and must never become one. No test or fixture referencesmixed_useany more, so the final pack can be written without touching them. Behaviour is unchanged — the public API already acceptedstring, so no casts or signature changes were needed. - Canonical repository moved to the project organization — every source, download, example, raw-content, and clone URL now points at
github.com/UWMD-OSP/UW-Markdown. Packagerepositorymetadata, the docs-site nav/footer/edit links, thellms.txtcorpus, and the AI guide were all updated in lockstep. Security reports now go to[email protected].
Fixed
- Two accepted RFCs were unreachable from the docs-site nav. RFC 0016 (verification receipts) and RFC 0017 (the Lite/Extended source split) were copied into the site by
prebuild.mjsand rendered, but the sidebar's RFC list stopped at 0015 — so two accepted, shipped RFCs could only be found by guessing the URL. Added both, plus 0018. landwas the canonical "no pack registered" example and stopped being one. RegisteringLAND_PACKbrokereceipts.test.tsand theconformance/receipts/refuse/02-no-pack-for-asset-classfixture, both of which usedasset_class: landto exerciseRCP_PACK_UNRESOLVED. Both moved tomixed_use— since superseded by the synthetic-identifier change below, which ends the shuffle for good.- The hospitality defaults table shipped without its invariant tests. Every asset-class defaults table has a dedicated
describeblock indefaults.test.tsassertinglow <= central <= high, theasset_class_defaultsource stamp, a citation and unit on every entry, and the expected field set — exceptHOSPITALITY_DEFAULTS, which was added without one and was therefore covered only by the registry-lookup tests. Blocks added for both hospitality and senior housing, plus two cross-field checks the older tables do not make: that the occupancy and vacancy bands are complementary, and that senior housing's labor band sits inside its expense band. npm run lintwas red locally but green in CI. Biome lintedtools/docs-site/public/editor/, the built web-editor bundle vendored into the docs site, producing 7,267 diagnostics from one generated file. The directory is gitignored, so a fresh CI checkout never saw it and CI passed; anyone running the lint script locally got a wall of noise from a file they had not written. Added to the Biome ignore list.@uwmd/exceland@uwmd/reportwere building against published core 1.1.0, not the workspace. Both pinned"@uwmd/core": "1.1.0"exactly while the workspace core was at 1.1.2, so npm could not satisfy the pin from the workspace and installed the published 1.1.0 into each package's nestednode_modules, shadowing the workspace link. Every build and test in those two packages — including the Excel↔calc-engine parity invariant, whose whole point is that one pack drives both — was silently verified against a different core than the repo's. It went unnoticed because 1.1.0 happened to export everything they referenced. Both pins now track the workspace version.- VS Code extension silently passed UW Lite files. It ran the structured parser over every
.uw.md; post-RFC-0017 that extension means UW Lite, where the structured parser finds no fenced sections — so it reported zero issues and acleanstatus for a document nothing had parsed. The parser is now chosen from the content viadetectUWSourceRepresentation: Lite documents getLITE_*parse errors plusLITE_COMPILE_*bridge errors, UWX documents get the full structured validator. Diagnostics are also anchored to the line they concern instead of all being pinned to line 1. Financial thresholds remain unavailable for Lite by construction —checkFinancialValidityreadsfrontmatter.quick_metrics, which the deal-summary bridge does not populate; a toggle for it was prototyped and removed because it did nothing. Extension bumped to 0.2.0.
Added
- Land calc pack — the ninth asset class, and the first that is not an income property.
LAND_PACKadds twelve deterministic metrics and deliberately omitscap_rate,dscr, anddebt_yield. Land has no stabilized income: itsnoi_modelis a carry model (taxes, CFD assessments, insurance, site security against incidental interim revenue), so NOI is normally negative. Capitalizing it would emit a "−1.6% cap rate" that reads as a yield when it is a carry burden — confidently wrong output, worse than none. Tests pin the omission and assert no land formula readsnet_operating_incomeat all. Land is underwritten instead on basis and density:price_per_buildable_unit,price_per_usable_acre,usable_land_ratio,basis_per_buildable_unit,carry_ratio, andland_to_sellout_ratio.LAND_DEFAULTSfollows the same logic and publishes norent_roll.*, expense-ratio, or exit-cap entry, with tests asserting those absences and that land's LTV band sits at or below every income class. Ships with theSundance-Ranch-Land-Buckeye-AZ.uwx.mdworked example (160 acres, 552 platted lots, $30k/lot) and aLAND_LAYOUTworkbook layout whose operating statement is a carry statement that nets negative. - Student-housing calc pack — the eighth asset class end-to-end.
STUDENT_HOUSING_PACKadds fourteen deterministic metrics. The class looks like multifamily but is not underwritten like it: leases are signed per bed, so every sizing and occupancy metric keys offproperty.total_beds(price_per_bed,loan_per_bed,noi_per_bed,revenue_per_bed,rent_per_bed_monthly, bed-countoccupancy), and a test asserts no metric readsproperty.total_units. The defining metric ispre_lease_rate— the share of beds committed for the coming academic year — because student housing re-leases its entire rent roll on one date, making pre-lease velocity the leading revenue indicator.preleased_bedsandoccupied_bedsare separate stored counts measured on different dates, never derived from each other; a test pins that they differ. Shipped withSTUDENT_HOUSING_DEFAULTS(14 triage-grade ranges including pre-lease rate and per-bed turnover cost, with reserves quoted per bed rather than per unit), theMill-Ave-Commons-Student-Tempe-AZ.uwx.mdworked example (600 beds; 5.75% cap, 1.30x DSCR, and honest negative leverage at 3.0% cash-on-cash), and aSTUDENT_HOUSING_LAYOUTworkbook layout carrying turnover/make-ready as its own expense row. - Senior-housing calc pack — the seventh asset class end-to-end.
SENIOR_HOUSING_PACKadds fourteen deterministic metrics. Sizing is per unit as in multifamily; the class-distinctive three arerevpor(revenue per occupied unit per month),labor_ratio, andcare_revenue_ratio— the numbers that say whether the operator, not the real estate, is carrying the deal.total_labor_expenseis a model-level subtotal rather than an entry insidenoi_model.expenses, so the three labor lines stay in the expense map and the operating statement still foots without double counting; the test asserts both halves. Shipped withSENIOR_HOUSING_DEFAULTS(15 triage-grade ranges, including a defaulted labor ratio and wage-growth band), theOcotillo-Senior-Living-Chandler-AZ.uwx.mdworked example, and aSENIOR_HOUSING_LAYOUTworkbook layout whose income lines carry a signed vacancy-loss row. - Hospitality calc pack — the sixth asset class end-to-end.
HOSPITALITY_PACKadds fourteen deterministic metrics keyed off keys (price_per_key,loan_per_key,noi_per_key) alongside the shared cap-rate / LTV / LTC / DSCR / debt-yield core, plus the four metrics that make a hotel an operating business rather than a lease:occupancy,adr,revpar, andgop_margin. Hospitality has no lease-based rent roll, sorent_rollcarries trailing-twelve room-night statistics (available room nights = keys × 365, occupied room nights from the STR report);noi_modelis USALI-shaped, with agross_operating_profitsubtotal struck above the management fee, fixed charges, and the FF&E reserve.RevPAR = ADR × occupancyholds by construction — all three read the same primitives. Shipped withHOSPITALITY_DEFAULTS(14 triage-grade ranges, wider expense and cap-rate bands than the lease-based classes), theSaguaro-Select-Hotel-Tempe-AZ.uwx.mdworked example, and aHOSPITALITY_LAYOUTworkbook layout.getPackForAssetClass,getAssetClassDefaults, andgetLayoutForAssetClassall resolvehospitality; Excel↔evaluator parity is pinned to 6 decimals in bothpacks/hospitality.test.tsand the converter'stoWorkbook.test.ts. .uwx.mdregistered in the VS Code extension — structured records on the new extension now get highlighting, folding, outline, and validation, where previously they got nothing at all. Structured content still on the legacy.uw.mdextension is detected and nudged toward migration.- Receipt verification in the VS Code extension — a
UW Markdown: Verify Receipt for This Dealcommand checks the<deal>.receipt.jsonsidecar against the open document. Verdicts map to notification severity (information / error / warning for verified / failed / unverifiable), with the full breakdown — pack, engine, policy set, digest, every stated result, and the §1 assurance boundary — in a UW Markdown Receipts output channel. Unsaved editor changes are offered as the likely cause of a failure. The verified notification states the boundary inline rather than showing a bare checkmark. The extension verifies but never issues: a receipt issued mid-authoring is stale on the next keystroke. Verification logic lives in avscode-free module so it unit-tests under plain vitest with no editor harness, and a new VS Code extension (build + test) CI job typechecks, bundles, and runs it — the extension previously had no CI coverage at all. - Receipts on the docs-site — adds
docs/UW_RECEIPTS.md, published at/guide/receipts: a human-facing explanation of what a receipt does and does not attest, how to issue and verify one, why verification has three outcomes rather than two (including theunverifiableexit code of 3), and why a receipt going stale after an edit is expected rather than alarming. Linked from the Tools nav and both guide sidebars, cross-referenced fromdocs/TOOLS.md(decision tree, CLI examples, web-editor entry) and a newdocs/GLOSSARY.mdentry. Also fixes a gap in the docs-site link rewriter: RFC links spelledrfcs/NNNN-slug.md— the correct GitHub-relative form from a file insidedocs/— previously failed the build's dead-link check, since onlydocs/rfcs/NNNN-slug.mdand bareNNNN-slug.mdwere recognized. - Receipts in the web editor — a Receipt tab issues and verifies RFC 0016 receipts entirely client-side via
@uwmd/core/browser; nothing is uploaded. Issuance runs the asset class's pack over the open deal and offers the.receipt.jsonsidecar; verification accepts a receipt file and continuously re-verifies against the in-editor document. The panel renders four states rather than three: core'sverified/failed/unverifiable, plus a UI-levelstale. A digest mismatch is reclassified as stale only when this session issued the receipt and the deal has since been edited — spec §6's "editors SHOULD treat any existing receipt as stale once a write lands" — while the same mismatch on a receipt that arrived as a file staysfailed, so an ordinary edit never reads as tampering and tampering never reads as an edit. PerUW_RECEIPT_v1.md§1 no verdict is a bare checkmark: each states what it attests, andverifiedcarries the "not correct, complete, audited, or approved" caveat inline rather than behind a disclosure. Web-editor suite goes 33 → 56 tests, including the stale-vs-failed distinction, an axe-core pass over the rendered verdict, and a test that forces the Web Crypto branch ofsha256TextHex(jsdom leaksprocess, so the component tests would otherwise only ever exercise the Node path the browser never takes). - Verification receipts (RFC 0016) — a detached JSON document binding a canonical digest of an underwriting record to the deterministic outputs a named calc pack produced from it, so a party who did not run the calculation can confirm offline that the numbers follow from the inputs. Adds the normative
spec/UW_RECEIPT_v1.mdandspec/schemas/uw-receipt.schema.json;receipts.tsin@uwmd/core(exported from bothindex.tsand the browser entry, since unsigned issuance and verification need no cryptographic dependency);uwmd receipt issueanduwmd receipt verify; and areceiptsconformance suite (--tier=receipts, 11 assertions) covering issuance, the four verification outcomes, and refusal. Verification is three-state —verified/failed/unverifiable— and never collapses "cannot decide" into either of the others. Two invariants are asserted without a baseline: re-issuance over an unmodified record reproduces the same digest and results, and every verification lands on exactly one of the three verdicts. Resolves RFC 0016's open question on engine-version mismatch in favour ofunverifiable(RCP-07) rather thanfailed, so a document is never blamed for an engine upgrade. A receipt attests that stated outputs follow from stated inputs; it attests nothing about whether those inputs are true, and consumers MUST NOT render averifiedverdict as an unqualified checkmark. - Local batch collection indexer — introduces @uwmd/batch, a deterministic directory runner that validates required deal envelopes, captures semantic digests, and emits JSON/CSV read models for database-adjacent underwriting workflows without changing the canonical .uw.md protocol.
[1.1.3] - 2026-08-04
Fixed
- Scoped CLI distribution — renames the npm package from the unpublishable uwmd name to @uwmd/cli; its installed executable remains uwmd.
[1.1.2] - 2026-08-04
Fixed
- Release lockfile completeness — regenerates the npm lockfile so clean CI installs include every package required by the published workspace manifests.
[1.1.1] - 2026-08-04
Fixed
- Coordinated npm release — publishes the
uwmdCLI with its exact@uwmd/[email protected]dependency after the initial scoped-package bootstrap release.
[1.1.0] - 2026-08-04
Added
- UW Lite / UWX transition foundation (RFC 0017) adds the pre-launch .uw.md Lite and .uwx.md Extended split. (This entry originally cited "RFCs 0015 and 0016": 0015 belongs to the unrelated portfolio-relationships proposal, and 0016 did not exist. The split is governed by RFC 0017; receipts are defined by RFC 0016, both accepted 2026-08-09. Receipts are not implemented — the original wording overstated what shipped.) Core now exposes representation constants, content-aware source detection, the parseUWXFile compatibility name, and a byte-identical legacy migration planner. The uwmd migrate-source command safely copies a structured legacy .uw.md to a sibling .uwx.md, refuses Lite/mixed content and existing destinations by default, and supports dry-run reporting.
- Deterministic UW Lite bridge compiles the deal-summary Lite profile into a UW Document Envelope/UWX source, preserves the complete human-readable source in a namespaced extension, and rejects unsupported periods, scenarios, or units instead of guessing. The reverse UWX-to-Lite projection emits a machine-readable omission report whenever advanced data is dropped. The CLI exposes both directions through convert, accepts Lite in export, and lists Lite/UWX in representation discovery. Receipt signing remains implementation work.
- UW Lite Markdown 1.0 parser foundation adds the normative constrained grammar, lossless source-located AST, explicit anchored fields, normalized currency/rate/ratio values and units, duplicate/ambiguity diagnostics, presentation-insensitive financial canonicalization, canonical rendering, browser-safe public APIs, a conformance fixture, and CLI parse/validate support. Parsing remains separate from deterministic envelope compilation.
- HTTP and MCP Binding 1.0 (RFC 0014 Phase E) — publishes stable
https://uwmd.org/deals/{deal_id}resource identities, an OpenAPI 3.1 contract, negotiated HTTP responses with semantic ETags and preconditions, MCP text/blob resources, compact dual structured/text tool results, resource links, all five reference tool handlers, and a runnable SDK-neutral adapter example. Core binding tests cover JSON/XML/CSV transport,304/406/412/415/428, resource variants, validation results, and Tier-2 source edits. - UW CSV Bundle 1.0 (RFC 0014 Phase D) — adds the normalized model-fidelity directory and deterministic ZIP codecs, manifest inventory and file hashes, semantic-digest verification, bounded extraction defenses, and all six named spreadsheet-safe view profiles. The shared registry and
uwmd convertnow round-trip Markdown, JSON, XML, and.uw.csv.ziprepresentations. - UW XML 1.0 and cross-format conversion (RFC 0014 Phase C) — adds the deterministic
uw-xmlcodec, namespace and normative mapping specification, structural XSD, semantic-digest verification, bounded secure parsing, and shared JSON/XML registry APIs.uwmd convertnow converts.uw.md, verified.uw.json, and verified.uw.xml; round-trip, hostile-input, registry, and CLI integration tests cover the implementation. - UW Document Envelope 1.0 and UW JSON 1.0 (RFC 0014 Phase A) — adds the format-neutral
UWDocumentEnvelope, a normative JSON Schema, one authoritative_metaand prose location per block, semantic canonicalization/digests, equivalence checks,CodecRegistry, the registereduw-jsoncodec, verified parsing, and digesteduwmd exportoutput. Core and CLI round-trip, tampering, registry, and schema tests cover the new contract. XML, CSV, discovery, and HTTP/MCP companion profiles now ship in the same 1.1 release train. - Protocol 1.2 representation discovery —
ImplementationManifestnow advertises typed representation descriptors; its normative schema mirrors the addition.negotiateRepresentationimplements Accept quality/specificity and fidelity filtering,resolveInputRepresentationresolves Content-Type, anduwmd formatsexposes the live registry for API/MCP hosts. - Owner-led governance mode — the owner may accept RFCs and merge owner-authored work immediately while the project is solo. External pull requests require owner review; collaborative 14-day normative comment periods activate automatically after the first outside contribution merges.
- Cross-platform release gates - CI now runs the complete build, workspace tests, and tiers 1-3 conformance suite on Windows/Node 20 in addition to the existing Ubuntu matrix. Core coverage is now a blocking gate at 70% lines, 70% statements, 70% branches, and 90% functions, based on a measured baseline of 72.45% / 72.45% / 74.25% / 91.92% respectively.
- Repository line-ending policy -
.gitattributespins text files to LF while retaining CRLF for Windows command scripts. - CI now builds and tests the web editor (
.github/workflows/ci.yml) — a newweb-editorjob builds@uwmd/core, then runs the web editor's ownnpm run build(tsc + vite) andnpm test(33 vitest cases). The rootnpm testonly covers workspace packages, andtools/*are intentionally not workspace members, so the editor's calc-integrity suite was never gated in CI. - Web editor: accessibility & keyboard pass (
tools/web-editor/) — a global:focus-visiblering (light on the navy toolbar) so keyboard users can see focus without cluttering mouse use; the New Deal dialog gainsaria-modal,aria-labelledby, real<label htmlFor>associations on every field, and initial focus (it already closed on Esc); the editor-views tabs are a labelled<nav>witharia-currenton the active tab; the footed-cell override inputs getaria-labels; (the report iframe already had atitle). A newa11y.test.tsxruns axe-core against the dialog and a flagged section view and fails on any serious/critical violation (color-contrast excluded — jsdom can't compute rendered colors). Web-editor suite is now 33 tests across 5 files. - Web editor: inline validator remediations (
tools/web-editor/src/components/SectionView.tsx) — the validator's issues for the active section now render in context at the top of that section (severity, code, field badge, message, and theBUILTIN_REMEDIATIONScopy), and the offending flat numeric field is flagged with a red border,aria-invalid, an associated<label>, and the remediation shown beneath it viaaria-describedby— all read off theValidationMessage(never re-authored, so the footer and the inline copy can't drift). The globalValidationPanelfooter is unchanged.SectionView.test.tsx(jsdom) pins that the remediation shows in-context and the field is marked invalid, and that a clean section shows nothing. Web-editor suite is now 31 tests across 4 files. - Web-editor component tests for the footed-model surfaces (jsdom) (
tools/web-editor/src/components/footed-model.test.tsx) — adds jsdom +@testing-library/react(dev-only; bundle unchanged) and pins the contract every footed surface shares: editing one input re-foots the dependent totals and dispatches exactly onesection_replace. CoversValuationModel(NOI change re-foots indicated value) and the newDcfModel(a year's NOI re-foots its levered cash flow; the gross exit value re-foots the whole disposition→net→proceeds waterfall; "+ Add year" appends a projection year in one dispatch). Component tests are*.test.tsxand opt into jsdom via a// @vitest-environment jsdomdocblock, so the pure node suite stays fast. Web-editor suite is now 29 tests across 3 files. - DCF footing in
@uwmd/core+ a DCF footed-model surface in the web editor (dcf.ts,tools/web-editor/src/components/DcfModel.tsx) — thedcfsection was the last calc-bearing section still edited as a flat numeric grid. NewderiveDCF(content)foots the relationships that follow unambiguously from a DCF block's own stored inputs — per projection yearnet_cash_flow_levered = NOI − annual_debt_serviceandcash_on_cash_return = levered / cumulative_equity_invested, and the exit waterfalldisposition_costs = exit_value_gross × disposition_costs_pct,exit_value_net = gross − disposition, andnet_proceeds_to_equity = net − loan_balance_at_exit. It deliberately leavesexit_value_gross(capitalizes a forward NOI the block doesn't store) andreturns.*(IRR / NPV / equity multiple — cash-flow-timing convention) as inputs, in the same narrow, self-contained spirit asderiveValuation. Pure, exported fromindex.ts+browser.ts, and pinned to the Parkview worked example indcf.test.ts(8 tests). The web editor's newDcfModelsurface edits the assumptions, the per-year cash-flow rows (add/remove years), and the exit gross + loan balance; the derived totals render as locked ƒ derived cells and IRR/NPV/equity multiple show read-only as engine-provided. Removes the five flatdcfentries from the editor's numeric catalog. Web-editor tests grew to 25 (a dcf array round-trip throughapplyEdit+ reparse, plus array-indexeddeepGet/deepSet). - Web editor test harness (
tools/web-editor/) — the calc-aware editor previously shipped with zero tests despite being the Tier-2/3 chokepoint. Adds Vitest (dev-only; not in the production bundle — bundle hash unchanged) with atestscript and a Node test environment, plus two suites:src/edits.test.ts(7 tests) pins therunEdit()contract — afrontmatter_setapplies and re-parses so in-memory state can't drift from the canonical bytes, a rejected op returns a failed outcome instead of throwing,EditSettingsthread confidence +human_review_requiredonto the written block,supersedemode archives the prior block (two fences) wherereplaceedits in place (one), andcarryForwardOverrides()keeps a pinned_meta.field_overridesalive across a later edit that doesn't restate it; andsrc/catalog.test.ts(16 tests) covers the path helpers and the wrapper-awaregetNumeric/setNumericthat preserve the{ value, … }provenance wrapper. 23 tests, all green;npm --prefix tools/web-editor run testandrun buildboth pass. - Deterministic section-footing in
@uwmd/core(derived.ts,rentroll.ts,opstatement.ts) —deriveRentRoll(content)rolls a unit-level (multifamily) or tenant-level (commercial) schedule up into the section's totals (GPR, in-place rent, physical occupancy, loss-to-lease, concessions, net effective rent, leased/vacant SF, rent PSF, WALT, plus a recomputedunit_mix_summary/tenant_concentration), tolerant of both the spec naming (nra_sqft/base_rent_annual) and the worked-example naming (leased_sf/annual_base_rent) and writing back to whichever total keys the block already uses.deriveOperatingStatement(content)foots EGI / total OpEx (excluding below-the-line capex + replacement reserves) / NOI / expense ratio / NOI margin from the income and expense line items, withother_incomeandutilitiessub-rollups. Both are pure (no I/O, never mutate), returnDerivedField[]via the sharedderived.tscollector (which drops any non-finite result so partial data never writesNaN), and honor invariant #1 (not AI math — plain deterministic arithmetic, the same spirit as the calc packs) and #4 (one source so the editor, CLI, and Excel converter never disagree). Exported from bothindex.tsandbrowser.ts; covered byrentroll.test.ts(13) andopstatement.test.ts(8). Extended to three more sections (debt.ts,sourcesuses.ts,valuation.ts):deriveDebt(content)foots monthly + annual debt service from the loan terms — fully-amortizing via apmt()byte-identical to the calc engine'spmtbuiltin,loan × ratewhen interest-only (detected fromamortization: "interest_only"or a missing/zero term), rate-key tolerant;deriveSourcesUses(content)foots the per-bucketsources.total/uses.total, the nestedclosing_costs.total, and the top-level project-cost mirrors from the line items, and reports the sources-vs-usesgap/balancedso an imbalance surfaces without being silently written;deriveValuation(content)foots the income-approachindicated_value = noi_used / cap_rate_applied(and its delta to purchase price) entirely from the block's own inputs. Covered bydebt.test.ts(6),sourcesuses.test.ts(6),valuation.test.ts(5). - Lender Package / Credit Memo report renderer in
@uwmd/core(report.ts) —renderReportHtml(parsed, opts)implements the spec's rendering targets §7.1 (Tier 1 Lender Package: cover page, executive summary with metric grid, property overview, proforma, rent-roll summary, debt structure, sources & uses, borrower summary, exit analysis, assumptions table with source badges + standard disclaimer) and §7.2 (Tier 2 Credit Memo: adds market analysis with comps, financial analysis with annual cash flows + stress matrix + break-even, due diligence, risk assessment, compliance, covenants, pipeline-log appendix). Output is a self-contained, print-aware HTML document (embeddedREPORT_CSSwith@page/@media printrules) or an<article>fragment for embedding. Zero dependencies, browser-safe, exported fromindex.tsandbrowser.ts; every number is read from the file (calc engine/pack output), never recomputed. Handles both multifamilyunit_mix_summaryand commercialtenantsrent rolls, single-variant maps, and missing sections (skipped + reported insectionsSkipped). Newuwmd reportCLI subcommand (--tier,--prepared-by,--output,--stdout). - PDF report pipeline (
packages/uwmd-report/, preview0.1.0) — new@uwmd/reportpackage providinguwmd-report <file.uw.md> [-o out.pdf] [--tier] [--format pdf|html]and a programmaticgenerateReport(parsed, opts)API. Prints core's report HTML to PDF via playwright-core with no bundled-browser download: resolves--browser/UWMD_REPORT_BROWSER, then system Chrome, then system Edge, then a Playwright-managed Chromium; typedBrowserNotFoundErrorwith remediation otherwise. Page layout is owned entirely by core'sREPORT_CSS(preferCSSPageSize, zero engine margins), so browser print of the HTML and the CLI PDF are identical.
Fixed
- Windows CRLF parsing -
parseUWFile()now accepts CRLF documents without silently dropping every fenced UW section. The parser retainsrawinput byte-for-byte while normalizing line endings only for structural scanning. Regression coverage exercises a complete CRLF section. - Unterminated section fences fail closed - a recognized
uw:sectionfence without a closing fence now raises typedUNCLOSED_SECTION_FENCEeven outside strict mode, preventing the remainder of a deal from being swallowed as one malformed block. - Unsupported document targets fail explicitly - the core
pdfanddocxrender targets now throw typedUnsupportedRenderFormatErrorinstead of returning successful-looking empty output. PDF callers are directed to@uwmd/report; DOCX remains explicitly unimplemented. - Publishable core package contents -
@uwmd/corenow explicitly ships its compileddistAPI and package README while excluding source tests. CI and the release workflow runverify-packagesso a package whose exports point at missing artifacts cannot be published again. - Web editor lint errors that were failing the CI
lintjob —biome lintflags (recommended-default errors):noAutofocuson the New Deal dialog and the footed-cell override input (replaced theautoFocusattribute with a ref +useEffect().focus()), andnoArrayIndexKeyon the DCF per-year rows (now keyed by the row's year). These had accumulated across the unpushed web-editor train; the lint job is green again.
Changed
- Dependency hardening - the workspace Vitest and coverage-provider floor is
- Canonical project identity - the repository is renamed, package repository links follow the new URL, and public package/homepage metadata now points to
https://uwmd.org. raised to 3.2.6, clearing the critical development-server advisory. Safe transitive lockfile updates also patch Vite, esbuild, PostCSS, fast-uri, tmp, and brace-expansion versions where upstream ranges permit. Remaining ExcelJS archive/UUID advisories require an upstream release or a separately validated converter migration before the separately versioned Excel add-on can publish. - Web editor 0.5.0 — rent-roll & operating-statement become underwriting models, not forms (
tools/web-editor/). NewRentRollModelandOperatingStatementModelsurfaces (sharedFooted.tsx) treat the line items as the only inputs and render the section totals as locked "ƒ derived" cells footed live by core'sderiveRentRoll/deriveOperatingStatement. Every mutation (cell edit, add/remove row, override, revert) clones the block, mutates one array, re-foots, writes back every non-pinned total, and dispatches a singlesection_replacethrough theapplyEdit()chokepoint, so the file can never be left internally inconsistent. A footed total can be pinned by hand via an inline override editor; the pin is recorded in the format's own_meta.field_overrides(reason: "overridden") and badged "manual" until reverted to formula. The edit dispatcher (edits.ts) now carriesfield_overridesforward across unrelated edits (the protocol'sbuildMeta()doesn't), and the generic scalar editor acceptslockedPathsso each footed total has exactly one editing path. Replaces the oldRentRollTable(deleted) and the hand-entered rent-roll/operating-statement numeric fields. The same pattern now extends to three more sections —DebtModel(loan terms in, monthly/annual debt service footed),SourcesUsesModel(every source/use line in, bucket + closing-cost + project-cost totals footed, with a live sources-vs-uses balance check), andValuationModel(NOI + cap rate in, income-approach indicated value footed) — via a sharedmodel-kit.tsx(theuseFootingwrite-path hook +InputRow/FootedRowrow primitives). Those three sections drop out of the flat numeric allow-list (catalog.ts); their footed totals are locked out of the generic scalar editor so each has exactly one editing path. @uwmd/core/browsernow exports the intelligence + calc-introspection surfaces —resolveValue/readInFile(cascade),rankGaps(value-of- information),inferGaps/summarizeGaps/readGapsContent, the asset-class default tables (getAssetClassDefaults/getDefaultRange/listDefaultedFields- the five
*_DEFAULTS), andgetExprDependencies/extractDependencyGraph, with their types. All confirmed browser-safe (no node, no SDK); previously only on the nodeindex.tsentry. Enables the web editor's intelligence and calc-transparency panels.
- the five
- Web editor 0.4.0 — extensive build-out (
tools/web-editor/): five tabs (Editor, Intelligence, Report, Diff, Source). New Intelligence tab surfaces Scope (every required input resolved through the fallback cascade with its source step + range) and Refine (VOI gap ranking with affected-output ranges and suggested questions). New edit-provenance bar threading actor/source/confidence/notes/human-review into_metaand toggling replace vs. append (supersede) mode (promotessection_replace→section_supersede). New assumptions editor capturing override rationale (is_overridden/original_value/override_rationale), generic field editor for every scalar leaf (narrative strings → textareas), add/remove rent-roll rows, clickable calc cards opening a formula + resolved-inputs- result detail (
CalcDetail), and a Diff tab (section + frontmatter changes since load/save via corediff()). Edit chokepoint unchanged (edits.ts→applyEdit()→ re-parse); modal Escape handling made global.
- result detail (
- Web editor 0.3.0 — richer editing surfaces (
tools/web-editor/): editable rent-roll tables (unit-mix rows for multifamily-style rolls, tenant rows for commercial — cell edits replace the row throughapplyEdit()); NOI line-item editor (income/expense entries; wrapped{value, …}fields are updated via a wrapper-awaresetNumericthat preserves rationale/source provenance, stored totals shown read-only with validator-flagged drift); snapshot-based undo/redo (restores prior canonical source verbatim; Ctrl+Z / Ctrl+Y / Ctrl+Shift+Z + toolbar); New Deal dialog scaffolding a blank file viagenerateBlankUWFile; per-section superseded-version history view; Source tab (read-only canonical bytes + copy); Ctrl+S download; numeric allow-list expanded from 8 to ~30 fields across eight sections (incl. DCF assumptions and operating statement). - Web editor rebuilt on React 18 + Tailwind CSS 4 (
tools/web-editor/, preview0.2.0) — replaces the vanilla-TS stage-3 editor. Adds: a pinned calc strip that evaluates the asset class's full pack viagetPackForAssetClass(all five shipped classes, was multifamily-only), block_metaprovenance chips (version/source/confidence/review-required), collapsible raw-JSON block view, per-section prose display, pipeline-log table, and a live Report Preview tab renderingrenderReportHtmlin a sandboxed iframe on every edit with Lender Package / Credit Memo toggle, Download HTML, and Print/PDF. The edit chokepoint is unchanged: every mutation flows throughsrc/edits.ts→applyEdit()→ re-parse, so in-memory state can never drift from canonical bytes (React state only holds the result). - Initial
.uw.jsonprototype in@uwmd/coreestablished the model-level round-trip and CLI export path later stabilized by RFC 0014 Phase A above.- Renderer unit tests in@uwmd/corecovering JSON projection, superseded history opt-in, CSV numeric/percent output, summary/chat rendering, chat truncation, and the explicit PDF/DOCX stub targets. - Self-storage calc pack + defaults + worked example + Excel layout —
SELF_STORAGE_PACKadds twelve deterministic metrics keyed off NRSF, rentable units, physical occupancy, and economic occupancy.SELF_STORAGE_DEFAULTSregisters cascade/refinement ranges,Sonoran-Self-Storage-Peoria-AZ.uwx.mdprovides a footing worked example, and@uwmd/excelnow emits a self-storage workbook layout with Excel↔evaluator parity to 6 decimals. - Declarative module loader/registry in
@uwmd/core—modules.tsvalidates and registers in-processModuleManifestobjects, including shape checks, formula/rule parsing, dependency load order, and tier/protocol/format compatibility. Dynamic imports, signing, and custom asset-class identifiers remain v2/RFC work. - Repo restructured into OSS-ready monorepo (
spec/,packages/,examples/,conformance/,tools/). @uwmd/corepackage (renamed fromuwmd).- UW Protocol v1 specification (
spec/UW_PROTOCOL_v1.md). - TypeScript protocol surface (
packages/uwmd-core/src/protocol.ts) —ViewerCapability,SectionViewModel,ModuleManifest,ProtocolError,BUILTIN_VIEW_MODELS, etc. - Module manifest JSON Schema (
spec/schemas/module-manifest.schema.json). - Canonical formatting helpers (
packages/uwmd-core/src/format.ts) —formatCurrency,formatPercent,formatRatio, etc. - Conformance test corpus (
conformance/tier-{1..4}/). - Single-file Tier-1 reference viewer (
tools/web-viewer/index.html). - Top-level OSS scaffolding (LICENSE, CONTRIBUTING, CODE_OF_CONDUCT, CI workflow, issue templates).
- Tier-2 Editor —
applyEdit()dispatcher in@uwmd/corecoveringfrontmatter_set,section_replace,section_supersede, andpipeline_log_append, withBUILTIN_EDIT_POLICIESenforcement and round-trip preservation. Newuwmd editCLI subcommand. - Tier-3 Calc Host — safe-expression parser + evaluator + built-ins (
sum,avg,min,max,coalesce,if,round,pmt,npv,irr) per protocol §VIII.1 EBNF, with fullCalcErrortaxonomy. Newuwmd calcCLI subcommand. - Validator wired to
BUILTIN_REMEDIATIONSregistry (no inline strings; per protocol §III.6). - Conformance runner (
scripts/run-conformance.mjs) executing tiers 1–4 with CI gate on tiers 1–3. Filled missing tier-2/3/4 fixtures (frontmatter-set-recommendationbefore/after pair,revpar-basic,dscr-from-section,l6-risk-ratingshape assertion). - JSON Schemas for all six boundary-crossing protocol types:
uwmd-block,edit-operation,protocol-error,implementation-manifest,calc-result,issue-remediation. Programmatic validator (scripts/validate-schemas.mjs) using ajv 2020 + ajv-formats with cross-file$refpre-registration. CI gate. - Governance scaffolding — SECURITY.md, GOVERNANCE.md (owner-led mode with contributor-activated collaborative safeguards and an RFC process), MAINTAINERS.md,
.github/CODEOWNERS, ROADMAP.md,docs/rfcs/directory with template and process README. - npm publish workflow (
.github/workflows/release.yml) — onv*tag, runs full test + conformance + schema-validation gate, then publishes@uwmd/coreto npm with provenance.prepublishOnlyscript in the package mirrors the same gate locally. - VS Code extension (
tools/vscode-uwmd/, preview0.1.0) — syntax highlighting for.uw.md(YAML frontmatter + Markdown + embedded JSON inuwmd jsonblocks), folding for frontmatter / fenced blocks / heading sections, document outline, and on-save validation surfacing every@uwmd/coreissue with itscode,title,remediation, andspec_ref. Bundled via esbuild; ships as.vsix. - Documentation site (
tools/docs-site/, preview0.1.0) — VitePress build covering the format spec, protocol spec, JSON Schemas, conformance corpus per tier, and the full set of project documents (roadmap, governance, contributing, security, RFC process). Repo-root markdown remains the single source of truth;scripts/prebuild.mjscopies content into the site tree at build time and rewrites relative links to site URLs. Ships as a 2.9 MB static bundle deployable to any static host. - Standalone CLI installer (
packages/uwmd-cli/, preview1.0.0) — newuwmdnpm package providingnpx uwmd <command>for non-developers who don't want to clone the repo. Thin wrapper that re-exposes the@uwmd/coreCLI module via a new./clisubpath export. Covers all eleven subcommands:init,parse,validate,compact,diff,render,edit,calc,run,summary,layers. Same Tier-1/2/3/4 conformance behavior as the in-repo CLI; no separate code path, so no calc-drift risk. @uwmd/core/browsersubpath export — new browser-safe entry point that excludes the agent runner and@anthropic-ai/sdkso the library can be bundled directly into web apps. Re-exports parser, validator, compactor, renderer, editor (applyEdit), calc engine (evaluateCalc), formatting helpers, and the full type / protocol surface. Source atpackages/uwmd-core/src/browser.ts.- v2 RFC drafts (
docs/rfcs/) — six initial drafts opened to start the v2 design conversation, one per item in ROADMAP §"v2 spec exploration": RFC 0001 (locale negotiation), RFC 0002 (Sigstore-style module signing), RFC 0003 (custom asset-class declarations from modules), RFC 0004 (language-agnostic conformance runner with TAP14 + JSON manifest output), RFC 0005 (stochastic calc declarations with seeded PCG-XSL-RR-64 PRNG), and RFC 0006 (hospitality reference module). All instatus: draft; none required for v1 conformance. - Excel converter (
packages/uwmd-excel/, preview0.1.0) — new@uwmd/excelpackage providinguwmd-excel <file.uw.md> [-o out.xlsx]and a programmatictoWorkbook(parsed)API. Generates a three-sheet multifamily workbook: an Underwriting sheet (header + named-range inputs block + derived-metric formulas block), an Operating Statement sheet (five income lines +EGI=SUM(income)+ eleven expense lines +total_opex=SUM(expenses)+NOI=EGI−total_opex), and a Pipeline Log audit sheet. The eight derived-metric formulas (cap rate, LTV, DSCR, debt yield, price/unit, loan/unit, loan/sqft, cash-on-cash) ship as Excel formulas referencing the workbook-scope named ranges (purchase_price,loan_amount,annual_debt_service,total_units,total_nra_sqft,equity_sponsor,noi) — these mirrorMULTIFAMILY_STARTER_PACKin@uwmd/coreexactly, so opening the workbook in Excel and runninguwmd calcagainst the same.uw.mdproduce identical numbers by construction. Editing any named-input cell or any line item ripples through to every dependent metric. Multifamily-only and.uw.md→.xlsxonly in 0.1.0; the reverse direction is deferred and the calc-aware web editor remains the canonical Tier-2 chokepoint for editing. - Calc-aware web editor (
tools/web-editor/, preview0.1.0) — Vite + plain TS bundle on top of@uwmd/core/browser. Drag-drop file load, sidebar with per-section validation badges, frontmatter editor (16 typed inputs spanning text / enum / list fields), and section views with typed numeric inputs on five calc-bearing sections (property,valuation,noi_model,debt_structure,sources_uses). Every edit dispatches throughapplyEdit()and reparses the file, so in-memory state can never drift from canonical source. Multifamily calc starter pack (cap rate, LTV, DSCR, debt yield, $/unit, $/sqft, price/unit, cash-on-cash) re-evaluates on every render viaevaluateCalc. Validation footer surfaces everyValidationMessagewith severity, code, section, andBUILTIN_REMEDIATIONScopy. Bundle size: 19 kB app + 11 kB core, gzipped. Replaces the originally-planned narrative-only Tier-2 web editor — that design was rejected because separating safe narrative edits from unsafe numeric edits creates two paths into the same file. - Lint toolchain — Biome
1.9.4configured at the repo root (biome.json) withnpm run lint/npm run formatscripts. Linter-only (no formatter enforcement) so the existing code style is preserved. Newlintjob in CI (.github/workflows/ci.yml). - CLI smoke tests —
uwmdCLI installer now ships a vitest suite (packages/uwmd-cli/test/cli.smoke.test.ts) covering--help,parse,validate, and missing-file error path against the bundled Parkview fixture. CLI was previously untested. .editorconfigat the repo root for cross-editor consistency (LF, UTF-8, 2-space, trim trailing whitespace except in.md).- Canonical asset-class calc packs in
@uwmd/core(packages/uwmd-core/src/packs/) —MULTIFAMILY_PACKis now the single source of truth for the eight multifamily derived metrics, exported from both@uwmd/coreand@uwmd/core/browseras aModuleManifest. Previously, the same eight calcs were defined twice — once in calc-engine syntax insidetools/web-editor/src/calc-pack.tsand again in Excel formula syntax insidepackages/uwmd-excel/src/multifamily.ts. Adding a metric required editing both. Both consumers now import from the canonical pack. - Office calc pack + defaults in
@uwmd/core—OFFICE_PACK(packages/uwmd-core/src/packs/office.ts) is the second built-in asset-class pack: eleven office derived metrics (cap rate, LTV, LTC, DSCR, debt yield, price/sqft, loan/sqft, NOI/sqft, operating expense ratio, cash-on-cash, occupancy) as aModuleManifest, exported from both@uwmd/coreand@uwmd/core/browser. Office field paths differ from multifamily — size isproperty.rentable_square_feet, sponsor equity issources_uses.sources.sponsor_equity, and the NOI model splits into nestedincome/expenses.OFFICE_DEFAULTS(defaults.ts) adds a 13-field office triage table (wider ranges than multifamily, plus office-specific TI/LC fields) registered for the cascade. NewgetPackForAssetClass(asset_class)registry helper selects the right pack by asset class;uwmd refineanduwmd scopenow resolve office deals automatically off the deal'sfrontmatter.asset_class. Every office metric is verified through bothevaluateCalc()and the emitted Excel formula to 6 decimals against the Riverside office worked example (packages/uwmd-core/src/packs/office.test.ts). - Retail calc pack + defaults + worked example in
@uwmd/core—RETAIL_PACK(packages/uwmd-core/src/packs/retail.ts) is the third built-in asset-class pack: twelve retail derived metrics, keyed off gross leasable area (property.gross_leasable_area) for the per-SF metrics and GLA for occupancy, plus a retail-distinctiveexpense_recovery_ratio(income.expense_reimbursements / expenses.total_operating_expenses) for NNN recovery.RETAIL_DEFAULTS(defaults.ts) adds a 13-field retail triage table including the NNNexpense_recovery_rate. New third worked exampleexamples/Cactus-Crossing-Retail-Mesa-AZ.uwx.md— a 95k SF grocery-anchored neighborhood center with a tenant-level rent roll, NNN reimbursements, and percentage rent; validates with zero issues. Both registries (getPackForAssetClass,getAssetClassDefaults) now resolveretail, souwmd refine/uwmd scopework for retail deals automatically. Every metric is verified throughevaluateCalc()and the emitted Excel formula to 6 decimals (packages/uwmd-core/src/packs/retail.test.ts). - Industrial calc pack + defaults + worked example in
@uwmd/core—INDUSTRIAL_PACK(packages/uwmd-core/src/packs/industrial.ts) is the fourth built-in asset-class pack, completing the four core commercial types: twelve metrics keyed off rentable building area (property.rentable_square_feet) and SF occupancy, including the NNNexpense_recovery_ratio.INDUSTRIAL_DEFAULTS(defaults.ts) adds a 13-field industrial triage table (lowest expense ratio + highest recovery + tightest caps of the four classes). New fourth worked exampleexamples/Ironwood-Logistics-Industrial-Tolleson-AZ.uwx.md— a 220k SF Class A bulk-distribution warehouse, multi-tenant NNN; validates with zero issues. Both registries now resolveindustrial, souwmd refine/uwmd scopework for industrial deals automatically. Every metric verified throughevaluateCalc()and the emitted Excel formula to 6 decimals (packages/uwmd-core/src/packs/industrial.test.ts). - Excel converter: office/retail/industrial layouts + multifamily NOI parity fix (
packages/uwmd-excel/) —@uwmd/excelis no longer multifamily-only. The converter engine (toWorkbook.ts) is now generic over aWorkbookLayout(layout.ts) selected byfrontmatter.asset_classvia a registry (layouts.ts,getLayoutForAssetClass); layouts ship for multifamily, office, retail, and industrial. A layout is just{ assetClass, pack, incomeLines, expenseLines, namedInputs }— the engine derives the calc-path→named-range map and the derived-metrics block from the layout (buildNamedRangeMap/buildDerivedMetrics) so formulas and named ranges can't drift. Bug fix: the operating statement previously computedEGI = SUM(income lines)while vacancy/credit-loss were stored as positive magnitudes, so the SUM added vacancy instead of subtracting it — overstating EGI and NOI, and (since thenoinamed range feeds cap-rate/DSCR/etc.) silently breaking Excel↔evaluator parity. Income deduction lines now carrysign: -1, soEGI = SUMandNOI = EGI − total opexfoot to the storedeffective_gross_income/net_operating_income. The round-trip test (toWorkbook.test.ts) was rewritten to compute parity for all four example deals (assert the statement foots, then evaluate every derived-metric formula against the workbook's named-range values and compare toevaluateCalcto 6 decimals) — the old test only checked formula text and missed the bug. The Riverside office example's operating statement was reconciled (+$2,550 utilities → $382,550 total opex) so EGI − opex foots to its stated $300,000 NOI; cap rate, DSCR, and all other narrative figures are unchanged. - AST → Excel formula emitter (
packages/uwmd-core/src/packs/excel-emit.ts) —emitExcelFormula(formula, { namedRanges })translates a Tier-3 safe expression into Excel syntax via a caller-supplied identifier-to-named-range map. Maps arithmetic 1:1, ternary toIF(), modulo toMOD(), logical&&/||toAND()/OR(), and the 17 supported builtins (sum/min/max/if/round/pmt/npv/irr/abs/floor/ceil/sqrt/pow/log/exp/fv/pv/nper). ThrowsExcelEmitErrorwith codesEXCEL-EMIT-PATH(unmapped identifier),EXCEL-EMIT-FN(no Excel equivalent),EXCEL-EMIT-OP(unhandled operator). New parity test inpacks.test.tsevaluates each multifamily metric both throughevaluateCalc()and through the emitted Excel formula (with named-range substitution) and asserts they agree to 6 decimals. - Pre-publication review follow-ups (REVIEW-2026-04-26.md) — first pass at the audit's punch list:
- Documentation on-ramps: ARCHITECTURE.md (system map: spec → reference library → tools → conformance corpus, with dependency rules and "where to start contributing" guide), VERSIONS.md (compatibility matrix across format / protocol /
@uwmd/core/ CLI / tools, plus pinning recommendations), docs/GLOSSARY.md (alphabetical entries for both format-specific and CRE domain terms with normative spec links), docs/TOOLS.md (decision tree for picking among the seven shipped tools), and tools/docs-site/tutorials/your-first-uwmd-file.md (5-step walkthrough from blank file to validated minimal.uw.md). All five are wired into the docs-site sidebar viaprebuild.mjsso the repo-root markdown remains the single source of truth. - Second worked example: examples/Riverside-Office-Phoenix-AZ.uwx.md — suburban office with a bridge-loan capital stack and tenant-level rent roll, demonstrating that the format is not multifamily-only. Validates with zero errors and a single intentional
FV_DSCR_BELOW_THRESHOLDwarning (bridge-loan DSCR is thin at close by design). - Coverage in CI: new
coveragejob in.github/workflows/ci.ymlrunsvitest --coverageon@uwmd/core, publishes a coverage table to the GitHub job summary, and uploads the fulllcovreport as a build artifact. Soft floor (continue-on-error: true) at the current baseline; will be tightened once the number is ratcheted up. Vitest config atpackages/uwmd-core/vitest.config.ts. - Malformed conformance fixtures: new
conformance/tier-1-reader/malformed/category with three fixtures (META_MISSING,META_LOW_CONFIDENCE_NO_REVIEW_FLAG,CC-04) and matchingexpected.jsondeclarations. The conformance runner (scripts/run-conformance.mjs) gained arunTier1Malformedpass that dispatches each fixture throughvalidateUWFileand asserts the actual issue codes are a superset of the expected codes. - Property-based tests on the calc engine: new
packages/uwmd-core/src/calc/calc.property.test.tsusingfast-checkto assert (1) parser totality on arbitrary ASCII input ≤ 256 chars (always returns AST or throws typedCalcError), (2) evaluator null-safety against the empty context (always returnsCalcValuescalar or throws typedCalcError), (3) Excel-emit grammar parity (every AST the constrained generator can produce emits to a non-empty string). Runs 500 + 300 + 300 cases per property in CI. - YAML parser hardening: new Appendix D in the format spec defines the supported YAML subset (scalars, mappings, sequences, comments) and the rejected features (anchors, aliases, explicit tags, block scalars, complex keys, directives). The parser at
packages/uwmd-core/src/parser.tsnow hard-rejects unsupported features at frontmatter parse time with a typedUWMDParseErrorcarrying the new validator codeUNSUPPORTED_YAML_FEATURE(registered inBUILTIN_REMEDIATIONS). Eight new unit tests inparser.test.tscover the rejection paths plus the all-supported subset. - Validator code taxonomy: new §III.6a in the protocol spec documents the three validator code prefixes (
CC-NNcross-section consistency,FV_*financial validity,META_*meta integrity) and their severity defaults. Pointer toBUILTIN_REMEDIATIONSand the validator implementation as the authoritative registry — the review's claim that codes were unregistered turned out to be wrong; the gap was that the categorization wasn't documented. - Pipeline state L3 documented: the L3 layer was missing from the Parkview example's
pipeline_statebecause it is intentionally reserved. The format spec now states this explicitly inside thepipeline_stateexample block — conforming files MUST NOT defineL3_*keys, and any layer key absent frompipeline_stateis treated aspendingby default. - README cleanup: dropped the stale "(in progress)" labels next to the protocol spec and the conformance corpus links. Both shipped per ROADMAP.md.
- Documentation on-ramps: ARCHITECTURE.md (system map: spec → reference library → tools → conformance corpus, with dependency rules and "where to start contributing" guide), VERSIONS.md (compatibility matrix across format / protocol /
- Two new v2 RFC drafts — RFC 0007 (sensitivity tables as a first-class calc primitive —
sensitivity_table()builtin returning a structured grid result) and RFC 0008 (alease_up_schedulestandard section for value-add and ground-up deals). Both instatus: draft; not implemented. - Architectural review v1.1 train (six-phase landing of
ARCHITECTURAL-REVIEW-2026-04-27.md) — full surface expansion across format, protocol, library, conformance, and docs in response to the three architectural concerns raised in the review (wrong-actor / wrong-tool writes, incomplete-data first-class support, AI-efficient representation). Implemented as six independently-shippable phases.- Phase 1 — Foundation (
_metaextensions + validator taxonomy +scopestage)._metagained five optional fields documented in Format Spec Part III §3:partial,provisional,field_overrides[],content_hash,parent_hash._meta.sourceenum widened by four additive values (user_input,asset_class_default,global_default,system_default); existingscenario_defaultretained. New normative §IX "Fallback cascade" in the protocol spec defines the seven-step resolution order producers MUST walk. NewDealStagevaluescopeadded belowscreeningfor back-of-napkin triage;STAGE_REQUIREMENTSwidened to support field-level + either-or readiness checks (required_field_paths,required_one_of). Validator code taxonomy finalized:CC-NN(existing),FV-NN(renamed from legacyFV_*strings, both emitted vialegacy_codefor one release),DQ-NN(new),INT-NN(new),POL-NN(new),META_*(existing).BUILTIN_REMEDIATIONSextended with one entry per new code. Confidence vshuman_review_requireddisambiguated as orthogonal in spec. - Phase 2 — Gaps section + cascade resolver + asset-class defaults +
INCOMPLETE_DATA_POLICIES. New optional standard sectiongapswithspec/schemas/sections/gaps.schema.json—items[]with(section, field_path, reason ∈ {missing, illegible, out_of_scope, deferred, blocked_by_dependency, awaiting_external}, blocks_stage, first_seen, last_checked, owner, note)plussummaryrollup. Newpackages/uwmd-core/src/defaults.tsexportsMULTIFAMILY_DEFAULTScovering 11 fields with low/central/high ranges (expense ratio, vacancy, rent growth, management fee, replacement reserve, debt rate, amortization, IO months, LTV, exit cap, closing costs); each entry version-pinned at1.0.0with the contract that range revisions bump the version. Newpackages/uwmd-core/src/cascade.tsexportsresolveValue(field_path, parsed, ctx?)walking the protocol §IX cascade;MarketDataLookupandInvestorProfileare interface-only (adopters bring their own). Newpackages/uwmd-core/src/gaps.tswithinferGaps,applyGapPolicy,summarizeGaps. NewINCOMPLETE_DATA_POLICIESregistry inprotocol.tswithGapAction = halt | degrade | substitute | deferandlookupIncompleteDataPolicyresolution; seeded with conservative(section, stage)defaults.applyEditgained an opt-inmaintain_gapsoption that recomputes the gaps section after every successful write under actorsystem/gaps-maintainer. - Phase 3 — Integrity (hashes + chains +
uwmd verify). Newpackages/uwmd-core/src/integrity-canonical.tsimplements RFC 8785 (JCS) JSON canonicalization — vendored ~40 LOC, zero dependencies — with the documented exclusion of_meta.content_hashand_meta.signaturefrom the hash input. Newpackages/uwmd-core/src/integrity.tsexportscomputeBlockHash,verifyChain,verifyProvenance, returning typedIntegrityIssue[]with codesINT-01(parent_hash mismatch),INT-03(partial chain),INT-04(content_hash recompute mismatch),POL-01(unauthorized actor perBUILTIN_EDIT_POLICIES),POL-02(section_replacewheresection_supersederequired). NewapplyEditAsyncrecomputescontent_hashon every write whenoptions.integrityis enabled and rejects edits whosectx.parentHashis stale withINT-02. Crypto split: Nodecrypto.createHashand Web Cryptosubtle.digestproduce byte-identical hashes verified inintegrity-canonical.test.ts. Newuwmd verify <file> [--integrity] [--policy] [--validate] [--json]subcommand combines all three checks;uwmd validate --integrityalso opts in inline. - Phase 4 — Context profiles for AI consumption. New
packages/uwmd-core/src/context-profiles.tsexportsbuildContext(parsed, profile, opts?)returning aContextResultwith{content, tokenEstimate, profile, sectionsIncluded, truncated}. Five normative profiles defined in new protocol §X "Context profiles":summary(frontmatter + quick_metrics + pipeline_state + gaps, ≤600 tokens),live(all non-superseded, prose included),compact(all non-superseded, JSON-only, minified, stable→volatile section order, ≤55% oflivetokens),full(every byte),relevant(filtered toopts.sections). Stable→volatile compact ordering targets prompt-cache-friendly prefixes for adopters using LLMs at scale.buildAgentContextrewritten as a thin shim overbuildContext(parsed, layer.consumed_profile, {sections: layer.reads})— externalAgentContextshape preserved.BANCROFT_LAYERSentries gained aconsumed_profilefield. Newuwmd render --profile=summary|live|compact|full|relevant [--sections=…] [--max-tokens=N] [--no-meta]flag. - Phase 5 — Refinement engine (dependency graph + perturbation VOI + L0a / L0b agents). New
packages/uwmd-core/src/calc/dependencies.tsexportsgetExprDependencies(ast)andextractDependencyGraph(parsed, {packs?})which walks everyMULTIFAMILY_PACKcalc plus everycustom_calculations[]entry into aDependencyGraphof(outputs: calc → inputs, inputs: input → calcs, formulas). Newpackages/uwmd-core/src/refinement.tsexportsrankGaps(parsed, opts?)returningRankGapsResultwithby_voi[]ranked by value-of-information andby_stage_blocking[]for non-numeric completeness gaps. The perturbation method evaluates each provisional input at its low / central / high values using interval arithmetic across the dependency graph, computes today's output range, then computes the collapsed range if the gap were known; VOI = today − collapsed, summed across affected outputs. Question templates ship indefaults.tsco-located with the ranges. Diagnostics surface non-monotonic outputs as warnings. Two new pipeline layers:L0a Scope(prerequisites: none; readsproperty; writesnoi_model,debt_structure,valuation,market_analysis,gaps;consumed_profile: summary) andL0b Scope Refinement(prerequisites:L0a; reads*; writesproperty,noi_model,debt_structure,gaps;consumed_profile: compact). Newuwmd scope <file>deterministically resolves every required input via the cascade and stamps results provisional atdeal_stage: scope. Newuwmd refine <file> [--targets=...] [--top=N] [--json]runsrankGapsand prints / emits the ranking. L0b prompt template atpackages/uwmd-core/src/agents/L0b-prompt.md. - Phase 6 — Conformance corpus + RFC drafts. New conformance fixtures: tier-1
04-scope-only.uw.md(back-of-napkin scope file), and six new malformed fixtures (04-broken-chain→INT-01,06-wrong-actor→POL-01with siblingpolicies.json,07-replace-where-supersede-required→POL-02,08-provisional-without-gap→DQ-01,09-partial-without-overrides→DQ-03). New tier-2 fixtures:gaps-section-update(post-write maintainer hook),parent-hash-stamp(integrity-aware edit),stale-parent-rejected(negative-pathINT-02). New tier-3 mode:dependency-graph-multifamilyexercisesextractDependencyGraph. New tier-4 fixtures:l0a-scope-deterministic(shape contract for the scope agent's writes) andconsumer-profile-contract(verifies every Bancroft layer declares the rightconsumed_profile). The conformance runner (scripts/run-conformance.mjs) gained async dispatch, integrity / policy code multiplexing on tier-1 malformed viaverifyChain+verifyProvenance, optional<id>.policies.jsonand<id>.options.jsonsiblings on tier-1/tier-2 fixtures,expected-error.jsonfor negative-path tier-2 fixtures,runTier3Refinement/runTier4Profilemodes, and a<volatile>mask oncontent_hashstrings (which canonicalize over a per-run timestamp). Final tally: 29 conformance fixtures pass, 0 fail. - Deferred / discussed — five items intentionally captured as RFC drafts rather than implementation in this train: RFC 0009 (
_metav2 sub-object reorganization, the only proposed breaking change; deferred to v2.0 with back-compat parser shim), RFC 0010 (_meta.integrity.signatureas a separate@uwmd/signingpackage; explicit non-goal as everyday open-standard usage), RFC 0011 (JWT-style scoped capability tokens for write authorization in orchestrator-bound deployments), RFC 0013 (per-section embedding sidecar for portfolio-scale "find similar deals", v3 territory), and a new "Range types and napkin mode" subsection added to RFC 0005 framing the v1.1 perturbation engine as the v1.1 approximation of a stochastic v2. Other items explicitly NOT pursued and documented in the architectural review's out-of-scope catalog: per-field provenance everywhere (rejected —field_overridesis the chosen mid-point), calc-engine confidence propagation (deferred indefinitely — math is murky), pure-halt mode for required fields (rejected — partial data is the norm), compiled binary.uwcformat (rejected with extended explanation), AST cache files (deferred until measured parsing speed becomes a problem), per-block telemetry (no current adopter need), asset-class default tables for non-multifamily (one pack per RFC; office / retail / industrial follow), andMarketDataLookupreference implementation (interface only — adopters bring CoStar / Yardi / internal).
- Phase 1 — Foundation (
- Calc engine v1.1 (Phase B) — protocol version bumped to
1.1.0with two additive Tier-3 changes. (1) Grammar gains short-circuiting logical operators&&and||between conditional and comparison precedence levels, with full null propagation (any null operand → null) andCALC-TYPE-001on non-boolean operands; the right side is only evaluated when the left does not determine the result. (2) Built-in function set expands by 10: math (abs,floor,ceil,sqrt,pow,log,exp) and financial (fv,pv,nper). All new financials use the same unsigned convention as the existingpmt(positivepv= loan balance, positivepmt= periodic payment) and admit zero-rate edge cases by closed-form fallback. Spec updated atspec/UW_PROTOCOL_v1.md§VIII.1 (EBNF) and §VIII.3 (builtin table). Backwards-compatible: every v1.0.0 expression continues to parse and evaluate identically.
Changed
@uwmd/coreno longer declares abinentry — the standaloneuwmdpackage owns the binary so the two packages don't conflict when both installed. Library consumers continue to import from@uwmd/core; CLI consumers should installuwmd(ornpx uwmd). The CLI module is now exposed as the@uwmd/core/clisubpath export.- Release workflow now publishes both
@uwmd/coreanduwmdtogether on eachv*tag, with a version-match gate that also verifiesuwmd's pinned dependency on the corresponding@uwmd/coreversion. - Quickstart in
README.mdand fixture-regen examples inCONTRIBUTING.mdupdated to usenpx uwmd …(ornpm run cli -- …from a clone) instead of the rawnode packages/uwmd-core/dist/cli.jspath. - Format spec — added RFC 2119 preamble; restored §4.18 (Pipeline Log) to canonical numeric position; clarified section count (21 standard + 1 meta).
- Protocol spec — fixed
.uw.institution.jsoncross-reference (now points to Appendix C.6); added "Normative schema:" links from §I.4, §III.1, §V, §VIII, §XI to the new JSON Schemas; added §XIII "Future work" consolidating v2 deferrals. - Root
npm testandnpm run buildnow run across all workspaces (--workspaces --if-present), not just@uwmd/core. The existing@uwmd/exceltest suite and the newuwmdCLI smoke tests are now covered. vitestbumped from^1.6.0to^3.0.0in@uwmd/core,@uwmd/excel, anduwmd(clears four moderatenpm auditadvisories in the vite/esbuild dev-server chain).esbuildbumped from^0.24.0to^0.25.0intools/vscode-uwmd(clears the dev-server CSRF advisory).vitebumped from^5.4.0to^7.0.0intools/web-editor; the web-editor lockfile now reports0 vulnerabilities.- Calc engine — internal AST node for ternary expressions renamed
cond.then→cond.consequentto avoid the false thenable signal the previous shape produced. No external API change (the AST is internal topackages/uwmd-core/src/calc/parser.tsandevaluator.ts). - Docs site prebuild (
tools/docs-site/scripts/prebuild.mjs) — now copies the six numbered RFC drafts into the site tree and rewritesdocs/rfcs/NNNN-…and bareNNNN-…link forms to/about/rfcs/NNNN-…. Fixes 18 dead links that were breakingnpm run buildfor the docs site. - Workspace lockfile policy —
packages/uwmd-core/package-lock.jsonremoved (workspace member, drifts from the root lockfile). Tools undertools/*are intentionally not workspace members and continue to commit their own lockfiles. Documented in CONTRIBUTING.md.
Removed
packages/uwmd-core/ANTHROPIC_API_KEY.env— local-dev convenience file containing a real API key. Deleted; the CLI already loadsANTHROPIC_API_KEYfromprocess.env. Future: set it via your shell or a gitignored~/.config/uwmd/.envinstead of inside the repo tree.
[1.0.0-pre] — pre-public
Pre-public development of the format spec (UW_FORMAT_SPEC_v1.md) and reference parser/validator/renderer/runner/Claude agent host inside uwmd/.