@githolon/dsl 0.113.4 → 0.115.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 (103) hide show
  1. package/assets/operation-contract.wasm +0 -0
  2. package/compile_package.mjs +23 -3
  3. package/compile_proof.d.mts +7 -0
  4. package/compile_proof.mjs +38 -3
  5. package/dart/CHANGELOG.md +60 -0
  6. package/dart/lib/src/application_framework_calls.dart +16 -6
  7. package/dart/lib/src/console_enrolment.dart +7 -0
  8. package/dart/lib/src/console_http_io.dart +22 -15
  9. package/dart/lib/src/dev_cloud.dart +62 -0
  10. package/dart/lib/src/generated_call.dart +14 -3
  11. package/dart/lib/src/nms_generated_runtime.dart +7 -0
  12. package/dart/lib/src/node_runner_transport.dart +77 -8
  13. package/dart/lib/src/nomos_trace.dart +107 -0
  14. package/dart/lib/src/operation_receipts.dart +74 -7
  15. package/dart/lib/src/reception/dart_reception.dart +174 -17
  16. package/dart/lib/src/rows_watch.dart +390 -0
  17. package/dart/lib/src/runner/node-entry.mjs +7678 -2860
  18. package/dart/lib/src/runtime_v2_transport.dart +3 -0
  19. package/dart/lib/src/sync_status_projection.dart +94 -1
  20. package/dart/lib/src/sync_status_reducer.dart +5 -2
  21. package/dart/lib/src/test_harness.dart +107 -41
  22. package/dart/lib/src/testing/test_peer.dart +107 -99
  23. package/dart/lib/src/testing/test_peer_host.dart +305 -0
  24. package/dart/lib/src/typed_watch_registry.dart +26 -3
  25. package/dart/pubspec.lock +1 -1
  26. package/dart/pubspec.yaml +2 -2
  27. package/dart/test/background_model_adoption_test.dart +1 -0
  28. package/dart/test/console_enrolment_test.dart +47 -0
  29. package/dart/test/custodian_warming_test.dart +105 -0
  30. package/dart/test/fixtures/offer_watch_order_peer.dart +74 -0
  31. package/dart/test/kernel_author_after_read_test.dart +6 -3
  32. package/dart/test/kernel_fence_test.dart +4 -1
  33. package/dart/test/model_adoption_test.dart +66 -0
  34. package/dart/test/nomos_trace_test.dart +91 -0
  35. package/dart/test/own_offer_watch_order_test.dart +90 -0
  36. package/dart/test/receiving_group_open_test.dart +4 -8
  37. package/dart/test/replica_readiness_test.dart +14 -4
  38. package/dart/test/rows_watch_test.dart +331 -0
  39. package/dart/test/runtime_projections_test.dart +41 -0
  40. package/dart/test/test_peer_host_selection_test.dart +49 -0
  41. package/dart_types/CHANGELOG.md +39 -0
  42. package/dart_types/lib/src/approved_model.dart +100 -6
  43. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +1063 -3
  44. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +89 -0
  45. package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pb.dart +14 -0
  46. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +2272 -130
  47. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pbenum.dart +65 -0
  48. package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +118 -2
  49. package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pb.dart +278 -9
  50. package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pb.dart +7 -85
  51. package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pb.dart +16 -0
  52. package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pb.dart +35 -0
  53. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +43 -2
  54. package/dart_types/pubspec.yaml +1 -1
  55. package/package.json +6 -2
  56. package/src/aggregate.ts +93 -0
  57. package/src/anonymous_value_interning.ts +205 -0
  58. package/src/baseline.ts +27 -0
  59. package/src/baseline_export.ts +432 -0
  60. package/src/baseline_import.ts +312 -0
  61. package/src/baseline_usda.ts +186 -0
  62. package/src/birth_key_rule.ts +472 -0
  63. package/src/births.ts +207 -24
  64. package/src/build_provenance.ts +17 -2
  65. package/src/business_birth.ts +50 -4
  66. package/src/check_package_main.ts +50 -6
  67. package/src/codegen_call_proto.ts +5 -18
  68. package/src/codegen_dart.ts +95 -3
  69. package/src/codegen_dart_client.ts +73 -15
  70. package/src/codegen_dart_facade.ts +58 -35
  71. package/src/codegen_dart_framework_calls.ts +32 -10
  72. package/src/codegen_dart_scenario.ts +7 -0
  73. package/src/codegen_proof.ts +5 -2
  74. package/src/codegen_result_proto.ts +8 -6
  75. package/src/codegen_result_usda.ts +4 -0
  76. package/src/codegen_ts.ts +135 -34
  77. package/src/compatibility_graph.ts +41 -4
  78. package/src/compatibility_usda.ts +11 -1
  79. package/src/compile_package_main.ts +272 -105
  80. package/src/composed_wire_identity.ts +24 -0
  81. package/src/directive.ts +6 -0
  82. package/src/domain_requirements.ts +75 -0
  83. package/src/engine_entry.ts +4 -48
  84. package/src/engine_javascript.ts +12 -6
  85. package/src/fields.ts +109 -0
  86. package/src/identity_usda.ts +77 -51
  87. package/src/index.ts +25 -1
  88. package/src/intent.ts +5 -0
  89. package/src/law_graph.ts +142 -1
  90. package/src/law_graph_usda.ts +25 -141
  91. package/src/law_layer_prims.ts +199 -0
  92. package/src/law_unit_selection.ts +45 -0
  93. package/src/law_unit_set_identity.ts +44 -0
  94. package/src/law_version.ts +107 -0
  95. package/src/package_config.ts +44 -0
  96. package/src/package_dependencies.ts +8 -0
  97. package/src/query/compile.ts +0 -0
  98. package/src/release_inspection.ts +24 -0
  99. package/src/release_publication.ts +298 -0
  100. package/src/value_object.ts +6 -3
  101. package/src/wire.ts +2 -0
  102. package/src/wire_paths.ts +17 -0
  103. package/src/workspace_routing.ts +1 -1
@@ -1,3 +1,4 @@
1
+ import { collectPackageSources, findBirthKeyWrites, formatBirthKeyWriteWarnings } from "./birth_key_rule.js";
1
2
  import type { Directive } from "./directive.js";
2
3
  // NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
3
4
  // remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
@@ -46,6 +47,7 @@ import type { Directive } from "./directive.js";
46
47
  import { execFile, execFileSync } from "node:child_process";
47
48
  import { promisify } from "node:util";
48
49
  import { checkOperationContracts, deriveInterfaceOffer } from "./operation_contract_check.js";
50
+ import { lawVersionClaim, type LawVersionClaim } from "./law_version.js";
49
51
  import { compilerIntermediateCacheRoot, compilerSourceName, compilerSourceRoot } from "./compiler_cache.js";
50
52
  import {
51
53
  APPLICATION_CONTRACT_MODULE,
@@ -80,6 +82,7 @@ import ts from "typescript";
80
82
  import type { AggregateHandle } from "./aggregate.js";
81
83
  import {
82
84
  generateDartDomain,
85
+ dartBrandNamesForModule,
83
86
  generateDartContracts,
84
87
  generateDartActivityRegistry,
85
88
  collectActivityDescriptors,
@@ -97,6 +100,8 @@ import {
97
100
  } from "./evolution_files.js";
98
101
  import { assertEvolutionModuleExports } from "./evolution.js";
99
102
  import { readHistoricalIdentityFrontier } from "./identity_frontier.js";
103
+ import { parseBaselineUsda, type ReleasedBaseline } from "./baseline_usda.js";
104
+ import { BaselineRefusal, assertBaselineRetained, importBaseline, verifyBaselinePin } from "./baseline_import.js";
100
105
  import {
101
106
  dartBridgeClientOperationDirectiveIds,
102
107
  generateDartDomainClient,
@@ -251,10 +256,6 @@ import {
251
256
  tsClientFactoryName,
252
257
  recursiveLazyTypes,
253
258
  } from "./codegen_ts.js";
254
- import {
255
- generateOpenApiFragment,
256
- mergeOpenApiFragments,
257
- } from "./codegen_openapi.js";
258
259
  import {
259
260
  domainInterfaceFromLawGraph,
260
261
  publicClientInterface,
@@ -269,6 +270,8 @@ import {
269
270
  packageRoutesLayerUsda,
270
271
  splitLawUnits,
271
272
  } from "./law_units.js";
273
+ import { lawUnitSetIdentityWith } from "./law_unit_set_identity.js";
274
+ import { domainRequirementsOf, type DomainUnitRequirement } from "./domain_requirements.js";
272
275
  import { checkDomainModules, formatFindings, hasRefusal } from "./law_check.js";
273
276
  import {
274
277
  currentEvolveLaw,
@@ -627,6 +630,7 @@ const compareLogicalPath = (left: string, right: string): number =>
627
630
  * inventories the USDZ/Git tree and re-hashes every logically named layer. */
628
631
  function emitLawClosure(
629
632
  packageName: string,
633
+ claimedVersion: LawVersionClaim | undefined,
630
634
  exportedDomains: readonly string[],
631
635
  runtimeDependencies: readonly RuntimePackageDependency[],
632
636
  domainAssets: readonly EmittedLawClosureAsset[],
@@ -673,6 +677,7 @@ function emitLawClosure(
673
677
  "package.usda",
674
678
  packageContractUsda({
675
679
  packageName,
680
+ ...(claimedVersion === undefined ? {} : { claimedVersion }),
676
681
  exportedDomains,
677
682
  runtimeDependencies,
678
683
  })
@@ -1268,6 +1273,7 @@ function closeComposedBusinessBirthPresentation(
1268
1273
  aggregateId?: unknown;
1269
1274
  rootId?: unknown;
1270
1275
  namespace?: unknown;
1276
+ keyMode?: unknown;
1271
1277
  keyField?: unknown;
1272
1278
  };
1273
1279
  type BirthDirective = DomainModule["directives"][number] & {
@@ -1281,14 +1287,15 @@ function closeComposedBusinessBirthPresentation(
1281
1287
  typeof birth.aggregateId !== "string" ||
1282
1288
  typeof birth.rootId !== "string" ||
1283
1289
  typeof birth.namespace !== "string" ||
1284
- typeof birth.keyField !== "string"
1290
+ (typeof birth.keyField !== "string" && birth.keyMode !== "minted")
1285
1291
  )
1286
1292
  return undefined;
1287
1293
  return [
1288
1294
  birth.aggregateId,
1289
1295
  birth.rootId,
1290
1296
  birth.namespace,
1291
- birth.keyField,
1297
+ // A minted birth has no key field; its identity here is the minted mode itself.
1298
+ typeof birth.keyField === "string" ? birth.keyField : "\u0001minted",
1292
1299
  ].join("\u0000");
1293
1300
  };
1294
1301
 
@@ -1338,6 +1345,8 @@ interface DartSharedSymbols {
1338
1345
  readonly frameworkDirIds: ReadonlySet<string>;
1339
1346
  readonly frameworkQueryIds: ReadonlySet<string>;
1340
1347
  readonly sharedVoNames: ReadonlySet<string>;
1348
+ /** Branded-id extension types used by ≥2 domains — declared ONCE in the framework file. */
1349
+ readonly sharedBrandNames: ReadonlySet<string>;
1341
1350
  readonly hasFrameworkSymbols: boolean;
1342
1351
  /** The synthetic module whose emit IS `nomos_framework.dart` — undefined when nothing is shared. */
1343
1352
  readonly frameworkMod: DomainModule | undefined;
@@ -1499,11 +1508,23 @@ function collectDartSharedSymbols(
1499
1508
  const sharedVos = [...sharedVoNames]
1500
1509
  .sort()
1501
1510
  .map((name) => voDescriptorByName.get(name)!);
1511
+ // TYPED IDS: a branded id used by two domains (a parent's business-birth payload and the child's key
1512
+ // field, typically) would otherwise declare one extension type per file and make the barrel ambiguous.
1513
+ const brandDomains = new Map<string, number>();
1514
+ for (const mod of modules) {
1515
+ for (const brand of dartBrandNamesForModule(mod)) {
1516
+ brandDomains.set(brand, (brandDomains.get(brand) ?? 0) + 1);
1517
+ }
1518
+ }
1519
+ const sharedBrandNames: ReadonlySet<string> = new Set(
1520
+ [...brandDomains.entries()].filter(([, n]) => n > 1).map(([name]) => name).sort()
1521
+ );
1502
1522
  const hasFrameworkSymbols =
1503
1523
  frameworkAggs.length > 0 ||
1504
1524
  frameworkDirs.length > 0 ||
1505
1525
  frameworkQueries.length > 0 ||
1506
- sharedVos.length > 0;
1526
+ sharedVos.length > 0 ||
1527
+ sharedBrandNames.size > 0;
1507
1528
  const frameworkMod: DomainModule | undefined = hasFrameworkSymbols
1508
1529
  ? {
1509
1530
  name: FRAMEWORK_FILE,
@@ -1521,6 +1542,7 @@ function collectDartSharedSymbols(
1521
1542
  frameworkDirIds: new Set(frameworkDirById.keys()),
1522
1543
  frameworkQueryIds: new Set(frameworkQueryById.keys()),
1523
1544
  sharedVoNames,
1545
+ sharedBrandNames,
1524
1546
  hasFrameworkSymbols,
1525
1547
  frameworkMod,
1526
1548
  };
@@ -1538,6 +1560,7 @@ function domainDedupOpts(
1538
1560
  readonly skipDirectiveIds: Set<string>;
1539
1561
  readonly skipQueryIds: Set<string>;
1540
1562
  readonly skipValueObjectNames: Set<string>;
1563
+ readonly skipBrandNames: Set<string>;
1541
1564
  };
1542
1565
  readonly foldsFramework: boolean;
1543
1566
  } {
@@ -1565,11 +1588,17 @@ function domainDedupOpts(
1565
1588
  .filter((d) => shared.sharedVoNames.has(d.name))
1566
1589
  .map((d) => d.name)
1567
1590
  );
1591
+ const skipBrandNames = new Set(
1592
+ [...dartBrandNamesForModule(modForDart)].filter((name) =>
1593
+ shared.sharedBrandNames.has(name)
1594
+ )
1595
+ );
1568
1596
  const foldsFramework =
1569
1597
  skipAggregateIds.size > 0 ||
1570
1598
  skipDirectiveIds.size > 0 ||
1571
1599
  skipQueryIds.size > 0 ||
1572
- skipValueObjectNames.size > 0;
1600
+ skipValueObjectNames.size > 0 ||
1601
+ skipBrandNames.size > 0;
1573
1602
  return {
1574
1603
  modForDart,
1575
1604
  skips: {
@@ -1577,6 +1606,7 @@ function domainDedupOpts(
1577
1606
  skipDirectiveIds,
1578
1607
  skipQueryIds,
1579
1608
  skipValueObjectNames,
1609
+ skipBrandNames,
1580
1610
  },
1581
1611
  foldsFramework,
1582
1612
  };
@@ -2145,10 +2175,18 @@ function emitDartOperationProto(
2145
2175
  return [protoPath, generatedBinding];
2146
2176
  }
2147
2177
 
2178
+ /** The compiled law's identities (CAP-773): the unit set is the law; the package is the `.law.usdz` bytes. */
2179
+ interface CompiledLawIdentities {
2180
+ readonly unitSet: string;
2181
+ readonly packageSha256: string;
2182
+ /** One requirement per application domain (CAP-666): its unit hash and its generated requirement identity. */
2183
+ readonly domainRequirements: readonly DomainUnitRequirement[];
2184
+ }
2185
+
2148
2186
  function emitDart(
2149
2187
  modules: readonly DomainModule[],
2150
2188
  dartOut: string,
2151
- domainHash: string,
2189
+ law: CompiledLawIdentities,
2152
2190
  lawGraphs: ReadonlyMap<string, LawGraph>,
2153
2191
  lawPaths: ReadonlyMap<string, ReadonlyMap<string, string>>,
2154
2192
  packageInterface: Record<string, DomainInterface> = {},
@@ -2224,7 +2262,7 @@ function emitDart(
2224
2262
  ? []
2225
2263
  : [{ module, graph, paths }];
2226
2264
  }),
2227
- { lawSha256: domainHash }
2265
+ { lawUnitSet: law.unitSet, lawPackageSha256: law.packageSha256, domainRequirements: law.domainRequirements }
2228
2266
  );
2229
2267
  if (applicationFrameworkCallsSource !== undefined) {
2230
2268
  const applicationFrameworkCallsPath = path.join(
@@ -2246,6 +2284,7 @@ function emitDart(
2246
2284
  FRAMEWORK_FILE,
2247
2285
  generateDartDomain(shared.frameworkMod, {
2248
2286
  additionalValueObjects: shared.frameworkMod.valueObjects ?? [],
2287
+ extraBrandNames: [...shared.sharedBrandNames],
2249
2288
  })
2250
2289
  );
2251
2290
  }
@@ -2364,7 +2403,7 @@ function emitDart(
2364
2403
  writeGenerated(mod.name, src);
2365
2404
 
2366
2405
  const clientSrc = generateDartDomainClient(modForDart, {
2367
- domainHash,
2406
+ domainHash: law.unitSet,
2368
2407
  callUsda: {
2369
2408
  graph: lawGraphs.get(mod.name)!,
2370
2409
  paths: lawPaths.get(mod.name)!,
@@ -2539,12 +2578,12 @@ const dartTypesImportFor = (generatedSource: string): string => {
2539
2578
  * just the floor generated code needs — a lagging pin forces tenants into dependency_overrides. */
2540
2579
  // 0.3.0: SurfacedValue<T>/SurfacedObservation/PendingConflictRow — generated Surfaced-field typing +
2541
2580
  // the pendingConflicts read rows.
2542
- const NOMOS_TYPES_DEP = "^0.12.0";
2581
+ const NOMOS_TYPES_DEP = "^0.14.0";
2543
2582
  // 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
2544
2583
  // DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
2545
2584
  // REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
2546
- const NOMOS_CLIENT_DEP = "^0.67.2";
2547
- const NOMOS_FLUTTER_DEP = "^0.66.4";
2585
+ const NOMOS_CLIENT_DEP = "^0.69.0";
2586
+ const NOMOS_FLUTTER_DEP = "^0.68.0";
2548
2587
  // DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in the Dart and Flutter
2549
2588
  // package pubspecs — a release without this bump fails the suite, so tenants never
2550
2589
  // land back in dependency_overrides (the exact co2 report this closes, third occurrence).
@@ -3282,7 +3321,7 @@ function removeGeneratedClientLawAssets(pkgDir: string): boolean {
3282
3321
  function emitDartPackages(
3283
3322
  modules: readonly DomainModule[],
3284
3323
  dartOut: string,
3285
- domainHash: string,
3324
+ law: CompiledLawIdentities,
3286
3325
  lawGraphs: ReadonlyMap<string, LawGraph>,
3287
3326
  lawPaths: ReadonlyMap<string, ReadonlyMap<string, string>>,
3288
3327
  packageInterface: Record<string, DomainInterface>,
@@ -3387,6 +3426,7 @@ function emitDartPackages(
3387
3426
  FRAMEWORK_FILE,
3388
3427
  generateDartDomain(shared.frameworkMod, {
3389
3428
  additionalValueObjects: shared.frameworkMod.valueObjects ?? [],
3429
+ extraBrandNames: [...shared.sharedBrandNames],
3390
3430
  })
3391
3431
  );
3392
3432
  }
@@ -3677,7 +3717,7 @@ function emitDartPackages(
3677
3717
  ? []
3678
3718
  : [{ module, graph, paths }];
3679
3719
  }),
3680
- { lawSha256: domainHash }
3720
+ { lawUnitSet: law.unitSet, lawPackageSha256: law.packageSha256, domainRequirements: law.domainRequirements }
3681
3721
  );
3682
3722
  const workspaceAccessReadImports = new Map<string, string>();
3683
3723
  for (const module of operationProtoModules) {
@@ -3727,7 +3767,7 @@ function emitDartPackages(
3727
3767
  const workspaceAccessReadImportUri = workspaceAccessReadImports.get(mod.name);
3728
3768
  const makeClient = (imports: readonly string[]) =>
3729
3769
  generateDartDomainClient(dd.modForDart, {
3730
- domainHash,
3770
+ domainHash: law.unitSet,
3731
3771
  callUsda: {
3732
3772
  graph: lawGraphs.get(mod.name)!,
3733
3773
  paths: lawPaths.get(mod.name)!,
@@ -3818,7 +3858,7 @@ function emitDartPackages(
3818
3858
  invitationPolicies: (
3819
3859
  dd.modForDart.applicationWorkspaceSharing ?? []
3820
3860
  ).map((policy) => ({
3821
- domainHash,
3861
+ domainHash: law.unitSet,
3822
3862
  query: policy.invitationLaw.pendingByRecipient.id,
3823
3863
  redeem: policy.invitationLaw.redeem.id,
3824
3864
  personField: policy.person,
@@ -3858,6 +3898,7 @@ function emitDartPackages(
3858
3898
  `export 'package:${packages.types}/${genRel}/${businessSharedFile}';`,
3859
3899
  `export 'package:nomos_client/src/workspace_open_progress.dart' show ApplicationWorkspaceOpenProgress;`,
3860
3900
  `export 'package:nomos_client/src/write_progress.dart' show NomosWrite, NomosWriteProgress, NomosWritePhase;`,
3901
+ `export 'package:nomos_client/src/rows_watch.dart' show NomosRows, NomosRowProjection, NomosRowsProjectionStream;`,
3861
3902
  `export 'package:nomos_types/nomos_types.dart' show CustodyError, CustodyPhase, CustodySource, NomosRecommendedAction;`,
3862
3903
  ...tenantTypesExportKeys
3863
3904
  .map((file) => {
@@ -4201,6 +4242,7 @@ async function main(): Promise<void> {
4201
4242
  "--preflight",
4202
4243
  "--inspect-sqlite",
4203
4244
  "--inspect-law",
4245
+ "--publishable",
4204
4246
  ]);
4205
4247
  for (const flag of argv.filter((arg) => arg.startsWith("--"))) {
4206
4248
  if (supportedFlags.has(flag)) continue;
@@ -4236,6 +4278,9 @@ async function main(): Promise<void> {
4236
4278
  // `dart analyze` on the generated Dart package after emission and fails the compile on red.
4237
4279
  const analyzeFlag =
4238
4280
  argv.includes("--analyze") || process.env.NOMOS_ANALYZE === "1";
4281
+ // A PUBLISHABLE compile (architecture/domain_release_lifecycle.md, step 1) must start from the
4282
+ // baseline exported from released custody: it refuses without a pinned `nomos.baseline.usda`.
4283
+ const publishableFlag = argv.includes("--publishable");
4239
4284
  const configArg = argv.find((a) => !a.startsWith("--")) ?? "nomos.project.ts";
4240
4285
  const configPath = path.resolve(process.cwd(), configArg);
4241
4286
  if (!existsSync(configPath)) fail(`config not found: ${configPath}`);
@@ -4320,6 +4365,39 @@ async function main(): Promise<void> {
4320
4365
  "nomos.project.ts no longer accepts 'layered': every law closure is layered, with no alternate compile lane"
4321
4366
  );
4322
4367
  }
4368
+ // ── THE RELEASED BASELINE (CAP-660) ──────────────────────────────────────────────────────────
4369
+ // `nomos.project.ts` pins the baseline by content address. Verify the bytes are the pinned ones
4370
+ // and read it now, before any identity is touched; the import and the retention proof follow.
4371
+ const baselineRefused = (error: unknown): never => {
4372
+ if (error instanceof BaselineRefusal) fail(`[${error.code}] ${error.message}`);
4373
+ throw error;
4374
+ };
4375
+ let releasedBaseline: ReleasedBaseline | undefined;
4376
+ let releasedBaselineSha256: string | undefined;
4377
+ if (cfg.baseline === undefined) {
4378
+ if (publishableFlag) {
4379
+ fail(
4380
+ "[baseline-required] a publishable compile must name the released baseline it extends: " +
4381
+ `run \`githolon baseline export --units build/${cfg.name}.units\` (or --release <id>) against the release ` +
4382
+ "you are extending, commit nomos.baseline.usda, and pin it in nomos.project.ts as " +
4383
+ "baseline: { artifact: \"./nomos.baseline.usda\", sha256: \"<printed sha256>\" }"
4384
+ );
4385
+ }
4386
+ } else {
4387
+ const baselinePath = path.resolve(cfgDir, cfg.baseline.artifact);
4388
+ if (!existsSync(baselinePath)) {
4389
+ fail(`[baseline-unreadable] pinned baseline not found: ${baselinePath}`);
4390
+ }
4391
+ const baselineText = readFileSync(baselinePath, "utf8");
4392
+ try {
4393
+ verifyBaselinePin(baselineText, cfg.baseline);
4394
+ releasedBaseline = parseBaselineUsda(baselineText);
4395
+ } catch (error) {
4396
+ if (error instanceof BaselineRefusal) baselineRefused(error);
4397
+ fail(`[baseline-unreadable] ${cfg.baseline.artifact}: ${(error as Error).message}`);
4398
+ }
4399
+ releasedBaselineSha256 = cfg.baseline.sha256;
4400
+ }
4323
4401
  let sourceTopologyDiagnostics: ReturnType<
4324
4402
  typeof assertPackageFeatureTopology
4325
4403
  > = [];
@@ -4391,6 +4469,7 @@ async function main(): Promise<void> {
4391
4469
  if (cfg.rootPart !== undefined) {
4392
4470
  applicationBirthPolicyDomains.push(APPLICATION_HOME_DOMAIN);
4393
4471
  }
4472
+ const authoredDomainModules: Mod[] = [];
4394
4473
  for (const domain of cfg.domains) {
4395
4474
  let authored: Mod = {};
4396
4475
  for (const rel of domain.modules) {
@@ -4416,10 +4495,32 @@ async function main(): Promise<void> {
4416
4495
  }
4417
4496
  }
4418
4497
  const expanded = expandBusinessBirthExports(authored, domain.key);
4498
+ authoredDomainModules.push(authored);
4419
4499
  if (directivesOf(expanded).some((dir) => dir.birthsChild === true)) {
4420
4500
  applicationBirthPolicyDomains.push(domain.key);
4421
4501
  }
4422
4502
  }
4503
+ // A workspace name is fixed at birth, and the framework enforces it at the kernel: every field-keyed
4504
+ // birth's key is write-once (aggregate.ts BIRTH_KEY_WRITE_ONCE_CODE). Statically WARN at each source line
4505
+ // that writes a key outside the birth in a way that rule will refuse, or that keys another instance
4506
+ // (CAP-804; architecture/choosing_a_birth_key.md). Advisory only — the runtime rule is the guarantee.
4507
+ {
4508
+ const birthKeyWarnings = findBirthKeyWrites(
4509
+ authoredDomainModules,
4510
+ collectPackageSources(
4511
+ cfg.domains.flatMap((domain) => domain.modules.map((rel) => path.resolve(cfgDir, rel))),
4512
+ cfgDir
4513
+ ),
4514
+ { root: cfgDir }
4515
+ );
4516
+ if (birthKeyWarnings.length > 0) {
4517
+ console.warn(
4518
+ `nomos-compile: ${birthKeyWarnings.length} birth-key write warning(s)`
4519
+ );
4520
+ for (const line of formatBirthKeyWriteWarnings(birthKeyWarnings))
4521
+ console.warn(line);
4522
+ }
4523
+ }
4423
4524
  stage("domain discovery imports");
4424
4525
  if (
4425
4526
  cfg.application?.homeGenesis !== undefined &&
@@ -5539,15 +5640,6 @@ async function main(): Promise<void> {
5539
5640
  finding.directiveId?.startsWith("nomos_internal_") === true ||
5540
5641
  (finding.directiveId !== undefined &&
5541
5642
  frameworkComposedDirectives.has(`${finding.domain}\0${finding.directiveId}`));
5542
- // Self-description is a compiler-owned presentation projection, not executable law. Build it
5543
- // once from the same composed modules and place it only in the separately routed describe plan;
5544
- // exact offer/read plans must not carry OpenAPI generation and Zod JSON-Schema machinery.
5545
- const runtimeOpenApiDocument = mergeOpenApiFragments(
5546
- domainModules.map((module) =>
5547
- generateOpenApiFragment(module, { includeFrameworkRoutes: true })
5548
- ),
5549
- { title: `${cfg.name} (nomos package)` }
5550
- );
5551
5643
 
5552
5644
  if (preflightFlag) {
5553
5645
  const businessFindings = authorTimeFindings.filter(
@@ -5622,12 +5714,36 @@ async function main(): Promise<void> {
5622
5714
  ? parseIdentityUsda(readFileSync(lockPath, "utf8"))
5623
5715
  : undefined;
5624
5716
  const historicalFrontier = readHistoricalIdentityFrontier(lockPath);
5717
+ // Outside Git custody (a clean export, a CI checkout without history) the released baseline IS
5718
+ // the evolution frontier: it carries every released aggregate and field by sid, so a removal
5719
+ // without a declared retirement still refuses. It carries no value contracts, so it never
5720
+ // produces a false in-place refusal.
5625
5721
  const historicalLock: CompilerIdentityModel | undefined =
5626
5722
  historicalFrontier === undefined
5627
- ? undefined
5723
+ ? releasedBaseline?.continuity
5628
5724
  : parseIdentityUsda(historicalFrontier.source);
5629
5725
  const inferredRenames: { domain: string; renames: InferredRename[] }[] = [];
5630
5726
  let nextIdentity = identityRegistryFromModel(priorLock);
5727
+ if (releasedBaseline !== undefined) {
5728
+ // Verify the ledger against released custody and import what it lacks, so a clean checkout
5729
+ // allocates the released sids and wire keys rather than fresh ones, and a diverged branch
5730
+ // refuses with the coordinate named (baseline_import.ts).
5731
+ try {
5732
+ const imported = importBaseline(
5733
+ nextIdentity,
5734
+ releasedBaseline,
5735
+ cfg.domains.flatMap((domain) => [domain.key, ...(domain.name === undefined ? [] : [domain.name])])
5736
+ );
5737
+ nextIdentity = imported.registry;
5738
+ console.log(
5739
+ `baseline: ${releasedBaseline.release === "none" ? "unreleased units" : `release ${releasedBaseline.release}`} ` +
5740
+ `(law ${releasedBaseline.unitSet}) — ${imported.verified} verified, ${imported.imported.length} identit${imported.imported.length === 1 ? "y" : "ies"} ` +
5741
+ `and ${imported.allocated.length} wire allocation(s) imported${imported.relabelled.length === 0 ? "" : `, ${imported.relabelled.length} released rename(s) carried`}`
5742
+ );
5743
+ } catch (error) {
5744
+ baselineRefused(error);
5745
+ }
5746
+ }
5631
5747
  const typedLawGraphs = new Map<string, LawGraph>();
5632
5748
  // The lock is project-wide, not package-local. Several package configs commonly live beside one
5633
5749
  // application stage; compiling one must not erase the stable identity of domains owned by its siblings.
@@ -5837,6 +5953,18 @@ async function main(): Promise<void> {
5837
5953
  return;
5838
5954
  }
5839
5955
 
5956
+ // ── BASELINE RETENTION PROOF (CAP-660) ───────────────────────────────────────────
5957
+ // Every released identity still resolves to its released sid and every released wire key is still
5958
+ // the canonical allocation. Reconciliation never rewrites an allocation, so this holds by
5959
+ // construction; proving it here keeps "the candidate extends the release" a checked fact.
5960
+ if (releasedBaseline !== undefined) {
5961
+ try {
5962
+ assertBaselineRetained(nextIdentity, releasedBaseline);
5963
+ } catch (error) {
5964
+ baselineRefused(error);
5965
+ }
5966
+ }
5967
+
5840
5968
  // ── ORDINARY-COMPILE EVOLUTION GATE ──────────────────────────────────────────────
5841
5969
  // Agents reliably run `compile`; they do not reliably discover an optional migration command.
5842
5970
  // Compare the candidate against the committed identity frontier here, after stable-id
@@ -5915,6 +6043,44 @@ async function main(): Promise<void> {
5915
6043
  // giant formatting-only diff in an established lock.
5916
6044
  writeFileSync(lockPath, serializeIdentityUsda(nextLock), "utf8");
5917
6045
 
6046
+ // USDA is rendered directly from the typed graph. The final closure writer below publishes these
6047
+ // exact texts under their logical paths; no parallel layer directory or JSON reconstruction exists.
6048
+ const v3Closure = [...typedLawGraphs.values()];
6049
+ const v3DomainAssets: EmittedLawClosureAsset[] = [];
6050
+ const v3DomainRoots: string[] = [];
6051
+ const v3LawPathByNode = new Map<LawGraph["nodes"][number], string>();
6052
+ const v3CompiledGraphs = new Map<LawGraph, ReturnType<typeof compileSparseLawGraphUsda>>();
6053
+ const typedDomainIdentityByName = new Map<string, string>();
6054
+ const typedLawPathsByName = new Map<string, ReadonlyMap<string, string>>();
6055
+ for (const [domain, graph] of typedLawGraphs) {
6056
+ const safeDomain = domain.replace(/[^A-Za-z0-9._-]/g, "_");
6057
+ const logicalPath = `domains/${safeDomain}.usda`;
6058
+ const compiledLayer = compileSparseLawGraphUsda(
6059
+ graph,
6060
+ logicalPath,
6061
+ v3Closure
6062
+ );
6063
+ v3CompiledGraphs.set(graph, compiledLayer);
6064
+ const layerText = compiledLayer.text;
6065
+ const sha256 = sha256HexUtf8(layerText);
6066
+ v3DomainAssets.push({
6067
+ logicalPath,
6068
+ sha256,
6069
+ utf8: layerText,
6070
+ });
6071
+ const domainRoot = compiledLayer.paths.get(graph.domain.entity.id);
6072
+ if (domainRoot === undefined) {
6073
+ fail(`domain '${domain}' did not emit its canonical OpenUSD root path`);
6074
+ }
6075
+ v3DomainRoots.push(domainRoot);
6076
+ for (const node of graph.nodes) {
6077
+ const lawPath = compiledLayer.paths.get(node.entity.id);
6078
+ if (lawPath !== undefined) v3LawPathByNode.set(node, lawPath);
6079
+ }
6080
+ typedDomainIdentityByName.set(domain, sha256);
6081
+ typedLawPathsByName.set(domain, compiledLayer.paths);
6082
+ }
6083
+
5918
6084
  // ── THE STRUCTURAL INTERFACE (#72) — directives (payload shapes) + aggregates (read
5919
6085
  // shapes, sid-keyed), keyed by DOMAIN. The SAME object is (a) baked into the generated
5920
6086
  // clients as their requirement and (b) staged on the deploy as the workspace's offer, so
@@ -5929,7 +6095,12 @@ async function main(): Promise<void> {
5929
6095
  `compiler surface invariant: domain '${key}' has no typed USDA graph`
5930
6096
  );
5931
6097
  }
5932
- const offeredInterface = domainInterfaceFromLawGraph(graph);
6098
+ // Nested record members carry the allocation the generated codecs encode them under, which
6099
+ // follows the composed layer's canonical prim (CAP-681).
6100
+ const offeredInterface = domainInterfaceFromLawGraph(
6101
+ graph,
6102
+ typedLawPathsByName.get(mod.name)
6103
+ );
5933
6104
  const di = publicClientInterface([mod], { [key]: offeredInterface })[key]!;
5934
6105
  const graphRoutes = graph.nodes
5935
6106
  .filter((node) => node.kind === "intent" && node.public)
@@ -6022,44 +6193,6 @@ async function main(): Promise<void> {
6022
6193
  }
6023
6194
  }
6024
6195
 
6025
- // USDA is rendered directly from the typed graph. The final closure writer below publishes these
6026
- // exact texts under their logical paths; no parallel layer directory or JSON reconstruction exists.
6027
- const v3Closure = [...typedLawGraphs.values()];
6028
- const v3DomainAssets: EmittedLawClosureAsset[] = [];
6029
- const v3DomainRoots: string[] = [];
6030
- const v3LawPathByNode = new Map<LawGraph["nodes"][number], string>();
6031
- const v3CompiledGraphs = new Map<LawGraph, ReturnType<typeof compileSparseLawGraphUsda>>();
6032
- const typedDomainIdentityByName = new Map<string, string>();
6033
- const typedLawPathsByName = new Map<string, ReadonlyMap<string, string>>();
6034
- for (const [domain, graph] of typedLawGraphs) {
6035
- const safeDomain = domain.replace(/[^A-Za-z0-9._-]/g, "_");
6036
- const logicalPath = `domains/${safeDomain}.usda`;
6037
- const compiledLayer = compileSparseLawGraphUsda(
6038
- graph,
6039
- logicalPath,
6040
- v3Closure
6041
- );
6042
- v3CompiledGraphs.set(graph, compiledLayer);
6043
- const layerText = compiledLayer.text;
6044
- const sha256 = sha256HexUtf8(layerText);
6045
- v3DomainAssets.push({
6046
- logicalPath,
6047
- sha256,
6048
- utf8: layerText,
6049
- });
6050
- const domainRoot = compiledLayer.paths.get(graph.domain.entity.id);
6051
- if (domainRoot === undefined) {
6052
- fail(`domain '${domain}' did not emit its canonical OpenUSD root path`);
6053
- }
6054
- v3DomainRoots.push(domainRoot);
6055
- for (const node of graph.nodes) {
6056
- const lawPath = compiledLayer.paths.get(node.entity.id);
6057
- if (lawPath !== undefined) v3LawPathByNode.set(node, lawPath);
6058
- }
6059
- typedDomainIdentityByName.set(domain, sha256);
6060
- typedLawPathsByName.set(domain, compiledLayer.paths);
6061
- }
6062
-
6063
6196
  // Index the transient graph once. Both projections follow the same nominal edges, including
6064
6197
  // transitive cross-domain dependencies. Per-command selection also follows reverse guard edges;
6065
6198
  // the retained package command projection remains the compatibility consumer until the runtime
@@ -6607,7 +6740,6 @@ async function main(): Promise<void> {
6607
6740
  const planEntrySource = (
6608
6741
  domainKeys: readonly string[],
6609
6742
  includeReports: boolean,
6610
- includeDescribe: boolean,
6611
6743
  sourceOverrides: ReadonlyMap<string, string> = new Map(),
6612
6744
  includePreloads = true,
6613
6745
  routingOverride?: Record<string, unknown>,
@@ -6659,12 +6791,6 @@ async function main(): Promise<void> {
6659
6791
  ...(!sharedRuntime && zodSeedNames.length > 0
6660
6792
  ? [` seeds: [${zodSeedNames.join(", ")}],`]
6661
6793
  : []),
6662
- ...(includeDescribe
6663
- ? [` openapiDocument: ${JSON.stringify(runtimeOpenApiDocument)},`]
6664
- : []),
6665
- ...(includeDescribe
6666
- ? [` packageTitle: ${JSON.stringify(`${cfg.name} (nomos package)`)},`]
6667
- : []),
6668
6794
  ...workspaceEffects.config,
6669
6795
  `});`,
6670
6796
  ...(includeMintInstalls
@@ -6841,7 +6967,6 @@ async function main(): Promise<void> {
6841
6967
  const compilePlan = async (
6842
6968
  domainKeys: readonly string[],
6843
6969
  includeReports: boolean,
6844
- includeDescribe: boolean,
6845
6970
  options: {
6846
6971
  readonly sourceOverrides?: ReadonlyMap<string, string>;
6847
6972
  readonly sliceTargets?: readonly PlanSourceSliceTarget[];
@@ -6880,7 +7005,6 @@ async function main(): Promise<void> {
6880
7005
  const signature = JSON.stringify({
6881
7006
  domains: ordered,
6882
7007
  reports: includeReports,
6883
- describe: includeDescribe,
6884
7008
  slice:
6885
7009
  effectiveSliceTargets?.map((target) => [
6886
7010
  target.modulePath,
@@ -6895,7 +7019,6 @@ async function main(): Promise<void> {
6895
7019
  planEntrySource(
6896
7020
  ordered,
6897
7021
  includeReports,
6898
- includeDescribe,
6899
7022
  options.sourceOverrides,
6900
7023
  options.includePreloads ?? options.sliceTargets === undefined,
6901
7024
  options.routingOverride,
@@ -7035,14 +7158,9 @@ async function main(): Promise<void> {
7035
7158
  const routePlan = async (
7036
7159
  selector: string,
7037
7160
  domainKeys: readonly string[],
7038
- includeReports = false,
7039
- includeDescribe = false
7161
+ includeReports = false
7040
7162
  ): Promise<void> => {
7041
- const asset = await compilePlan(
7042
- domainKeys,
7043
- includeReports,
7044
- includeDescribe
7045
- );
7163
+ const asset = await compilePlan(domainKeys, includeReports);
7046
7164
  planRoutes.push({ selector, planSha256: asset.sha256 });
7047
7165
  };
7048
7166
  const routeAsset = (selector: string, asset: UsdPlanAsset): void => {
@@ -7086,7 +7204,7 @@ async function main(): Promise<void> {
7086
7204
  // content hash, and emitted sorted by selector/hash), so compile them concurrently and bind
7087
7205
  // routes afterwards in declaration order.
7088
7206
  const domainPlans = await Promise.all(
7089
- orderedDomainKeys.map((key) => compilePlan([key], false, false))
7207
+ orderedDomainKeys.map((key) => compilePlan([key], false))
7090
7208
  );
7091
7209
  orderedDomainKeys.forEach((key, index) => {
7092
7210
  const asset = domainPlans[index]!;
@@ -7158,7 +7276,7 @@ async function main(): Promise<void> {
7158
7276
  const source = ` ${JSON.stringify(
7159
7277
  BUSINESS_BIRTH_FRAMEWORK_PLAN_DOMAIN
7160
7278
  )}: [${selectedSources.join(", ")}],`;
7161
- frameworkPlan = compilePlan([domain], false, false, {
7279
+ frameworkPlan = compilePlan([domain], false, {
7162
7280
  sourceOverrides: new Map([[domain, source]]),
7163
7281
  includePreloads: false,
7164
7282
  cacheKey: "business-birth-framework",
@@ -7228,7 +7346,7 @@ async function main(): Promise<void> {
7228
7346
  selectedModule,
7229
7347
  ...generated,
7230
7348
  ].join(", ")}],`;
7231
- const asset = await compilePlan([domain], false, false, {
7349
+ const asset = await compilePlan([domain], false, {
7232
7350
  sourceOverrides: new Map([[domain, source]]),
7233
7351
  sliceTargets: selected,
7234
7352
  });
@@ -7386,7 +7504,7 @@ async function main(): Promise<void> {
7386
7504
  )}: [${moduleExpression}],`;
7387
7505
  workspaceInvariantPlans.push({
7388
7506
  invariantIds: members.map(([id]) => id),
7389
- plan: compilePlan([domain], false, false, {
7507
+ plan: compilePlan([domain], false, {
7390
7508
  sourceOverrides: new Map([[domain, domainSource]]),
7391
7509
  sliceTargets: [...selectedByKey.values()],
7392
7510
  cacheKey: `workspace-invariants:${directiveId}:${members
@@ -7403,9 +7521,11 @@ async function main(): Promise<void> {
7403
7521
  }
7404
7522
  }
7405
7523
  stage("workspace invariant plans");
7406
- const reportRoutes: Promise<void>[] = [
7407
- routePlan("describe", orderedDomainKeys, false, true),
7408
- ];
7524
+ // No `describe` route: the kernel derives a workspace's description from its installed law
7525
+ // (rpc_describe renders it from the command contract and read projection), so a compiled
7526
+ // self-description plan over every domain was never executed, and it was the one composition
7527
+ // file a single-domain edit moved (CAP-658).
7528
+ const reportRoutes: Promise<void>[] = [];
7409
7529
  for (const reportId of Object.keys(cfg.reports ?? {}))
7410
7530
  reportRoutes.push(routePlan(`report:report:${reportId}`, [], true));
7411
7531
  const routeTable = (
@@ -7447,7 +7567,7 @@ async function main(): Promise<void> {
7447
7567
  }
7448
7568
  }
7449
7569
  await Promise.all(reportRoutes);
7450
- stage("describe + report plans");
7570
+ stage("report plans");
7451
7571
  // Adaptive offer splitting deliberately compiles candidate parents before retaining their children.
7452
7572
  // Only route-reachable content is custody; exploratory compiler work never becomes law bytes.
7453
7573
  const routedPlanHashes = new Set(planRoutes.map((route) => route.planSha256));
@@ -7747,6 +7867,7 @@ async function main(): Promise<void> {
7747
7867
  };
7748
7868
  let lawClosure = emitLawClosure(
7749
7869
  cfg.name,
7870
+ cfg.version === undefined ? undefined : lawVersionClaim(cfg.version),
7750
7871
  domainModules.map((module) => module.domain ?? module.name),
7751
7872
  runtimeDependencies,
7752
7873
  v3DomainAssets,
@@ -7908,6 +8029,7 @@ async function main(): Promise<void> {
7908
8029
  };
7909
8030
  lawClosure = emitLawClosure(
7910
8031
  cfg.name,
8032
+ cfg.version === undefined ? undefined : lawVersionClaim(cfg.version),
7911
8033
  domainModules.map((module) => module.domain ?? module.name),
7912
8034
  runtimeDependencies,
7913
8035
  v3DomainAssets,
@@ -7934,8 +8056,10 @@ async function main(): Promise<void> {
7934
8056
  if (probeFailure !== undefined) fail(probeFailure);
7935
8057
  }
7936
8058
  stage("boot probe (verdict)");
7937
- // The composed USDA closure is the law. Generated clients, lifecycle rows and
7938
- // compatibility metadata all bind this identity, never the retired flattened envelope.
8059
+ // The PACKAGE identity: this compiler's own closure of the package (root.usda, routes and its derived
8060
+ // finished goods included). It names the `.law.usdz` bytes and the package lane; it is NOT what a
8061
+ // unit-lane target serves (that target's kernel composes the units itself, CAP-773). The law
8062
+ // identity is the unit set identity below.
7939
8063
  const domainHash = lawClosure.identity;
7940
8064
  const rootAsset = lawClosure.assets.find(
7941
8065
  (asset) => asset.logicalPath === lawClosure.rootLogicalPath
@@ -7949,11 +8073,18 @@ async function main(): Promise<void> {
7949
8073
  .map((asset) => [`layers/${asset.logicalPath}`, asset.bytes] as const),
7950
8074
  ]);
7951
8075
  writeFileSync(lawUsdzPath, lawUsdz);
7952
- // The compiled law's identity on its own line, for release gates that need the hash and nothing else.
8076
+ // The package closure's identity on its own line, for gates that check the `.law.usdz` bytes.
7953
8077
  writeFileSync(path.join(outDir, `${cfg.name}.law.sha256`), `${domainHash}\n`, "utf8");
7954
8078
  // D21: the same law, authored as per-domain units the kernel composes. Split from the closure just
7955
8079
  // emitted (its bytes and identity are untouched), previewed for exact coverage, and written as plain
7956
8080
  // trees: in custody each unit is a git tree, so git owns compression and deduplication.
8081
+ // THE LAW IDENTITY (CAP-773): the unit set identity of the units below, the kernel's own definition
8082
+ // (law_unit_set_identity.ts). Releases, generated clients and qualification name the law by it.
8083
+ let unitSetIdentity: string;
8084
+ // THE PER-DOMAIN REQUIREMENTS (CAP-666): each application domain's unit hash beside the identity of its own
8085
+ // compatibility requirement, derived from the units just split and the interfaces just emitted. Written into
8086
+ // no law or package (the era rule); the generated clients, the approved model and the inventory carry them.
8087
+ let domainRequirements: readonly DomainUnitRequirement[];
7957
8088
  {
7958
8089
  const lawUnits = (() => {
7959
8090
  try {
@@ -7975,6 +8106,13 @@ async function main(): Promise<void> {
7975
8106
  }
7976
8107
  writeFileSync(path.join(unitPath, LAW_UNIT_MANIFEST), unit.manifest);
7977
8108
  }
8109
+ unitSetIdentity = lawUnitSetIdentityWith(
8110
+ (bytes) => createHash("sha256").update(bytes).digest("hex"),
8111
+ lawUnits.map((unit) => ({ key: unit.key, unitHash: unit.hash }))
8112
+ );
8113
+ // The law identity on its own line, for publishers and gates that need it and nothing else.
8114
+ writeFileSync(path.join(outDir, `${cfg.name}.units.sha256`), `${unitSetIdentity}\n`, "utf8");
8115
+ domainRequirements = domainRequirementsOf(lawUnits, packageInterface, sha256HexUtf8);
7978
8116
  }
7979
8117
  const layeredRootPath = path.join(
7980
8118
  lawClosureAssetsPath,
@@ -8133,6 +8271,7 @@ async function main(): Promise<void> {
8133
8271
  package: cfg.name,
8134
8272
  // Binds the summary to the published package asset consumed by an application stage.
8135
8273
  sourceIdentity: composedUsdIdentity!,
8274
+ unitSetIdentity,
8136
8275
  domainHash,
8137
8276
  lawClosureIdentity: lawClosure.identity,
8138
8277
  domains: presentationDomainModules.map((m) => ({
@@ -8168,7 +8307,10 @@ async function main(): Promise<void> {
8168
8307
 
8169
8308
  const summaryTxt = [
8170
8309
  `${cfg.name} — compiled Nomos domain package`,
8171
- `law (domainHash): ${domainHash} # composed typed USDA closure identity`,
8310
+ `law (unit set): ${unitSetIdentity} # THE law identity: ${cfg.name}.units/, as releases, clients and qualification name it (${cfg.name}.units.sha256)`,
8311
+ `claimed version: ${cfg.version ?? "none"} # orders versions of law '${cfg.name}' (nomos.project.ts version); covered by the unit set identity`,
8312
+ `package closure: ${domainHash} # package — not what targets serve: this compiler's closure of ${cfg.name}.law.usdz (${cfg.name}.law.sha256)`,
8313
+ `served law: githolon domains status <ws> # each target's kernel composes the units into its own closure (it depends on that kernel's composer)`,
8172
8314
  `law transport: ${path.basename(
8173
8315
  lawUsdzPath
8174
8316
  )} # lossless one-file USDZ envelope; not canonical custody`,
@@ -8190,6 +8332,11 @@ async function main(): Promise<void> {
8190
8332
  d.counts.join(", ") || "—"
8191
8333
  } sums: ${d.sums.join(", ") || "—"}`,
8192
8334
  ]),
8335
+ ...(releasedBaseline === undefined
8336
+ ? [`baseline: NONE # not a publishable build; githolon compile --publishable needs a pinned nomos.baseline.usda`]
8337
+ : [
8338
+ `baseline: ${releasedBaselineSha256} # extends ${releasedBaseline.release === "none" ? "unreleased units" : `release ${releasedBaseline.release}`}, law ${releasedBaseline.unitSet} (${releasedBaseline.source})`,
8339
+ ]),
8193
8340
  `deploy: githolon deploy <ws> --file ${cfg.name}.law.usdz`,
8194
8341
  packageAssetPath !== undefined
8195
8342
  ? `package asset: ${path.basename(
@@ -8222,6 +8369,7 @@ async function main(): Promise<void> {
8222
8369
  const provenance = buildProvenanceStatement({
8223
8370
  packageName: cfg.name,
8224
8371
  domainHash,
8372
+ unitSetIdentity,
8225
8373
  artifacts: [
8226
8374
  { name: path.basename(lawUsdzPath) },
8227
8375
  {
@@ -8245,6 +8393,16 @@ async function main(): Promise<void> {
8245
8393
  id: "https://github.com/Captain-App/nomos2",
8246
8394
  version: dslVersion,
8247
8395
  },
8396
+ ...(releasedBaseline === undefined || releasedBaselineSha256 === undefined
8397
+ ? {}
8398
+ : {
8399
+ baseline: {
8400
+ sha256: releasedBaselineSha256,
8401
+ source: releasedBaseline.source,
8402
+ release: releasedBaseline.release,
8403
+ unitSet: releasedBaseline.unitSet,
8404
+ },
8405
+ }),
8248
8406
  });
8249
8407
  writeFileSync(
8250
8408
  path.join(outDir, `${cfg.name}.provenance.json`),
@@ -8272,7 +8430,9 @@ async function main(): Promise<void> {
8272
8430
  );
8273
8431
  const clientOptions = {
8274
8432
  packageName: cfg.name,
8275
- domainHash,
8433
+ // THE LAW the generated clients name: the unit set (CAP-773), never the package closure.
8434
+ domainHash: unitSetIdentity,
8435
+ domainRequirements,
8276
8436
  lawAssetName: `${cfg.name}.law.usdz`,
8277
8437
  interface: packageInterface,
8278
8438
  kernelContracts,
@@ -8380,10 +8540,9 @@ async function main(): Promise<void> {
8380
8540
  writeKeyProofUsda(file: string, format: string, key: string): void;
8381
8541
  };
8382
8542
  const declarationProofFormat = "nomos.typescript-declaration-proof.v1";
8383
- const normalizedClientSource = clientSource.replaceAll(
8384
- domainHash,
8385
- "<nomos-composed-law-identity>"
8386
- );
8543
+ const normalizedClientSource = clientSource
8544
+ .replaceAll(unitSetIdentity, "<nomos-law-unit-set-identity>")
8545
+ .replaceAll(domainHash, "<nomos-package-identity>");
8387
8546
  const declarationInputIdentity = createHash("sha256")
8388
8547
  .update(`${declarationProofFormat}\0`)
8389
8548
  .update(normalizedClientSource)
@@ -8863,7 +9022,7 @@ async function main(): Promise<void> {
8863
9022
  const r = emitDartPackages(
8864
9023
  presentationDomainModules,
8865
9024
  dartOut,
8866
- domainHash,
9025
+ { unitSet: unitSetIdentity, packageSha256: domainHash, domainRequirements },
8867
9026
  typedLawGraphs,
8868
9027
  typedLawPathsByName,
8869
9028
  packageInterface,
@@ -8891,7 +9050,7 @@ async function main(): Promise<void> {
8891
9050
  dartFiles = emitDart(
8892
9051
  presentationDomainModules,
8893
9052
  dartOut,
8894
- domainHash,
9053
+ { unitSet: unitSetIdentity, packageSha256: domainHash, domainRequirements },
8895
9054
  typedLawGraphs,
8896
9055
  typedLawPathsByName,
8897
9056
  packageInterface,
@@ -9274,10 +9433,12 @@ async function main(): Promise<void> {
9274
9433
  // ── summary ──
9275
9434
  const rel = (p: string) => path.relative(process.cwd(), p);
9276
9435
  console.log(`nomos-compile: ${cfg.name}`);
9436
+ console.log(` law unit set ${unitSetIdentity} (${rel(path.join(outDir, `${cfg.name}.units`))})`);
9437
+ if (cfg.version !== undefined) console.log(` version ${cfg.name}@${cfg.version} (claimed; covered by the unit set identity)`);
9277
9438
  console.log(
9278
- ` law ${rel(lawUsdzPath)} (${lawUsdz.byteLength} transport bytes)`
9439
+ ` package ${domainHash} — package — not what targets serve (${rel(lawUsdzPath)}, ${lawUsdz.byteLength} transport bytes)`
9279
9440
  );
9280
- console.log(` domainHash ${domainHash}`);
9441
+ console.log(` served githolon domains status <ws> — each target's kernel composes the units into its own closure`);
9281
9442
  console.log(
9282
9443
  custodyPlacementDeclarations.length > 0
9283
9444
  ? ` placement ${rel(custodyPlacementDeclarationsPath)} (${
@@ -9506,6 +9667,7 @@ async function main(): Promise<void> {
9506
9667
  dslDir: string;
9507
9668
  configPath: string;
9508
9669
  argumentIdentity: string;
9670
+ importedSources?: readonly string[];
9509
9671
  }): unknown;
9510
9672
  generatedOutputFiles(
9511
9673
  outDir: string,
@@ -9525,6 +9687,11 @@ async function main(): Promise<void> {
9525
9687
  dslDir: DSL_DIR,
9526
9688
  configPath,
9527
9689
  argumentIdentity: compileArgumentIdentity,
9690
+ importedSources: [
9691
+ ...((globalThis as Record<symbol, unknown>)[
9692
+ Symbol.for("nomos.compile.importedSources")
9693
+ ] as ReadonlySet<string> | undefined ?? []),
9694
+ ],
9528
9695
  });
9529
9696
  const outputs = proof.generatedOutputFiles(outDir, [
9530
9697
  lockPath,