Status: implemented
Implemented — this change has shipped.
RFC 0083: Split @uwmd/verify, the reference implementation of the standard, out of @uwmd/core
Accepted 2026-10-13 under the owner's RFC 0078 decision D8 ("Split later: a small @uwmd/verify reference implementation of the standard, with @uwmd/core as the starter toolkit on top that still re-exports it. Separate follow-up."). This RFC fixes the boundary and the open implementation questions. It changes no normative text, so neither the Format (3.0) nor the Protocol (4.0.0) version moves. Implemented; released in 4.1.0. See Resolution.
Summary
Publish a new package, @uwmd/verify, that contains everything a party needs to read and verify a .uw.md document and nothing it does not: the reader, validation, the safe-expression evaluator and quantization, stated-precision comparison, the metric dictionary, receipts, integrity, the module system, and the optional verification profiles (representations, portfolio, sensitivity, stochastic). @uwmd/core becomes the starter toolkit (defaults, cascade, refinement, editor, renderer, report, display formatting, Excel emission, agents, CLI) built on top of it. Core depends on @uwmd/verify at an exact version and re-exports its whole surface, so no existing import of @uwmd/core or @uwmd/core/browser breaks.
Motivation
RFC 0078 drew the line between the standard and the starter products in the specs. The packages do not yet follow that line:
The boundary test is not executable. RFC 0078's test is that a lender can verify a
.uw.mdfrom any producer using only the standard. Today the only code that does this isconformance/metrics/verifier.mjs, a conformance script that imports deepdist/files from core and is not published. An adopter who wants a verifier has to install all of core: editor, cascade, defaults, agents and the Anthropic provider loader included.Seven imports cross from standard code into starter code (mapped 2026-10-13 over the 68-file closure of the standard modules; the seventh,
validator.ts→gaps.ts, was found in Phase 1):Edge Symbol Effect receipts.ts→packs/index.tsgetPackForAssetClassreceipt issue and verify builtin-class-validation.ts→packs/index.tsgetPackForAssetClassevery validateUWFilecalc/index.ts→format.tsformat*the optional displaystring of everyevaluateCalclite-bridge.ts→runner.tsbuildMetareaches the validator and receipts bindings.ts→editor.tsapplyEditAsyncdrags in editor, runner, gaps market-data.ts→cascade.tstype MarketDataLookuptype only validator.ts→gaps.tsreadGapsContentthe DQ rules of every validateUWFileprotocol.tsmixes both sides. About 420 of its 3,284 lines are starter material: display and badges, view models (BUILTIN_VIEW_MODELS), the cascade order, the agent host and capability tokens (RFC 0078 D10, D11). The view-model typesSectionViewModelandFieldViewHintand the typeCascadeStepstay normative: module manifests declareview_models, andGapActionnames a cascade step.The browser entry cannot verify integrity.
verifyChain,verifyProvenanceandcomputeBlockHashare exported only fromindex.tsalthoughintegrity.tsalready falls back to Web Crypto.
Proposed change
1. The package
packages/uwmd-verify, published as @uwmd/verify, ESM only, with two entries: . (Node) and ./browser (no static node: imports; integrity's SHA-256 loads node:crypto through a dynamic import guarded by a runtime check for Node and uses Web Crypto everywhere else, so a browser never evaluates it). Its runtime dependencies are fast-xml-parser and fflate, which move from core (both are used only by the representations codecs). It has no optional peers. The Phase 1 closure test allows exactly these packages and that one guarded site.
It contains the modules the 2026-10-13 map classified as standard:
- Reader:
parser,yaml-scalar,types,scenario,metadata,version,block-roles,section-resolution. - Validation:
validator,builtin-class-content,builtin-class-validation,asset-class,period-path,periods,period-validation, the lease-up, replacement-funding and reserve-account structure checks, andreadGapsContent. - Stated-figure verifiers: cash-flow series, the lease-up schedule, lease-up and property cash flows, waterfall, reserve accounts and draw bindings, replacement funding, capital stack, property cash-flow inputs.
- Calculation: the
calc/parser, evaluator, builtins, errors, quantization, day count, dependencies, dated flows and stated precision. - Metric dictionary:
spec/metrics/builtin.jsonshipped as package data, andverifyStatedMetrics(below). - Receipts, integrity, modules:
receipts,integrity,integrity-canonical,module-signing,modules,module-runtime. - Profiles: representations (envelope, codecs, UWJSON, UWXML, UWCSV, zip safety, Lite and the Lite bridge, source representation, composition, negotiation, deal packages, lease abstracts), portfolio, sensitivity, stochastic (with the PCG64 sampler), market data documents, and the binding operations that only read.
- The normative part of
protocol.ts: types, error codes, edit policies, version constants, module manifest types and the conformance manifest types (ImplementationManifest, the tier and profile vocabularies). The manifest valueREFERENCE_IMPLEMENTATION_MANIFESTdescribes@uwmd/core(org.uwmd.core, core's version, starter capabilities such as editing and agent hosting), so it stays in core withuwmd manifest.
Everything else stays in @uwmd/core.
2. The new verification API
verifyStatedMetrics(parsed, options?) evaluates every dictionary entry for the document's asset class and compares it with the document's stated value, returning one result per stated metric (verified, failed, or unverifiable with a reason). It promotes the logic of conformance/metrics/verifier.mjs, which already uses only the reader, the evaluator and compareStatedMetric (RFC 0079), into the package. The conformance fixture then imports the published function instead of deep dist/ paths, and its import-graph control (conformance/metrics/controls.mjs) becomes a package-level closure test.
3. Core on top
@uwmd/coreadds a dependency on@uwmd/verifypinned to an exact version (the house rule for@uwmd/*dependents), andsrc/index.tsandsrc/browser.tsre-export every@uwmd/verifysymbol they export today. A test asserts the re-exported names are a superset of the 4.0.x surface.evaluateCalcin verify returns nodisplaystring (the field is optional incalc-result.schema.json). Core's exportedevaluateCalcaddsdisplayas today, so its callers see no change. So does core's exportedevaluateModuleCalculations, whose outcomes carryCalcResults.bindings.ts: the source-edit adapter stays in core and is injected into the binding operations; the read operations move to verify.buildMetamoves fromrunner.tstometadata.ts;MarketDataLookupmoves tomarket-data.ts.- Receipts record the engine that computed them, and a verifier compares it with its own (UW Receipt §5.3,
RCP-07). That engine is@uwmd/verify(VERIFY_VERSION) for every receipt, including those issued through core: naming@uwmd/corewould make a verifier holding only@uwmd/verifyreport a tampered core-issued receipt asunverifiableinstead offailed, which defeats the RFC 0078 boundary test. Theissuerstays the tool; core's exportedissueReceiptdefaults it to@uwmd/core@<CORE_VERSION>, and core re-exports verify'sverifyReceiptunchanged. The receipt baselines record verify's version from then on. An explicitengine,engine_versionorissuerwins either way. - The starter parts of
protocol.tsmove to a core module (starter-protocol.ts); core re-exports them under the same names. @uwmd/signingdepends on@uwmd/verifyfor its payload builders. Its capability-token code keeps importing theCapability*types from core.
Decisions
Each question was decided from precedent, per AGENTS.md.
| Question | Decision | Precedent |
|---|---|---|
| Do the builtin calc packs belong in verify? Packs are starter products under RFC 0078. | The twelve builtin pack manifests ship in verify as data. The Excel emitter, pack documentation and every other pack tool stay in core. | A receipt names a pack id and version, and a verifier that does not hold that pack must answer unverifiable (UW Receipt §5, RCP-05/RCP-06). Packs are module manifests, and the module system is normative. Every pack equals its dictionary entries by an exact parity test (RFC 0078 Phase 2). A verify package that answered unverifiable for every builtin receipt would fail D8's purpose. The packs' definitions stay normative only through the dictionary. |
| Does builtin-class validation keep evaluating packs? | Yes, unchanged, against the manifests now in verify. | RFC 0080 class rules (CC-HOSP-*, MOD-CALC-ERROR) read pack results; changing what they read is a separate change (AGENTS.md: one PR, one topic). |
| Does verify format display strings? | No. Core adds display. | RFC 0078 D11: display conventions are a starter guide; display is optional in calc-result.schema.json. |
| How does verify version? | Independent semver, first published as 0.1.0. | Every package other than core/CLI versions independently (release.yml header); new packages start at 0.x (signing 0.1.0, lake 0.1.0). Core/CLI stay paired with PROTOCOL_VERSION. |
| Does core's version move? | Minor (4.1.0): new dependency, additive surface, no breaking change. | Semver per surface (invariant 6); RFC 0078 "No breaking change … the package split (D8) is a follow-up with re-exports." |
| Does verify ship a CLI? | Not in this RFC. The portable driver keeps running uwmd. A uwmd-verify bin is a follow-up. | One PR, one topic. The portable driver contract (RFC 0004) is unchanged. |
| Is this a Protocol change? | No. No document's meaning or verification changes. | Wiki 11: from 3.0 the Protocol moves only when meaning or verification changes. |
Compatibility analysis
- Documents: none affected. No spec text, schema or conformance expectation changes.
@uwmd/coreusers: none break. Every 4.0.x export remains, from both entries, with the same name and behavior.browser.tsgains the integrity verification exports it lacked (additive).- Implementers of the standard: unaffected. The Reader and Verifier tiers and the six profiles are unchanged.
- Modules: unaffected. The manifest schema and module contract do not move.
- Release tooling:
check-release-readiness.mjsassumes every package pins core and publishes after it. Verify publishes first and core pins it, so the readiness rules gain a verify-first ordering.
Conformance impact
No fixture's expected result changes. conformance/metrics/verifier.mjs switches from deep dist/ imports to @uwmd/verify's verifyStatedMetrics and must still report the same 187/0/0 against the lender fixture. controls.mjs keeps its import-graph assertion, now stated as: the @uwmd/verify build reaches no core module.
Reference implementation
In three phases, each its own PR:
- Cut the edges in place (this PR). Inside core: move
buildMetaandMarketDataLookup, andreadGapsContentwith the gaps content types intogaps-content.ts; splitdisplayout ofevaluateCalc(starter wrappers incalc-display.tsforevaluateCalcandevaluateModuleCalculations); inject the bindings edit adapter (bindings-edit.tswires the editor); splitprotocol.tsintostarter-protocol.ts; move the builtin pack manifests into a standard-side module (builtin-packs/), withpacks/re-exporting them; addverifyStatedMetricswith the dictionary as generated package data; add a closure test listing the standard module set and asserting that its import graph never reaches a starter module. No new package and no public API change except the addedverifyStatedMetricsexport and its types. - Create the package. Move the standard set to
packages/uwmd-verify; core depends on it and re-exports; movefast-xml-parser/fflate; repoint signing, batch, lake andtools/vscode-uwmd; update the hardcoded workspace lists (package.jsonworkspaces,verify-lockfile,verify-versions,verify-packages,release-packages,check-release-readiness,release.yml,publish-cli-recovery.yml) and the hardcoded core source paths (verify-versions,verify-release,docs-site-sources,spec-refs,verify-codes,release-state.test,verify-pcg64-reference.py,vercel-ignore-build.sh); add theVERSIONS.mdrow; update wiki 03, 08 and 11,docs/starter/and the packageCLAUDE.mdfiles. Every module outside the Phase 1 closure list stays in core as a starter module; for example,calculation-context.ts(its only user is the CLI) stays in core. Implementation notes: verify's./browserentry exports the same surface as.(no module is Node-only); a test that needs a starter module stays in core, named for that module;publish-cli-recovery.ymlneeded no change (it probes the CLI's core pin, and a published core implies its verify version published first); and the envelope's defaultgeneratorlabel stays@uwmd/core, so representation bytes do not change. - Release. Core/CLI 4.1.0 with
@uwmd/verify0.1.0. The first publication of a new package name needs a trusted publisher on npmjs.com, an owner step done before the tag. Released in 4.1.0 (PR #320).
Test plan: the closure test (phase 1) and the re-export superset test (phase 2); the full suite, typecheck:tests and conformance unchanged at every phase; the metrics fixture at 187/0/0 through verifyStatedMetrics; verify-packages packing @uwmd/verify and type-checking a consumer that imports only it.
Alternatives considered
- Keep packs injectable only. Verify would need the caller to pass the pack for every receipt, and a lender holding only
@uwmd/verifywould getRCP-05for every builtin receipt. Rejected: it fails the boundary test. - Rebuild packs from the dictionary. The dictionary carries no pack id or version, which receipts name. Adding them is a dictionary schema change and belongs in its own RFC.
- Split the repository. RFC 0078 deferred this: the boundary is drawn in the specs and packages first.
Unresolved questions
None.
Resolution
Accepted 2026-10-13 under RFC 0078 decision D8; implemented and released 2026-10-13 in 4.1.0 (#320). The release published @uwmd/verify 0.1.0 first, then core/CLI 4.1.0, signing 0.4.0, batch 0.9.0, Excel 0.9.17, report 0.8.29 and lake 0.3.0, pairing Stable Protocol 4.0.0 with Format 3.0. No normative text changed.
The boundary test holds on the published packages: @uwmd/verify, installed with no @uwmd/core, parses and validates an example, verifies its stated metrics, verifies a receipt issued by core 4.1.0 and reports a tampered copy failed; batch and lake install without core; and core re-exports every verify export.