@uwmd/core 1.3.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/browser.d.ts +14 -6
  2. package/dist/browser.d.ts.map +1 -1
  3. package/dist/browser.js +8 -4
  4. package/dist/browser.js.map +1 -1
  5. package/dist/capital-stack.d.ts +116 -0
  6. package/dist/capital-stack.d.ts.map +1 -0
  7. package/dist/capital-stack.js +227 -0
  8. package/dist/capital-stack.js.map +1 -0
  9. package/dist/cascade.d.ts +60 -6
  10. package/dist/cascade.d.ts.map +1 -1
  11. package/dist/cascade.js +93 -14
  12. package/dist/cascade.js.map +1 -1
  13. package/dist/cli.js +269 -11
  14. package/dist/cli.js.map +1 -1
  15. package/dist/composition.d.ts +182 -0
  16. package/dist/composition.d.ts.map +1 -0
  17. package/dist/composition.js +536 -0
  18. package/dist/composition.js.map +1 -0
  19. package/dist/context.js +23 -23
  20. package/dist/defaults.d.ts +18 -0
  21. package/dist/defaults.d.ts.map +1 -1
  22. package/dist/defaults.js +64 -0
  23. package/dist/defaults.js.map +1 -1
  24. package/dist/index.d.ts +14 -6
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +8 -4
  27. package/dist/index.js.map +1 -1
  28. package/dist/lite-bridge.d.ts +9 -0
  29. package/dist/lite-bridge.d.ts.map +1 -1
  30. package/dist/lite-bridge.js +44 -5
  31. package/dist/lite-bridge.js.map +1 -1
  32. package/dist/lite.js +37 -1
  33. package/dist/lite.js.map +1 -1
  34. package/dist/market-data.d.ts +134 -0
  35. package/dist/market-data.d.ts.map +1 -0
  36. package/dist/market-data.js +292 -0
  37. package/dist/market-data.js.map +1 -0
  38. package/dist/packs/index.d.ts +1 -0
  39. package/dist/packs/index.d.ts.map +1 -1
  40. package/dist/packs/index.js +3 -0
  41. package/dist/packs/index.js.map +1 -1
  42. package/dist/packs/mixed-use.d.ts +3 -0
  43. package/dist/packs/mixed-use.d.ts.map +1 -0
  44. package/dist/packs/mixed-use.js +204 -0
  45. package/dist/packs/mixed-use.js.map +1 -0
  46. package/dist/protocol.d.ts +57 -8
  47. package/dist/protocol.d.ts.map +1 -1
  48. package/dist/protocol.js +153 -4
  49. package/dist/protocol.js.map +1 -1
  50. package/dist/receipts.d.ts +189 -9
  51. package/dist/receipts.d.ts.map +1 -1
  52. package/dist/receipts.js +373 -14
  53. package/dist/receipts.js.map +1 -1
  54. package/dist/report.js +165 -165
  55. package/dist/types.d.ts +54 -1
  56. package/dist/types.d.ts.map +1 -1
  57. package/dist/types.js.map +1 -1
  58. package/dist/validator.d.ts.map +1 -1
  59. package/dist/validator.js +263 -6
  60. package/dist/validator.js.map +1 -1
  61. package/dist/version.d.ts +1 -1
  62. package/dist/version.js +1 -1
  63. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"cascade.d.ts","sourceRoot":"","sources":["../src/cascade.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAGjD,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAA4B,MAAM,YAAY,CAAC;AAIpF,MAAM,WAAW,gBAAgB;IAC/B;;;;;OAKG;IACH,OAAO,CACL,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,GAC7C;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,GAAG,IAAI,CAAC;IACrF,+EAA+E;IAC/E,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,oEAAoE;IACpE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,gFAAgF;IAChF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,qFAAqF;IACrF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B;yDACqD;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB;AAID,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,SAAS,CAAC;IAClB,IAAI,EAAE,WAAW,CAAC;IAClB,kFAAkF;IAClF,KAAK,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACvD,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AA4ED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAC1B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,YAAY,EACpB,GAAG,GAAE,cAAmB,GACvB,aAAa,CA8Ef;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAE5E"}
1
+ {"version":3,"file":"cascade.d.ts","sourceRoot":"","sources":["../src/cascade.ts"],"names":[],"mappings":"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"}
package/dist/cascade.js CHANGED
@@ -1,11 +1,52 @@
1
1
  // Fallback cascade resolver — Protocol §V.7
2
2
  //
3
- // Walks the seven cascade steps in order; first hit wins. The producer that
3
+ // Walks the eight cascade steps in order (seven before RFC 0021 §5 added
4
+ // `inherited_assumption` at protocol 1.5.0); first hit wins. The producer that
4
5
  // uses this stamps the resulting `_meta.source` with the cascade step that
5
6
  // produced the value. See FORMAT_SPEC §3.4 (provisional flag) and §2.6
6
7
  // (source tags).
7
8
  import { getAssetClassDefaults } from './defaults.js';
8
9
  import { deepGet } from './parser.js';
10
+ /**
11
+ * Raised when two equidistant ancestors assert the same field. Diamond
12
+ * inheritance resolves explicitly or not at all — silently picking one would
13
+ * make the answer depend on graph traversal order, which is the class of bug
14
+ * this whole RFC is written to avoid.
15
+ */
16
+ export class AmbiguousInheritanceError extends Error {
17
+ code = 'COMP-AMBIGUOUS-INHERIT';
18
+ field_path;
19
+ ancestors;
20
+ constructor(field_path, ancestors) {
21
+ super(`[COMP-AMBIGUOUS-INHERIT] Equidistant ancestors ${ancestors
22
+ .map((a) => `'${a}'`)
23
+ .join(' and ')} both assert '${field_path}'. Resolve which is authoritative rather than letting traversal order decide.`);
24
+ this.name = 'AmbiguousInheritanceError';
25
+ this.field_path = field_path;
26
+ this.ancestors = ancestors;
27
+ }
28
+ }
29
+ /**
30
+ * Select the inherited value for `field_path`: the nearest ancestor wins.
31
+ *
32
+ * Exported because a host often needs the winning ancestor's identity to stamp
33
+ * `_meta.inherited_from`, not merely the value.
34
+ */
35
+ export function selectInheritedAssumption(field_path, inherited) {
36
+ const holders = inherited.filter((a) => Object.hasOwn(a.values, field_path));
37
+ if (holders.length === 0)
38
+ return null;
39
+ let nearest = holders[0];
40
+ for (const candidate of holders.slice(1)) {
41
+ if (candidate.distance < nearest.distance)
42
+ nearest = candidate;
43
+ }
44
+ const tied = holders.filter((a) => a.distance === nearest.distance && a.document_id !== nearest.document_id);
45
+ if (tied.length > 0) {
46
+ throw new AmbiguousInheritanceError(field_path, [nearest, ...tied].map((a) => a.document_id).sort());
47
+ }
48
+ return nearest;
49
+ }
9
50
  // ─── Internal helpers ────────────────────────────────────────────────────────
10
51
  function getBlock(parsed, sectionId) {
11
52
  const entry = parsed.sections[sectionId];
@@ -81,12 +122,16 @@ function findBySource(parsed, field_path, wanted) {
81
122
  *
82
123
  * Cascade order:
83
124
  * 1. user_override (in-file, source-tag match)
84
- * 2. user_input (in-file, source-tag match)
85
- * 3. investor_profile (ctx.profile)
86
- * 4. market_data (ctx.market)
87
- * 5. asset_class_default (defaults registry)
88
- * 6. global_default (ctx.global)
89
- * 7. system_default (ctx.system)
125
+ * 2. user_input (in-file, source-tag match; also market_data_accepted)
126
+ * 3. inherited_assumption (ctx.inherited — composition DAG only)
127
+ * 4. investor_profile (ctx.profile)
128
+ * 5. market_data (ctx.market)
129
+ * 6. asset_class_default (defaults registry)
130
+ * 7. global_default (ctx.global)
131
+ * 8. system_default (ctx.system)
132
+ *
133
+ * Throws `AmbiguousInheritanceError` when two equidistant ancestors assert the
134
+ * same field — the one case where resolution refuses rather than choosing.
90
135
  *
91
136
  * Returns `undefined` value with step='system_default' as a sentinel only when
92
137
  * every step misses; callers SHOULD treat that as an unresolvable input and
@@ -98,12 +143,45 @@ export function resolveValue(field_path, parsed, ctx = {}) {
98
143
  if (override) {
99
144
  return { value: override.value, source: 'user_override', step: 'user_override' };
100
145
  }
101
- // Step 2: user_input (also accept 'manual' as a synonym for typed-in values)
102
- const input = findBySource(parsed, field_path, ['user_input', 'manual']);
146
+ // Step 2: user_input ('manual' is a synonym for typed-in values).
147
+ //
148
+ // `market_data_accepted` resolves here too (RFC 0022 §4). It is an in-file
149
+ // value of record, not a fallback: the analyst accepted one observation of
150
+ // one vintage, and a later live lookup MUST NOT silently replace it. Placing
151
+ // it here rather than at step 4 is what makes promotion stop the field being
152
+ // ranked as an unresolved gap.
153
+ //
154
+ // The returned `source` is the tag found, so `market_data_accepted` survives
155
+ // resolution rather than being flattened into `user_input`. Only the *step*
156
+ // is shared — the cascade is normatively ordered and a producer must not
157
+ // reorder it (protocol §IX), so a promoted value is a distinctly tagged value
158
+ // resolved at an existing step rather than a step of its own. Extending the
159
+ // cascade is a protocol change, which is what RFC 0021 §5 did for
160
+ // `inherited_assumption`; RFC 0022 deliberately did not need one.
161
+ const input = findBySource(parsed, field_path, ['user_input', 'manual', 'market_data_accepted']);
103
162
  if (input) {
104
163
  return { value: input.value, source: input.source, step: 'user_input' };
105
164
  }
106
- // Step 3: investor_profile
165
+ // Step 3: inherited_assumption (RFC 0021 §5).
166
+ //
167
+ // Below `user_input`, so a value entered on the deal always wins —
168
+ // inheritance supplies defaults, never overrides. Above `investor_profile`,
169
+ // because a named ancestor in this deal's own composition DAG is more
170
+ // specific than an institution-wide preference set.
171
+ if (ctx.inherited && ctx.inherited.length > 0) {
172
+ // Throws on equidistant ancestors rather than picking one. Deliberate: a
173
+ // silent pick would make the answer depend on traversal order.
174
+ const ancestor = selectInheritedAssumption(field_path, ctx.inherited);
175
+ if (ancestor) {
176
+ return {
177
+ value: ancestor.values[field_path],
178
+ source: 'inherited_assumption',
179
+ step: 'inherited_assumption',
180
+ resolved_from: ancestor.document_id,
181
+ };
182
+ }
183
+ }
184
+ // Step 4: investor_profile
107
185
  if (ctx.profile && field_path in ctx.profile.values) {
108
186
  return {
109
187
  value: ctx.profile.values[field_path],
@@ -112,7 +190,7 @@ export function resolveValue(field_path, parsed, ctx = {}) {
112
190
  resolved_from: ctx.profile.source_id,
113
191
  };
114
192
  }
115
- // Step 4: market_data
193
+ // Step 5: market_data
116
194
  const asset_class = ctx.asset_class ??
117
195
  parsed.frontmatter.asset_class ??
118
196
  'multifamily';
@@ -124,6 +202,7 @@ export function resolveValue(field_path, parsed, ctx = {}) {
124
202
  source: 'market_data',
125
203
  step: 'market_data',
126
204
  range: hit.range,
205
+ ...(hit.source_id ? { resolved_from: hit.source_id } : {}),
127
206
  };
128
207
  }
129
208
  }
@@ -132,7 +211,7 @@ export function resolveValue(field_path, parsed, ctx = {}) {
132
211
  if (inFileMarket) {
133
212
  return { value: inFileMarket.value, source: 'market_data', step: 'market_data' };
134
213
  }
135
- // Step 5: asset_class_default
214
+ // Step 6: asset_class_default
136
215
  const table = getAssetClassDefaults(asset_class);
137
216
  const range = table?.fields[field_path];
138
217
  if (range) {
@@ -144,7 +223,7 @@ export function resolveValue(field_path, parsed, ctx = {}) {
144
223
  resolved_from: `${table?.asset_class}@${table?.version}`,
145
224
  };
146
225
  }
147
- // Step 6: global_default
226
+ // Step 7: global_default
148
227
  if (ctx.global && field_path in ctx.global.values) {
149
228
  return {
150
229
  value: ctx.global.values[field_path],
@@ -152,7 +231,7 @@ export function resolveValue(field_path, parsed, ctx = {}) {
152
231
  step: 'global_default',
153
232
  };
154
233
  }
155
- // Step 7: system_default
234
+ // Step 8: system_default
156
235
  if (ctx.system && field_path in ctx.system.values) {
157
236
  return {
158
237
  value: ctx.system.values[field_path],
@@ -1 +1 @@
1
- {"version":3,"file":"cascade.js","sourceRoot":"","sources":["../src/cascade.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cascade.js","sourceRoot":"","sources":["../src/cascade.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -2,8 +2,8 @@
2
2
  // uwmd CLI — command-line interface for .uw.md files
3
3
  // Commands: parse, validate, compact, diff, init, summary, render
4
4
  // Usage: uwmd <command> <file> [options]
5
- import { readFileSync, writeFileSync, existsSync } from 'node:fs';
6
- import { resolve, basename } from 'node:path';
5
+ import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync } from 'node:fs';
6
+ import { resolve, basename, dirname } from 'node:path';
7
7
  import { parseUWFile } from './parser.js';
8
8
  import { validateUWFile } from './validator.js';
9
9
  import { compact, diff } from './compactor.js';
@@ -24,6 +24,8 @@ import { runBancroftAgent } from './agents/bancroft.js';
24
24
  import { buildContext } from './context-profiles.js';
25
25
  import { rankGaps } from './refinement.js';
26
26
  import { resolveValue } from './cascade.js';
27
+ import { parseUWPart, resolveComposition, externalizeSection, stringifyUWPart, UWPART_EXTENSION, } from './composition.js';
28
+ import { createDocumentMarketData, parseMarketDataDocument } from './market-data.js';
27
29
  import { getAssetClassDefaults } from './defaults.js';
28
30
  import { MULTIFAMILY_PACK, getPackForAssetClass } from './packs/index.js';
29
31
  import { issueReceipt, verifyReceipt, assertUWReceipt } from './receipts.js';
@@ -171,7 +173,9 @@ function cmdValidate(file, flags) {
171
173
  }
172
174
  async function cmdVerify(file, flags) {
173
175
  const content = readFile(file);
174
- const parsed = parseUWFile(content);
176
+ // --resolved verifies the assembled record. Without it an externalized
177
+ // section verifies as a directive, which is not the document anyone means.
178
+ const parsed = withResolved(parseUWFile(content), file, flags);
175
179
  // No flag set → run all three.
176
180
  const onlyValidate = flags['validate'] === true;
177
181
  const onlyIntegrity = flags['integrity'] === true;
@@ -329,7 +333,11 @@ function cmdSummary(file) {
329
333
  }
330
334
  async function cmdExport(file, flags) {
331
335
  const includeSuperseded = !(flags['no-superseded'] === true || flags['compact'] === true);
332
- const loaded = await loadEnvelope(file);
336
+ // --resolved exports the assembled record. Exporting the directive instead
337
+ // would ship a document whose rent roll is a list of filenames.
338
+ const loaded = flags['resolved']
339
+ ? toUWEnvelope(withResolved(parseUWFile(readFile(file)), file, flags))
340
+ : await loadEnvelope(file);
333
341
  const envelope = includeSuperseded ? loaded : { ...loaded, superseded: {} };
334
342
  const text = stringifyUWEnvelope(await stampEnvelopeDigest(envelope));
335
343
  // Default output path: swap the .uw.md suffix for .uw.json (fall back to appending).
@@ -419,9 +427,15 @@ async function cmdConvert(file, flags) {
419
427
  const projection = projectUWEnvelopeToLite(envelope);
420
428
  encoded = projection.content;
421
429
  extension = '.uw.md';
422
- if (projection.report.lossy) {
430
+ // Two independent losses, reported separately. A record whose only loss is
431
+ // an externalized section omits zero *paths*, so folding these together
432
+ // would print "omitted 0 advanced path(s)" over a missing rent roll.
433
+ if (projection.report.omitted_paths.length > 0) {
423
434
  console.warn(`Warning: Lite projection omitted ${projection.report.omitted_paths.length} advanced path(s).`);
424
435
  }
436
+ if (projection.report.externalized_sections.length > 0) {
437
+ console.warn(`Warning: Lite projection omitted ${projection.report.externalized_sections.length} externalized section(s), unresolved here: ${projection.report.externalized_sections.join(', ')}.`);
438
+ }
425
439
  if (typeof flags['projection-report'] === 'string') {
426
440
  writeFileSync(resolve(flags['projection-report']), JSON.stringify(projection.report, null, 2), 'utf-8');
427
441
  }
@@ -644,6 +658,65 @@ function cmdReport(file, flags) {
644
658
  console.log(`Rendered ${label} → ${basename(outPath)} (${result.sectionsRendered.length} sections${result.sectionsSkipped.length ? `, skipped: ${result.sectionsSkipped.join(', ')}` : ''})`);
645
659
  console.log('For PDF output, use @uwmd/report (uwmd-report) or print this HTML from a browser.');
646
660
  }
661
+ /**
662
+ * Load `--market-data <file>` into a cascade-ready lookup, or return null when
663
+ * the flag is absent. Exits with a message rather than throwing, so a bad
664
+ * observation set is a usage error rather than a stack trace.
665
+ */
666
+ function loadMarketData(flags) {
667
+ const path = flags['market-data'];
668
+ if (!path)
669
+ return null;
670
+ try {
671
+ const doc = parseMarketDataDocument(parseUWFile(readFile(path)));
672
+ return {
673
+ lookup: createDocumentMarketData(doc),
674
+ identity: {
675
+ document_id: doc.document_id,
676
+ as_of: doc.as_of,
677
+ provider: doc.provider,
678
+ geo: doc.geo,
679
+ },
680
+ };
681
+ }
682
+ catch (e) {
683
+ console.error(`Market data: ${e instanceof Error ? e.message : String(e)}`);
684
+ process.exit(1);
685
+ }
686
+ }
687
+ function cmdMarketDataValidate(file, flags) {
688
+ const parsed = parseUWFile(readFile(file));
689
+ try {
690
+ const doc = parseMarketDataDocument(parsed);
691
+ if (flags['json']) {
692
+ process.stdout.write(`${JSON.stringify({ valid: true, ...doc }, null, 2)}\n`);
693
+ }
694
+ else {
695
+ console.log(`OK ${doc.document_id}`);
696
+ console.log(` as of ${doc.as_of} — ${doc.provider}`);
697
+ console.log(` ${doc.geo}${doc.asset_class ? ` — ${doc.asset_class}` : ''}`);
698
+ console.log(` ${doc.observations.length} observation(s):`);
699
+ for (const o of doc.observations) {
700
+ console.log(` ${o.field_path} = ${String(o.value)} ${o.unit}${o.confidence ? ` (${o.confidence})` : ''}`);
701
+ console.log(` basis: ${o.basis}`);
702
+ }
703
+ // Restated on every run because it is the thing people forget.
704
+ console.log('\n Attributable, not verified: this says the observations');
705
+ console.log(' are traceable, not that they are accurate or current.');
706
+ }
707
+ }
708
+ catch (e) {
709
+ const message = e instanceof Error ? e.message : String(e);
710
+ if (flags['json']) {
711
+ process.stdout.write(`${JSON.stringify({ valid: false, error: message }, null, 2)}\n`);
712
+ }
713
+ else {
714
+ console.error(`INVALID ${file}`);
715
+ console.error(` ${message}`);
716
+ }
717
+ process.exit(1);
718
+ }
719
+ }
647
720
  function cmdScope(file, flags) {
648
721
  const content = readFile(file);
649
722
  const parsed = parseUWFile(content);
@@ -655,12 +728,19 @@ function cmdScope(file, flags) {
655
728
  console.error(`No published asset-class default table for '${assetClass}'.`);
656
729
  process.exit(1);
657
730
  }
731
+ const market = loadMarketData(flags);
658
732
  const out = {};
659
733
  for (const path of Object.keys(table.fields)) {
660
- const r = resolveValue(path, parsed);
734
+ const r = resolveValue(path, parsed, {
735
+ asset_class: assetClass,
736
+ ...(market ? { market: market.lookup } : {}),
737
+ });
661
738
  out[path] = {
662
739
  value: r.value,
663
740
  step: r.step,
741
+ // Surfaced so a promoted value stays distinguishable in the output: it
742
+ // resolves at the `user_input` step but is tagged `market_data_accepted`.
743
+ source: r.source,
664
744
  range: r.range,
665
745
  resolved_from: r.resolved_from,
666
746
  };
@@ -670,6 +750,7 @@ function cmdScope(file, flags) {
670
750
  deal_stage_target: 'scope',
671
751
  asset_class: assetClass,
672
752
  defaults_table: `${assetClass}@${table.version}`,
753
+ ...(market ? { market_data: market.identity } : {}),
673
754
  resolved: out,
674
755
  };
675
756
  const text = JSON.stringify(payload, null, 2);
@@ -692,7 +773,16 @@ function cmdRefine(file, flags) {
692
773
  parsed.frontmatter.asset_class ??
693
774
  'multifamily';
694
775
  const pack = getPackForAssetClass(assetClass) ?? MULTIFAMILY_PACK;
695
- const result = rankGaps(parsed, { targets, top, packs: [pack] });
776
+ // With `--market-data`, a field the observation set covers is no longer a
777
+ // gap the cascade cannot fill, so it drops out of the VOI ranking. That is
778
+ // the point: the ranking should tell you what is still worth diligencing.
779
+ const market = loadMarketData(flags);
780
+ const result = rankGaps(parsed, {
781
+ targets,
782
+ top,
783
+ packs: [pack],
784
+ ...(market ? { cascadeContext: { asset_class: assetClass, market: market.lookup } } : {}),
785
+ });
696
786
  if (flags['json']) {
697
787
  process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
698
788
  return;
@@ -729,6 +819,137 @@ for (let _i = 0; _i < args.length; _i++) {
729
819
  } // skip flag + its value
730
820
  positional.push(a);
731
821
  }
822
+ // ─── Composition (RFC 0021) ──────────────────────────────────────────────────
823
+ /** Load every `.uwpart.md` in a directory, keyed by `part_id`. */
824
+ function loadPartsDir(dir) {
825
+ const parts = new Map();
826
+ if (!existsSync(dir))
827
+ return parts;
828
+ for (const file of readdirSync(dir).filter((f) => f.endsWith(UWPART_EXTENSION)).sort()) {
829
+ const path = resolve(dir, file);
830
+ const part = parseUWPart(parseUWFile(readFileSync(path, 'utf-8')), { filename: file });
831
+ if (parts.has(part.part_id)) {
832
+ console.error(`Duplicate part_id '${part.part_id}' in ${dir}`);
833
+ process.exit(1);
834
+ }
835
+ parts.set(part.part_id, part);
836
+ }
837
+ return parts;
838
+ }
839
+ /** Where fragments live for a record, unless told otherwise. */
840
+ function defaultPartsDir(file) {
841
+ return resolve(dirname(resolve(file)), 'parts');
842
+ }
843
+ function cmdCompose(file, flags) {
844
+ const section = flags['externalize'];
845
+ if (typeof section !== 'string') {
846
+ console.error('Usage: uwmd compose <file> --externalize <section> [--collection-key <k>] [--collection-path <p>]');
847
+ process.exit(1);
848
+ }
849
+ const collectionKey = flags['collection-key'] ?? 'unit_id';
850
+ const collectionPath = flags['collection-path'] ?? 'units';
851
+ const parsed = parseUWFile(readFile(file));
852
+ let result;
853
+ try {
854
+ result = externalizeSection(parsed, {
855
+ section,
856
+ collectionKey,
857
+ collectionPath,
858
+ ...(flags['part-prefix'] ? { partIdPrefix: flags['part-prefix'] } : {}),
859
+ });
860
+ }
861
+ catch (e) {
862
+ console.error(e instanceof Error ? e.message : String(e));
863
+ process.exit(1);
864
+ }
865
+ const outDir = flags['out-dir'] ? resolve(flags['out-dir']) : defaultPartsDir(file);
866
+ const recordPath = flags['in-place']
867
+ ? resolve(file)
868
+ : flags['output']
869
+ ? resolve(flags['output'])
870
+ : resolve(replaceUWExtension(file, '.externalized.uwx.md'));
871
+ const recordText = stringifyUWX(toUWEnvelope(result.document));
872
+ if (flags['dry-run']) {
873
+ console.log(`Would write ${result.parts.length} fragment(s) to ${outDir}:`);
874
+ for (const part of result.parts)
875
+ console.log(` ${part.part_id}${UWPART_EXTENSION}`);
876
+ console.log(`Would write the record to ${recordPath}`);
877
+ return;
878
+ }
879
+ mkdirSync(outDir, { recursive: true });
880
+ for (const part of result.parts) {
881
+ writeFileSync(resolve(outDir, `${part.part_id}${UWPART_EXTENSION}`), stringifyUWPart(part), 'utf-8');
882
+ }
883
+ writeFileSync(recordPath, recordText, 'utf-8');
884
+ console.log(`Externalized ${section} → ${result.parts.length} fragment(s) in ${basename(outDir)}/`);
885
+ console.log(`Record written to ${basename(recordPath)}`);
886
+ // Externalizing is a packaging decision, not a model change — worth saying,
887
+ // because the file looks dramatically different afterwards.
888
+ console.log('The resolved record has the same semantic digest as the original.');
889
+ }
890
+ function cmdResolve(file, flags) {
891
+ const partsDir = flags['parts'] ? resolve(flags['parts']) : defaultPartsDir(file);
892
+ const parsed = parseUWFile(readFile(file));
893
+ let parts;
894
+ try {
895
+ parts = loadPartsDir(partsDir);
896
+ }
897
+ catch (e) {
898
+ console.error(e instanceof Error ? e.message : String(e));
899
+ process.exit(1);
900
+ }
901
+ const resolution = resolveComposition(parsed, { parts });
902
+ if (flags['json']) {
903
+ process.stdout.write(`${JSON.stringify({
904
+ status: resolution.status,
905
+ externalized: resolution.externalized,
906
+ parts_available: parts.size,
907
+ issues: resolution.issues,
908
+ }, null, 2)}\n`);
909
+ }
910
+ else {
911
+ console.log(`${resolution.status === 'resolved' ? 'RESOLVED' : 'UNRESOLVED'} ${basename(file)}`);
912
+ console.log(` ${parts.size} fragment(s) available in ${basename(partsDir)}/`);
913
+ console.log(` externalized section(s): ${resolution.externalized.join(', ') || '(none)'}`);
914
+ for (const issue of resolution.issues) {
915
+ console.log(` [${issue.code}] ${issue.message}`);
916
+ }
917
+ if (resolution.status === 'unresolved') {
918
+ // The rule worth restating: under-resolution is never a smaller answer.
919
+ console.log('\n Sections stay externalized rather than resolving partially —');
920
+ console.log(' a collection missing rows would still total and still validate.');
921
+ }
922
+ }
923
+ if (flags['output'] && resolution.status === 'resolved') {
924
+ const outPath = resolve(flags['output']);
925
+ writeFileSync(outPath, stringifyUWX(toUWEnvelope(resolution.document)), 'utf-8');
926
+ if (!flags['json'])
927
+ console.log(` resolved record → ${basename(outPath)}`);
928
+ }
929
+ if (resolution.status !== 'resolved')
930
+ process.exit(1);
931
+ }
932
+ /**
933
+ * Resolve a record's externalized sections before another command reads it.
934
+ *
935
+ * Returns the record unchanged when `--resolved` was not requested, so callers
936
+ * can apply it unconditionally. Exits when resolution fails: verifying or
937
+ * exporting a partially-resolved record would report on a document that does
938
+ * not exist.
939
+ */
940
+ function withResolved(parsed, file, flags) {
941
+ if (!flags['resolved'])
942
+ return parsed;
943
+ const partsDir = flags['parts'] ? resolve(flags['parts']) : defaultPartsDir(file);
944
+ const resolution = resolveComposition(parsed, { parts: loadPartsDir(partsDir) });
945
+ if (resolution.status !== 'resolved') {
946
+ console.error(`Cannot resolve ${basename(file)} against ${basename(partsDir)}/:`);
947
+ for (const issue of resolution.issues)
948
+ console.error(` [${issue.code}] ${issue.message}`);
949
+ process.exit(1);
950
+ }
951
+ return resolution.document;
952
+ }
732
953
  // Top-level async wrapper so `run --live` can use await
733
954
  (async () => {
734
955
  switch (command) {
@@ -748,7 +969,7 @@ for (let _i = 0; _i < args.length; _i++) {
748
969
  break;
749
970
  case 'verify':
750
971
  if (!positional[0]) {
751
- console.error('Usage: uwmd verify <file> [--validate] [--integrity] [--policy] [--json]');
972
+ console.error('Usage: uwmd verify <file> [--validate] [--integrity] [--policy] [--resolved] [--json]');
752
973
  process.exit(1);
753
974
  }
754
975
  await cmdVerify(positional[0], flags);
@@ -1020,7 +1241,7 @@ for (let _i = 0; _i < args.length; _i++) {
1020
1241
  break;
1021
1242
  case 'export':
1022
1243
  if (!positional[0]) {
1023
- console.error('Usage: uwmd export <file.uw.md> [--output <file.uw.json>] [--no-superseded] [--stdout]');
1244
+ console.error('Usage: uwmd export <file.uw.md> [--output <file.uw.json>] [--no-superseded] [--resolved] [--stdout]');
1024
1245
  process.exit(1);
1025
1246
  }
1026
1247
  await cmdExport(positional[0], flags);
@@ -1044,18 +1265,47 @@ for (let _i = 0; _i < args.length; _i++) {
1044
1265
  break;
1045
1266
  case 'scope':
1046
1267
  if (!positional[0]) {
1047
- console.error('Usage: uwmd scope <file> [--asset-class multifamily] [--output <file>]');
1268
+ console.error('Usage: uwmd scope <file> [--asset-class multifamily] [--market-data <file>] [--output <file>]');
1048
1269
  process.exit(1);
1049
1270
  }
1050
1271
  cmdScope(positional[0], flags);
1051
1272
  break;
1052
1273
  case 'refine':
1053
1274
  if (!positional[0]) {
1054
- console.error('Usage: uwmd refine <file> [--targets dscr,debt_yield] [--top 5] [--json]');
1275
+ console.error('Usage: uwmd refine <file> [--targets dscr,debt_yield] [--top 5] [--market-data <file>] [--json]');
1055
1276
  process.exit(1);
1056
1277
  }
1057
1278
  cmdRefine(positional[0], flags);
1058
1279
  break;
1280
+ case 'compose':
1281
+ if (!positional[0]) {
1282
+ console.error('Usage: uwmd compose <file> --externalize <section> [--collection-key <k>] [--collection-path <p>] [--out-dir <dir>] [--in-place] [--dry-run]');
1283
+ process.exit(1);
1284
+ }
1285
+ cmdCompose(positional[0], flags);
1286
+ break;
1287
+ case 'resolve':
1288
+ if (!positional[0]) {
1289
+ console.error('Usage: uwmd resolve <file> [--parts <dir>] [--output <file>] [--json]');
1290
+ process.exit(1);
1291
+ }
1292
+ cmdResolve(positional[0], flags);
1293
+ break;
1294
+ case 'market-data': {
1295
+ const sub = positional[0];
1296
+ if (sub === 'validate') {
1297
+ if (!positional[1]) {
1298
+ console.error('Usage: uwmd market-data validate <file> [--json]');
1299
+ process.exit(1);
1300
+ }
1301
+ cmdMarketDataValidate(positional[1], flags);
1302
+ }
1303
+ else {
1304
+ console.error('Usage: uwmd market-data validate <file> [--json]');
1305
+ process.exit(1);
1306
+ }
1307
+ break;
1308
+ }
1059
1309
  case 'lease': {
1060
1310
  const sub = positional[0];
1061
1311
  if (sub === 'validate') {
@@ -1191,6 +1441,8 @@ Commands:
1191
1441
  init Generate a blank .uwx.md file
1192
1442
  summary <file> Print quick metrics to terminal
1193
1443
  export <file> Export a digested UW JSON 1.0 document
1444
+ compose <file> Externalize a section into .uwpart.md fragments (RFC 0021)
1445
+ resolve <file> Resolve externalized sections from a parts directory (RFC 0021)
1194
1446
  formats List registered machine representations
1195
1447
  convert <file> Convert Lite/UWX/JSON/XML/CSV bundle representations
1196
1448
  report <file> Render the lender package / credit memo HTML (§7.1/§7.2)
@@ -1209,6 +1461,12 @@ Options:
1209
1461
  --strict Throw on parse errors instead of collecting
1210
1462
  --format <f> Render format: json|csv|chat|summary (render, default: summary)
1211
1463
  --no-superseded Drop append-only history from the .uw.json export (export)
1464
+ --resolved Resolve externalized sections first (verify, export)
1465
+ --parts <dir> Fragment directory (compose, resolve, --resolved; default: ./parts)
1466
+ --externalize <s> Section to split into fragments (compose)
1467
+ --collection-key <k> Row identity field (compose, default: unit_id)
1468
+ --collection-path <p> Field the rows occupy (compose, default: units)
1469
+ --in-place Overwrite the input record (compose)
1212
1470
  --stdout Write export/convert output to stdout
1213
1471
  --to <format> Target: lite|uwx|uw-json|uw-xml|uw-csv-bundle (convert)
1214
1472
  --projection-report Write the UWX-to-Lite omission report as JSON (convert)