@uwmd/core 1.7.0 → 1.8.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 (89) hide show
  1. package/dist/agents/bancroft.js +4 -1
  2. package/dist/agents/bancroft.js.map +1 -1
  3. package/dist/asset-class.d.ts +98 -0
  4. package/dist/asset-class.d.ts.map +1 -0
  5. package/dist/asset-class.js +226 -0
  6. package/dist/asset-class.js.map +1 -0
  7. package/dist/browser.d.ts +21 -7
  8. package/dist/browser.d.ts.map +1 -1
  9. package/dist/browser.js +22 -4
  10. package/dist/browser.js.map +1 -1
  11. package/dist/calc/errors.d.ts +1 -1
  12. package/dist/calc/errors.d.ts.map +1 -1
  13. package/dist/calc/errors.js.map +1 -1
  14. package/dist/calc/evaluator.js +25 -1
  15. package/dist/calc/evaluator.js.map +1 -1
  16. package/dist/calc/prng.d.ts +59 -0
  17. package/dist/calc/prng.d.ts.map +1 -0
  18. package/dist/calc/prng.js +181 -0
  19. package/dist/calc/prng.js.map +1 -0
  20. package/dist/calc/sensitivity.d.ts +76 -0
  21. package/dist/calc/sensitivity.d.ts.map +1 -0
  22. package/dist/calc/sensitivity.js +160 -0
  23. package/dist/calc/sensitivity.js.map +1 -0
  24. package/dist/calc/stochastic.d.ts +97 -0
  25. package/dist/calc/stochastic.d.ts.map +1 -0
  26. package/dist/calc/stochastic.js +265 -0
  27. package/dist/calc/stochastic.js.map +1 -0
  28. package/dist/cascade.d.ts.map +1 -1
  29. package/dist/cascade.js +8 -4
  30. package/dist/cascade.js.map +1 -1
  31. package/dist/cli-args.d.ts +37 -0
  32. package/dist/cli-args.d.ts.map +1 -0
  33. package/dist/cli-args.js +93 -0
  34. package/dist/cli-args.js.map +1 -0
  35. package/dist/cli.js +119 -72
  36. package/dist/cli.js.map +1 -1
  37. package/dist/editor.js +25 -6
  38. package/dist/editor.js.map +1 -1
  39. package/dist/index.d.ts +22 -8
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +23 -4
  42. package/dist/index.js.map +1 -1
  43. package/dist/init.d.ts.map +1 -1
  44. package/dist/init.js +16 -4
  45. package/dist/init.js.map +1 -1
  46. package/dist/integrity-canonical.d.ts.map +1 -1
  47. package/dist/integrity-canonical.js +10 -2
  48. package/dist/integrity-canonical.js.map +1 -1
  49. package/dist/integrity.d.ts +92 -4
  50. package/dist/integrity.d.ts.map +1 -1
  51. package/dist/integrity.js +195 -9
  52. package/dist/integrity.js.map +1 -1
  53. package/dist/migrate-source-tags.d.ts +34 -0
  54. package/dist/migrate-source-tags.d.ts.map +1 -0
  55. package/dist/migrate-source-tags.js +179 -0
  56. package/dist/migrate-source-tags.js.map +1 -0
  57. package/dist/module-runtime.d.ts +57 -0
  58. package/dist/module-runtime.d.ts.map +1 -0
  59. package/dist/module-runtime.js +167 -0
  60. package/dist/module-runtime.js.map +1 -0
  61. package/dist/module-signing.d.ts +85 -0
  62. package/dist/module-signing.d.ts.map +1 -0
  63. package/dist/module-signing.js +136 -0
  64. package/dist/module-signing.js.map +1 -0
  65. package/dist/modules.d.ts +42 -0
  66. package/dist/modules.d.ts.map +1 -1
  67. package/dist/modules.js +124 -1
  68. package/dist/modules.js.map +1 -1
  69. package/dist/parser.d.ts.map +1 -1
  70. package/dist/parser.js +27 -1
  71. package/dist/parser.js.map +1 -1
  72. package/dist/protocol.d.ts +191 -21
  73. package/dist/protocol.d.ts.map +1 -1
  74. package/dist/protocol.js +289 -32
  75. package/dist/protocol.js.map +1 -1
  76. package/dist/runner.d.ts.map +1 -1
  77. package/dist/runner.js +9 -3
  78. package/dist/runner.js.map +1 -1
  79. package/dist/types.d.ts +118 -5
  80. package/dist/types.d.ts.map +1 -1
  81. package/dist/types.js +44 -0
  82. package/dist/types.js.map +1 -1
  83. package/dist/validator.d.ts +26 -2
  84. package/dist/validator.d.ts.map +1 -1
  85. package/dist/validator.js +201 -19
  86. package/dist/validator.js.map +1 -1
  87. package/dist/version.d.ts +1 -1
  88. package/dist/version.js +1 -1
  89. package/package.json +6 -2
package/dist/cli.js CHANGED
@@ -3,10 +3,11 @@
3
3
  // Commands: parse, validate, compact, diff, init, summary, render
4
4
  // Usage: uwmd <command> <file> [options]
5
5
  import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync } from 'node:fs';
6
- import { resolve, basename, dirname } from 'node:path';
6
+ import { resolve, basename } 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';
10
+ import { migrateSourceTags } from './migrate-source-tags.js';
10
11
  import { generateBlankUWFile } from './init.js';
11
12
  import { render } from './renderer.js';
12
13
  import { renderReportHtml } from './report.js';
@@ -16,6 +17,7 @@ import { CORE_CODEC_REGISTRY } from './codecs.js';
16
17
  import { writeAgentBlock } from './runner.js';
17
18
  import { applyEdit } from './editor.js';
18
19
  import { verifyChain, verifyProvenance } from './integrity.js';
20
+ import { REFERENCE_IMPLEMENTATION_MANIFEST } from './protocol.js';
19
21
  import { loadModuleManifest, createModuleRegistry, ModuleRegistryError } from './modules.js';
20
22
  import { cmdLeaseValidate, cmdLeaseProject, cmdPackageCreate, cmdPackageVerify, cmdPackageList, cmdPackageToContext, cmdPackageContextValidate, cmdPackageEdges, } from './cli-packages.js';
21
23
  import { evaluateCalc } from './calc/index.js';
@@ -34,6 +36,7 @@ import { detectUWSourceRepresentation, UWX_REPRESENTATION_ID, UW_LITE_SOURCE_DES
34
36
  import { parseUWLite } from './lite.js';
35
37
  import { compileUWLite, projectUWEnvelopeToLite, stringifyUWX, } from './lite-bridge.js';
36
38
  import { UW_LITE_REPRESENTATION_ID } from './source-representation.js';
39
+ import { parseFlags, extractPositionals, replaceUWExtension, hostTierFlag, defaultPartsDir, readManifestFile, } from './cli-args.js';
37
40
  const [, , command, ...args] = process.argv;
38
41
  function readFile(path) {
39
42
  const resolved = resolve(path);
@@ -54,30 +57,6 @@ function loadInstitutionConfig(path) {
54
57
  return undefined;
55
58
  }
56
59
  }
57
- function parseFlags(rawArgs) {
58
- const flags = {};
59
- for (let i = 0; i < rawArgs.length; i++) {
60
- const arg = rawArgs[i];
61
- if (arg.startsWith('--')) {
62
- const body = arg.slice(2);
63
- const eqIdx = body.indexOf('=');
64
- if (eqIdx >= 0) {
65
- flags[body.slice(0, eqIdx)] = body.slice(eqIdx + 1);
66
- continue;
67
- }
68
- const key = body;
69
- const next = rawArgs[i + 1];
70
- if (next && !next.startsWith('--')) {
71
- flags[key] = next;
72
- i++;
73
- }
74
- else {
75
- flags[key] = true;
76
- }
77
- }
78
- }
79
- return flags;
80
- }
81
60
  // ─── Commands ─────────────────────────────────────────────────────────────────
82
61
  function cmdParse(file, flags) {
83
62
  const content = readFile(file);
@@ -96,11 +75,24 @@ function cmdParse(file, flags) {
96
75
  for (const warning of detection.warnings)
97
76
  console.warn(`Warning: ${warning}`);
98
77
  const parsed = parseUWFile(content, { strict: flags['strict'] === true });
78
+ // The whole ParsedUWFile minus `raw` (which is just the input echoed back).
79
+ // Four fields — custom_calculations, custom_scenarios, extensions, and the
80
+ // full `superseded` blocks — used to be dropped here, so a caller that
81
+ // trusted `uwmd parse` to be "the parsed file" silently lost every custom
82
+ // calculation and every x_* extension in the document.
83
+ //
84
+ // `superseded_blocks` is kept alongside `superseded` for compatibility: it
85
+ // carries only each prior block's `content`, where `superseded` carries the
86
+ // whole block. New callers should read `superseded`.
99
87
  const output = {
100
88
  frontmatter: parsed.frontmatter,
101
89
  sections: Object.fromEntries(Object.entries(parsed.sections).map(([id, block]) => [id, block])),
102
90
  prose: parsed.prose,
103
91
  pipeline_log: parsed.pipeline_log.map(b => b.content),
92
+ custom_calculations: parsed.custom_calculations,
93
+ custom_scenarios: parsed.custom_scenarios,
94
+ extensions: parsed.extensions,
95
+ superseded: parsed.superseded,
104
96
  superseded_blocks: Object.fromEntries(Object.entries(parsed.superseded).map(([id, blocks]) => [id, blocks.map(b => b.content)])),
105
97
  };
106
98
  console.log(JSON.stringify(output, null, flags['compact'] ? 0 : 2));
@@ -171,6 +163,40 @@ function cmdValidate(file, flags) {
171
163
  console.log('');
172
164
  process.exit(result.errors.length > 0 ? 1 : 0);
173
165
  }
166
+ /**
167
+ * Build the `verifyChain` options from `--signing --keystore=<path>`.
168
+ *
169
+ * `@uwmd/signing` is reached by dynamic import and declared as an optional peer
170
+ * - the same arrangement `@anthropic-ai/sdk` gets. `@uwmd/core` must not take a
171
+ * crypto dependency to satisfy a flag most invocations never pass, and the
172
+ * layering invariant (core depends on no sibling package) forbids a static
173
+ * import outright.
174
+ */
175
+ async function signingOptions(flags) {
176
+ if (!flags['signing'])
177
+ return {};
178
+ const keystore = flags['keystore'];
179
+ if (typeof keystore !== 'string' || keystore.length === 0) {
180
+ console.error('--signing requires --keystore=<path> to a JSON key store.');
181
+ process.exit(1);
182
+ }
183
+ let signing;
184
+ try {
185
+ signing = (await import('@uwmd/signing'));
186
+ }
187
+ catch {
188
+ console.error('--signing needs the optional @uwmd/signing package. Install it with `npm i @uwmd/signing`.');
189
+ process.exit(1);
190
+ }
191
+ try {
192
+ const store = await signing.loadKeyStoreFile(resolve(keystore));
193
+ return { signatureVerifier: signing.createBlockSignatureVerifier(store) };
194
+ }
195
+ catch (error) {
196
+ console.error(error.message);
197
+ process.exit(1);
198
+ }
199
+ }
174
200
  async function cmdVerify(file, flags) {
175
201
  const content = readFile(file);
176
202
  // --resolved verifies the assembled record. Without it an externalized
@@ -191,8 +217,8 @@ async function cmdVerify(file, flags) {
191
217
  }
192
218
  let chain;
193
219
  let prov;
194
- if (runAll || onlyIntegrity) {
195
- chain = await verifyChain(parsed);
220
+ if (runAll || onlyIntegrity || flags['signing']) {
221
+ chain = await verifyChain(parsed, await signingOptions(flags));
196
222
  sections['integrity'] = chain;
197
223
  if (!chain.ok)
198
224
  hadError = true;
@@ -219,6 +245,12 @@ async function cmdVerify(file, flags) {
219
245
  }
220
246
  if (chain) {
221
247
  console.log(`\nIntegrity: ${chain.ok ? 'ok' : 'FAIL'} — chains_with_hashes=${chain.chains_with_hashes}, chains_verified=${chain.chains_verified}`);
248
+ if (chain.signatures_present > 0) {
249
+ const checked = flags['signing']
250
+ ? `${chain.signatures_verified} verified`
251
+ : 'not checked (pass --signing --keystore=<path>)';
252
+ console.log(`Signatures: ${chain.signatures_present} present, ${checked}`);
253
+ }
222
254
  for (const issue of chain.issues) {
223
255
  console.log(` [${issue.severity.toUpperCase()}] ${issue.code}${issue.section ? ` [${issue.section}]` : ''}: ${issue.message}`);
224
256
  }
@@ -253,6 +285,24 @@ function cmdCompact(file, flags) {
253
285
  writeFileSync(outPath, compacted, 'utf-8');
254
286
  console.log(`Compacted ${totalSuperseded} superseded block(s). Wrote to: ${outPath}`);
255
287
  }
288
+ function cmdMigrateSourceTags(file, flags) {
289
+ const content = readFile(file);
290
+ const result = migrateSourceTags(content);
291
+ for (const source of result.unmapped) {
292
+ console.error(`[unmapped] _meta.source '${source}' was left in place — rewrite it by hand.`);
293
+ }
294
+ if (result.changed === 0) {
295
+ console.log(`No legacy _meta.source values found in ${basename(file)} — nothing to migrate.`);
296
+ return;
297
+ }
298
+ if (flags['dry-run']) {
299
+ console.log(`Would migrate ${result.changed} block(s) in ${basename(file)} (RFC 0031 actor/resolution split).`);
300
+ return;
301
+ }
302
+ const outPath = flags['output'] ? resolve(flags['output']) : resolve(file);
303
+ writeFileSync(outPath, result.content, 'utf-8');
304
+ console.log(`Migrated ${result.changed} block(s). Wrote to: ${outPath}`);
305
+ }
256
306
  function cmdDiff(fileA, fileB) {
257
307
  const contentA = readFile(fileA);
258
308
  const contentB = readFile(fileB);
@@ -402,14 +452,6 @@ async function loadEnvelope(file) {
402
452
  * rather than `deal.uw.json`. (`.uw.md` does not match a `.uwx.md` filename —
403
453
  * the suffixes genuinely differ — so it was silently a no-op, not a mis-match.)
404
454
  */
405
- function replaceUWExtension(file, extension) {
406
- const lower = file.toLowerCase();
407
- for (const suffix of [UWX_EXTENSION, '.uw.csv.zip', '.uw.md', '.uw.json', '.uw.xml']) {
408
- if (lower.endsWith(suffix))
409
- return `${file.slice(0, -suffix.length)}${extension}`;
410
- }
411
- return `${file}${extension}`;
412
- }
413
455
  async function cmdConvert(file, flags) {
414
456
  const requested = flags['to'];
415
457
  if (typeof requested !== 'string') {
@@ -488,18 +530,6 @@ async function cmdReceiptIssue(file, flags) {
488
530
  console.log('A receipt attests that these outputs follow from this record. It does not attest that the inputs are true.');
489
531
  }
490
532
  // ─── Modules ──────────────────────────────────────────────────────────────────
491
- function readManifestFile(path) {
492
- try {
493
- return { ok: true, manifest: JSON.parse(readFileSync(resolve(path), 'utf-8')) };
494
- }
495
- catch (e) {
496
- return { ok: false, message: e instanceof Error ? e.message : String(e) };
497
- }
498
- }
499
- function hostTierFlag(flags) {
500
- const tier = flags['tier'];
501
- return typeof tier === 'string' ? tier : undefined;
502
- }
503
533
  function cmdModulesValidate(files, flags) {
504
534
  const hostTier = hostTierFlag(flags);
505
535
  const reports = files.map((file) => {
@@ -809,16 +839,7 @@ function cmdRefine(file, flags) {
809
839
  }
810
840
  // ─── Router ───────────────────────────────────────────────────────────────────
811
841
  const flags = parseFlags(args);
812
- // Positional args: non-flag tokens that don't immediately follow a --flag
813
- const positional = [];
814
- for (let _i = 0; _i < args.length; _i++) {
815
- const a = args[_i];
816
- if (a.startsWith('--')) {
817
- _i++;
818
- continue;
819
- } // skip flag + its value
820
- positional.push(a);
821
- }
842
+ const positional = extractPositionals(args);
822
843
  // ─── Composition (RFC 0021) ──────────────────────────────────────────────────
823
844
  /** Load every `.uwpart.md` in a directory, keyed by `part_id`. */
824
845
  function loadPartsDir(dir) {
@@ -836,10 +857,6 @@ function loadPartsDir(dir) {
836
857
  }
837
858
  return parts;
838
859
  }
839
- /** Where fragments live for a record, unless told otherwise. */
840
- function defaultPartsDir(file) {
841
- return resolve(dirname(resolve(file)), 'parts');
842
- }
843
860
  function cmdCompose(file, flags) {
844
861
  const section = flags['externalize'];
845
862
  if (typeof section !== 'string') {
@@ -969,7 +986,7 @@ function withResolved(parsed, file, flags) {
969
986
  break;
970
987
  case 'verify':
971
988
  if (!positional[0]) {
972
- console.error('Usage: uwmd verify <file> [--validate] [--integrity] [--policy] [--resolved] [--json]');
989
+ console.error('Usage: uwmd verify <file> [--validate] [--integrity] [--policy] [--signing --keystore=<path>] [--resolved] [--json]');
973
990
  process.exit(1);
974
991
  }
975
992
  await cmdVerify(positional[0], flags);
@@ -988,6 +1005,13 @@ function withResolved(parsed, file, flags) {
988
1005
  }
989
1006
  cmdDiff(positional[0], positional[1]);
990
1007
  break;
1008
+ case 'migrate':
1009
+ if (!positional[0] || !flags['source-tags']) {
1010
+ console.error('Usage: uwmd migrate <file> --source-tags [--output <file>] [--dry-run]');
1011
+ process.exit(1);
1012
+ }
1013
+ cmdMigrateSourceTags(positional[0], flags);
1014
+ break;
991
1015
  case 'render': {
992
1016
  if (!positional[0]) {
993
1017
  console.error('Usage: uwmd render <file> [--format <json|csv|chat|summary>] [--profile <summary|live|compact|full|relevant>] [--sections a,b,c] [--max-tokens 12000] [--no-meta] [--output <file>] [--tier screener|analyst]');
@@ -1175,6 +1199,13 @@ function withResolved(parsed, file, flags) {
1175
1199
  confidence: flags['confidence'] ?? 'medium',
1176
1200
  };
1177
1201
  const result = applyEdit(fileContent, parsedFile, op, ctx);
1202
+ // --json reports the edit instead of performing it: the conformance driver
1203
+ // needs the resulting content, and a driver that rewrote fixtures as a side
1204
+ // effect of reading them would corrupt the corpus it is testing.
1205
+ if (flags['json']) {
1206
+ console.log(JSON.stringify(result, null, 2));
1207
+ process.exit(result.ok ? 0 : 1);
1208
+ }
1178
1209
  if (!result.ok || !result.content) {
1179
1210
  console.error(`Edit rejected: [${result.error?.code}] ${result.error?.message}`);
1180
1211
  if (result.error?.pointer)
@@ -1214,16 +1245,21 @@ function withResolved(parsed, file, flags) {
1214
1245
  locale: 'en-US',
1215
1246
  };
1216
1247
  const results = decls.map((d) => evaluateCalc(d, ctx));
1217
- for (const r of results) {
1218
- if (r.ok) {
1219
- console.log(`${r.calc_id} = ${r.display ?? String(r.value)}`);
1220
- }
1221
- else {
1222
- console.log(`${r.calc_id} ERROR [${r.error?.code}] ${r.error?.message}`);
1223
- }
1224
- }
1248
+ // Under --json, stdout is exactly one JSON document — the conformance CLI
1249
+ // protocol (§II.6a) depends on that, and printing the human lines first
1250
+ // would make every calc response unparseable.
1225
1251
  if (flags['json']) {
1226
- console.log(JSON.stringify(results, null, 2));
1252
+ console.log(JSON.stringify(results.length === 1 ? results[0] : results, null, 2));
1253
+ }
1254
+ else {
1255
+ for (const r of results) {
1256
+ if (r.ok) {
1257
+ console.log(`${r.calc_id} = ${r.display ?? String(r.value)}`);
1258
+ }
1259
+ else {
1260
+ console.log(`${r.calc_id} ERROR [${r.error?.code}] ${r.error?.message}`);
1261
+ }
1262
+ }
1227
1263
  }
1228
1264
  if (results.some((r) => !r.ok))
1229
1265
  process.exit(1);
@@ -1402,6 +1438,11 @@ function withResolved(parsed, file, flags) {
1402
1438
  }
1403
1439
  break;
1404
1440
  }
1441
+ case 'manifest':
1442
+ // The conformance CLI protocol's identity call (§II.6a). Emitted with no
1443
+ // surrounding prose so a driver can read it without a parser.
1444
+ console.log(JSON.stringify(REFERENCE_IMPLEMENTATION_MANIFEST, null, 2));
1445
+ break;
1405
1446
  case 'receipt': {
1406
1447
  const sub = positional[0];
1407
1448
  if (sub === 'issue') {
@@ -1432,11 +1473,14 @@ Commands:
1432
1473
  parse <file> Parse file and output JSON
1433
1474
  validate <file> Run all validation checks
1434
1475
  verify <file> Validate + verify integrity (hashes) + provenance (actor/policy)
1476
+ --signing --keystore=<path> also checks block signatures (RFC 0010,
1477
+ via the optional @uwmd/signing package)
1435
1478
  render <file> Render to output format (see --format)
1436
1479
  run <file> Invoke a Bancroft agent (see --agent)
1437
1480
  edit <file> <op.json> Apply an EditOperation (Tier-2)
1438
1481
  compact <file> Strip superseded blocks
1439
1482
  diff <file-a> <file-b> Compare two files section by section
1483
+ migrate <file> --source-tags Rewrite legacy _meta.source values into the actor/resolution split (RFC 0031)
1440
1484
  calc <file> <calc.json> Evaluate a calc declaration or inline formula (Tier-3)
1441
1485
  init Generate a blank .uwx.md file
1442
1486
  summary <file> Print quick metrics to terminal
@@ -1452,16 +1496,19 @@ Commands:
1452
1496
  modules validate|list Validate module manifest files, or list the registry they form
1453
1497
  lease validate|project Validate a lease abstract, or project it to a rent-roll row (RFC 0018)
1454
1498
  package create|verify|... Build, verify, list, or project a UW Deal Package (RFC 0018)
1499
+ manifest Print this implementation's ImplementationManifest (conformance protocol)
1455
1500
  layers List Bancroft agent layers
1456
1501
 
1457
1502
  Options:
1458
1503
  --output <path> Output file path (compact, init)
1459
1504
  --dry-run Show what would change without writing (compact)
1460
- --json JSON output (validate)
1505
+ --json JSON output on stdout, nothing else (validate, calc, edit)
1461
1506
  --strict Throw on parse errors instead of collecting
1462
1507
  --format <f> Render format: json|csv|chat|summary (render, default: summary)
1463
1508
  --no-superseded Drop append-only history from the .uw.json export (export)
1464
1509
  --resolved Resolve externalized sections first (verify, export)
1510
+ --signing Check _meta.signature on every block (verify; requires --keystore)
1511
+ --keystore <path> JSON key store mapping kid to public key (verify --signing)
1465
1512
  --parts <dir> Fragment directory (compose, resolve, --resolved; default: ./parts)
1466
1513
  --externalize <s> Section to split into fragments (compose)
1467
1514
  --collection-key <k> Row identity field (compose, default: unit_id)