@githolon/dsl 0.114.0 → 0.116.2

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 (86) hide show
  1. package/assets/operation-contract.wasm +0 -0
  2. package/dart/CHANGELOG.md +55 -0
  3. package/dart/lib/src/application_framework_calls.dart +7 -1
  4. package/dart/lib/src/console_enrolment.dart +7 -0
  5. package/dart/lib/src/console_http_io.dart +22 -15
  6. package/dart/lib/src/dev_cloud.dart +62 -0
  7. package/dart/lib/src/nms_generated_runtime.dart +1 -0
  8. package/dart/lib/src/operation_receipts.dart +74 -7
  9. package/dart/lib/src/reception/dart_reception.dart +26 -6
  10. package/dart/lib/src/result_usda.dart +64 -27
  11. package/dart/lib/src/runner/node-entry.mjs +4733 -1534
  12. package/dart/lib/src/runtime_v2_transport.dart +3 -0
  13. package/dart/lib/src/sync_status_projection.dart +11 -0
  14. package/dart/pubspec.lock +1 -1
  15. package/dart/pubspec.yaml +2 -2
  16. package/dart/test/console_enrolment_test.dart +47 -0
  17. package/dart/test/kernel_refused_follow_test.dart +36 -0
  18. package/dart/test/model_adoption_test.dart +49 -0
  19. package/dart/test/result_usda_test.dart +67 -0
  20. package/dart/test/runtime_projections_test.dart +41 -0
  21. package/dart_types/CHANGELOG.md +30 -0
  22. package/dart_types/lib/src/approved_model.dart +78 -1
  23. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +528 -2
  24. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +24 -0
  25. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +1897 -112
  26. package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +98 -0
  27. package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pbenum.dart +11 -1
  28. package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pb.dart +16 -0
  29. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +43 -2
  30. package/dart_types/lib/src/vo_canonical.dart +185 -0
  31. package/dart_types/pubspec.lock +1 -1
  32. package/dart_types/pubspec.yaml +3 -1
  33. package/dart_types/test/vo_canonical_test.dart +91 -0
  34. package/package.json +4 -2
  35. package/src/aggregate.ts +95 -0
  36. package/src/anonymous_value_interning.ts +205 -0
  37. package/src/baseline.ts +27 -0
  38. package/src/baseline_export.ts +432 -0
  39. package/src/baseline_import.ts +312 -0
  40. package/src/baseline_usda.ts +185 -0
  41. package/src/birth_key_rule.ts +472 -0
  42. package/src/births.ts +207 -24
  43. package/src/build_package.ts +47 -7
  44. package/src/build_provenance.ts +10 -0
  45. package/src/business_birth.ts +50 -4
  46. package/src/check_package_main.ts +50 -6
  47. package/src/codegen_call_proto.ts +5 -18
  48. package/src/codegen_call_usda.ts +6 -3
  49. package/src/codegen_dart.ts +114 -60
  50. package/src/codegen_dart_client.ts +52 -6
  51. package/src/codegen_dart_facade.ts +57 -35
  52. package/src/codegen_dart_framework_calls.ts +20 -1
  53. package/src/codegen_proof.ts +5 -2
  54. package/src/codegen_result_proto.ts +8 -6
  55. package/src/codegen_result_usda.ts +12 -4
  56. package/src/codegen_ts.ts +109 -10
  57. package/src/compatibility_graph.ts +41 -4
  58. package/src/compatibility_usda.ts +11 -1
  59. package/src/compile_package_main.ts +222 -52
  60. package/src/composed_wire_identity.ts +24 -0
  61. package/src/directive.ts +6 -0
  62. package/src/domain_requirements.ts +75 -0
  63. package/src/engine_entry.ts +12 -4
  64. package/src/engine_javascript.ts +12 -6
  65. package/src/fields.ts +109 -0
  66. package/src/identity_usda.ts +80 -103
  67. package/src/index.ts +35 -1
  68. package/src/intent.ts +33 -3
  69. package/src/law_check.ts +34 -0
  70. package/src/law_graph.ts +142 -1
  71. package/src/law_graph_usda.ts +27 -144
  72. package/src/law_layer_prims.ts +174 -0
  73. package/src/law_unit_selection.ts +45 -0
  74. package/src/law_units.ts +7 -6
  75. package/src/law_version.ts +108 -0
  76. package/src/package_config.ts +44 -0
  77. package/src/package_dependencies.ts +8 -0
  78. package/src/point_guard.ts +5 -1
  79. package/src/release_inspection.ts +6 -4
  80. package/src/release_publication.ts +6 -5
  81. package/src/usda_text.ts +78 -3
  82. package/src/value_identity.ts +355 -0
  83. package/src/value_object.ts +35 -4
  84. package/src/wire.ts +2 -0
  85. package/src/wire_paths.ts +17 -0
  86. package/src/workspace_routing.ts +1 -1
@@ -1,4 +1,6 @@
1
+ import { collectPackageSources, findBirthKeyWrites, formatBirthKeyWriteWarnings } from "./birth_key_rule.js";
1
2
  import type { Directive } from "./directive.js";
3
+ import { usdaString } from "./usda_text.js";
2
4
  // NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
3
5
  // remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
4
6
  // wasm32-wasip1 artifact {kernel · projection · embedded
@@ -46,6 +48,7 @@ import type { Directive } from "./directive.js";
46
48
  import { execFile, execFileSync } from "node:child_process";
47
49
  import { promisify } from "node:util";
48
50
  import { checkOperationContracts, deriveInterfaceOffer } from "./operation_contract_check.js";
51
+ import { lawVersionClaim, type LawVersionClaim } from "./law_version.js";
49
52
  import { compilerIntermediateCacheRoot, compilerSourceName, compilerSourceRoot } from "./compiler_cache.js";
50
53
  import {
51
54
  APPLICATION_CONTRACT_MODULE,
@@ -80,6 +83,7 @@ import ts from "typescript";
80
83
  import type { AggregateHandle } from "./aggregate.js";
81
84
  import {
82
85
  generateDartDomain,
86
+ dartBrandNamesForModule,
83
87
  generateDartContracts,
84
88
  generateDartActivityRegistry,
85
89
  collectActivityDescriptors,
@@ -97,6 +101,8 @@ import {
97
101
  } from "./evolution_files.js";
98
102
  import { assertEvolutionModuleExports } from "./evolution.js";
99
103
  import { readHistoricalIdentityFrontier } from "./identity_frontier.js";
104
+ import { parseBaselineUsda, type ReleasedBaseline } from "./baseline_usda.js";
105
+ import { BaselineRefusal, assertBaselineRetained, importBaseline, verifyBaselinePin } from "./baseline_import.js";
100
106
  import {
101
107
  dartBridgeClientOperationDirectiveIds,
102
108
  generateDartDomainClient,
@@ -266,6 +272,7 @@ import {
266
272
  splitLawUnits,
267
273
  } from "./law_units.js";
268
274
  import { lawUnitSetIdentityWith } from "./law_unit_set_identity.js";
275
+ import { domainRequirementsOf, type DomainUnitRequirement } from "./domain_requirements.js";
269
276
  import { checkDomainModules, formatFindings, hasRefusal } from "./law_check.js";
270
277
  import {
271
278
  currentEvolveLaw,
@@ -624,6 +631,7 @@ const compareLogicalPath = (left: string, right: string): number =>
624
631
  * inventories the USDZ/Git tree and re-hashes every logically named layer. */
625
632
  function emitLawClosure(
626
633
  packageName: string,
634
+ claimedVersion: LawVersionClaim | undefined,
627
635
  exportedDomains: readonly string[],
628
636
  runtimeDependencies: readonly RuntimePackageDependency[],
629
637
  domainAssets: readonly EmittedLawClosureAsset[],
@@ -670,6 +678,7 @@ function emitLawClosure(
670
678
  "package.usda",
671
679
  packageContractUsda({
672
680
  packageName,
681
+ ...(claimedVersion === undefined ? {} : { claimedVersion }),
673
682
  exportedDomains,
674
683
  runtimeDependencies,
675
684
  })
@@ -809,7 +818,7 @@ function emitLawClosure(
809
818
  return [
810
819
  ` def NomosLawAsset "${primName}"`,
811
820
  " {",
812
- ` custom string nomos:logicalPath = ${JSON.stringify(
821
+ ` custom string nomos:logicalPath = ${usdaString(
813
822
  asset.logicalPath
814
823
  )}`,
815
824
  ` custom string nomos:sha256 = "${asset.sha256}"`,
@@ -1265,6 +1274,7 @@ function closeComposedBusinessBirthPresentation(
1265
1274
  aggregateId?: unknown;
1266
1275
  rootId?: unknown;
1267
1276
  namespace?: unknown;
1277
+ keyMode?: unknown;
1268
1278
  keyField?: unknown;
1269
1279
  };
1270
1280
  type BirthDirective = DomainModule["directives"][number] & {
@@ -1278,14 +1288,15 @@ function closeComposedBusinessBirthPresentation(
1278
1288
  typeof birth.aggregateId !== "string" ||
1279
1289
  typeof birth.rootId !== "string" ||
1280
1290
  typeof birth.namespace !== "string" ||
1281
- typeof birth.keyField !== "string"
1291
+ (typeof birth.keyField !== "string" && birth.keyMode !== "minted")
1282
1292
  )
1283
1293
  return undefined;
1284
1294
  return [
1285
1295
  birth.aggregateId,
1286
1296
  birth.rootId,
1287
1297
  birth.namespace,
1288
- birth.keyField,
1298
+ // A minted birth has no key field; its identity here is the minted mode itself.
1299
+ typeof birth.keyField === "string" ? birth.keyField : "\u0001minted",
1289
1300
  ].join("\u0000");
1290
1301
  };
1291
1302
 
@@ -1335,6 +1346,8 @@ interface DartSharedSymbols {
1335
1346
  readonly frameworkDirIds: ReadonlySet<string>;
1336
1347
  readonly frameworkQueryIds: ReadonlySet<string>;
1337
1348
  readonly sharedVoNames: ReadonlySet<string>;
1349
+ /** Branded-id extension types used by ≥2 domains — declared ONCE in the framework file. */
1350
+ readonly sharedBrandNames: ReadonlySet<string>;
1338
1351
  readonly hasFrameworkSymbols: boolean;
1339
1352
  /** The synthetic module whose emit IS `nomos_framework.dart` — undefined when nothing is shared. */
1340
1353
  readonly frameworkMod: DomainModule | undefined;
@@ -1496,11 +1509,23 @@ function collectDartSharedSymbols(
1496
1509
  const sharedVos = [...sharedVoNames]
1497
1510
  .sort()
1498
1511
  .map((name) => voDescriptorByName.get(name)!);
1512
+ // TYPED IDS: a branded id used by two domains (a parent's business-birth payload and the child's key
1513
+ // field, typically) would otherwise declare one extension type per file and make the barrel ambiguous.
1514
+ const brandDomains = new Map<string, number>();
1515
+ for (const mod of modules) {
1516
+ for (const brand of dartBrandNamesForModule(mod)) {
1517
+ brandDomains.set(brand, (brandDomains.get(brand) ?? 0) + 1);
1518
+ }
1519
+ }
1520
+ const sharedBrandNames: ReadonlySet<string> = new Set(
1521
+ [...brandDomains.entries()].filter(([, n]) => n > 1).map(([name]) => name).sort()
1522
+ );
1499
1523
  const hasFrameworkSymbols =
1500
1524
  frameworkAggs.length > 0 ||
1501
1525
  frameworkDirs.length > 0 ||
1502
1526
  frameworkQueries.length > 0 ||
1503
- sharedVos.length > 0;
1527
+ sharedVos.length > 0 ||
1528
+ sharedBrandNames.size > 0;
1504
1529
  const frameworkMod: DomainModule | undefined = hasFrameworkSymbols
1505
1530
  ? {
1506
1531
  name: FRAMEWORK_FILE,
@@ -1518,6 +1543,7 @@ function collectDartSharedSymbols(
1518
1543
  frameworkDirIds: new Set(frameworkDirById.keys()),
1519
1544
  frameworkQueryIds: new Set(frameworkQueryById.keys()),
1520
1545
  sharedVoNames,
1546
+ sharedBrandNames,
1521
1547
  hasFrameworkSymbols,
1522
1548
  frameworkMod,
1523
1549
  };
@@ -1535,6 +1561,7 @@ function domainDedupOpts(
1535
1561
  readonly skipDirectiveIds: Set<string>;
1536
1562
  readonly skipQueryIds: Set<string>;
1537
1563
  readonly skipValueObjectNames: Set<string>;
1564
+ readonly skipBrandNames: Set<string>;
1538
1565
  };
1539
1566
  readonly foldsFramework: boolean;
1540
1567
  } {
@@ -1562,11 +1589,17 @@ function domainDedupOpts(
1562
1589
  .filter((d) => shared.sharedVoNames.has(d.name))
1563
1590
  .map((d) => d.name)
1564
1591
  );
1592
+ const skipBrandNames = new Set(
1593
+ [...dartBrandNamesForModule(modForDart)].filter((name) =>
1594
+ shared.sharedBrandNames.has(name)
1595
+ )
1596
+ );
1565
1597
  const foldsFramework =
1566
1598
  skipAggregateIds.size > 0 ||
1567
1599
  skipDirectiveIds.size > 0 ||
1568
1600
  skipQueryIds.size > 0 ||
1569
- skipValueObjectNames.size > 0;
1601
+ skipValueObjectNames.size > 0 ||
1602
+ skipBrandNames.size > 0;
1570
1603
  return {
1571
1604
  modForDart,
1572
1605
  skips: {
@@ -1574,6 +1607,7 @@ function domainDedupOpts(
1574
1607
  skipDirectiveIds,
1575
1608
  skipQueryIds,
1576
1609
  skipValueObjectNames,
1610
+ skipBrandNames,
1577
1611
  },
1578
1612
  foldsFramework,
1579
1613
  };
@@ -2146,6 +2180,8 @@ function emitDartOperationProto(
2146
2180
  interface CompiledLawIdentities {
2147
2181
  readonly unitSet: string;
2148
2182
  readonly packageSha256: string;
2183
+ /** One requirement per application domain (CAP-666): its unit hash and its generated requirement identity. */
2184
+ readonly domainRequirements: readonly DomainUnitRequirement[];
2149
2185
  }
2150
2186
 
2151
2187
  function emitDart(
@@ -2227,7 +2263,7 @@ function emitDart(
2227
2263
  ? []
2228
2264
  : [{ module, graph, paths }];
2229
2265
  }),
2230
- { lawUnitSet: law.unitSet, lawPackageSha256: law.packageSha256 }
2266
+ { lawUnitSet: law.unitSet, lawPackageSha256: law.packageSha256, domainRequirements: law.domainRequirements }
2231
2267
  );
2232
2268
  if (applicationFrameworkCallsSource !== undefined) {
2233
2269
  const applicationFrameworkCallsPath = path.join(
@@ -2249,6 +2285,7 @@ function emitDart(
2249
2285
  FRAMEWORK_FILE,
2250
2286
  generateDartDomain(shared.frameworkMod, {
2251
2287
  additionalValueObjects: shared.frameworkMod.valueObjects ?? [],
2288
+ extraBrandNames: [...shared.sharedBrandNames],
2252
2289
  })
2253
2290
  );
2254
2291
  }
@@ -2519,6 +2556,11 @@ const dartTypesImportFor = (generatedSource: string): string => {
2519
2556
  `import 'package:nomos_types/src/foreign_ref.dart' show NmsInternalForeignRefs;`,
2520
2557
  ]
2521
2558
  : []),
2559
+ ...(generatedSource.includes("NmsInternalValueIdentity.")
2560
+ ? [
2561
+ `import 'package:nomos_types/src/vo_canonical.dart' show NmsInternalValueIdentity;`,
2562
+ ]
2563
+ : []),
2522
2564
  ];
2523
2565
  const invitationHelpers = [
2524
2566
  ...(generatedSource.includes("NmsInternalInvitations.")
@@ -2542,12 +2584,12 @@ const dartTypesImportFor = (generatedSource: string): string => {
2542
2584
  * just the floor generated code needs — a lagging pin forces tenants into dependency_overrides. */
2543
2585
  // 0.3.0: SurfacedValue<T>/SurfacedObservation/PendingConflictRow — generated Surfaced-field typing +
2544
2586
  // the pendingConflicts read rows.
2545
- const NOMOS_TYPES_DEP = "^0.13.0";
2587
+ const NOMOS_TYPES_DEP = "^0.15.0";
2546
2588
  // 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
2547
2589
  // DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
2548
2590
  // REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
2549
- const NOMOS_CLIENT_DEP = "^0.68.0";
2550
- const NOMOS_FLUTTER_DEP = "^0.67.0";
2591
+ const NOMOS_CLIENT_DEP = "^0.70.1";
2592
+ const NOMOS_FLUTTER_DEP = "^0.68.2";
2551
2593
  // DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in the Dart and Flutter
2552
2594
  // package pubspecs — a release without this bump fails the suite, so tenants never
2553
2595
  // land back in dependency_overrides (the exact co2 report this closes, third occurrence).
@@ -3370,6 +3412,7 @@ function emitDartPackages(
3370
3412
  m[1] !== "package:nomos_types/src/versioned_nomos_ref.dart" &&
3371
3413
  m[1] !== "package:nomos_types/src/foreign_ref.dart" &&
3372
3414
  m[1] !== "package:nomos_types/src/invitation.dart" &&
3415
+ m[1] !== "package:nomos_types/src/vo_canonical.dart" &&
3373
3416
  m[1] !== `${FRAMEWORK_FILE}.dart` &&
3374
3417
  !siblingTypeFiles.has(m[1]!)
3375
3418
  ) {
@@ -3390,6 +3433,7 @@ function emitDartPackages(
3390
3433
  FRAMEWORK_FILE,
3391
3434
  generateDartDomain(shared.frameworkMod, {
3392
3435
  additionalValueObjects: shared.frameworkMod.valueObjects ?? [],
3436
+ extraBrandNames: [...shared.sharedBrandNames],
3393
3437
  })
3394
3438
  );
3395
3439
  }
@@ -3680,7 +3724,7 @@ function emitDartPackages(
3680
3724
  ? []
3681
3725
  : [{ module, graph, paths }];
3682
3726
  }),
3683
- { lawUnitSet: law.unitSet, lawPackageSha256: law.packageSha256 }
3727
+ { lawUnitSet: law.unitSet, lawPackageSha256: law.packageSha256, domainRequirements: law.domainRequirements }
3684
3728
  );
3685
3729
  const workspaceAccessReadImports = new Map<string, string>();
3686
3730
  for (const module of operationProtoModules) {
@@ -4205,6 +4249,7 @@ async function main(): Promise<void> {
4205
4249
  "--preflight",
4206
4250
  "--inspect-sqlite",
4207
4251
  "--inspect-law",
4252
+ "--publishable",
4208
4253
  ]);
4209
4254
  for (const flag of argv.filter((arg) => arg.startsWith("--"))) {
4210
4255
  if (supportedFlags.has(flag)) continue;
@@ -4240,6 +4285,9 @@ async function main(): Promise<void> {
4240
4285
  // `dart analyze` on the generated Dart package after emission and fails the compile on red.
4241
4286
  const analyzeFlag =
4242
4287
  argv.includes("--analyze") || process.env.NOMOS_ANALYZE === "1";
4288
+ // A PUBLISHABLE compile (architecture/domain_release_lifecycle.md, step 1) must start from the
4289
+ // baseline exported from released custody: it refuses without a pinned `nomos.baseline.usda`.
4290
+ const publishableFlag = argv.includes("--publishable");
4243
4291
  const configArg = argv.find((a) => !a.startsWith("--")) ?? "nomos.project.ts";
4244
4292
  const configPath = path.resolve(process.cwd(), configArg);
4245
4293
  if (!existsSync(configPath)) fail(`config not found: ${configPath}`);
@@ -4324,6 +4372,39 @@ async function main(): Promise<void> {
4324
4372
  "nomos.project.ts no longer accepts 'layered': every law closure is layered, with no alternate compile lane"
4325
4373
  );
4326
4374
  }
4375
+ // ── THE RELEASED BASELINE (CAP-660) ──────────────────────────────────────────────────────────
4376
+ // `nomos.project.ts` pins the baseline by content address. Verify the bytes are the pinned ones
4377
+ // and read it now, before any identity is touched; the import and the retention proof follow.
4378
+ const baselineRefused = (error: unknown): never => {
4379
+ if (error instanceof BaselineRefusal) fail(`[${error.code}] ${error.message}`);
4380
+ throw error;
4381
+ };
4382
+ let releasedBaseline: ReleasedBaseline | undefined;
4383
+ let releasedBaselineSha256: string | undefined;
4384
+ if (cfg.baseline === undefined) {
4385
+ if (publishableFlag) {
4386
+ fail(
4387
+ "[baseline-required] a publishable compile must name the released baseline it extends: " +
4388
+ `run \`githolon baseline export --units build/${cfg.name}.units\` (or --release <id>) against the release ` +
4389
+ "you are extending, commit nomos.baseline.usda, and pin it in nomos.project.ts as " +
4390
+ "baseline: { artifact: \"./nomos.baseline.usda\", sha256: \"<printed sha256>\" }"
4391
+ );
4392
+ }
4393
+ } else {
4394
+ const baselinePath = path.resolve(cfgDir, cfg.baseline.artifact);
4395
+ if (!existsSync(baselinePath)) {
4396
+ fail(`[baseline-unreadable] pinned baseline not found: ${baselinePath}`);
4397
+ }
4398
+ const baselineText = readFileSync(baselinePath, "utf8");
4399
+ try {
4400
+ verifyBaselinePin(baselineText, cfg.baseline);
4401
+ releasedBaseline = parseBaselineUsda(baselineText);
4402
+ } catch (error) {
4403
+ if (error instanceof BaselineRefusal) baselineRefused(error);
4404
+ fail(`[baseline-unreadable] ${cfg.baseline.artifact}: ${(error as Error).message}`);
4405
+ }
4406
+ releasedBaselineSha256 = cfg.baseline.sha256;
4407
+ }
4327
4408
  let sourceTopologyDiagnostics: ReturnType<
4328
4409
  typeof assertPackageFeatureTopology
4329
4410
  > = [];
@@ -4395,6 +4476,7 @@ async function main(): Promise<void> {
4395
4476
  if (cfg.rootPart !== undefined) {
4396
4477
  applicationBirthPolicyDomains.push(APPLICATION_HOME_DOMAIN);
4397
4478
  }
4479
+ const authoredDomainModules: Mod[] = [];
4398
4480
  for (const domain of cfg.domains) {
4399
4481
  let authored: Mod = {};
4400
4482
  for (const rel of domain.modules) {
@@ -4420,10 +4502,32 @@ async function main(): Promise<void> {
4420
4502
  }
4421
4503
  }
4422
4504
  const expanded = expandBusinessBirthExports(authored, domain.key);
4505
+ authoredDomainModules.push(authored);
4423
4506
  if (directivesOf(expanded).some((dir) => dir.birthsChild === true)) {
4424
4507
  applicationBirthPolicyDomains.push(domain.key);
4425
4508
  }
4426
4509
  }
4510
+ // A workspace name is fixed at birth, and the framework enforces it at the kernel: every field-keyed
4511
+ // birth's key is write-once (aggregate.ts BIRTH_KEY_WRITE_ONCE_CODE). Statically WARN at each source line
4512
+ // that writes a key outside the birth in a way that rule will refuse, or that keys another instance
4513
+ // (CAP-804; architecture/choosing_a_birth_key.md). Advisory only — the runtime rule is the guarantee.
4514
+ {
4515
+ const birthKeyWarnings = findBirthKeyWrites(
4516
+ authoredDomainModules,
4517
+ collectPackageSources(
4518
+ cfg.domains.flatMap((domain) => domain.modules.map((rel) => path.resolve(cfgDir, rel))),
4519
+ cfgDir
4520
+ ),
4521
+ { root: cfgDir }
4522
+ );
4523
+ if (birthKeyWarnings.length > 0) {
4524
+ console.warn(
4525
+ `nomos-compile: ${birthKeyWarnings.length} birth-key write warning(s)`
4526
+ );
4527
+ for (const line of formatBirthKeyWriteWarnings(birthKeyWarnings))
4528
+ console.warn(line);
4529
+ }
4530
+ }
4427
4531
  stage("domain discovery imports");
4428
4532
  if (
4429
4533
  cfg.application?.homeGenesis !== undefined &&
@@ -5617,12 +5721,36 @@ async function main(): Promise<void> {
5617
5721
  ? parseIdentityUsda(readFileSync(lockPath, "utf8"))
5618
5722
  : undefined;
5619
5723
  const historicalFrontier = readHistoricalIdentityFrontier(lockPath);
5724
+ // Outside Git custody (a clean export, a CI checkout without history) the released baseline IS
5725
+ // the evolution frontier: it carries every released aggregate and field by sid, so a removal
5726
+ // without a declared retirement still refuses. It carries no value contracts, so it never
5727
+ // produces a false in-place refusal.
5620
5728
  const historicalLock: CompilerIdentityModel | undefined =
5621
5729
  historicalFrontier === undefined
5622
- ? undefined
5730
+ ? releasedBaseline?.continuity
5623
5731
  : parseIdentityUsda(historicalFrontier.source);
5624
5732
  const inferredRenames: { domain: string; renames: InferredRename[] }[] = [];
5625
5733
  let nextIdentity = identityRegistryFromModel(priorLock);
5734
+ if (releasedBaseline !== undefined) {
5735
+ // Verify the ledger against released custody and import what it lacks, so a clean checkout
5736
+ // allocates the released sids and wire keys rather than fresh ones, and a diverged branch
5737
+ // refuses with the coordinate named (baseline_import.ts).
5738
+ try {
5739
+ const imported = importBaseline(
5740
+ nextIdentity,
5741
+ releasedBaseline,
5742
+ cfg.domains.flatMap((domain) => [domain.key, ...(domain.name === undefined ? [] : [domain.name])])
5743
+ );
5744
+ nextIdentity = imported.registry;
5745
+ console.log(
5746
+ `baseline: ${releasedBaseline.release === "none" ? "unreleased units" : `release ${releasedBaseline.release}`} ` +
5747
+ `(law ${releasedBaseline.unitSet}) — ${imported.verified} verified, ${imported.imported.length} identit${imported.imported.length === 1 ? "y" : "ies"} ` +
5748
+ `and ${imported.allocated.length} wire allocation(s) imported${imported.relabelled.length === 0 ? "" : `, ${imported.relabelled.length} released rename(s) carried`}`
5749
+ );
5750
+ } catch (error) {
5751
+ baselineRefused(error);
5752
+ }
5753
+ }
5626
5754
  const typedLawGraphs = new Map<string, LawGraph>();
5627
5755
  // The lock is project-wide, not package-local. Several package configs commonly live beside one
5628
5756
  // application stage; compiling one must not erase the stable identity of domains owned by its siblings.
@@ -5832,6 +5960,18 @@ async function main(): Promise<void> {
5832
5960
  return;
5833
5961
  }
5834
5962
 
5963
+ // ── BASELINE RETENTION PROOF (CAP-660) ───────────────────────────────────────────
5964
+ // Every released identity still resolves to its released sid and every released wire key is still
5965
+ // the canonical allocation. Reconciliation never rewrites an allocation, so this holds by
5966
+ // construction; proving it here keeps "the candidate extends the release" a checked fact.
5967
+ if (releasedBaseline !== undefined) {
5968
+ try {
5969
+ assertBaselineRetained(nextIdentity, releasedBaseline);
5970
+ } catch (error) {
5971
+ baselineRefused(error);
5972
+ }
5973
+ }
5974
+
5835
5975
  // ── ORDINARY-COMPILE EVOLUTION GATE ──────────────────────────────────────────────
5836
5976
  // Agents reliably run `compile`; they do not reliably discover an optional migration command.
5837
5977
  // Compare the candidate against the committed identity frontier here, after stable-id
@@ -5910,6 +6050,44 @@ async function main(): Promise<void> {
5910
6050
  // giant formatting-only diff in an established lock.
5911
6051
  writeFileSync(lockPath, serializeIdentityUsda(nextLock), "utf8");
5912
6052
 
6053
+ // USDA is rendered directly from the typed graph. The final closure writer below publishes these
6054
+ // exact texts under their logical paths; no parallel layer directory or JSON reconstruction exists.
6055
+ const v3Closure = [...typedLawGraphs.values()];
6056
+ const v3DomainAssets: EmittedLawClosureAsset[] = [];
6057
+ const v3DomainRoots: string[] = [];
6058
+ const v3LawPathByNode = new Map<LawGraph["nodes"][number], string>();
6059
+ const v3CompiledGraphs = new Map<LawGraph, ReturnType<typeof compileSparseLawGraphUsda>>();
6060
+ const typedDomainIdentityByName = new Map<string, string>();
6061
+ const typedLawPathsByName = new Map<string, ReadonlyMap<string, string>>();
6062
+ for (const [domain, graph] of typedLawGraphs) {
6063
+ const safeDomain = domain.replace(/[^A-Za-z0-9._-]/g, "_");
6064
+ const logicalPath = `domains/${safeDomain}.usda`;
6065
+ const compiledLayer = compileSparseLawGraphUsda(
6066
+ graph,
6067
+ logicalPath,
6068
+ v3Closure
6069
+ );
6070
+ v3CompiledGraphs.set(graph, compiledLayer);
6071
+ const layerText = compiledLayer.text;
6072
+ const sha256 = sha256HexUtf8(layerText);
6073
+ v3DomainAssets.push({
6074
+ logicalPath,
6075
+ sha256,
6076
+ utf8: layerText,
6077
+ });
6078
+ const domainRoot = compiledLayer.paths.get(graph.domain.entity.id);
6079
+ if (domainRoot === undefined) {
6080
+ fail(`domain '${domain}' did not emit its canonical OpenUSD root path`);
6081
+ }
6082
+ v3DomainRoots.push(domainRoot);
6083
+ for (const node of graph.nodes) {
6084
+ const lawPath = compiledLayer.paths.get(node.entity.id);
6085
+ if (lawPath !== undefined) v3LawPathByNode.set(node, lawPath);
6086
+ }
6087
+ typedDomainIdentityByName.set(domain, sha256);
6088
+ typedLawPathsByName.set(domain, compiledLayer.paths);
6089
+ }
6090
+
5913
6091
  // ── THE STRUCTURAL INTERFACE (#72) — directives (payload shapes) + aggregates (read
5914
6092
  // shapes, sid-keyed), keyed by DOMAIN. The SAME object is (a) baked into the generated
5915
6093
  // clients as their requirement and (b) staged on the deploy as the workspace's offer, so
@@ -5924,7 +6102,12 @@ async function main(): Promise<void> {
5924
6102
  `compiler surface invariant: domain '${key}' has no typed USDA graph`
5925
6103
  );
5926
6104
  }
5927
- const offeredInterface = domainInterfaceFromLawGraph(graph);
6105
+ // Nested record members carry the allocation the generated codecs encode them under, which
6106
+ // follows the composed layer's canonical prim (CAP-681).
6107
+ const offeredInterface = domainInterfaceFromLawGraph(
6108
+ graph,
6109
+ typedLawPathsByName.get(mod.name)
6110
+ );
5928
6111
  const di = publicClientInterface([mod], { [key]: offeredInterface })[key]!;
5929
6112
  const graphRoutes = graph.nodes
5930
6113
  .filter((node) => node.kind === "intent" && node.public)
@@ -6017,44 +6200,6 @@ async function main(): Promise<void> {
6017
6200
  }
6018
6201
  }
6019
6202
 
6020
- // USDA is rendered directly from the typed graph. The final closure writer below publishes these
6021
- // exact texts under their logical paths; no parallel layer directory or JSON reconstruction exists.
6022
- const v3Closure = [...typedLawGraphs.values()];
6023
- const v3DomainAssets: EmittedLawClosureAsset[] = [];
6024
- const v3DomainRoots: string[] = [];
6025
- const v3LawPathByNode = new Map<LawGraph["nodes"][number], string>();
6026
- const v3CompiledGraphs = new Map<LawGraph, ReturnType<typeof compileSparseLawGraphUsda>>();
6027
- const typedDomainIdentityByName = new Map<string, string>();
6028
- const typedLawPathsByName = new Map<string, ReadonlyMap<string, string>>();
6029
- for (const [domain, graph] of typedLawGraphs) {
6030
- const safeDomain = domain.replace(/[^A-Za-z0-9._-]/g, "_");
6031
- const logicalPath = `domains/${safeDomain}.usda`;
6032
- const compiledLayer = compileSparseLawGraphUsda(
6033
- graph,
6034
- logicalPath,
6035
- v3Closure
6036
- );
6037
- v3CompiledGraphs.set(graph, compiledLayer);
6038
- const layerText = compiledLayer.text;
6039
- const sha256 = sha256HexUtf8(layerText);
6040
- v3DomainAssets.push({
6041
- logicalPath,
6042
- sha256,
6043
- utf8: layerText,
6044
- });
6045
- const domainRoot = compiledLayer.paths.get(graph.domain.entity.id);
6046
- if (domainRoot === undefined) {
6047
- fail(`domain '${domain}' did not emit its canonical OpenUSD root path`);
6048
- }
6049
- v3DomainRoots.push(domainRoot);
6050
- for (const node of graph.nodes) {
6051
- const lawPath = compiledLayer.paths.get(node.entity.id);
6052
- if (lawPath !== undefined) v3LawPathByNode.set(node, lawPath);
6053
- }
6054
- typedDomainIdentityByName.set(domain, sha256);
6055
- typedLawPathsByName.set(domain, compiledLayer.paths);
6056
- }
6057
-
6058
6203
  // Index the transient graph once. Both projections follow the same nominal edges, including
6059
6204
  // transitive cross-domain dependencies. Per-command selection also follows reverse guard edges;
6060
6205
  // the retained package command projection remains the compatibility consumer until the runtime
@@ -7729,6 +7874,7 @@ async function main(): Promise<void> {
7729
7874
  };
7730
7875
  let lawClosure = emitLawClosure(
7731
7876
  cfg.name,
7877
+ cfg.version === undefined ? undefined : lawVersionClaim(cfg.version),
7732
7878
  domainModules.map((module) => module.domain ?? module.name),
7733
7879
  runtimeDependencies,
7734
7880
  v3DomainAssets,
@@ -7890,6 +8036,7 @@ async function main(): Promise<void> {
7890
8036
  };
7891
8037
  lawClosure = emitLawClosure(
7892
8038
  cfg.name,
8039
+ cfg.version === undefined ? undefined : lawVersionClaim(cfg.version),
7893
8040
  domainModules.map((module) => module.domain ?? module.name),
7894
8041
  runtimeDependencies,
7895
8042
  v3DomainAssets,
@@ -7941,6 +8088,10 @@ async function main(): Promise<void> {
7941
8088
  // THE LAW IDENTITY (CAP-773): the unit set identity of the units below, the kernel's own definition
7942
8089
  // (law_unit_set_identity.ts). Releases, generated clients and qualification name the law by it.
7943
8090
  let unitSetIdentity: string;
8091
+ // THE PER-DOMAIN REQUIREMENTS (CAP-666): each application domain's unit hash beside the identity of its own
8092
+ // compatibility requirement, derived from the units just split and the interfaces just emitted. Written into
8093
+ // no law or package (the era rule); the generated clients, the approved model and the inventory carry them.
8094
+ let domainRequirements: readonly DomainUnitRequirement[];
7944
8095
  {
7945
8096
  const lawUnits = (() => {
7946
8097
  try {
@@ -7968,6 +8119,7 @@ async function main(): Promise<void> {
7968
8119
  );
7969
8120
  // The law identity on its own line, for publishers and gates that need it and nothing else.
7970
8121
  writeFileSync(path.join(outDir, `${cfg.name}.units.sha256`), `${unitSetIdentity}\n`, "utf8");
8122
+ domainRequirements = domainRequirementsOf(lawUnits, packageInterface, sha256HexUtf8);
7971
8123
  }
7972
8124
  const layeredRootPath = path.join(
7973
8125
  lawClosureAssetsPath,
@@ -8163,6 +8315,7 @@ async function main(): Promise<void> {
8163
8315
  const summaryTxt = [
8164
8316
  `${cfg.name} — compiled Nomos domain package`,
8165
8317
  `law (unit set): ${unitSetIdentity} # THE law identity: ${cfg.name}.units/, as releases, clients and qualification name it (${cfg.name}.units.sha256)`,
8318
+ `claimed version: ${cfg.version ?? "none"} # orders versions of law '${cfg.name}' (nomos.project.ts version); covered by the unit set identity`,
8166
8319
  `package closure: ${domainHash} # package — not what targets serve: this compiler's closure of ${cfg.name}.law.usdz (${cfg.name}.law.sha256)`,
8167
8320
  `served law: githolon domains status <ws> # each target's kernel composes the units into its own closure (it depends on that kernel's composer)`,
8168
8321
  `law transport: ${path.basename(
@@ -8186,6 +8339,11 @@ async function main(): Promise<void> {
8186
8339
  d.counts.join(", ") || "—"
8187
8340
  } sums: ${d.sums.join(", ") || "—"}`,
8188
8341
  ]),
8342
+ ...(releasedBaseline === undefined
8343
+ ? [`baseline: NONE # not a publishable build; githolon compile --publishable needs a pinned nomos.baseline.usda`]
8344
+ : [
8345
+ `baseline: ${releasedBaselineSha256} # extends ${releasedBaseline.release === "none" ? "unreleased units" : `release ${releasedBaseline.release}`}, law ${releasedBaseline.unitSet} (${releasedBaseline.source})`,
8346
+ ]),
8189
8347
  `deploy: githolon deploy <ws> --file ${cfg.name}.law.usdz`,
8190
8348
  packageAssetPath !== undefined
8191
8349
  ? `package asset: ${path.basename(
@@ -8242,6 +8400,16 @@ async function main(): Promise<void> {
8242
8400
  id: "https://github.com/Captain-App/nomos2",
8243
8401
  version: dslVersion,
8244
8402
  },
8403
+ ...(releasedBaseline === undefined || releasedBaselineSha256 === undefined
8404
+ ? {}
8405
+ : {
8406
+ baseline: {
8407
+ sha256: releasedBaselineSha256,
8408
+ source: releasedBaseline.source,
8409
+ release: releasedBaseline.release,
8410
+ unitSet: releasedBaseline.unitSet,
8411
+ },
8412
+ }),
8245
8413
  });
8246
8414
  writeFileSync(
8247
8415
  path.join(outDir, `${cfg.name}.provenance.json`),
@@ -8271,6 +8439,7 @@ async function main(): Promise<void> {
8271
8439
  packageName: cfg.name,
8272
8440
  // THE LAW the generated clients name: the unit set (CAP-773), never the package closure.
8273
8441
  domainHash: unitSetIdentity,
8442
+ domainRequirements,
8274
8443
  lawAssetName: `${cfg.name}.law.usdz`,
8275
8444
  interface: packageInterface,
8276
8445
  kernelContracts,
@@ -8860,7 +9029,7 @@ async function main(): Promise<void> {
8860
9029
  const r = emitDartPackages(
8861
9030
  presentationDomainModules,
8862
9031
  dartOut,
8863
- { unitSet: unitSetIdentity, packageSha256: domainHash },
9032
+ { unitSet: unitSetIdentity, packageSha256: domainHash, domainRequirements },
8864
9033
  typedLawGraphs,
8865
9034
  typedLawPathsByName,
8866
9035
  packageInterface,
@@ -8888,7 +9057,7 @@ async function main(): Promise<void> {
8888
9057
  dartFiles = emitDart(
8889
9058
  presentationDomainModules,
8890
9059
  dartOut,
8891
- { unitSet: unitSetIdentity, packageSha256: domainHash },
9060
+ { unitSet: unitSetIdentity, packageSha256: domainHash, domainRequirements },
8892
9061
  typedLawGraphs,
8893
9062
  typedLawPathsByName,
8894
9063
  packageInterface,
@@ -9272,6 +9441,7 @@ async function main(): Promise<void> {
9272
9441
  const rel = (p: string) => path.relative(process.cwd(), p);
9273
9442
  console.log(`nomos-compile: ${cfg.name}`);
9274
9443
  console.log(` law unit set ${unitSetIdentity} (${rel(path.join(outDir, `${cfg.name}.units`))})`);
9444
+ if (cfg.version !== undefined) console.log(` version ${cfg.name}@${cfg.version} (claimed; covered by the unit set identity)`);
9275
9445
  console.log(
9276
9446
  ` package ${domainHash} — package — not what targets serve (${rel(lawUsdzPath)}, ${lawUsdz.byteLength} transport bytes)`
9277
9447
  );
@@ -0,0 +1,24 @@
1
+ // NOMOS — The wire identity of an operation field under OpenUSD composition.
2
+ //
3
+ // Node-free on purpose: the compatibility graph reaches this from the law bundle (the release law's
4
+ // plans bundle for a neutral platform), where `codegen_call_proto.ts` cannot live (it mints stable
5
+ // ids through `node:crypto`). Both the generated codecs and the compatibility requirement derive a
6
+ // nested member's allocation here, so the two can never disagree (CAP-681).
7
+
8
+ /**
9
+ * OpenUSD composition may share structurally identical anonymous records between
10
+ * operation contracts. `compileSparseLawGraphUsda` records that sharing by
11
+ * redirecting each duplicate field path to the canonical field prim. The wire
12
+ * identity must follow that composed path too; using the duplicate graph-local
13
+ * SID makes generated clients speak a different Protobuf schema from the law the
14
+ * kernel actually installs.
15
+ */
16
+ export function composedWireIdentity(
17
+ paths: ReadonlyMap<string, string>,
18
+ entity: { readonly id: string; readonly sid: string; readonly wireKey?: string },
19
+ ): string {
20
+ if (entity.wireKey !== undefined) return entity.wireKey;
21
+ const path = paths.get(entity.id);
22
+ const canonical = path?.match(/\/(?:Fields\/f_|Variants\/v_)([0-9a-f]{16})$/i)?.[1];
23
+ return canonical ?? entity.sid;
24
+ }
package/src/directive.ts CHANGED
@@ -536,6 +536,12 @@ export interface Directive<P = unknown> extends AuthenticatedMember {
536
536
  * `attestationAuthorizedGate`/`.attestationAuthorized()` naming convention; the declaration is the noun.)
537
537
  */
538
538
  readonly birthsChild?: boolean;
539
+ /** THE TYPED BIRTH KEY of a business birth (`birth(Aggregate, spec)`): the key's typed-id family, brand and
540
+ * the name's namespace. Lowered to the `nomos:birthKeyType` / `nomos:birthKeyBrand` /
541
+ * `nomos:birthKeyNamespace` era keys on the birthing intent; the kernel then requires every carried recipe
542
+ * of this intent to carry `nomos:birthKey`, checks its shape, and re-derives the child's name from it.
543
+ * Absent on every other directive (and on all historical law), so those chains replay unchanged. */
544
+ readonly birthKey?: import("./births.js").BirthKeyDeclaration;
539
545
  /** Additional ordinary offers which the born workspace's typed law requires in genesis.
540
546
  * This is compiler/runtime metadata attached only to a birthing directive. It contains no
541
547
  * payload or fallback value: the kernel checks that the re-derived recipe invokes each named