@githolon/dsl 0.102.5 → 0.103.1

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 (132) hide show
  1. package/compile_package.mjs +43 -2
  2. package/compile_proof.d.mts +47 -0
  3. package/compile_proof.mjs +343 -0
  4. package/dart/CHANGELOG.md +14 -0
  5. package/dart/lib/src/dev_cloud.dart +8 -8
  6. package/dart/lib/src/nms_generated_runtime.dart +2 -0
  7. package/dart/lib/src/runner/node-entry.mjs +5539 -1228
  8. package/dart/lib/src/runner/offline-install.mjs +4578 -528
  9. package/dart/lib/src/test_harness.dart +206 -152
  10. package/dart/lib/src/webview_bridge.dart +239 -102
  11. package/dart/pubspec.lock +1 -1
  12. package/dart/pubspec.yaml +2 -2
  13. package/dart/test/acquire_session_credentials_test.dart +4 -3
  14. package/dart/test/acquire_session_test.dart +9 -0
  15. package/dart/test/adv_realm_interference_test.dart +7 -6
  16. package/dart/test/cloud_bound_topology_test.dart +3 -3
  17. package/dart/test/cluster_births_test.dart +4 -3
  18. package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
  19. package/dart/test/cluster_local_autosync_test.dart +3 -2
  20. package/dart/test/cluster_scale_test.dart +3 -2
  21. package/dart/test/dlq_session_surface_test.dart +7 -6
  22. package/dart/test/first_app_gauntlet_test.dart +14 -21
  23. package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
  24. package/dart/test/four_layer_nested_topology_test.dart +4 -4
  25. package/dart/test/harness_phase_timeout_test.dart +2 -2
  26. package/dart/test/local_sync_inert_test.dart +1 -1
  27. package/dart/test/parent_signer_recovery_test.dart +2 -2
  28. package/dart/test/sealing_client_test.dart +4 -3
  29. package/dart/test/session_bound_test.dart +8 -7
  30. package/dart/test/test_harness_test.dart +13 -12
  31. package/dart/test/vo_read_typing_test.dart +4 -3
  32. package/dart/test/watch_after_offer_stall_test.dart +4 -3
  33. package/dart/test/webview_bridge_test.dart +102 -5
  34. package/dart_types/CHANGELOG.md +5 -0
  35. package/dart_types/lib/nomos_types.dart +9 -0
  36. package/dart_types/lib/src/nomos_ref.dart +20 -0
  37. package/dart_types/lib/src/optional_ref.dart +85 -0
  38. package/dart_types/lib/src/sha256_digest.dart +41 -0
  39. package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
  40. package/dart_types/pubspec.yaml +1 -1
  41. package/dart_types/test/optional_ref_test.dart +44 -0
  42. package/dart_types/test/sha256_digest_test.dart +18 -0
  43. package/package.json +9 -5
  44. package/runtime/generated-application-host.js +5365 -1105
  45. package/src/aggregate.ts +18 -0
  46. package/src/application_composition.ts +6 -32
  47. package/src/attested_core.ts +6 -3
  48. package/src/birth_meter.ts +1 -0
  49. package/src/birth_meter_ensure.ts +28 -2
  50. package/src/births.ts +1 -1
  51. package/src/build_package.ts +162 -538
  52. package/src/business_birth.ts +19 -3
  53. package/src/canonical_json.ts +21 -0
  54. package/src/check_package_main.ts +73 -33
  55. package/src/clear_directive.ts +19 -8
  56. package/src/codegen_dart.ts +305 -22
  57. package/src/codegen_dart_application.ts +862 -292
  58. package/src/codegen_dart_client.ts +1084 -407
  59. package/src/codegen_dart_scenario.ts +12 -12
  60. package/src/codegen_proof.ts +169 -39
  61. package/src/codegen_provider_dart.ts +17 -1
  62. package/src/codegen_ts.ts +298 -106
  63. package/src/compat_manifest.ts +2 -2
  64. package/src/compat_package_main.ts +8 -12
  65. package/src/compile_package_main.ts +1860 -592
  66. package/src/compose.ts +1 -1
  67. package/src/directive.ts +208 -15
  68. package/src/engine_entry.ts +165 -111
  69. package/src/evolve_preview.ts +29 -69
  70. package/src/extremum.ts +1 -1
  71. package/src/fields.ts +183 -4
  72. package/src/flow_gate.ts +75 -2
  73. package/src/framework/application_home.ts +2 -0
  74. package/src/framework/auth_provider_law.ts +2 -2
  75. package/src/framework/birth_meter.ts +7 -19
  76. package/src/framework/bootstrap.ts +0 -2
  77. package/src/framework/business_corrections.ts +9 -5
  78. package/src/framework/domain_lifecycle.ts +15 -29
  79. package/src/framework/encryption_scope.ts +2 -1
  80. package/src/framework/framework_aggregates.ts +2 -0
  81. package/src/framework/genesis.ts +16 -14
  82. package/src/framework/home.ts +20 -14
  83. package/src/framework/owned_custody.ts +9 -3
  84. package/src/framework/platform_admin.ts +3 -3
  85. package/src/framework/relations.ts +21 -13
  86. package/src/framework/signing_identity.ts +45 -40
  87. package/src/framework/workspace_invariant.ts +245 -0
  88. package/src/framework/workspaces.ts +23 -29
  89. package/src/index.ts +36 -16
  90. package/src/intent.ts +178 -6
  91. package/src/law_check.ts +32 -11
  92. package/src/law_graph.ts +2902 -0
  93. package/src/law_graph_usda.ts +1799 -0
  94. package/src/law_identity.ts +435 -0
  95. package/src/law_value_type.ts +230 -0
  96. package/src/lsp_server.ts +58 -29
  97. package/src/ops.ts +7 -0
  98. package/src/package_config.ts +141 -0
  99. package/src/package_dependencies.ts +108 -0
  100. package/src/package_dependency_usage.ts +150 -0
  101. package/src/package_source_imports.ts +71 -0
  102. package/src/plan_source_slice.ts +335 -0
  103. package/src/point_guard.ts +192 -0
  104. package/src/precondition.ts +44 -9
  105. package/src/read_projection_namespace.ts +1 -1
  106. package/src/runtime_zod.ts +44 -0
  107. package/src/secondary_ensure.ts +67 -14
  108. package/src/shape_compat.ts +1 -1
  109. package/src/stable_id_identity_bridge.ts +149 -0
  110. package/src/stable_id_lockfile.ts +140 -6
  111. package/src/stable_ids.ts +1 -1
  112. package/src/stable_ids_for_module.ts +348 -0
  113. package/src/stable_ids_types.ts +1 -1
  114. package/src/sum.ts +1 -1
  115. package/src/usd_state.ts +1 -1
  116. package/src/usda_relationship_integrity.ts +66 -0
  117. package/src/usda_text.ts +18 -0
  118. package/src/usdz.ts +78 -0
  119. package/src/value_object.ts +95 -0
  120. package/src/wire.ts +19 -0
  121. package/src/wire_encode.ts +22 -0
  122. package/src/workspace_sharding.ts +52 -36
  123. package/src/workspace_sharing.ts +28 -4
  124. package/src/zod_introspection.ts +1 -1
  125. package/dart/test/cluster_attested_test.dart +0 -201
  126. package/src/compile_engine.ts +0 -595
  127. package/src/manifest.ts +0 -1476
  128. package/src/read_projection_composition.ts +0 -117
  129. package/src/read_projection_manifest.ts +0 -115
  130. package/src/relation_composition.ts +0 -286
  131. package/src/usd.ts +0 -1967
  132. package/src/usd_layers.ts +0 -1776
@@ -6,13 +6,13 @@
6
6
  // If a file isn't this / hosting this / authoring for this / proving this — it's gone.
7
7
 
8
8
  /**
9
- * `nomos-compile` — ONE COMMAND: TS DSL domains → a deployable `.package.usda` (#M4).
9
+ * `nomos-compile` — ONE COMMAND: TS DSL domains → a canonical typed USDA closure.
10
10
  *
11
11
  * Run from a package that depends on `@githolon/dsl` (esbuild + tsx ride along as its
12
- * dependencies — no devDependency setup), next to a `nomos.package.mjs` config:
12
+ * dependencies — no devDependency setup), next to a typed `nomos.project.ts` authoring file:
13
13
  *
14
14
  * export default {
15
- * name: "guestbook", // → build/guestbook.package.usda
15
+ * name: "guestbook", // → build/guestbook.law.usdz + readable USDA
16
16
  * domains: [
17
17
  * { key: "guestbook", modules: ["./domains/guestbook.ts"] },
18
18
  * ],
@@ -28,12 +28,8 @@
28
28
  * Reports: `{ reports: { my_report_v1: "./report/my_report.ts#myReportV1" } }`.
29
29
  *
30
30
  * It emits into `outDir` (default `<config dir>/build`):
31
- * * `<name>.package.usda` — the deployable package (engine bundle + USD IR);
32
- * its sha256 IS the deploy `domainHash` (`policy:{hash}`).
33
- * * `<name>.deploy.json` — `{packageUsda}`: the EXACT body
34
- * `POST /v2/workspaces/:ws/domains` accepts as `application/json` on Nomos Cloud.
35
- * Admission/projection manifests are embedded in the package USDA and are not host
36
- * sidecars.
31
+ * * `<name>.law/` — the exact logically named, human-readable USDA stage.
32
+ * * `<name>.law.usdz` — a lossless transport archive of that closure.
37
33
  * * `<name>.proof.mts` — the GENERATED PROOF (codegen_proof.ts): a
38
34
  * runnable, domain-shaped live e2e synthesized from the law's own directives /
39
35
  * queries / counts. Run it with `githolon proof`. Never fatal: an unsampleable
@@ -46,11 +42,13 @@
46
42
  * `globalThis.plan`.
47
43
  */
48
44
  import { execFileSync } from "node:child_process";
45
+ import { createHash } from "node:crypto";
49
46
  import { expandCapabilityExports, isCapabilityDecl } from "./capability_exports.js";
50
47
  import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
51
48
  import { createRequire } from "node:module";
52
49
  import path from "node:path";
53
50
  import { fileURLToPath, pathToFileURL } from "node:url";
51
+ import ts from "typescript";
54
52
 
55
53
  import type { AggregateHandle } from "./aggregate.js";
56
54
  import {
@@ -97,10 +95,8 @@ import type { CombinedDecl } from "./combined.js";
97
95
  import type { ImpureCapabilityDecl } from "./codegen_provider_dart.js";
98
96
  import {
99
97
  aggregatesOf,
100
- buildIdentity,
101
98
  composeDomainModule,
102
99
  directivesOf,
103
- emitUsdJsonForModules,
104
100
  sha256HexUtf8,
105
101
  type Mod,
106
102
  } from "./build_package.js";
@@ -134,36 +130,30 @@ import {
134
130
  mergeRelationTypeDeclarations,
135
131
  type RelationTypeDecl,
136
132
  } from "./relation_schema.js";
137
- import {
138
- emitLayeredDomain,
139
- flattenLayeredUsd,
140
- layeredRootUsda,
141
- nativePlanPackageUsda,
142
- parseUsdaDocument,
143
- usdaLayerText,
144
- type LayeredModuleInput,
145
- type UsdPlanAsset,
146
- type UsdPlanRoute,
147
- } from "./usd_layers.js";
148
- import { domainManifest, canonicalJson } from "./manifest.js";
133
+ import { canonicalJson } from "./canonical_json.js";
134
+ import { stableIdsForModule } from "./stable_ids_for_module.js";
149
135
  import type { WireSchema } from "./wire.js";
136
+ import { encodeKernelSchema } from "./wire_encode.js";
150
137
  import {
151
138
  deriveStableIdContinuity,
152
139
  type InferredRename,
153
140
  type StableIds,
154
141
  } from "./stable_ids.js";
155
142
  import {
143
+ identityRegistryFromStableIdLockfile,
156
144
  parseStableIdLockfile,
157
145
  serializeStableIdLockfile,
158
146
  type StableIdLockfile,
159
147
  } from "./stable_id_lockfile.js";
160
- import { emitUsd, type UsdLayer } from "./usd.js";
161
- import { emitLayeredRelationUsda, type RelationLayerInput } from "./relation_composition.js";
148
+ import { reconcileStableIdsWithIdentity } from "./stable_id_identity_bridge.js";
149
+ import { discoverLawGraph, type LawGraph } from "./law_graph.js";
150
+ import { emitFrameworkLawUsda, emitSparseLawGraphUsda } from "./law_graph_usda.js";
162
151
  import { assertNoDartBarrelCollisions, type BarrelExportedFile } from "./dart_barrel_guard.js";
163
152
  import { generateTsClient, tsApplicationName, tsClientFactoryName, recursiveLazyTypes } from "./codegen_ts.js";
164
153
  import { generateOpenApiFragment, mergeOpenApiFragments } from "./codegen_openapi.js";
165
154
  import { domainInterface, type DomainInterface } from "./shape_compat.js";
166
155
  import { generateTsProof, proofLegsIndex } from "./codegen_proof.js";
156
+ import { usdzPack } from "./usdz.js";
167
157
  import { checkDomainModules, formatFindings, hasRefusal } from "./law_check.js";
168
158
  import type { NomosCompat } from "./compat_manifest.js";
169
159
  import { requiredKernelFeatures, type FeatureProbeModule } from "./kernel_capabilities.js";
@@ -176,10 +166,55 @@ import {
176
166
  packageAssetUsda,
177
167
  type PackageRootPart,
178
168
  } from "./application_composition.js";
169
+ import {
170
+ packageContractUsda,
171
+ type RuntimePackageDependency,
172
+ } from "./package_dependencies.js";
173
+ import {
174
+ validateNomosPackageConfig,
175
+ type NomosPackageConfig,
176
+ type NomosPackageDomainConfig,
177
+ } from "./package_config.js";
178
+ import { assertUsdaRelationshipIntegrity } from "./usda_relationship_integrity.js";
179
+ import { assertPublicPackageImports } from "./package_source_imports.js";
180
+ import { packageDependencyUsageFindings } from "./package_dependency_usage.js";
181
+ import {
182
+ PlanSourceSliceProgram,
183
+ type PlanSourceSliceTarget,
184
+ } from "./plan_source_slice.js";
179
185
 
180
186
  const DSL_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
181
187
 
182
- interface DomainConfig {
188
+ /**
189
+ * Compiler-owned declarations injected by business-birth lowering. They are ordinary typed law, but
190
+ * have no tenant export from which plan provenance could be rediscovered. Keep that provenance explicit
191
+ * here so a generated offer can never silently fall back to initializing the whole tenant domain.
192
+ */
193
+ const BUSINESS_BIRTH_FRAMEWORK_PLAN_SOURCES: Readonly<Record<string, PlanSourceSliceTarget>> = {
194
+ enrollSigner: {
195
+ modulePath: path.join(DSL_DIR, "src", "framework", "signing_identity.ts"),
196
+ exportName: "enrollSigner",
197
+ },
198
+ seedAuthProvider: {
199
+ modulePath: path.join(DSL_DIR, "src", "framework", "auth_provider_law.ts"),
200
+ exportName: "seedAuthProvider",
201
+ },
202
+ seedBirthAttestation: {
203
+ modulePath: path.join(DSL_DIR, "src", "framework", "home.ts"),
204
+ exportName: "seedBirthAttestation",
205
+ },
206
+ seedGenesis: {
207
+ modulePath: path.join(DSL_DIR, "src", "framework", "genesis.ts"),
208
+ exportName: "seedGenesis",
209
+ },
210
+ nomosBirthRecordEnsure: {
211
+ modulePath: path.join(DSL_DIR, "src", "birth_meter_ensure.ts"),
212
+ exportName: "nomosBirthRecordEnsure",
213
+ },
214
+ };
215
+ const BUSINESS_BIRTH_FRAMEWORK_PLAN_DOMAIN = "__nomos_birth_genesis_plan_v1";
216
+
217
+ interface DomainConfig extends NomosPackageDomainConfig {
183
218
  readonly key: string;
184
219
  readonly name?: string;
185
220
  readonly modules: readonly string[];
@@ -223,8 +258,7 @@ interface DomainConfig {
223
258
 
224
259
  type DartSupportImport = "vendored" | "package";
225
260
 
226
- interface PackageConfig {
227
- readonly name: string;
261
+ interface PackageConfig extends NomosPackageConfig {
228
262
  readonly domains: readonly DomainConfig[];
229
263
  /** The one OpenUSD model root this independently published package exports. */
230
264
  readonly rootPart?: PackageRootPart;
@@ -323,6 +357,229 @@ function fail(msg: string): never {
323
357
  process.exit(1);
324
358
  }
325
359
 
360
+ interface EmittedLawClosureAsset {
361
+ readonly logicalPath: string;
362
+ readonly sha256: string;
363
+ readonly utf8: string;
364
+ }
365
+
366
+ interface UsdPlanAsset {
367
+ readonly sha256: string;
368
+ readonly javascript: string;
369
+ readonly runtimeSha256: string;
370
+ /** Optional compiler diagnostic only; never enters package identity or custody. */
371
+ readonly profile?: readonly { readonly module: string; readonly bytesInOutput: number }[];
372
+ /** Hypothetical bytes after moving public runtime modules into one package-shared asset. */
373
+ readonly sharedRuntimeProfileBytes?: number;
374
+ /** Diagnostic-only executable paired with the shared runtime boot probe; never enters custody. */
375
+ readonly sharedRuntimeProfileJavascript?: string;
376
+ }
377
+
378
+ interface UsdRuntimeAsset {
379
+ readonly sha256: string;
380
+ readonly javascript: string;
381
+ }
382
+
383
+ interface UsdPlanRoute {
384
+ readonly selector: string;
385
+ readonly planSha256: string;
386
+ }
387
+
388
+ interface EmittedLawClosure {
389
+ readonly rootLogicalPath: "root.usda";
390
+ readonly identity: string;
391
+ readonly assets: readonly {
392
+ readonly logicalPath: string;
393
+ readonly sha256: string;
394
+ readonly bytes: Uint8Array;
395
+ }[];
396
+ }
397
+
398
+ /** Build the production-shaped v3 stage entirely from compiler-authored bytes. The deploy host may
399
+ * shuttle this object, but it cannot select layers, compose opinions or mint any identity: the kernel
400
+ * inventories the USDZ/Git tree and re-hashes every logically named layer. */
401
+ function emitLawClosure(
402
+ packageName: string,
403
+ exportedDomains: readonly string[],
404
+ runtimeDependencies: readonly RuntimePackageDependency[],
405
+ domainAssets: readonly EmittedLawClosureAsset[],
406
+ runtime: UsdRuntimeAsset,
407
+ plans: readonly UsdPlanAsset[],
408
+ routes: readonly UsdPlanRoute[],
409
+ ): EmittedLawClosure {
410
+ const planByHash = new Map(plans.map((plan) => [plan.sha256, plan] as const));
411
+ if (sha256HexUtf8(runtime.javascript) !== runtime.sha256) {
412
+ fail(`v3 closure: runtime source bytes do not match declared digest '${runtime.sha256}'`);
413
+ }
414
+ for (const plan of plans) {
415
+ if (sha256HexUtf8(plan.javascript) !== plan.sha256) {
416
+ fail(`v3 closure: plan source bytes do not match declared digest '${plan.sha256}'`);
417
+ }
418
+ }
419
+ const contentAsset = (sha256: string): string => `@nomos+sha256:${sha256}@`;
420
+ const routeOpinions: string[] = [
421
+ "#usda 1.0",
422
+ "(",
423
+ " customLayerData = {",
424
+ ' string "nomos:format" = "nomos.law-layer.v3"',
425
+ ' string "nomos:logicalPath" = "runtime/routes"',
426
+ " }",
427
+ ")",
428
+ "",
429
+ 'def Scope "__NomosRuntime_7e9c"',
430
+ "{",
431
+ ' custom string nomos:kind = "runtime"',
432
+ ' custom string nomos:format = "nomos.openusd-plan-metadata.v3"',
433
+ "",
434
+ ' def Scope "Routes"',
435
+ " {",
436
+ ];
437
+ [...routes]
438
+ .sort((left, right) => left.selector.localeCompare(right.selector))
439
+ .forEach((route, index) => {
440
+ if (!planByHash.has(route.planSha256)) {
441
+ fail(`v3 closure: route '${route.selector}' names missing plan '${route.planSha256}'`);
442
+ }
443
+ routeOpinions.push(
444
+ ` def Scope "r${String(index).padStart(6, "0")}"`,
445
+ " {",
446
+ ` custom string nomos:selector = ${JSON.stringify(route.selector)}`,
447
+ ` custom string nomos:plan = "${route.planSha256}"`,
448
+ " }",
449
+ "",
450
+ );
451
+ });
452
+ if (routeOpinions[routeOpinions.length - 1] === "") routeOpinions.pop();
453
+ routeOpinions.push(" }", "}", "");
454
+
455
+ const textAsset = (logicalPath: string, utf8: string): EmittedLawClosureAsset => ({
456
+ logicalPath,
457
+ sha256: sha256HexUtf8(utf8),
458
+ utf8,
459
+ });
460
+ const framework = textAsset("framework.usda", emitFrameworkLawUsda());
461
+ const packageContract = textAsset("package.usda", packageContractUsda({
462
+ packageName,
463
+ exportedDomains,
464
+ runtimeDependencies,
465
+ }));
466
+ const routeLayer = textAsset("runtime/routes.usda", routeOpinions.join("\n"));
467
+ const runtimeSource = textAsset(`runtime/bases/${runtime.sha256}.mjs`, runtime.javascript);
468
+ const runtimeLayer = textAsset(`runtime/bases/${runtime.sha256}.usda`, [
469
+ "#usda 1.0",
470
+ "(",
471
+ " customLayerData = {",
472
+ ' string "nomos:format" = "nomos.law-layer.v3"',
473
+ ` string "nomos:logicalPath" = "runtime/bases/${runtime.sha256}"`,
474
+ " }",
475
+ ")",
476
+ "",
477
+ 'def Scope "__NomosRuntime_7e9c"',
478
+ "{",
479
+ ' def Scope "Bases"',
480
+ " {",
481
+ ` def Scope "b_${runtime.sha256}"`,
482
+ " {",
483
+ ` custom string nomos:sha256 = "${runtime.sha256}"`,
484
+ ' custom string nomos:language = "javascript"',
485
+ ` custom asset nomos:source = ${contentAsset(runtime.sha256)}`,
486
+ " }",
487
+ " }",
488
+ "}",
489
+ "",
490
+ ].join("\n"));
491
+ const sortedPlans = [...plans]
492
+ .sort((left, right) => left.sha256.localeCompare(right.sha256));
493
+ const planSources = sortedPlans.map((plan) =>
494
+ textAsset(`runtime/plans/${plan.sha256}.mjs`, plan.javascript));
495
+ const planLayers = sortedPlans.map((plan) => textAsset(`runtime/plans/${plan.sha256}.usda`, [
496
+ "#usda 1.0",
497
+ "(",
498
+ " customLayerData = {",
499
+ ' string "nomos:format" = "nomos.law-layer.v3"',
500
+ ` string "nomos:logicalPath" = "runtime/plans/${plan.sha256}"`,
501
+ " }",
502
+ ")",
503
+ "",
504
+ 'def Scope "__NomosRuntime_7e9c"',
505
+ "{",
506
+ ' def Scope "Plans"',
507
+ " {",
508
+ ` def Scope "p_${plan.sha256}"`,
509
+ " {",
510
+ ` custom string nomos:sha256 = "${plan.sha256}"`,
511
+ ' custom string nomos:language = "javascript"',
512
+ ` custom rel nomos:runtime = </__NomosRuntime_7e9c/Bases/b_${plan.runtimeSha256}>`,
513
+ ` custom asset nomos:source = ${contentAsset(plan.sha256)}`,
514
+ " }",
515
+ " }",
516
+ "}",
517
+ "",
518
+ ].join("\n")));
519
+ const sublayers = [packageContract, routeLayer, runtimeLayer, ...planLayers, ...domainAssets, framework];
520
+ // `usdcat` permits relationships to absent prims because that is valid general OpenUSD. Nomos law
521
+ // does not: internal typed references must resolve in the authenticated stage. Check the declarative
522
+ // layers before hashing or publishing them. Executable MJS is an authenticated asset rather than
523
+ // USDA syntax, so it is inventoried below but deliberately never parsed as a sublayer.
524
+ assertUsdaRelationshipIntegrity([packageContract, runtimeLayer, ...planLayers, ...domainAssets, framework]);
525
+ const custodyAssets = [...sublayers, runtimeSource, ...planSources]
526
+ .sort((left, right) => left.logicalPath.localeCompare(right.logicalPath));
527
+ const root = textAsset("root.usda", [
528
+ "#usda 1.0",
529
+ "(",
530
+ ' defaultPrim = "Nomos"',
531
+ " customLayerData = {",
532
+ ' string "nomos:closureMode" = "content-addressed"',
533
+ ' string "nomos:format" = "nomos.law-stage.v3"',
534
+ " }",
535
+ " subLayers = [",
536
+ ...sublayers.map((asset, index) =>
537
+ ` @${asset.logicalPath}@${index + 1 === sublayers.length ? "" : ","}`),
538
+ " ]",
539
+ ")",
540
+ "",
541
+ 'def NomosLawClosure "__NomosClosure_7e9c"',
542
+ "{",
543
+ ' custom string nomos:rootLogicalPath = "root.usda"',
544
+ ' def Scope "Assets"',
545
+ " {",
546
+ ...custodyAssets.flatMap((asset) => {
547
+ const primName = `a_${sha256HexUtf8(asset.logicalPath)}`;
548
+ return [
549
+ ` def NomosLawAsset "${primName}"`,
550
+ " {",
551
+ ` custom string nomos:logicalPath = ${JSON.stringify(asset.logicalPath)}`,
552
+ ` custom string nomos:sha256 = "${asset.sha256}"`,
553
+ " }",
554
+ ];
555
+ }),
556
+ " }",
557
+ "}",
558
+ "",
559
+ ].join("\n"));
560
+ const assets = [root, ...custodyAssets]
561
+ .sort((left, right) => left.logicalPath.localeCompare(right.logicalPath));
562
+ const identity = createHash("sha256");
563
+ identity.update("nomos-law-stage-v3\0");
564
+ identity.update(root.logicalPath);
565
+ identity.update("\0");
566
+ for (const asset of assets) {
567
+ identity.update(asset.logicalPath);
568
+ identity.update("\0");
569
+ identity.update(asset.sha256);
570
+ identity.update("\0");
571
+ }
572
+ return {
573
+ rootLogicalPath: "root.usda",
574
+ identity: identity.digest("hex"),
575
+ assets: assets.map(({ logicalPath, sha256, utf8 }) => ({
576
+ logicalPath,
577
+ sha256,
578
+ bytes: new TextEncoder().encode(utf8),
579
+ })),
580
+ };
581
+ }
582
+
326
583
  /** Locate stock OpenUSD's usdcat without making it a runtime dependency. */
327
584
  function resolveUsdcat(): string | undefined {
328
585
  const candidates = [process.env.NOMOS_USDCAT, "/usr/bin/usdcat", "usdcat"]
@@ -551,6 +808,12 @@ const DART_SUPPORT_CORE_SRC = [
551
808
  "foreign_ref.dart",
552
809
  "activity.dart",
553
810
  "nomos_ref.dart",
811
+ // Immutable publication pin + stable series and the required freshness sum
812
+ // type. Pure data; the generated application owns the effectful check.
813
+ "versioned_nomos_ref.dart",
814
+ // Optional first-class references are explicit required choices in generated
815
+ // action inputs: absent or present, never an accidentally omitted argument.
816
+ "optional_ref.dart",
554
817
  // Branded identity wrappers (NomosPrincipal/NomosDeviceSecret) — the generated cross-workspace birth
555
818
  // methods type `actor`/`authorSecret` as these (not bare strings), so both must vendor. Pure data.
556
819
  "principal.dart",
@@ -1037,7 +1300,7 @@ function emitDart(
1037
1300
  `import '${VENDORED_TYPES_SHIM}'`,
1038
1301
  )
1039
1302
  .replace(
1040
- /import 'package:nomos_types\/src\/(?:nomos_ref|foreign_ref)\.dart'/g,
1303
+ /import 'package:nomos_types\/src\/(?:nomos_ref|foreign_ref|versioned_nomos_ref)\.dart'/g,
1041
1304
  `import '${VENDORED_TYPES_SHIM}'`,
1042
1305
  );
1043
1306
  writeFileSync(path.join(genDir, "src", f), src, "utf8");
@@ -1247,6 +1510,9 @@ function emitDart(
1247
1510
  } catch (e) {
1248
1511
  fail((e as Error).message);
1249
1512
  }
1513
+ const sha256DigestExport = [...sourceByFile.values()].some((source) => source.includes("Sha256Digest"))
1514
+ ? ", Sha256Digest"
1515
+ : "";
1250
1516
  const barrelLines = [
1251
1517
  "// GENERATED by nomos-compile — the package's public entrypoint. DO NOT EDIT.",
1252
1518
  "// Regenerated on every compile; generated implementation lives under src/generated/.",
@@ -1270,8 +1536,8 @@ function emitDart(
1270
1536
  // The generated clients import their runtime support directly. Keeping it out of the public barrel is
1271
1537
  // the surface firewall: a tenant import receives business types and application APIs, never raw runtime.
1272
1538
  supportImport === "vendored"
1273
- ? `export '${genRel}/src/${VENDORED_TYPES_SHIM}' show NomosRef;`
1274
- : `export 'package:nomos_types/nomos_types.dart' show NomosRef;`,
1539
+ ? `export '${genRel}/src/${VENDORED_TYPES_SHIM}' show NomosRef, NomosOptionalRef, VersionedNomosRef, ReferenceFreshness, ReferenceCurrent, ReferenceStale, ReferenceFreshnessUnavailable${sha256DigestExport};`
1540
+ : `export 'package:nomos_types/nomos_types.dart' show NomosRef, NomosOptionalRef, VersionedNomosRef, ReferenceFreshness, ReferenceCurrent, ReferenceStale, ReferenceFreshnessUnavailable${sha256DigestExport};`,
1275
1541
  ...(contractsImportUri !== undefined
1276
1542
  ? tenantDartExport(`${genRel}/${CONTRACTS_FILE}.dart`, sourceByFile.get(CONTRACTS_FILE) ?? "")
1277
1543
  : []),
@@ -1310,6 +1576,9 @@ const dartTypesImportFor = (generatedSource: string): string => {
1310
1576
  ...(generatedSource.includes("NmsInternalNomosRefs.")
1311
1577
  ? [`import 'package:nomos_types/src/nomos_ref.dart' show NmsInternalNomosRefs;`]
1312
1578
  : []),
1579
+ ...(generatedSource.includes("NmsInternalVersionedNomosRefs.")
1580
+ ? [`import 'package:nomos_types/src/versioned_nomos_ref.dart' show NmsInternalVersionedNomosRefs;`]
1581
+ : []),
1313
1582
  ...(generatedSource.includes("NmsInternalForeignRefs.")
1314
1583
  ? [`import 'package:nomos_types/src/foreign_ref.dart' show NmsInternalForeignRefs;`]
1315
1584
  : []),
@@ -1334,8 +1603,8 @@ const NOMOS_TYPES_DEP = "^0.8.0";
1334
1603
  // 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
1335
1604
  // DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
1336
1605
  // REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
1337
- const NOMOS_CLIENT_DEP = "^0.64.1";
1338
- const NOMOS_FLUTTER_DEP = "^0.63.1";
1606
+ const NOMOS_CLIENT_DEP = "^0.64.3";
1607
+ const NOMOS_FLUTTER_DEP = "^0.63.4";
1339
1608
  // DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in dsl/dart/pubspec.yaml
1340
1609
  // and dsl/dart_types/pubspec.yaml — a Dart release without this bump fails the suite, so tenants never
1341
1610
  // land back in dependency_overrides (the exact co2 report this closes, third occurrence).
@@ -1767,6 +2036,34 @@ function reconcilePubspec(
1767
2036
  return true;
1768
2037
  }
1769
2038
 
2039
+ /** Replace only the compiler-owned embedded-law asset in an existing generated test package.
2040
+ * Tenant assets remain untouched; retired `.deploy.json` and prior `.law.usdz` entries under the
2041
+ * generated directory cannot survive a USDA-native regeneration. */
2042
+ function reconcileGeneratedLawAsset(pkgDir: string, generatedAsset: string): boolean {
2043
+ const p = path.join(pkgDir, "pubspec.yaml");
2044
+ if (!existsSync(p)) return false;
2045
+ const source = readFileSync(p, "utf8");
2046
+ const lines = source.split(/\r?\n/);
2047
+ const compilerAsset = /^\s+-\s+lib\/src\/generated\/[^\s]+\.(?:deploy\.json|law\.usdz)\s*$/;
2048
+ const replacement = ` - ${generatedAsset}`;
2049
+ let replaced = false;
2050
+ const nextLines = lines.filter((line) => {
2051
+ if (!compilerAsset.test(line)) return true;
2052
+ if (replaced) return false;
2053
+ replaced = true;
2054
+ return false;
2055
+ });
2056
+ if (replaced) {
2057
+ const assetHeader = nextLines.findIndex((line) => /^\s{2}assets:\s*$/.test(line));
2058
+ if (assetHeader < 0) return false;
2059
+ nextLines.splice(assetHeader + 1, 0, replacement);
2060
+ }
2061
+ const next = nextLines.join("\n");
2062
+ if (next === source) return false;
2063
+ writeFileSync(p, next, "utf8");
2064
+ return true;
2065
+ }
2066
+
1770
2067
  /**
1771
2068
  * THE LAYERED-PACKAGES EMIT (`dart.packages`) — three sibling pub packages under `dartOut`:
1772
2069
  * `<types>/` (pure data, depends only on `nomos_types`), `<client>/` (effectful runtime clients,
@@ -1787,7 +2084,7 @@ function emitDartPackages(
1787
2084
  names: Readonly<Record<string, string>> | undefined,
1788
2085
  compat?: NomosCompat,
1789
2086
  describePackageName = packages.client,
1790
- deployJsonPath?: string,
2087
+ lawUsdzPath?: string,
1791
2088
  ): { written: string[]; cursed: string[] } {
1792
2089
  for (const [role, name] of Object.entries(packages)) {
1793
2090
  if (typeof name !== "string" || !isPubPackageName(name)) {
@@ -1842,6 +2139,7 @@ function emitDartPackages(
1842
2139
  !m[1]!.startsWith("dart:") &&
1843
2140
  m[1] !== "package:nomos_types/nomos_types.dart" &&
1844
2141
  m[1] !== "package:nomos_types/src/nomos_ref.dart" &&
2142
+ m[1] !== "package:nomos_types/src/versioned_nomos_ref.dart" &&
1845
2143
  m[1] !== "package:nomos_types/src/foreign_ref.dart" &&
1846
2144
  m[1] !== "package:nomos_types/src/invitation.dart" &&
1847
2145
  m[1] !== `${FRAMEWORK_FILE}.dart` &&
@@ -1972,11 +2270,14 @@ function emitDartPackages(
1972
2270
  );
1973
2271
  const businessSharedFile = "business_shared.dart";
1974
2272
  const businessSharedPath = path.join(typesGen, businessSharedFile);
2273
+ const sha256DigestExport = [...typesSources.values()].some((source) => source.includes("Sha256Digest"))
2274
+ ? ", Sha256Digest"
2275
+ : "";
1975
2276
  writeFileSync(
1976
2277
  businessSharedPath,
1977
2278
  [
1978
2279
  "// GENERATED by nomos-compile — shared business values and activity; no runtime machinery.",
1979
- `export 'package:nomos_types/nomos_types.dart' show NomosRef, Identity, User, Recipient, Invitation, ActivityEvent, ActivityRef, ActivitySeverity, SurfacedObservation, SurfacedValue, SurfacedResolved, SurfacedPending, SurfacedEvolutionRequired;`,
2280
+ `export 'package:nomos_types/nomos_types.dart' show NomosRef, NomosOptionalRef, VersionedNomosRef, ReferenceFreshness, ReferenceCurrent, ReferenceStale, ReferenceFreshnessUnavailable${sha256DigestExport}, Identity, User, Recipient, Invitation, ActivityEvent, ActivityRef, ActivitySeverity, SurfacedObservation, SurfacedValue, SurfacedResolved, SurfacedPending, SurfacedEvolutionRequired;`,
1980
2281
  ...(sharedBusinessTypeNames.length === 0
1981
2282
  ? []
1982
2283
  : [`export '${FRAMEWORK_FILE}.dart' show ${sharedBusinessTypeNames.join(", ")};`]),
@@ -2204,11 +2505,11 @@ function emitDartPackages(
2204
2505
  // ── the TEST package (optional) — generated business scenarios, test-only dependency ──
2205
2506
  if (packages.test !== undefined) {
2206
2507
  const testGen = freshGenDir(packages.test);
2207
- if (deployJsonPath === undefined) fail("dart.packages.test requires the compiled application package");
2208
- const embeddedDeployName = `${describePackageName}.deploy.json`;
2209
- const embeddedDeployPath = path.join(testGen, embeddedDeployName);
2210
- copyFileSync(deployJsonPath, embeddedDeployPath);
2211
- written.push(embeddedDeployPath);
2508
+ if (lawUsdzPath === undefined) fail("dart.packages.test requires the compiled canonical law archive");
2509
+ const embeddedLawName = `${describePackageName}.law.usdz`;
2510
+ const embeddedLawPath = path.join(testGen, embeddedLawName);
2511
+ copyFileSync(lawUsdzPath, embeddedLawPath);
2512
+ written.push(embeddedLawPath);
2212
2513
  const scenarioSurfaces = perDomain.length > 1
2213
2514
  ? (() => {
2214
2515
  const topology = resolveApplicationTopology(modules);
@@ -2241,7 +2542,7 @@ function emitDartPackages(
2241
2542
  dd.modForDart,
2242
2543
  packages.client,
2243
2544
  packages.test,
2244
- embeddedDeployName,
2545
+ embeddedLawName,
2245
2546
  {
2246
2547
  surfaceName,
2247
2548
  applicationFile,
@@ -2284,11 +2585,15 @@ function emitDartPackages(
2284
2585
  ` test: ^1.25.0`,
2285
2586
  `flutter:`,
2286
2587
  ` assets:`,
2287
- ` - lib/${genRel}/${embeddedDeployName}`,
2588
+ ` - lib/${genRel}/${embeddedLawName}`,
2288
2589
  ``,
2289
2590
  ].join("\n"),
2290
2591
  { nomos_client: NOMOS_CLIENT_DEP },
2291
2592
  );
2593
+ reconcileGeneratedLawAsset(
2594
+ pkgDir(packages.test),
2595
+ `lib/${genRel}/${embeddedLawName}`,
2596
+ );
2292
2597
  }
2293
2598
 
2294
2599
  // ── the COMPAT package (optional, MIGRATION-ONLY) — pure `dart.names` typedefs over the types package ──
@@ -2365,20 +2670,26 @@ function emitDartPackages(
2365
2670
 
2366
2671
  async function main(): Promise<void> {
2367
2672
  const argv = process.argv.slice(2);
2368
- const supportedFlags = new Set(["--require-usdcat", "--compact", "--analyze"]);
2673
+ const supportedFlags = new Set(["--require-usdcat", "--compact", "--analyze", "--preflight"]);
2369
2674
  for (const flag of argv.filter((arg) => arg.startsWith("--"))) {
2370
2675
  if (supportedFlags.has(flag)) continue;
2371
2676
  if (flag === "--layered") {
2372
- fail("--layered no longer exists: every package is a USD layer stack and every compile proves its flattened law");
2677
+ fail("--layered no longer exists: every law is a composed typed USDA layer stack");
2373
2678
  }
2374
2679
  fail(`unknown compile flag '${flag}'`);
2375
2680
  }
2376
2681
  const requireUsdcat = argv.includes("--require-usdcat") || process.env.NOMOS_REQUIRE_USDCAT === "1";
2377
2682
  const compactFlag = argv.includes("--compact") || process.env["NOMOS_COMPACT"] === "1";
2683
+ // Fast source/law feedback for large tenant packages. This deliberately stops
2684
+ // before plan bundling, USD emission, Dart generation and Dart dependency
2685
+ // checks; it is a strict author-time gate, not a substitute for a release
2686
+ // compile. Treat warnings as failures here so a suspect birth recipe never
2687
+ // spends minutes producing artifacts that will immediately be discarded.
2688
+ const preflightFlag = argv.includes("--preflight") || process.env.NOMOS_PREFLIGHT === "1";
2378
2689
  // §"Shipped (1–4)" follow-up: `githolon compile --analyze` (or NOMOS_ANALYZE=1) runs
2379
2690
  // `dart analyze` on the generated Dart package after emission and fails the compile on red.
2380
2691
  const analyzeFlag = argv.includes("--analyze") || process.env.NOMOS_ANALYZE === "1";
2381
- const configArg = argv.find((a) => !a.startsWith("--")) ?? "nomos.package.mjs";
2692
+ const configArg = argv.find((a) => !a.startsWith("--")) ?? "nomos.project.ts";
2382
2693
  const configPath = path.resolve(process.cwd(), configArg);
2383
2694
  if (!existsSync(configPath)) fail(`config not found: ${configPath}`);
2384
2695
  const cfgDir = path.dirname(configPath);
@@ -2421,26 +2732,33 @@ async function main(): Promise<void> {
2421
2732
  fail(`application composition refused: ${(error as Error).message}`);
2422
2733
  }
2423
2734
  }
2424
- const cfg = configValue as PackageConfig | undefined;
2425
- if (!cfg || typeof cfg.name !== "string" || !Array.isArray(cfg.domains) || cfg.domains.length === 0) {
2426
- fail(`config must default-export { name, domains: [{ key, modules }, ...] }`);
2735
+ let cfg: PackageConfig;
2736
+ let runtimeDependencies: readonly RuntimePackageDependency[];
2737
+ try {
2738
+ const validated = validateNomosPackageConfig(configValue);
2739
+ cfg = validated.config as PackageConfig;
2740
+ runtimeDependencies = validated.runtimeDependencies;
2741
+ } catch (error) {
2742
+ fail(`package contract refused: ${(error as Error).message}`);
2427
2743
  }
2428
2744
  if (Object.prototype.hasOwnProperty.call(cfg, "layered")) {
2429
- fail("nomos.package.mjs no longer accepts 'layered': every package is layered, with no alternate compile lane");
2745
+ fail("nomos.project.ts no longer accepts 'layered': every law closure is layered, with no alternate compile lane");
2430
2746
  }
2431
- // ANY readable name works ("tool-library", "tool_library", "Tool Library"):
2432
- // artifact FILENAMES keep it verbatim; generated SYMBOLS normalize it
2433
- // (toolLibraryClient, TOOL_LIBRARY_DOMAIN_HASH — see codegen_ts.ts).
2434
- if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(cfg.name)) {
2435
- fail(`package name '${cfg.name}' is not a safe basename (letters, digits, spaces, '.', '-', '_')`);
2747
+ try {
2748
+ assertPublicPackageImports(
2749
+ cfg.domains.flatMap((domain) => domain.modules.map((module) => path.resolve(cfgDir, module))),
2750
+ );
2751
+ } catch (error) {
2752
+ fail(`package import boundary refused: ${(error as Error).message}`);
2436
2753
  }
2437
-
2438
- // EVERY GENERATED APPLICATION IS A COMPLETE PRODUCT ENTRYPOINT. The compiler privately appends the
2439
- // authority bootstrap law that turns verified credential evidence into durable device authorship. Tenant
2440
- // config never names this domain and presentation filters it back out: a package cannot emit the generated
2441
- // application facade while omitting the private machinery needed to open it. The deliberately obscure key
2442
- // reduces collision likelihood, but collision handling is still fail-closed: never reinterpret a tenant's
2443
- // same-named concept as framework machinery.
2754
+ // EVERY PUBLISHED APPLICATION IS A COMPLETE PRODUCT ENTRYPOINT. `rootPart` is the explicit OpenUSD
2755
+ // application/package boundary: only that independently published root privately receives the authority
2756
+ // bootstrap law which turns verified credential evidence into durable device authorship. Ordinary law
2757
+ // dependencies (including stage-zero and the Nomos controller) must not each invent another application
2758
+ // home; doing so makes the composed runtime expose several equally valid first-device routes. Tenant config
2759
+ // never names the private domain and presentation filters it back out. The deliberately obscure key reduces
2760
+ // collision likelihood, but collision handling is still fail-closed: never reinterpret a tenant's same-named
2761
+ // concept as framework machinery.
2444
2762
  if (cfg.domains.some((d) => d.key === APPLICATION_HOME_DOMAIN)) {
2445
2763
  fail(
2446
2764
  `domain key '${APPLICATION_HOME_DOMAIN}' is reserved by Nomos for private application authority law; ` +
@@ -2449,14 +2767,32 @@ async function main(): Promise<void> {
2449
2767
  }
2450
2768
  const domainsToCompile: DomainConfig[] = [
2451
2769
  ...cfg.domains,
2452
- {
2453
- key: APPLICATION_HOME_DOMAIN,
2454
- modules: [path.join(DSL_DIR, "src", "framework", "application_home.ts")],
2455
- },
2770
+ ...(cfg.rootPart === undefined
2771
+ ? []
2772
+ : [{
2773
+ key: APPLICATION_HOME_DOMAIN,
2774
+ modules: [path.join(DSL_DIR, "src", "framework", "application_home.ts")],
2775
+ }]),
2456
2776
  ];
2457
2777
 
2458
2778
  const outDir = path.resolve(cfgDir, cfg.outDir ?? "build");
2459
2779
  mkdirSync(outDir, { recursive: true });
2780
+ // Interrupted pre-cutover compiles left thousands of private source/probe files beside public
2781
+ // artifacts. They are neither custody nor diagnostics; clear only the compiler-owned namespace.
2782
+ for (const entry of readdirSync(outDir)) {
2783
+ if (entry.startsWith(".nomos_")) rmSync(path.join(outDir, entry), { recursive: true, force: true });
2784
+ }
2785
+ // Cutover hygiene: a compiler rerun must not leave retired law representations looking current.
2786
+ // These were produced by the pre-v3 flatten/reconstruction path and have no runtime authority.
2787
+ for (const retired of [
2788
+ `${cfg.name}.package.usda`,
2789
+ `${cfg.name}.layered.usda`,
2790
+ `${cfg.name}.law-closure.json`,
2791
+ `${cfg.name}.deploy.json`,
2792
+ ]) rmSync(path.join(outDir, retired), { force: true });
2793
+ for (const retiredDir of ["layers", "v3-layers", `${cfg.name}.relation-layers`]) {
2794
+ rmSync(path.join(outDir, retiredDir), { recursive: true, force: true });
2795
+ }
2460
2796
  // The package-wide executable lane no longer exists. Remove its ignored build artifact on every
2461
2797
  // native compile so a developer cannot accidentally inspect or execute stale bytes as canonical law.
2462
2798
  rmSync(path.join(outDir, ".nomos_lump.js"), { force: true });
@@ -2471,7 +2807,30 @@ async function main(): Promise<void> {
2471
2807
  // Every executable is an independently addressable lazy plan asset. There is no package-wide
2472
2808
  // eager/monolithic execution mode: keeping that alternative would let it become canonical again.
2473
2809
  const entryDomainSourceByKey = new Map<string, string>();
2810
+ const entryPreloadSourceByKey = new Map<string, string>();
2474
2811
  const entryMintInstallByKey = new Map<string, string>();
2812
+ const entryGeneratedSourceExprsByKey = new Map<string, readonly string[]>();
2813
+ const executableDirectiveIdsByDomain = new Map<string, readonly string[]>();
2814
+ const executableDirectivesByDomain = new Map<string, ReadonlyMap<
2815
+ string,
2816
+ Parameters<typeof synthesizePreconditionInvariants>[0]
2817
+ >>();
2818
+ const offerSourceExportsByDomain = new Map<string, ReadonlyMap<string, {
2819
+ readonly modulePath: string;
2820
+ readonly exportName: string;
2821
+ readonly aggregateId: string;
2822
+ }>>();
2823
+ const workspaceInvariantSourceExportsByDomain = new Map<string, ReadonlyMap<string, {
2824
+ readonly on: string;
2825
+ readonly modulePath: string;
2826
+ readonly exportName: string;
2827
+ readonly remappedIntent?: {
2828
+ readonly modulePath: string;
2829
+ readonly exportName: string;
2830
+ };
2831
+ }>>();
2832
+ const nativeWorkspaceInvariantIds = new Set<string>();
2833
+ const nativeWorkspaceInvariantReadIds = new Set<string>();
2475
2834
  const routingInput: Record<string, Mod[]> = {};
2476
2835
  const domainModules: DomainModule[] = [];
2477
2836
  // THE SELF-DESCRIBE DECLS (G1): per domain key, the FINISHED plain read/maintenance decls the
@@ -2480,26 +2839,61 @@ async function main(): Promise<void> {
2480
2839
  // pass finished decls through), serialized into the generated entry as `openapiDecls`, so the
2481
2840
  // kernel-run describe reproduces THIS compile's `<name>.openapi.json` byte-for-byte.
2482
2841
  const openapiDeclsByDomain: Record<string, unknown> = {};
2483
- const layeredDomains: { key: string; inputs: LayeredModuleInput[] }[] = [];
2484
- /**
2485
- * THE CAPABILITY LOWERING (capability_marketplace.md §4 — the deploy-warning
2486
- * prerequisite + the executor's task→capability→domain map). Rides the
2487
- * UNHASHED deploy sidecar only (a top-level deploy-body key, like
2488
- * `dispositions`) — never the read manifest the wasm parses, never any
2489
- * hash-bearing artifact: declaring a capability moves no identity hash, and
2490
- * capability-free deploy bodies stay byte-identical (omit-when-empty).
2491
- */
2492
- const capabilityLowerings: {
2493
- capability: string;
2494
- domain: string;
2495
- aggregateId: string;
2496
- obligation: string;
2497
- complete: string;
2498
- fail: string;
2499
- block: string;
2500
- deadLetter: string;
2501
- tasksQueryId: string | null;
2502
- }[] = [];
2842
+ // A tenant module may import declarations from another configured law module (business-birth
2843
+ // composition is the common case). Ordinary ES evaluation then initializes that entire closure
2844
+ // inside one `require()` thunk. Discover those configured-module edges once and emit them as
2845
+ // warm-only thunks: one dependency per maintenance quantum, never merged into the caller's domain.
2846
+ const configuredModulePaths = new Set(
2847
+ domainsToCompile.flatMap((domain) => domain.modules.map((rel) => path.resolve(cfgDir, rel))),
2848
+ );
2849
+ const configuredDependencies = new Map<string, string[]>();
2850
+ const resolveConfiguredImport = (importer: string, specifier: string): string | undefined => {
2851
+ if (!specifier.startsWith(".")) return undefined;
2852
+ const raw = path.resolve(path.dirname(importer), specifier);
2853
+ const ext = path.extname(raw);
2854
+ const candidates = [
2855
+ raw,
2856
+ ...(ext === ".js" || ext === ".mjs" || ext === ".cjs"
2857
+ ? [raw.slice(0, -ext.length) + ".ts", raw.slice(0, -ext.length) + ".tsx"]
2858
+ : []),
2859
+ ...(ext === "" ? [`${raw}.ts`, `${raw}.tsx`, path.join(raw, "index.ts")] : []),
2860
+ ];
2861
+ return candidates.find((candidate) => configuredModulePaths.has(candidate));
2862
+ };
2863
+ for (const modulePath of configuredModulePaths) {
2864
+ if (!existsSync(modulePath)) continue;
2865
+ const imports = ts.preProcessFile(readFileSync(modulePath, "utf8"), true, true).importedFiles;
2866
+ configuredDependencies.set(
2867
+ modulePath,
2868
+ imports
2869
+ .map(({ fileName }) => resolveConfiguredImport(modulePath, fileName))
2870
+ .filter((dependency): dependency is string => dependency !== undefined),
2871
+ );
2872
+ }
2873
+ const preloadDependenciesFor = (domain: DomainConfig): string[] => {
2874
+ const roots = domain.modules.map((rel) => path.resolve(cfgDir, rel));
2875
+ const ownIndex = new Map(roots.map((modulePath, index) => [modulePath, index]));
2876
+ const ordered: string[] = [];
2877
+ const added = new Set<string>();
2878
+ const visiting = new Set<string>();
2879
+ const visit = (modulePath: string, rootIndex: number): void => {
2880
+ if (visiting.has(modulePath)) return;
2881
+ visiting.add(modulePath);
2882
+ for (const dependency of configuredDependencies.get(modulePath) ?? []) {
2883
+ const dependencyIndex = ownIndex.get(dependency);
2884
+ // An earlier source in this domain has already initialized by the time this source runs.
2885
+ if (dependencyIndex !== undefined && dependencyIndex <= rootIndex) continue;
2886
+ visit(dependency, dependencyIndex ?? rootIndex);
2887
+ if (!added.has(dependency)) {
2888
+ added.add(dependency);
2889
+ ordered.push(dependency);
2890
+ }
2891
+ }
2892
+ visiting.delete(modulePath);
2893
+ };
2894
+ roots.forEach((modulePath, index) => visit(modulePath, index));
2895
+ return ordered;
2896
+ };
2503
2897
 
2504
2898
  for (const d of domainsToCompile) {
2505
2899
  if (typeof d.key !== "string" || !Array.isArray(d.modules) || d.modules.length === 0) {
@@ -2507,12 +2901,48 @@ async function main(): Promise<void> {
2507
2901
  }
2508
2902
  const mods: Mod[] = [];
2509
2903
  const sourceExprs: string[] = [];
2904
+ const directOfferExports = new Map<string, { modulePath: string; exportName: string; aggregateId: string }>();
2905
+ const directWorkspaceInvariantExports = new Map<string, {
2906
+ modulePath: string;
2907
+ exportName: string;
2908
+ }>();
2510
2909
  for (const rel of d.modules) {
2511
2910
  const abs = path.resolve(cfgDir, rel);
2512
2911
  if (!existsSync(abs)) fail(`domain '${d.key}': module not found: ${abs}`);
2513
2912
  const m = (await import(pathToFileURL(abs).href)) as Mod;
2514
2913
  mods.push(m);
2515
2914
  sourceExprs.push(`() => require(${JSON.stringify(abs)})`);
2915
+ for (const [exportName, value] of Object.entries(m)) {
2916
+ if (isWorkspaceInvariantDecl(value)) {
2917
+ directWorkspaceInvariantExports.set(value.id, { modulePath: abs, exportName });
2918
+ }
2919
+ if (
2920
+ value !== null && typeof value === "object" &&
2921
+ typeof (value as { id?: unknown }).id === "string" &&
2922
+ typeof (value as { plan?: unknown }).plan === "function" &&
2923
+ typeof (value as { aggregateId?: unknown }).aggregateId === "string"
2924
+ ) {
2925
+ directOfferExports.set((value as { id: string }).id, {
2926
+ modulePath: abs,
2927
+ exportName,
2928
+ aggregateId: (value as { aggregateId: string }).aggregateId,
2929
+ });
2930
+ }
2931
+ // `birth(Aggregate)` is intentionally a high-level declaration. Its public parent birth and
2932
+ // private child seed are compiler-lowered executable offers, so preserve their authored
2933
+ // symbol origin here rather than condemning both to the whole-domain fallback plan.
2934
+ if (
2935
+ value !== null && typeof value === "object" &&
2936
+ (value as { __isBusinessBirth?: unknown }).__isBusinessBirth === true &&
2937
+ typeof (value as { aggregate?: { id?: unknown } }).aggregate?.id === "string"
2938
+ ) {
2939
+ const stem = ((value as { aggregate: { id: string } }).aggregate.id)
2940
+ .replace(/[^A-Za-z0-9]/g, "");
2941
+ const aggregateId = (value as { aggregate: { id: string } }).aggregate.id;
2942
+ directOfferExports.set(`nomosBirth${stem}`, { modulePath: abs, exportName, aggregateId });
2943
+ directOfferExports.set(`nomosSeed${stem}`, { modulePath: abs, exportName, aggregateId });
2944
+ }
2945
+ }
2516
2946
  }
2517
2947
  let merged: Mod = {};
2518
2948
  for (const m of mods) merged = { ...merged, ...m };
@@ -2627,12 +3057,20 @@ async function main(): Promise<void> {
2627
3057
 
2628
3058
  const domainSource = ` ${JSON.stringify(d.key)}: [${sourceExprs.join(", ")}],`;
2629
3059
  entryDomainSourceByKey.set(d.key, domainSource);
3060
+ entryGeneratedSourceExprsByKey.set(d.key, sourceExprs.slice(d.modules.length));
3061
+ const preloadDependencies = preloadDependenciesFor(d);
3062
+ if (preloadDependencies.length > 0) {
3063
+ entryPreloadSourceByKey.set(
3064
+ d.key,
3065
+ ` ${JSON.stringify(d.key)}: [${preloadDependencies.map((abs) => `() => { require(${JSON.stringify(abs)}); }`).join(", ")}],`,
3066
+ );
3067
+ }
2630
3068
  routingInput[d.key] = mods;
2631
3069
  // ── THE AUX INVARIANT-ROUTING KEY (#47, sharding slice 7 — the invariant-gate
2632
3070
  // flame pass): in a LAZY lump, the derived sharding law registers a SECOND
2633
3071
  // time under its own domain key, and the ROUTING table points the sharding
2634
3072
  // workspace-invariant ids at THAT key only. A per-write homing-invariant
2635
- // dispatch (`workspaceInvariantReads`/`workspaceInvariant`) then forces ONLY
3073
+ // assertion dispatch then forces ONLY
2636
3074
  // the small framework module — never the tenant domain's whole zod/module
2637
3075
  // graph (the measured ~30 ms-per-dispatch floor on co2 estate_structures).
2638
3076
  // Directive dispatch is untouched (the sharding directives stay registered
@@ -2657,6 +3095,37 @@ async function main(): Promise<void> {
2657
3095
  ...discover<QueryDecl>(merged, isQueryDecl),
2658
3096
  ...resolveNamed<QueryDecl>(merged, d.queries, "queries"),
2659
3097
  ];
3098
+ // A directive's `.reads(query)` already carries the complete typed QueryDecl. Requiring the
3099
+ // same query to be separately exported made a correct, private captured read disappear from
3100
+ // law discovery and fail later as "reads unknown query". Seat every referenced declaration in
3101
+ // the domain automatically. It stays implementation-private unless the author also exports it;
3102
+ // export is presentation, `.reads(...)` is the executable-law dependency.
3103
+ {
3104
+ const byId = new Map(queries.map((query) => [query.id, query] as const));
3105
+ for (const directive of directivesOf(merged)) {
3106
+ for (const declared of directive.declaredQueryReads ?? []) {
3107
+ const existing = byId.get(declared.id);
3108
+ if (existing !== undefined) {
3109
+ if (existing.returns !== declared.returns ||
3110
+ JSON.stringify(existing.key) !== JSON.stringify(declared.key)) {
3111
+ fail(
3112
+ `domain '${d.key}': captured read '${declared.id}' has conflicting declarations ` +
3113
+ `(exported ${existing.returns}[${existing.key.join(",")}], directive ` +
3114
+ `${directive.id} ${declared.returns}[${declared.key.join(",")}])`,
3115
+ );
3116
+ }
3117
+ continue;
3118
+ }
3119
+ const internal = {
3120
+ ...declared,
3121
+ businessBirthInternal: true as const,
3122
+ } as QueryDecl;
3123
+ queries.push(internal);
3124
+ byId.set(internal.id, internal);
3125
+ }
3126
+ }
3127
+ queries.sort((left, right) => left.id.localeCompare(right.id));
3128
+ }
2660
3129
  // METERED BIRTH (slice A): a domain declaring ≥1 `.births()` directive gets the meter's declared
2661
3130
  // queries appended (dedup by id) so the openapi self-describe decls + read routing carry them —
2662
3131
  // the same auto-ensure `composeDomainModule` applies to the manifest half.
@@ -2750,22 +3219,6 @@ async function main(): Promise<void> {
2750
3219
  const capByAggregate = new Map<string, ImpureCapabilityDecl>();
2751
3220
  for (const c of [...discoveredCapabilities, ...namedCapabilities]) capByAggregate.set(c.aggregate.id, c);
2752
3221
  const impureCapabilities = [...capByAggregate.values()];
2753
- for (const c of impureCapabilities) {
2754
- const meta = c as unknown as { name?: unknown; capability?: unknown; tasks?: { id?: unknown } };
2755
- const cap = typeof meta.name === "string" ? meta.name : typeof meta.capability === "string" ? meta.capability : null;
2756
- if (cap === null) continue; // a binding-free quartet lowers nothing (omit-when-absent)
2757
- capabilityLowerings.push({
2758
- capability: cap,
2759
- domain: d.key,
2760
- aggregateId: c.aggregate.id,
2761
- obligation: c.obligation.id,
2762
- complete: c.complete.id,
2763
- fail: c.fail.id,
2764
- block: c.block.id,
2765
- deadLetter: c.deadLetter.id,
2766
- tasksQueryId: typeof meta.tasks?.id === "string" ? meta.tasks.id : null,
2767
- });
2768
- }
2769
3222
  const extraAggregates = resolveNamed<AggregateHandle>(merged, d.extraAggregates, "extraAggregates");
2770
3223
 
2771
3224
  // Dart-codegen hints (frontend-only — never law; see DomainConfig):
@@ -2813,6 +3266,54 @@ async function main(): Promise<void> {
2813
3266
  // no-path / ambiguous / cross-home / unroutable law is a NAMED COMPILE
2814
3267
  // ERROR here — never a domain silently recorded as identity-excluded.
2815
3268
  const composed = domainModules[domainModules.length - 1]!;
3269
+ const executableDirectives = (composed.directives ?? []).filter(
3270
+ (directive) => (directive as { manifestOnly?: unknown }).manifestOnly !== true,
3271
+ );
3272
+ const executableDirectiveIds = executableDirectives.map((directive) => directive.id).sort();
3273
+ executableDirectiveIdsByDomain.set(d.key, executableDirectiveIds);
3274
+ executableDirectivesByDomain.set(
3275
+ d.key,
3276
+ new Map(executableDirectives.map((directive) => [directive.id, directive])),
3277
+ );
3278
+ offerSourceExportsByDomain.set(
3279
+ d.key,
3280
+ new Map([...directOfferExports].filter(([directiveId]) => executableDirectiveIds.includes(directiveId))),
3281
+ );
3282
+ const invariantSources = new Map<string, {
3283
+ on: string;
3284
+ modulePath: string;
3285
+ exportName: string;
3286
+ remappedIntent?: { modulePath: string; exportName: string };
3287
+ }>();
3288
+ for (const invariant of composed.workspaceInvariants ?? []) {
3289
+ if (invariant.__nomosEvaluator !== undefined) {
3290
+ nativeWorkspaceInvariantIds.add(invariant.id);
3291
+ continue;
3292
+ }
3293
+ if (invariant.__nomosReadBindings !== undefined) {
3294
+ nativeWorkspaceInvariantReadIds.add(invariant.id);
3295
+ }
3296
+ const direct = directWorkspaceInvariantExports.get(invariant.id);
3297
+ if (direct !== undefined) {
3298
+ invariantSources.set(invariant.id, { ...direct, on: invariant.on });
3299
+ continue;
3300
+ }
3301
+ const original = [...directWorkspaceInvariantExports].find(([id]) =>
3302
+ invariant.id === `${invariant.on}__${id}`
3303
+ );
3304
+ const remappedIntent = directOfferExports.get(invariant.on);
3305
+ if (original !== undefined && remappedIntent !== undefined) {
3306
+ invariantSources.set(invariant.id, {
3307
+ ...original[1],
3308
+ on: invariant.on,
3309
+ remappedIntent: {
3310
+ modulePath: remappedIntent.modulePath,
3311
+ exportName: remappedIntent.exportName,
3312
+ },
3313
+ });
3314
+ }
3315
+ }
3316
+ workspaceInvariantSourceExportsByDomain.set(d.key, invariantSources);
2816
3317
 
2817
3318
  // ── THE SELF-DESCRIBE DECLS (G1): the FINISHED plain read/maintenance decls the lump's
2818
3319
  // `describe` reconstructs the openapi fragment from. Directives + aggregates are read from
@@ -2885,169 +3386,88 @@ async function main(): Promise<void> {
2885
3386
  }
2886
3387
  }
2887
3388
 
2888
- // ── ONE USD LAYER PER MODULE (Φ applied per module, not once over the
2889
- // composed fold) — the layered emission `usd_layers.ts` proves flattens back
2890
- // to the EXACT canonical IR (asserted below, fail-closed). Per-module decls
2891
- // are discovered the same way as above, on each module's OWN exports; config
2892
- // export-names resolve on whichever module actually exports them; config
2893
- // `extraAggregates` ride the FIRST (weakest) layer, prelude-style. Compiler-
2894
- // discovered reads are seated beside the aggregate schema they read.
2895
- {
2896
- const usedStems = new Set<string>();
2897
- let inputs: LayeredModuleInput[] = mods.map((m, i) => {
2898
- const authoredPath = d.modules[i];
2899
- let stem = authoredPath === undefined
2900
- ? `nomos-derived-${i + 1}`
2901
- : path.basename(authoredPath).replace(/\.[^.]*$/, "");
2902
- for (let n = 2; usedStems.has(stem); n++) stem = `${stem}-${n}`;
2903
- usedStems.add(stem);
2904
-
2905
- const presentNames = (names: readonly string[] | undefined) =>
2906
- (names ?? []).filter((n) => (m as Record<string, unknown>)[n] !== undefined);
2907
- const layerQueries = [
2908
- ...discover<QueryDecl>(m, isQueryDecl),
2909
- ...resolveNamed<QueryDecl>(m, presentNames(d.queries), "queries"),
2910
- ];
2911
- const layerDeriveds = [
2912
- ...discover<DerivedDecl>(m, isDerivedDecl),
2913
- ...resolveNamed<DerivedDecl>(m, presentNames(d.deriveds), "deriveds"),
2914
- ];
2915
- const layerCombineds = [
2916
- ...discover<CombinedDecl>(m, isCombinedDecl),
2917
- ...resolveNamed<CombinedDecl>(m, presentNames(d.combineds), "combineds"),
2918
- ];
2919
- // Lower the module through the SAME compiler-owned expansion seam as the
2920
- // canonical fold. A hand-built `{aggregates,directives}` approximation silently
2921
- // dropped relation, invariant, attestation, flow and projection opinions, so the
2922
- // pretty USD stack was not the law. Single-module composition retains those
2923
- // generated opinions in the layer where their authored cause lives.
2924
- const module = composeDomainModule({
2925
- name: d.name ?? d.key,
2926
- domain: d.key,
2927
- modules: [m],
2928
- ...(i === 0 && extraAggregates.length > 0 ? { extraAggregates } : {}),
2929
- // Compiler-synthesized/domain-wide declarations have no honest authored
2930
- // module owner. Seat them once in the weakest prelude layer so USD and
2931
- // Nomos both see one contribution instead of duplicating them per source file.
2932
- ...(i === 0 && (composed.workspaceInvariants?.length ?? 0) > 0
2933
- ? { workspaceInvariants: composed.workspaceInvariants }
2934
- : {}),
2935
- ...(i === 0 && workspaceTypes.length > 0 ? { workspaceTypes } : {}),
2936
- ...(i === 0 && impureCapabilities.length > 0 ? { impureCapabilities } : {}),
2937
- ...(layerQueries.length > 0 ? { queries: layerQueries } : {}),
2938
- ...(layerDeriveds.length > 0 ? { deriveds: layerDeriveds } : {}),
2939
- ...(layerCombineds.length > 0 ? { combineds: layerCombineds } : {}),
2940
- });
2941
- return { name: stem, module };
2942
- });
2943
- if (inputs[0] !== undefined) {
2944
- // Work on shallow module copies: package-owned declarations usually belong
2945
- // in the weakest prelude, but a read recipe belongs beside the aggregate
2946
- // schema it reads. Putting a compiler-added query in an unrelated prelude
2947
- // makes that individual USD layer ill-typed even though the whole stack has
2948
- // the target in a stronger layer.
2949
- inputs = inputs.map((input) => ({
2950
- ...input,
2951
- module: { ...input.module } as DomainModule,
2952
- }));
2953
- // Config-named and compiler-derived reads are package opinions rather than
2954
- // exports of one source file. Seat only declarations missing from every
2955
- // authored layer, once. Reads sit beside their returned aggregate; all other
2956
- // domain-wide declarations sit in the weakest prelude.
2957
- for (const field of [
2958
- "queries",
2959
- "counts",
2960
- "spatials",
2961
- "sums",
2962
- "mins",
2963
- "maxes",
2964
- "deriveds",
2965
- "combineds",
2966
- "workspaceTypes",
2967
- "workspaceInvariants",
2968
- "orderedReads",
2969
- "exists_",
2970
- ] as const) {
2971
- const canonical = (composed as unknown as Record<string, unknown>)[field];
2972
- if (!Array.isArray(canonical)) continue;
2973
- // A package-level language may be declared in a different source module from the
2974
- // aggregate its generated read indexes (workspaceSharing is the canonical case).
2975
- // Single-module composition then discovers the read in the language module even though
2976
- // that layer cannot type it. Move every targeted declaration beside its aggregate schema
2977
- // before filling genuinely missing package opinions. This changes no composed law: one
2978
- // declaration id still contributes once; it merely gives every authored USD layer a
2979
- // self-contained, valid read projection.
2980
- for (const value of canonical) {
2981
- if (value === null || typeof value !== "object") continue;
2982
- const id = (value as { id?: unknown }).id;
2983
- if (typeof id !== "string") continue;
2984
- const declarationTarget = field === "queries"
2985
- ? (value as { returns?: unknown }).returns
2986
- : (["counts", "spatials", "sums", "mins", "maxes", "deriveds", "combineds"] as const)
2987
- .includes(field as never)
2988
- ? (value as { of?: unknown }).of
2989
- : undefined;
2990
- if (typeof declarationTarget !== "string") continue;
2991
- const ownerIndex = inputs.findIndex((input) =>
2992
- input.module.aggregates.some((aggregate) => aggregate.id === declarationTarget));
2993
- if (ownerIndex < 0) continue;
2994
- for (const [inputIndex, input] of inputs.entries()) {
2995
- if (inputIndex === ownerIndex) continue;
2996
- const module = input.module as DomainModule & Record<string, unknown>;
2997
- const existing = module[field];
2998
- if (!Array.isArray(existing)) continue;
2999
- (module as Record<string, unknown>)[field] = existing.filter((candidate) =>
3000
- candidate === null || typeof candidate !== "object" ||
3001
- (candidate as { id?: unknown }).id !== id);
3002
- }
3003
- const owner = inputs[ownerIndex]!.module as DomainModule & Record<string, unknown>;
3004
- const ownerValues = Array.isArray(owner[field]) ? owner[field] as unknown[] : [];
3005
- if (!ownerValues.some((candidate) =>
3006
- candidate !== null && typeof candidate === "object" &&
3007
- (candidate as { id?: unknown }).id === id)) {
3008
- (owner as Record<string, unknown>)[field] = [...ownerValues, value];
3009
- }
3010
- }
3011
- const present = new Set(inputs.flatMap((input) => {
3012
- const values = (input.module as unknown as Record<string, unknown>)[field];
3013
- return Array.isArray(values)
3014
- ? values.flatMap((value) => value !== null && typeof value === "object" && typeof (value as { id?: unknown }).id === "string"
3015
- ? [(value as { id: string }).id]
3016
- : [])
3017
- : [];
3018
- }));
3019
- const missing = canonical.filter((value) =>
3020
- value !== null && typeof value === "object" && typeof (value as { id?: unknown }).id === "string"
3021
- ? !present.has((value as { id: string }).id)
3022
- : true);
3023
- for (const value of missing) {
3024
- const declarationTarget = value !== null && typeof value === "object"
3025
- ? field === "queries"
3026
- ? (value as { returns?: unknown }).returns
3027
- : (["counts", "spatials", "sums", "mins", "maxes", "deriveds", "combineds"] as const).includes(field as never)
3028
- ? (value as { of?: unknown }).of
3029
- : undefined
3030
- : undefined;
3031
- const ownerIndex = typeof declarationTarget === "string"
3032
- ? inputs.findIndex((input) => input.module.aggregates.some((aggregate) => aggregate.id === declarationTarget))
3033
- : -1;
3034
- const targetIndex = ownerIndex >= 0 ? ownerIndex : 0;
3035
- const target = inputs[targetIndex]!.module as DomainModule & Record<string, unknown>;
3036
- const existing = target[field];
3037
- target[field] = [...(Array.isArray(existing) ? existing : []), value];
3038
- }
3039
- }
3040
- }
3041
- layeredDomains.push({ key: d.key, inputs });
3042
- }
3389
+ }
3390
+
3391
+ const dependencyUseFindings = packageDependencyUsageFindings({
3392
+ packageName: cfg.name,
3393
+ exportedDomains: cfg.domains.map((domain) => domain.key),
3394
+ runtimeDependencies,
3395
+ modules: domainModules,
3396
+ });
3397
+ if (dependencyUseFindings.length > 0) {
3398
+ console.error(
3399
+ `nomos-compile: runtime package dependency contract refused ` +
3400
+ `(${dependencyUseFindings.length} cross-package reference error(s))`,
3401
+ );
3402
+ for (const line of formatFindings(dependencyUseFindings)) console.error(` ✗ ${line}`);
3403
+ process.exitCode = 1;
3404
+ return;
3043
3405
  }
3044
3406
 
3045
3407
  // Presentation is a separate concern from executable law. The private application/home domain remains
3046
3408
  // in `domainModules` for hashing, routing, self-description, replay and static checking, but generated
3047
3409
  // tenant clients/proofs/interfaces contain business vocabulary only.
3048
- const presentationDomainModules = closeComposedBusinessBirthPresentation(domainModules).filter(
3410
+ const closedDomainModules = closeComposedBusinessBirthPresentation(domainModules);
3411
+ const closedDomainModulesByName = new Map(closedDomainModules.map((module) => [module.name, module]));
3412
+ const presentationDomainModules = closedDomainModules.filter(
3049
3413
  (mod) => (mod.domain ?? mod.name) !== APPLICATION_HOME_DOMAIN,
3050
3414
  );
3415
+ // Run semantic author-time checks before stable-id reconciliation, plan bundling, codegen, or
3416
+ // custody emission. A statically invalid package should fail in seconds and must never leave a
3417
+ // plausible deployable artifact behind merely because the runtime representation changed shape.
3418
+ const authorTimeFindings = checkDomainModules(domainModules);
3419
+ // Self-description is a compiler-owned presentation projection, not executable law. Build it
3420
+ // once from the same composed modules and place it only in the separately routed describe plan;
3421
+ // exact offer/read plans must not carry OpenAPI generation and Zod JSON-Schema machinery.
3422
+ const runtimeOpenApiDocument = mergeOpenApiFragments(
3423
+ domainModules.map((module) => generateOpenApiFragment(module, { includeFrameworkRoutes: true })),
3424
+ { title: `${cfg.name} (nomos package)` },
3425
+ );
3426
+
3427
+ if (preflightFlag) {
3428
+ const businessFindings = authorTimeFindings.filter(
3429
+ (finding) =>
3430
+ finding.domain !== APPLICATION_HOME_DOMAIN &&
3431
+ finding.directiveId?.startsWith("nomos_internal_") !== true,
3432
+ );
3433
+ console.log(`nomos-preflight: ${cfg.name} (${domainModules.length} domain(s))`);
3434
+ if (businessFindings.length === 0) {
3435
+ console.log(" check ✓ author-time law checks clean; full compile is justified");
3436
+ return;
3437
+ }
3438
+ console.error(` check ${businessFindings.length} finding(s); full compile skipped:`);
3439
+ for (const line of formatFindings(businessFindings)) {
3440
+ const mark = line.includes("REFUSE") ? "✗" : line.includes("WARN") ? "⚠" : "•";
3441
+ console.error(` ${mark} ${line}`);
3442
+ }
3443
+ process.exitCode = 1;
3444
+ return;
3445
+ }
3446
+
3447
+ const isFrameworkAuthorTimeFinding = (finding: (typeof authorTimeFindings)[number]) =>
3448
+ finding.domain === APPLICATION_HOME_DOMAIN || finding.directiveId?.startsWith("nomos_internal_") === true;
3449
+ const earlyBusinessRefusals = authorTimeFindings.filter(
3450
+ (finding) => !isFrameworkAuthorTimeFinding(finding) && finding.level === "refuse",
3451
+ );
3452
+ const earlyPrivateRefusals = authorTimeFindings.filter(
3453
+ (finding) => isFrameworkAuthorTimeFinding(finding) && finding.level === "refuse",
3454
+ );
3455
+ if (earlyBusinessRefusals.length > 0) {
3456
+ console.error(`nomos-compile: author-time law check refused before plan compilation:`);
3457
+ for (const line of formatFindings(earlyBusinessRefusals)) console.error(` ✗ ${line}`);
3458
+ process.exitCode = 1;
3459
+ return;
3460
+ }
3461
+ if (earlyPrivateRefusals.length > 0) {
3462
+ console.error(
3463
+ `nomos-compile: Nomos lifecycle self-check failed inside this compiler release before plan compilation`,
3464
+ );
3465
+ if (process.env.NOMOS_DEBUG_FRAMEWORK === "1") {
3466
+ for (const line of formatFindings(earlyPrivateRefusals)) console.error(` ${line}`);
3467
+ }
3468
+ process.exitCode = 1;
3469
+ return;
3470
+ }
3051
3471
 
3052
3472
  // ── STABLE-ID CONTINUITY (#58 — names are labels, identity is minted) ──
3053
3473
  // Derive each composed domain's stable ids from the PRIOR compile: the committed
@@ -3063,11 +3483,17 @@ async function main(): Promise<void> {
3063
3483
  ? parseStableIdLockfile(JSON.parse(readFileSync(lockPath, "utf8")))
3064
3484
  : undefined;
3065
3485
  const inferredRenames: { domain: string; renames: InferredRename[] }[] = [];
3486
+ let nextIdentity = identityRegistryFromStableIdLockfile(priorLock);
3487
+ const typedLawGraphs = new Map<string, LawGraph>();
3066
3488
  // The lock is project-wide, not package-local. Several package configs commonly live beside one
3067
3489
  // application stage; compiling one must not erase the stable identity of domains owned by its siblings.
3068
3490
  // Current package domains are replaced below, while absent domains remain only as continuity metadata
3069
3491
  // and never leak into this package's interface, law, clients or deployment.
3070
- const nextLock: StableIdLockfile = { v: 1, domains: { ...(priorLock?.domains ?? {}) } };
3492
+ const nextLock: StableIdLockfile = {
3493
+ v: 1,
3494
+ domains: { ...(priorLock?.domains ?? {}) },
3495
+ identity: nextIdentity,
3496
+ };
3071
3497
  for (const mod of domainModules) {
3072
3498
  const identityName = mod.name;
3073
3499
  // The CURRENT canonical aggregate schemas (a pre-continuity manifest pass —
@@ -3079,23 +3505,18 @@ async function main(): Promise<void> {
3079
3505
  global?: boolean;
3080
3506
  }[];
3081
3507
  try {
3082
- const preManifest = domainManifest(mod);
3083
- currentAggs = preManifest.aggregates.map((a) => ({
3084
- id: a.id,
3085
- schema: a.schema,
3508
+ currentAggs = mod.aggregates.map((aggregate) => ({
3509
+ id: aggregate.id,
3510
+ schema: Object.fromEntries(
3511
+ Object.entries(encodeKernelSchema(aggregate)).sort(([left], [right]) => left.localeCompare(right)),
3512
+ ) as WireSchema,
3086
3513
  // One framework aggregate may be composed into many independently compiled
3087
3514
  // domain packages. Give that shared row one global stable-id scope so the
3088
3515
  // installed read manifest sees the same field contract from every package.
3089
- global: mod.aggregates.some(
3090
- (candidate) => candidate.id === a.id && !isNamespacedAggregate(candidate),
3091
- ),
3092
- // The CURRENT field kinds (off the pre-continuity stableIds emission) — the
3093
- // pair rule's kind axis ("rename + add a field" in one release still infers).
3094
- kinds: Object.fromEntries(
3095
- Object.entries(preManifest.stableIds?.aggregates[a.id]?.fields ?? {}).map(
3096
- ([f, v]) => [f, v.kind ?? ""] as [string, string],
3097
- ),
3098
- ),
3516
+ global: !isNamespacedAggregate(aggregate),
3517
+ // The CURRENT field kinds come straight from the typed field declarations. The rename
3518
+ // pair rule needs this axis, not a serialized manifest projection of it.
3519
+ kinds: Object.fromEntries(Object.entries(aggregate.fields).map(([field, value]) => [field, value.kind])),
3099
3520
  }));
3100
3521
  } catch {
3101
3522
  // A palette-gap domain has no canonical identity (recorded-and-excluded later);
@@ -3111,20 +3532,41 @@ async function main(): Promise<void> {
3111
3532
  prior = locked.stableIds;
3112
3533
  priorSchemas = locked.schemas;
3113
3534
  }
3114
- const { continuity, inferred } = deriveStableIdContinuity(
3535
+ const proposed = deriveStableIdContinuity(
3115
3536
  identityName,
3116
3537
  currentAggs,
3117
3538
  prior,
3118
3539
  priorSchemas,
3119
3540
  );
3541
+ const reconciled = reconcileStableIdsWithIdentity(
3542
+ nextIdentity,
3543
+ identityName,
3544
+ currentAggs.map((aggregate) => ({
3545
+ id: aggregate.id,
3546
+ fields: Object.keys(aggregate.schema),
3547
+ ...(aggregate.global === undefined ? {} : { global: aggregate.global }),
3548
+ })),
3549
+ proposed.continuity,
3550
+ proposed.inferred,
3551
+ );
3552
+ nextIdentity = reconciled.registry;
3553
+ nextLock.identity = nextIdentity;
3554
+ const continuity = reconciled.continuity;
3555
+ const inferred = proposed.inferred;
3556
+ // W4 typed compiler graph: reconcile independently-lived intent/read identities,
3557
+ // link every current target as a kind-safe ref, and refuse unresolved structure
3558
+ // before any USDA string is rendered. Later W4 stages move emitters/codegen onto
3559
+ // this same process-local graph; it is never serialized as another law IR.
3560
+ // Package-wide business-intent composition can make a directly callable child-birth route private
3561
+ // independently of domain declaration order. Build custody from that same closed view used by generated
3562
+ // clients/interfaces; using the pre-closure executable module here published a USDA-only `birth` route.
3563
+ const graphModule = closedDomainModulesByName.get(mod.name) ?? mod;
3564
+ const discoveredGraph = discoverLawGraph(graphModule, continuity, nextIdentity);
3565
+ nextIdentity = discoveredGraph.registry;
3566
+ nextLock.identity = nextIdentity;
3567
+ typedLawGraphs.set(identityName, discoveredGraph.graph);
3120
3568
  mod.stableIdContinuity = continuity;
3121
- // The layered emission's per-module layers must carry the SAME sids (the flatten
3122
- // homomorphism is byte-asserted), so the continuity rides every layer input too.
3123
- for (const ld of layeredDomains) {
3124
- if (ld.key === (mod.domain ?? mod.name)) {
3125
- for (const inp of ld.inputs) inp.module.stableIdContinuity = continuity;
3126
- }
3127
- }
3569
+ graphModule.stableIdContinuity = continuity;
3128
3570
  if (inferred.length > 0) inferredRenames.push({ domain: identityName, renames: inferred });
3129
3571
  // ── THE fromBirth EVOLVE GUARD (dart_codegen_parity.md): birth-presence may be declared at a
3130
3572
  // field's FIRST appearance (fresh sid — every row of the type is born under the invariant) or
@@ -3158,7 +3600,7 @@ async function main(): Promise<void> {
3158
3600
  // The lockfile records the EMITTED surface (sids + lineage + kinds — the next
3159
3601
  // compile's pair rule reads the kinds), not the bare continuity.
3160
3602
  nextLock.domains[identityName] = {
3161
- stableIds: domainManifest(mod).stableIds ?? continuity,
3603
+ stableIds: stableIdsForModule(mod),
3162
3604
  schemas: Object.fromEntries(currentAggs.map((a) => [a.id, a.schema])),
3163
3605
  };
3164
3606
  }
@@ -3177,6 +3619,34 @@ async function main(): Promise<void> {
3177
3619
  for (const mod of presentationDomainModules) {
3178
3620
  const key = mod.domain ?? mod.name;
3179
3621
  const di = domainInterface(mod, nextLock.domains[mod.name]?.stableIds);
3622
+ const graph = typedLawGraphs.get(mod.name);
3623
+ if (graph === undefined) {
3624
+ fail(`compiler surface invariant: domain '${key}' has no typed USDA graph`);
3625
+ }
3626
+ const graphRoutes = graph.nodes
3627
+ .filter((node) => node.kind === "intent" && node.public)
3628
+ .map((node) => node.kind === "intent" ? node.publicRoute : "")
3629
+ .sort();
3630
+ const graphRouteSet = new Set(graphRoutes);
3631
+ if (graphRouteSet.size !== graphRoutes.length) {
3632
+ const duplicateRoutes = [...graphRouteSet]
3633
+ .filter((route) => graphRoutes.filter((candidate) => candidate === route).length > 1)
3634
+ .sort();
3635
+ fail(
3636
+ `compiler surface invariant: typed USDA for domain '${key}' publishes duplicate route(s): ` +
3637
+ duplicateRoutes.join(", "),
3638
+ );
3639
+ }
3640
+ const interfaceRoutes = Object.keys(di.directives).sort();
3641
+ const missingFromLaw = interfaceRoutes.filter((route) => !graphRouteSet.has(route));
3642
+ const missingFromClient = graphRoutes.filter((route) => !(route in di.directives));
3643
+ if (missingFromLaw.length > 0 || missingFromClient.length > 0) {
3644
+ fail(
3645
+ `compiler surface invariant: generated client and typed USDA disagree for domain '${key}'` +
3646
+ `${missingFromLaw.length === 0 ? "" : `; client-only: ${missingFromLaw.join(", ")}`}` +
3647
+ `${missingFromClient.length === 0 ? "" : `; USDA-only: ${missingFromClient.join(", ")}`}`,
3648
+ );
3649
+ }
3180
3650
  const prior = packageInterface[key];
3181
3651
  packageInterface[key] = prior
3182
3652
  ? {
@@ -3206,6 +3676,24 @@ async function main(): Promise<void> {
3206
3676
  }
3207
3677
  }
3208
3678
 
3679
+ // USDA is rendered directly from the typed graph. The final closure writer below publishes these
3680
+ // exact texts under their logical paths; no parallel layer directory or JSON reconstruction exists.
3681
+ const v3Closure = [...typedLawGraphs.values()];
3682
+ const v3DomainAssets: EmittedLawClosureAsset[] = [];
3683
+ const typedDomainIdentityByName = new Map<string, string>();
3684
+ for (const [domain, graph] of typedLawGraphs) {
3685
+ const safeDomain = domain.replace(/[^A-Za-z0-9._-]/g, "_");
3686
+ const logicalPath = `domains/${safeDomain}.usda`;
3687
+ const layerText = emitSparseLawGraphUsda(graph, logicalPath, v3Closure);
3688
+ const sha256 = sha256HexUtf8(layerText);
3689
+ v3DomainAssets.push({
3690
+ logicalPath,
3691
+ sha256,
3692
+ utf8: layerText,
3693
+ });
3694
+ typedDomainIdentityByName.set(domain, sha256);
3695
+ }
3696
+
3209
3697
  // ── reports ──
3210
3698
  const entryReports: string[] = [];
3211
3699
  for (const [reportId, ref] of Object.entries(cfg.reports ?? {})) {
@@ -3225,6 +3713,18 @@ async function main(): Promise<void> {
3225
3713
  // law whose invariant declarations are inconsistent refuses to COMPILE, and the
3226
3714
  // table can never disagree with what a full force would register).
3227
3715
  const routing = collectEngineRouting(routingInput);
3716
+ // Zanzibar identities are namespaced by the domain's STABLE USDA identity, not its mutable dispatch
3717
+ // label. Carry that one label→identity table into every plan asset so writeTuple() emits the same
3718
+ // object type the typed law graph placed on gates and relation declarations.
3719
+ const relationNamespaceOf = Object.fromEntries(
3720
+ domainModules.map((mod) => {
3721
+ const graph = typedLawGraphs.get(mod.name);
3722
+ if (graph === undefined) {
3723
+ throw new Error(`domain '${mod.name}' has no typed law graph for its relation namespace`);
3724
+ }
3725
+ return [mod.domain ?? mod.name, graph.domain.entity.id] as const;
3726
+ }),
3727
+ );
3228
3728
  // AMBIENT-SLOT SEEDING (lazy boot only): zod v4 writes
3229
3729
  // `globalThis.__zod_global{Config,Registry}` at MODULE INIT. The sealed sandbox
3230
3730
  // freezes globalThis after the lump's top level — an eager lump's zod init ran
@@ -3241,7 +3741,10 @@ async function main(): Promise<void> {
3241
3741
  const zodSeedImports: string[] = [];
3242
3742
  const zodSeedNames: string[] = [];
3243
3743
  {
3244
- const zodPkgJson = [cfgDir, DSL_DIR]
3744
+ // Runtime law imports Zod through this DSL package. Resolve that exact instance first: a tenant
3745
+ // monorepo may hoist an unrelated Zod v3 for other tooling, and seeding that package bloats every
3746
+ // plan while failing to initialize the Zod v4 ambient slots the domain actually uses.
3747
+ const zodPkgJson = [DSL_DIR, cfgDir]
3245
3748
  .map((dir) => {
3246
3749
  try {
3247
3750
  return createRequire(pathToFileURL(path.join(dir, "noop.js"))).resolve("zod/package.json");
@@ -3282,14 +3785,12 @@ async function main(): Promise<void> {
3282
3785
  })
3283
3786
  .find((p): p is string => p !== undefined);
3284
3787
  if (!esbuildPkgJson) fail(`esbuild not found — reinstall @githolon/dsl (esbuild is one of its dependencies)`);
3285
- const esbuildPkg = JSON.parse(readFileSync(esbuildPkgJson, "utf8")) as { bin?: string | Record<string, string> };
3286
- const esbuildBinRel = typeof esbuildPkg.bin === "string" ? esbuildPkg.bin : esbuildPkg.bin?.["esbuild"];
3287
- // Exec the bin DIRECTLY (not via `node`): esbuild's postinstall replaces the
3288
- // JS shim with the platform-native binary, which only an exec can run.
3289
- const esbuildBin = path.join(path.dirname(esbuildPkgJson), esbuildBinRel ?? "bin/esbuild");
3290
3788
  const esbuildMod = (await import(pathToFileURL(path.join(path.dirname(esbuildPkgJson), "lib", "main.js")).href)) as typeof import("esbuild");
3291
3789
 
3292
3790
  const compact = compactFlag || cfg.compact === true;
3791
+ const runtimeZodPath = path.join(DSL_DIR, "src", "runtime_zod.ts");
3792
+ const runtimeZodPackage = createRequire(import.meta.url).resolve("zod/package.json");
3793
+ const runtimeZodV4Path = path.join(path.dirname(runtimeZodPackage), "v4", "index.js");
3293
3794
 
3294
3795
  // ── CONTENT-ADDRESSED PLAN ASSETS ─────────────────────────────────────────────
3295
3796
  // The ledger package no longer executes the package-wide compatibility lump above. Build the
@@ -3299,85 +3800,668 @@ async function main(): Promise<void> {
3299
3800
  const planAssets: UsdPlanAsset[] = [];
3300
3801
  const planRoutes: UsdPlanRoute[] = [];
3301
3802
  const planBySignature = new Map<string, UsdPlanAsset>();
3803
+ const planByHash = new Map<string, UsdPlanAsset>();
3804
+ const planProofCodec = await import("../compile_proof.mjs" as string) as {
3805
+ readPlanCompileProof(options: {
3806
+ proofPath: string;
3807
+ baseIdentity: string;
3808
+ runtimeSha256: string;
3809
+ digestFile?: (file: string) => string;
3810
+ }): { javascript: string; sha256: string } | undefined;
3811
+ writePlanCompileProof(options: {
3812
+ proofPath: string;
3813
+ baseIdentity: string;
3814
+ runtimeSha256: string;
3815
+ plan: string;
3816
+ planSha256: string;
3817
+ inputs: readonly { readonly path: string; readonly sha256: string }[];
3818
+ }): void;
3819
+ };
3820
+ const planCacheDir = path.join(cfgDir, "node_modules", ".cache", "nomos", "plans");
3821
+ const planInputDigestCache = new Map<string, string>();
3822
+ const planBytesDigest = (bytes: Uint8Array): string => createHash("sha256").update(bytes).digest("hex");
3823
+ const planInputDigest = (file: string): string => {
3824
+ const absolute = path.resolve(file);
3825
+ const prior = planInputDigestCache.get(absolute);
3826
+ if (prior !== undefined) return prior;
3827
+ const digest = planBytesDigest(readFileSync(absolute));
3828
+ planInputDigestCache.set(absolute, digest);
3829
+ return digest;
3830
+ };
3831
+ const planCompilerIdentity = planBytesDigest(Buffer.concat([
3832
+ Buffer.from("nomos.plan-compiler.v1\0", "utf8"),
3833
+ readFileSync(fileURLToPath(import.meta.url)),
3834
+ Buffer.from(`\0esbuild:${esbuildMod.version}\0`, "utf8"),
3835
+ ]));
3302
3836
  let planSequence = 0;
3303
3837
  const orderedDomainKeys = [...entryDomainSourceByKey.keys()];
3304
- const planEntrySource = (domainKeys: readonly string[], includeReports: boolean): string => {
3838
+ const frameworkPlanModulePaths = [...new Set(
3839
+ Object.values(BUSINESS_BIRTH_FRAMEWORK_PLAN_SOURCES).map((source) => source.modulePath),
3840
+ )];
3841
+ const planSourceSlicer = new PlanSourceSliceProgram(
3842
+ [...configuredModulePaths, ...frameworkPlanModulePaths],
3843
+ cfgDir,
3844
+ );
3845
+ const routingFor = (domainKeys: readonly string[]) => {
3305
3846
  const wanted = new Set(domainKeys);
3306
- const decls = Object.fromEntries(Object.entries(openapiDeclsByDomain).filter(([key]) => wanted.has(key)));
3847
+ const retain = (table: Readonly<Record<string, readonly string[]>> | undefined) =>
3848
+ Object.fromEntries(
3849
+ Object.entries(table ?? {}).flatMap(([id, domains]) => {
3850
+ const selected = domains.filter((domain) => wanted.has(domain));
3851
+ return selected.length === 0 ? [] : [[id, selected]];
3852
+ }),
3853
+ );
3854
+ return {
3855
+ derivedOf: retain(routing.derivedOf),
3856
+ combinedOf: retain(routing.combinedOf),
3857
+ aggregateInvariantOf: retain(routing.aggregateInvariantOf),
3858
+ relationOf: retain(routing.relationOf),
3859
+ workspaceInvariantOf: retain(routing.workspaceInvariantOf),
3860
+ namespacedAggregatesOf: Object.fromEntries(
3861
+ Object.entries(routing.namespacedAggregatesOf ?? {}).filter(([domain]) => wanted.has(domain)),
3862
+ ),
3863
+ qualifiableRelationsOf: Object.fromEntries(
3864
+ Object.entries(routing.qualifiableRelationsOf ?? {}).filter(([domain]) => wanted.has(domain)),
3865
+ ),
3866
+ relationNamespaceOf: Object.fromEntries(
3867
+ Object.entries(relationNamespaceOf).filter(([domain]) => wanted.has(domain)),
3868
+ ),
3869
+ };
3870
+ };
3871
+ const planEntrySource = (
3872
+ domainKeys: readonly string[],
3873
+ includeReports: boolean,
3874
+ includeDescribe: boolean,
3875
+ sourceOverrides: ReadonlyMap<string, string> = new Map(),
3876
+ includePreloads = true,
3877
+ routingOverride?: Record<string, unknown>,
3878
+ routedDomainFallback?: string,
3879
+ includeMintInstalls = true,
3880
+ sharedRuntime = false,
3881
+ ): string => {
3307
3882
  return [
3308
3883
  `// AUTO-GENERATED Nomos plan asset from ${path.basename(configPath)} — do not edit.`,
3309
3884
  `declare function require(id: string): Record<string, unknown>;`,
3310
- ...zodSeedImports,
3885
+ ...(sharedRuntime ? [] : zodSeedImports),
3311
3886
  `import { registerEngine } from "@githolon/dsl/engine-entry";`,
3312
3887
  ``,
3313
3888
  `registerEngine({`,
3314
3889
  ` domains: {`,
3315
3890
  ...domainKeys.map((key) => {
3316
- const source = entryDomainSourceByKey.get(key);
3891
+ const source = sourceOverrides.get(key) ?? entryDomainSourceByKey.get(key);
3317
3892
  if (source === undefined) throw new Error(`missing generated entry source for '${key}'`);
3318
3893
  return source;
3319
3894
  }),
3320
3895
  ` },`,
3896
+ ...(includePreloads ? (() => {
3897
+ const preloads = domainKeys.flatMap((key) => {
3898
+ const source = entryPreloadSourceByKey.get(key);
3899
+ return source === undefined ? [] : [source];
3900
+ });
3901
+ return preloads.length === 0 ? [] : [` preloads: {`, ...preloads, ` },`];
3902
+ })() : []),
3321
3903
  ...(includeReports && entryReports.length > 0 ? [` reports: {`, ...entryReports, ` },`] : []),
3322
- ` routing: ${JSON.stringify(routing)},`,
3323
- ...(zodSeedNames.length > 0 ? [` seeds: [${zodSeedNames.join(", ")}],`] : []),
3324
- ...(Object.keys(decls).length > 0 ? [` openapiDecls: ${JSON.stringify(decls)},`] : []),
3325
- ` packageTitle: ${JSON.stringify(`${cfg.name} (nomos package)`)},`,
3904
+ ` routing: ${JSON.stringify(routingOverride ?? routingFor(domainKeys))},`,
3905
+ ...(routedDomainFallback === undefined
3906
+ ? []
3907
+ : [` routedDomainFallback: ${JSON.stringify(routedDomainFallback)},`]),
3908
+ ...(!sharedRuntime && zodSeedNames.length > 0 ? [` seeds: [${zodSeedNames.join(", ")}],`] : []),
3909
+ ...(includeDescribe ? [` openapiDocument: ${JSON.stringify(runtimeOpenApiDocument)},`] : []),
3910
+ ...(includeDescribe ? [` packageTitle: ${JSON.stringify(`${cfg.name} (nomos package)`)},`] : []),
3326
3911
  `});`,
3327
- ...domainKeys.flatMap((key) => {
3328
- const install = entryMintInstallByKey.get(key);
3329
- return install === undefined ? [] : [install];
3330
- }),
3912
+ ...(includeMintInstalls
3913
+ ? domainKeys.flatMap((key) => {
3914
+ const install = entryMintInstallByKey.get(key);
3915
+ return install === undefined ? [] : [install];
3916
+ })
3917
+ : []),
3331
3918
  ``,
3332
3919
  ].join("\n");
3333
3920
  };
3334
- const compilePlan = (domainKeys: readonly string[], includeReports: boolean): UsdPlanAsset => {
3921
+ const sharedPrivateRuntimeModules = [...new Set([
3922
+ ...configuredModulePaths,
3923
+ ...frameworkPlanModulePaths,
3924
+ ])]
3925
+ .filter((modulePath) => modulePath.startsWith(path.join(DSL_DIR, "src") + path.sep))
3926
+ .sort()
3927
+ .map((source) => ({
3928
+ id: `nomos:runtime-source/${path.relative(path.join(DSL_DIR, "src"), source)
3929
+ .replaceAll(path.sep, "/")
3930
+ .replace(/\.[^.]+$/, "")}`,
3931
+ source,
3932
+ }));
3933
+ const publicRuntimeCandidates = [
3934
+ "@githolon/dsl",
3935
+ "@githolon/dsl/framework/workspaces",
3936
+ "@githolon/dsl/workspace-sharding",
3937
+ "zod",
3938
+ ] as const;
3939
+ const authoredRuntimeImports = planSourceSlicer.externalRuntimeImports(publicRuntimeCandidates);
3940
+ const sharedRuntimeModules: Array<{
3941
+ readonly id: string;
3942
+ readonly source: string;
3943
+ /** Undefined retains a complete namespace; named exports let esbuild erase build-only code. */
3944
+ readonly exports?: readonly string[];
3945
+ }> = [
3946
+ ...publicRuntimeCandidates.flatMap((id) => {
3947
+ const used = authoredRuntimeImports.get(id);
3948
+ return used === undefined ? [] : [{ id, source: id, ...(used === null ? {} : { exports: used }) }];
3949
+ }),
3950
+ // registerEngine is compiler-emitted into every plan entry, rather than imported by tenant
3951
+ // source, so declare its one authenticated runtime symbol explicitly.
3952
+ { id: "@githolon/dsl/engine-entry", source: "@githolon/dsl/engine-entry", exports: ["registerEngine"] },
3953
+ ...sharedPrivateRuntimeModules,
3954
+ ];
3955
+ const sharedRuntimeModuleIds = sharedRuntimeModules.map((module) => module.id);
3956
+ const sharedPlanEntrySource = (source: string): string => {
3957
+ let rewritten = source;
3958
+ for (const module of sharedPrivateRuntimeModules) {
3959
+ rewritten = rewritten.replaceAll(JSON.stringify(module.source), JSON.stringify(module.id));
3960
+ }
3961
+ return rewritten;
3962
+ };
3963
+ let sharedRuntimeProfileBytes: number | undefined;
3964
+ let sharedRuntimeProfileJavascript: string | undefined;
3965
+ let sharedRuntimeProfileInputs:
3966
+ | readonly { readonly module: string; readonly bytesInOutput: number }[]
3967
+ | undefined;
3968
+ const compileSharedRuntimeProfile = async (): Promise<number> => {
3969
+ if (sharedRuntimeProfileBytes !== undefined) return sharedRuntimeProfileBytes;
3970
+ const sourcePath = path.join(outDir, ".nomos_shared_runtime_profile.ts");
3971
+ const bindings = sharedRuntimeModules.map((_module, index) => `__module${index}`);
3972
+ writeFileSync(sourcePath, [
3973
+ `// Diagnostic prototype only: the authenticated package-shared runtime proposed for typed USDA custody.`,
3974
+ ...sharedRuntimeModules.map((module, index) => module.exports === undefined
3975
+ ? `import * as ${bindings[index]} from ${JSON.stringify(module.source)};`
3976
+ : `import { ${module.exports.map((name, exportIndex) =>
3977
+ `${name} as ${bindings[index]}_${exportIndex}`).join(", ")} } from ${JSON.stringify(module.source)};`),
3978
+ ...sharedRuntimeModules.flatMap((module, index) => module.exports === undefined
3979
+ ? []
3980
+ : [`const ${bindings[index]} = Object.freeze({ ${module.exports.map((name, exportIndex) =>
3981
+ `${JSON.stringify(name)}: ${bindings[index]}_${exportIndex}`).join(", ")} });`]),
3982
+ `const __modules: Record<string, Record<string, unknown>> = Object.freeze({`,
3983
+ ...sharedRuntimeModules.map((module, index) =>
3984
+ ` ${JSON.stringify(module.id)}: ${bindings[index]},`),
3985
+ `});`,
3986
+ `Object.defineProperty(globalThis, "require", {`,
3987
+ ` configurable: false, enumerable: false, writable: false,`,
3988
+ ` value: (id: string): Record<string, unknown> => {`,
3989
+ ` const found = __modules[id];`,
3990
+ ` if (found === undefined) throw new Error(\`unknown authenticated runtime module '\${id}'\`);`,
3991
+ ` return found;`,
3992
+ ` },`,
3993
+ `});`,
3994
+ ``,
3995
+ ].join("\n"), "utf8");
3996
+ const result = await esbuildMod.build({
3997
+ entryPoints: [sourcePath],
3998
+ absWorkingDir: cfgDir,
3999
+ bundle: true,
4000
+ write: false,
4001
+ format: "iife",
4002
+ target: "es2020",
4003
+ platform: "neutral",
4004
+ charset: "utf8",
4005
+ define: { __nmsRuntimeLaw: "true" },
4006
+ alias: { "zod/v4": runtimeZodV4Path, zod: runtimeZodPath },
4007
+ minify: compact,
4008
+ metafile: process.env.NOMOS_PROFILE_PLANS === "1",
4009
+ logLevel: "silent",
4010
+ });
4011
+ rmSync(sourcePath, { force: true });
4012
+ const output = result.outputFiles?.[0];
4013
+ if (output === undefined) fail("shared runtime profile produced no JavaScript output");
4014
+ sharedRuntimeProfileJavascript = canonicalizeEngineJavascript(output.text.trimEnd() + "\n");
4015
+ sharedRuntimeProfileBytes = Buffer.byteLength(sharedRuntimeProfileJavascript, "utf8");
4016
+ if (result.metafile !== undefined) {
4017
+ const emitted = Object.values(result.metafile.outputs)[0]?.inputs ?? {};
4018
+ sharedRuntimeProfileInputs = Object.entries(emitted)
4019
+ .map(([module, contribution]) => ({
4020
+ module,
4021
+ bytesInOutput: contribution.bytesInOutput,
4022
+ }))
4023
+ .sort((left, right) => right.bytesInOutput - left.bytesInOutput || left.module.localeCompare(right.module));
4024
+ }
4025
+ return sharedRuntimeProfileBytes;
4026
+ };
4027
+ const compilePlan = async (
4028
+ domainKeys: readonly string[],
4029
+ includeReports: boolean,
4030
+ includeDescribe: boolean,
4031
+ options: {
4032
+ readonly sourceOverrides?: ReadonlyMap<string, string>;
4033
+ readonly sliceTargets?: readonly PlanSourceSliceTarget[];
4034
+ readonly includePreloads?: boolean;
4035
+ readonly cacheKey?: string;
4036
+ readonly routingOverride?: Record<string, unknown>;
4037
+ readonly routedDomainFallback?: string;
4038
+ readonly includeMintInstalls?: boolean;
4039
+ } = {},
4040
+ ): Promise<UsdPlanAsset> => {
3335
4041
  const ordered = orderedDomainKeys.filter((key) => domainKeys.includes(key));
3336
- const signature = JSON.stringify({ domains: ordered, reports: includeReports });
4042
+ const signature = JSON.stringify({
4043
+ domains: ordered,
4044
+ reports: includeReports,
4045
+ describe: includeDescribe,
4046
+ slice: options.sliceTargets?.map((target) => [target.modulePath, target.exportName]) ?? null,
4047
+ cacheKey: options.cacheKey ?? null,
4048
+ });
3337
4049
  const hit = planBySignature.get(signature);
3338
4050
  if (hit !== undefined) return hit;
3339
- const sourcePath = path.join(outDir, `.nomos_plan_${planSequence++}.ts`);
3340
- writeFileSync(sourcePath, planEntrySource(ordered, includeReports), "utf8");
3341
- const args = [
3342
- sourcePath,
3343
- "--bundle", "--format=iife", "--target=es2020", "--platform=neutral", "--charset=utf8",
3344
- "--define:__nmsRuntimeLaw=true",
3345
- ...(compact ? ["--minify"] : []),
3346
- ];
3347
- let bundled = execFileSync(esbuildBin, args, {
3348
- encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], cwd: cfgDir, maxBuffer: 256 * 1024 * 1024,
3349
- }).trimEnd() + "\n";
3350
- bundled = canonicalizeEngineJavascript(bundled);
3351
- if (!/globalThis\.plan\s*=/.test(bundled) && !/globalThis\["plan"\]\s*=/.test(bundled)) {
3352
- fail(`generated plan asset does not assign globalThis.plan — refusing to package`);
4051
+ await compileSharedRuntimeProfile();
4052
+ const entrySource = sharedPlanEntrySource(planEntrySource(
4053
+ ordered,
4054
+ includeReports,
4055
+ includeDescribe,
4056
+ options.sourceOverrides,
4057
+ options.includePreloads ?? options.sliceTargets === undefined,
4058
+ options.routingOverride,
4059
+ options.routedDomainFallback,
4060
+ options.includeMintInstalls ?? true,
4061
+ true,
4062
+ ));
4063
+ let planProfile: readonly { readonly module: string; readonly bytesInOutput: number }[] | undefined;
4064
+ const profilePlan = process.env.NOMOS_PROFILE_PLANS === "1";
4065
+ const runtimeSha256 = sha256HexUtf8(sharedRuntimeProfileJavascript!);
4066
+ const baseIdentity = sha256HexUtf8([
4067
+ planCompilerIdentity,
4068
+ runtimeSha256,
4069
+ path.resolve(cfgDir),
4070
+ compact ? "compact" : "readable",
4071
+ signature,
4072
+ entrySource,
4073
+ ].join("\0"));
4074
+ const cachePlanPath = path.join(planCacheDir, `${baseIdentity}.mjs`);
4075
+ const cacheProofPath = path.join(planCacheDir, `${baseIdentity}.proof.usda`);
4076
+ const cached = profilePlan ? undefined : planProofCodec.readPlanCompileProof({
4077
+ proofPath: cacheProofPath,
4078
+ baseIdentity,
4079
+ runtimeSha256,
4080
+ digestFile: planInputDigest,
4081
+ });
4082
+ let bundled: string;
4083
+ if (cached !== undefined) {
4084
+ bundled = cached.javascript;
4085
+ } else {
4086
+ const sourcePath = path.join(outDir, `.nomos_plan_${planSequence++}.ts`);
4087
+ writeFileSync(sourcePath, entrySource, "utf8");
4088
+ const result = await esbuildMod.build({
4089
+ entryPoints: [sourcePath],
4090
+ absWorkingDir: cfgDir,
4091
+ bundle: true,
4092
+ write: false,
4093
+ format: "iife",
4094
+ target: "es2020",
4095
+ platform: "neutral",
4096
+ charset: "utf8",
4097
+ define: { __nmsRuntimeLaw: "true" },
4098
+ minify: compact,
4099
+ external: [...sharedRuntimeModuleIds],
4100
+ metafile: true,
4101
+ logLevel: "silent",
4102
+ ...(options.sliceTargets === undefined
4103
+ ? {}
4104
+ : { plugins: [planSourceSlicer.plugin(options.sliceTargets)] }),
4105
+ });
4106
+ rmSync(sourcePath, { force: true });
4107
+ const output = result.outputFiles?.[0];
4108
+ if (output === undefined) fail("shared-runtime plan compile produced no JavaScript output");
4109
+ bundled = canonicalizeEngineJavascript(output.text.trimEnd() + "\n");
4110
+ const emitted = Object.values(result.metafile.outputs)[0]?.inputs ?? {};
4111
+ if (profilePlan) {
4112
+ planProfile = Object.entries(emitted)
4113
+ .map(([module, contribution]) => ({
4114
+ module,
4115
+ bytesInOutput: contribution.bytesInOutput,
4116
+ }))
4117
+ .sort((left, right) => right.bytesInOutput - left.bytesInOutput || left.module.localeCompare(right.module));
4118
+ }
4119
+ const inputs = Object.keys(emitted)
4120
+ .map((module) => path.isAbsolute(module) ? module : path.resolve(cfgDir, module))
4121
+ .filter((file) => path.resolve(file) !== path.resolve(sourcePath) && existsSync(file))
4122
+ .map((file) => ({ path: file, sha256: planInputDigest(file) }))
4123
+ .sort((left, right) => left.path.localeCompare(right.path));
4124
+ if (inputs.length > 0) {
4125
+ mkdirSync(planCacheDir, { recursive: true });
4126
+ const temporary = `${cachePlanPath}.tmp-${process.pid}`;
4127
+ writeFileSync(temporary, bundled, "utf8");
4128
+ renameSync(temporary, cachePlanPath);
4129
+ planProofCodec.writePlanCompileProof({
4130
+ proofPath: cacheProofPath,
4131
+ baseIdentity,
4132
+ runtimeSha256,
4133
+ plan: cachePlanPath,
4134
+ planSha256: sha256HexUtf8(bundled),
4135
+ inputs,
4136
+ });
4137
+ }
4138
+ }
4139
+ const planBytes = Buffer.byteLength(bundled, "utf8");
4140
+ // The authenticated base owns registerEngine/globalThis.plan; the selected plan is deliberately
4141
+ // incomplete alone. Kernel execution evaluates the content-addressed base bytecode first, then
4142
+ // this plan bytecode in the same fresh sandbox.
4143
+ const sha256 = sha256HexUtf8(bundled);
4144
+ const duplicate = planByHash.get(sha256);
4145
+ if (duplicate !== undefined) {
4146
+ planBySignature.set(signature, duplicate);
4147
+ return duplicate;
3353
4148
  }
3354
- const asset = { sha256: sha256HexUtf8(bundled), javascript: bundled };
4149
+ const asset = {
4150
+ sha256,
4151
+ javascript: bundled,
4152
+ runtimeSha256,
4153
+ ...(planProfile === undefined ? {} : { profile: planProfile }),
4154
+ sharedRuntimeProfileBytes: planBytes,
4155
+ sharedRuntimeProfileJavascript: bundled,
4156
+ };
3355
4157
  planBySignature.set(signature, asset);
4158
+ planByHash.set(sha256, asset);
3356
4159
  planAssets.push(asset);
3357
4160
  return asset;
3358
4161
  };
3359
- const routePlan = (selector: string, domainKeys: readonly string[], includeReports = false): void => {
3360
- const asset = compilePlan(domainKeys, includeReports);
4162
+ const routePlan = async (
4163
+ selector: string,
4164
+ domainKeys: readonly string[],
4165
+ includeReports = false,
4166
+ includeDescribe = false,
4167
+ ): Promise<void> => {
4168
+ const asset = await compilePlan(domainKeys, includeReports, includeDescribe);
3361
4169
  planRoutes.push({ selector, planSha256: asset.sha256 });
3362
4170
  };
3363
- for (const key of orderedDomainKeys) routePlan(`plan:domain:${key}`, [key]);
3364
- routePlan("describe", orderedDomainKeys);
3365
- for (const reportId of Object.keys(cfg.reports ?? {})) routePlan(`report:report:${reportId}`, [], true);
3366
- const routeTable = (
4171
+ const routeAsset = (selector: string, asset: UsdPlanAsset): void => {
4172
+ planRoutes.push({ selector, planSha256: asset.sha256 });
4173
+ };
4174
+ const domainPlanByKey = new Map<string, UsdPlanAsset>();
4175
+ const aggregateInvariantPlanOverrides = new Map<string, UsdPlanAsset>();
4176
+ const workspaceInvariantPlanOverrides = new Map<string, UsdPlanAsset>();
4177
+ const retainGeneratedInvariantRoutes = (
4178
+ domain: string,
4179
+ directiveId: string,
4180
+ asset: UsdPlanAsset,
4181
+ ): void => {
4182
+ const directive = executableDirectivesByDomain.get(domain)?.get(directiveId);
4183
+ if (directive === undefined) return;
4184
+ for (const invariant of [
4185
+ ...synthesizePreconditionInvariants(directive),
4186
+ ...synthesizeBirthQuotaInvariants(directive),
4187
+ ]) {
4188
+ // These bodies are pure compiler desugarings of this directive and are reconstructed by
4189
+ // buildDomainSlice from the exact offer plan. Hand-authored invariant exports are deliberately
4190
+ // excluded: they retain their independently addressed source ownership.
4191
+ if (!nativeWorkspaceInvariantIds.has(invariant.id)) {
4192
+ workspaceInvariantPlanOverrides.set(invariant.id, asset);
4193
+ }
4194
+ }
4195
+ };
4196
+ for (const key of orderedDomainKeys) {
4197
+ const asset = await compilePlan([key], false, false);
4198
+ domainPlanByKey.set(key, asset);
4199
+ routeAsset(`plan:domain:${key}`, asset);
4200
+ }
4201
+
4202
+ // Public offers are addressed more narrowly than domains. Split direct TypeScript exports by
4203
+ // authored source span, then symbol-slice each shard so one large domain file cannot impose one
4204
+ // uninterruptible module initializer on every intent. The threshold is source-proportional rather
4205
+ // than an arbitrary object count; framework-generated/unaddressable directives safely retain the
4206
+ // domain plan while still receiving an explicit offer route.
4207
+ const OFFER_SHARD_SOURCE_SPAN_BYTES = 8 * 1024;
4208
+ // Generated JavaScript closure bytes are a better proxy for parse/init work than declaration count.
4209
+ // Start with source-local shards to avoid quadratic compiles, then recursively split any executable
4210
+ // closure above this ceiling. A single irreducible offer is retained and remains visible to profiling.
4211
+ // Current CO2 evidence shows source initialization, not transport size alone, is the limiting
4212
+ // quantum: a 115 KiB estate shard can still take >200 ms when it initializes many Zod/directive
4213
+ // graphs together. Keep grouped offer closures below 48 KiB and split authored source spans at
4214
+ // 8 KiB; an irreducible single offer remains explicit and attributable instead of being hidden
4215
+ // inside a domain-wide fallback.
4216
+ const OFFER_PLAN_MAX_UTF8_BYTES = 48 * 1024;
4217
+ for (const domain of orderedDomainKeys) {
4218
+ const fallback = domainPlanByKey.get(domain)!;
4219
+ const exports = offerSourceExportsByDomain.get(domain) ?? new Map();
4220
+ const routed = new Set<string>();
4221
+
4222
+ // Business-birth lowering injects several framework offers which execute as one child-genesis
4223
+ // transaction. Compile their typed source closures into one shared plan: preparing any one exact
4224
+ // route makes the whole compiler-owned genesis vocabulary resident, while every selector remains
4225
+ // independently authenticated by the kernel. A missing source is a compiler error, never a slow
4226
+ // whole-domain fallback.
4227
+ const frameworkTargets = [...(executableDirectiveIdsByDomain.get(domain) ?? [])]
4228
+ .flatMap((directiveId) => {
4229
+ const target = BUSINESS_BIRTH_FRAMEWORK_PLAN_SOURCES[directiveId];
4230
+ return target === undefined ? [] : [{ directiveId, ...target }];
4231
+ });
4232
+ if (frameworkTargets.length > 0) {
4233
+ const selectedModules = new Map<string, Array<{ directiveId: string; exportName: string }>>();
4234
+ for (const target of frameworkTargets) {
4235
+ const entries = selectedModules.get(target.modulePath) ?? [];
4236
+ entries.push({ directiveId: target.directiveId, exportName: target.exportName });
4237
+ selectedModules.set(target.modulePath, entries);
4238
+ }
4239
+ const selectedSources = [...selectedModules].map(([modulePath, entries]) =>
4240
+ `() => ({${entries.map((entry) =>
4241
+ `${JSON.stringify(entry.exportName)}: require(${JSON.stringify(modulePath)})[${JSON.stringify(entry.exportName)}]`).join(", ")}})`,
4242
+ );
4243
+ const source = ` ${JSON.stringify(BUSINESS_BIRTH_FRAMEWORK_PLAN_DOMAIN)}: [${selectedSources.join(", ")}],`;
4244
+ const asset = await compilePlan([domain], false, false, {
4245
+ sourceOverrides: new Map([[domain, source]]),
4246
+ includePreloads: false,
4247
+ cacheKey: "business-birth-framework",
4248
+ routingOverride: { relationNamespaceOf },
4249
+ routedDomainFallback: BUSINESS_BIRTH_FRAMEWORK_PLAN_DOMAIN,
4250
+ includeMintInstalls: false,
4251
+ });
4252
+ for (const target of frameworkTargets) {
4253
+ routeAsset(`plan:offer:${domain}:${target.directiveId}`, asset);
4254
+ retainGeneratedInvariantRoutes(domain, target.directiveId, asset);
4255
+ routed.add(target.directiveId);
4256
+ }
4257
+ }
4258
+ const byModule = new Map<string, Array<{
4259
+ directiveId: string;
4260
+ exportName: string;
4261
+ aggregateId: string;
4262
+ position: number;
4263
+ }>>();
4264
+ for (const [directiveId, source] of exports) {
4265
+ if (routed.has(directiveId)) continue;
4266
+ const position = planSourceSlicer.exportPosition({
4267
+ modulePath: source.modulePath,
4268
+ exportName: source.exportName,
4269
+ });
4270
+ if (position === undefined) continue;
4271
+ const entries = byModule.get(source.modulePath) ?? [];
4272
+ entries.push({ directiveId, exportName: source.exportName, aggregateId: source.aggregateId, position });
4273
+ byModule.set(source.modulePath, entries);
4274
+ }
4275
+ for (const [modulePath, entries] of byModule) {
4276
+ entries.sort((left, right) => left.position - right.position || left.directiveId.localeCompare(right.directiveId));
4277
+ const shards: typeof entries[] = [];
4278
+ let current: typeof entries = [];
4279
+ let shardStart = 0;
4280
+ for (const entry of entries) {
4281
+ if (current.length > 0 && entry.position - shardStart >= OFFER_SHARD_SOURCE_SPAN_BYTES) {
4282
+ shards.push(current);
4283
+ current = [];
4284
+ }
4285
+ if (current.length === 0) shardStart = entry.position;
4286
+ current.push(entry);
4287
+ }
4288
+ if (current.length > 0) shards.push(current);
4289
+ const compileOfferShard = async (shard: typeof entries): Promise<void> => {
4290
+ const selected: PlanSourceSliceTarget[] = shard.map((entry) => ({
4291
+ modulePath,
4292
+ exportName: entry.exportName,
4293
+ }));
4294
+ const selectedModule = `() => ({${shard.map((entry) =>
4295
+ `${JSON.stringify(entry.exportName)}: require(${JSON.stringify(modulePath)})[${JSON.stringify(entry.exportName)}]`).join(", ")}})`;
4296
+ const generated = entryGeneratedSourceExprsByKey.get(domain) ?? [];
4297
+ const source = ` ${JSON.stringify(domain)}: [${[selectedModule, ...generated].join(", ")}],`;
4298
+ const asset = await compilePlan([domain], false, false, {
4299
+ sourceOverrides: new Map([[domain, source]]),
4300
+ sliceTargets: selected,
4301
+ });
4302
+ if (shard.length > 1 && Buffer.byteLength(asset.javascript, "utf8") > OFFER_PLAN_MAX_UTF8_BYTES) {
4303
+ const midpoint = (shard[0]!.position + shard[shard.length - 1]!.position) / 2;
4304
+ let splitAt = shard.findIndex((entry) => entry.position >= midpoint);
4305
+ if (splitAt <= 0 || splitAt >= shard.length) splitAt = Math.ceil(shard.length / 2);
4306
+ await compileOfferShard(shard.slice(0, splitAt));
4307
+ await compileOfferShard(shard.slice(splitAt));
4308
+ return;
4309
+ }
4310
+ for (const entry of shard) {
4311
+ routeAsset(`plan:offer:${domain}:${entry.directiveId}`, asset);
4312
+ retainGeneratedInvariantRoutes(domain, entry.directiveId, asset);
4313
+ if (entry.directiveId.startsWith("nomosSeed")) {
4314
+ // The generated seed writes the born aggregate immediately. Its `.fromBirth()` / user
4315
+ // aggregate invariant is part of the same authored symbol closure, so route that first
4316
+ // gate check through the already-warmed slice instead of cold-loading the whole domain.
4317
+ aggregateInvariantPlanOverrides.set(entry.aggregateId, asset);
4318
+ aggregateInvariantPlanOverrides.set(`${domain}.${entry.aggregateId}`, asset);
4319
+ }
4320
+ routed.add(entry.directiveId);
4321
+ }
4322
+ };
4323
+ for (const shard of shards) await compileOfferShard(shard);
4324
+ }
4325
+ for (const directiveId of executableDirectiveIdsByDomain.get(domain) ?? []) {
4326
+ if (!routed.has(directiveId)) routeAsset(`plan:offer:${domain}:${directiveId}`, fallback);
4327
+ }
4328
+ }
4329
+ // Hand-authored workspace invariants are independently addressable law too. The previous
4330
+ // fallback compiled their entire domain merely to run one `reads`/`assert` pair, even though
4331
+ // offer compilation already knows how to retain one authored export closure. Build that exact
4332
+ // symbol slice here for invariants which could not ride their guarded offer's plan.
4333
+ // When `language(...)` remapped private effect invariants onto public intents, retain only those
4334
+ // public intent exports and reconstruct that language so the existing remapping machinery remains
4335
+ // the sole owner of payload adaptation.
4336
+ for (const domain of orderedDomainKeys) {
4337
+ const byDirective = new Map<string, Array<[
4338
+ string,
4339
+ {
4340
+ readonly on: string;
4341
+ readonly modulePath: string;
4342
+ readonly exportName: string;
4343
+ readonly remappedIntent?: PlanSourceSliceTarget;
4344
+ },
4345
+ ]>>();
4346
+ for (const entry of workspaceInvariantSourceExportsByDomain.get(domain) ?? []) {
4347
+ const members = byDirective.get(entry[1].on) ?? [];
4348
+ members.push(entry);
4349
+ byDirective.set(entry[1].on, members);
4350
+ }
4351
+ for (const [directiveId, unsortedMembers] of byDirective) {
4352
+ // Every invariant firing on one offer is evaluated consecutively. Give those routes one
4353
+ // compiled slice so the kernel boots the shared DSL/module closure once and re-arms the
4354
+ // same resident sandbox for each bounded reads/assert pair. Per-invariant plans duplicated
4355
+ // that closure and paid one QuickJS heap boot per predicate on the offer flame path.
4356
+ const members = unsortedMembers.sort(([left], [right]) => left.localeCompare(right));
4357
+ const selectedByKey = new Map<string, PlanSourceSliceTarget>();
4358
+ const selected = (target: PlanSourceSliceTarget): void => {
4359
+ selectedByKey.set(`${target.modulePath}\0${target.exportName}`, target);
4360
+ };
4361
+ for (const [, source] of members) {
4362
+ selected(source);
4363
+ if (source.remappedIntent !== undefined) selected(source.remappedIntent);
4364
+ }
4365
+ const invariantProperties = members.map(([invariantId, source], index) =>
4366
+ `${JSON.stringify(`nmsInvariant${index}_${invariantId}`)}: require(${JSON.stringify(source.modulePath)})[${JSON.stringify(source.exportName)}]`
4367
+ );
4368
+ const remappedIntents = [...new Map(
4369
+ members.flatMap(([, source]) => source.remappedIntent === undefined
4370
+ ? []
4371
+ : [[`${source.remappedIntent.modulePath}\0${source.remappedIntent.exportName}`, source.remappedIntent] as const]),
4372
+ ).values()];
4373
+ const languageProperty = remappedIntents.length === 0
4374
+ ? []
4375
+ : [`__nomosInvariantLanguage: require("@githolon/dsl").language(${JSON.stringify(domain)}, [${remappedIntents.map((intent) =>
4376
+ `require(${JSON.stringify(intent.modulePath)})[${JSON.stringify(intent.exportName)}]`
4377
+ ).join(", ")}])`];
4378
+ const moduleExpression = `() => ({${[...invariantProperties, ...languageProperty].join(", ")}})`;
4379
+ const domainSource = ` ${JSON.stringify(domain)}: [${moduleExpression}],`;
4380
+ const asset = await compilePlan([domain], false, false, {
4381
+ sourceOverrides: new Map([[domain, domainSource]]),
4382
+ sliceTargets: [...selectedByKey.values()],
4383
+ cacheKey: `workspace-invariants:${directiveId}:${members.map(([id]) => id).join(",")}`,
4384
+ });
4385
+ for (const [invariantId] of members) {
4386
+ workspaceInvariantPlanOverrides.set(invariantId, asset);
4387
+ }
4388
+ }
4389
+ }
4390
+ await routePlan("describe", orderedDomainKeys, false, true);
4391
+ for (const reportId of Object.keys(cfg.reports ?? {})) await routePlan(`report:report:${reportId}`, [], true);
4392
+ const routeTable = async (
3367
4393
  prefix: string,
3368
4394
  table: Readonly<Record<string, readonly string[]>> | undefined,
3369
- ): void => {
3370
- for (const [id, domains] of Object.entries(table ?? {})) routePlan(`${prefix}${id}`, domains);
4395
+ ): Promise<void> => {
4396
+ for (const [id, domains] of Object.entries(table ?? {})) await routePlan(`${prefix}${id}`, domains);
3371
4397
  };
3372
- routeTable("report:derive:", routing.derivedOf);
3373
- routeTable("report:combine:", routing.combinedOf);
3374
- routeTable("report:aggregateInvariant:", routing.aggregateInvariantOf);
3375
- routeTable("report:invariant:", routing.relationOf);
3376
- routeTable("report:workspaceInvariantReads:", routing.workspaceInvariantOf);
3377
- routeTable("report:workspaceInvariant:", routing.workspaceInvariantOf);
3378
- const workspaceInvariantDomains = [...new Set(Object.values(routing.workspaceInvariantOf ?? {}).flat())];
3379
- if (workspaceInvariantDomains.length > 0) {
3380
- routePlan("report:workspaceInvariantList", workspaceInvariantDomains);
4398
+ await routeTable("report:derive:", routing.derivedOf);
4399
+ await routeTable("report:combine:", routing.combinedOf);
4400
+ for (const [id, domains] of Object.entries(routing.aggregateInvariantOf ?? {})) {
4401
+ const override = aggregateInvariantPlanOverrides.get(id);
4402
+ if (override !== undefined) routeAsset(`report:aggregateInvariant:${id}`, override);
4403
+ else await routePlan(`report:aggregateInvariant:${id}`, domains);
4404
+ }
4405
+ await routeTable("report:invariant:", routing.relationOf);
4406
+ for (const [id, domains] of Object.entries(routing.workspaceInvariantOf ?? {})) {
4407
+ // Typed native evaluators are complete USDA custody interpreted by Rust. Emitting an engine route
4408
+ // for them is not merely redundant: it falsely advertises a JavaScript fallback and keeps dead law
4409
+ // reachable. A malformed/unknown native evaluator fails closed while parsing the installed stage.
4410
+ if (nativeWorkspaceInvariantIds.has(id)) continue;
4411
+ const override = workspaceInvariantPlanOverrides.get(id);
4412
+ if (override !== undefined) {
4413
+ if (!nativeWorkspaceInvariantReadIds.has(id)) {
4414
+ routeAsset(`report:workspaceInvariantReads:${id}`, override);
4415
+ }
4416
+ routeAsset(`report:workspaceInvariant:${id}`, override);
4417
+ } else {
4418
+ if (!nativeWorkspaceInvariantReadIds.has(id)) {
4419
+ await routePlan(`report:workspaceInvariantReads:${id}`, domains);
4420
+ }
4421
+ await routePlan(`report:workspaceInvariant:${id}`, domains);
4422
+ }
4423
+ }
4424
+ // Adaptive offer splitting deliberately compiles candidate parents before retaining their children.
4425
+ // Only route-reachable content is custody; exploratory compiler work never becomes law bytes.
4426
+ const routedPlanHashes = new Set(planRoutes.map((route) => route.planSha256));
4427
+ const routedPlanAssets = planAssets.filter((asset) => routedPlanHashes.has(asset.sha256));
4428
+ const javascriptWorkspaceInvariantReadIds = [...new Set(
4429
+ planRoutes
4430
+ .map((route) => route.selector)
4431
+ .filter((selector) => selector.startsWith("report:workspaceInvariantReads:"))
4432
+ .map((selector) => selector.slice("report:workspaceInvariantReads:".length)),
4433
+ )].sort();
4434
+ const rustWorkspaceInvariantReadIds = [...nativeWorkspaceInvariantReadIds].sort();
4435
+ if (process.env.NOMOS_PROFILE_PLANS === "1") {
4436
+ const routesByPlan = new Map<string, string[]>();
4437
+ for (const route of planRoutes) {
4438
+ const selectors = routesByPlan.get(route.planSha256) ?? [];
4439
+ selectors.push(route.selector);
4440
+ routesByPlan.set(route.planSha256, selectors);
4441
+ }
4442
+ const profilePath = path.join(outDir, "plan-profile.json");
4443
+ writeFileSync(profilePath, JSON.stringify({
4444
+ package: cfg.name,
4445
+ sharedRuntime: sharedRuntimeProfileBytes === undefined ? null : {
4446
+ sourceBytes: sharedRuntimeProfileBytes,
4447
+ modules: sharedRuntimeModuleIds,
4448
+ inputs: sharedRuntimeProfileInputs ?? [],
4449
+ },
4450
+ workspaceInvariantReads: {
4451
+ rustLoweredIds: rustWorkspaceInvariantReadIds,
4452
+ javascriptFallbackIds: javascriptWorkspaceInvariantReadIds,
4453
+ },
4454
+ plans: routedPlanAssets
4455
+ .map((asset) => ({
4456
+ sha256: asset.sha256,
4457
+ sourceBytes: Buffer.byteLength(asset.javascript, "utf8"),
4458
+ sharedRuntimeSourceBytes: asset.sharedRuntimeProfileBytes ?? null,
4459
+ routes: [...(routesByPlan.get(asset.sha256) ?? [])].sort(),
4460
+ inputs: asset.profile ?? [],
4461
+ }))
4462
+ .sort((left, right) => right.sourceBytes - left.sourceBytes || left.sha256.localeCompare(right.sha256)),
4463
+ }, null, 2) + "\n", "utf8");
4464
+ console.error(` plans ${profilePath} (diagnostic only; excluded from custody)`);
3381
4465
  }
3382
4466
 
3383
4467
  // ── 1a. THE CAPTURED-READ GATE (#57, lifted-when-declared since #58) ──
@@ -3405,7 +4489,7 @@ async function main(): Promise<void> {
3405
4489
  ((d as { declaredForeignReads?: unknown[] }).declaredForeignReads ?? []).length > 0,
3406
4490
  ),
3407
4491
  );
3408
- if (planAssets.some((asset) => asset.javascript.includes(CAPTURED_READ_LUMP_MARKER)) && !declaresCapturedReads) {
4492
+ if (routedPlanAssets.some((asset) => asset.javascript.includes(CAPTURED_READ_LUMP_MARKER)) && !declaresCapturedReads) {
3409
4493
  fail(
3410
4494
  `this law references read() — the CAPTURED-READ lane — but NO directive declares a ` +
3411
4495
  `captured-read query, so the sealed engine would provide no nomos.read and every ` +
@@ -3446,207 +4530,218 @@ async function main(): Promise<void> {
3446
4530
  // fresh frozen context, then force every domain — and refuse to package on any
3447
4531
  // failure that is not the expected unknown-directive refusal.
3448
4532
  {
3449
- // A CHILD process: the probe must freeze a REAL global object (a `vm` context
3450
- // global refuses Object.freeze), and the lump must run STRICT (the engine
3451
- // evals it with the STRICT flag — sloppy mode would silently no-op the very
3452
- // frozen-global writes the probe exists to catch).
4533
+ // Worker isolates in ONE child process: every plan must still freeze a REAL
4534
+ // global object (a `vm` context global refuses Object.freeze), and the lump
4535
+ // must run STRICT (the engine evals it with the STRICT flag — sloppy mode
4536
+ // would silently no-op the very frozen-global writes the probe exists to
4537
+ // catch). A prior implementation started one OS process per plan, serially;
4538
+ // on substantial law that made process startup, rather than law checking,
4539
+ // the dominant compile cost.
3453
4540
  const probePath = path.join(outDir, ".nomos_boot_probe.cjs");
3454
4541
  writeFileSync(
3455
4542
  probePath,
3456
4543
  [
3457
4544
  `// AUTO-GENERATED by nomos-compile — the frozen-sandbox lazy-boot probe.`,
3458
4545
  `const fs = require("node:fs");`,
3459
- `const plan = fs.readFileSync(process.argv[2], "utf8");`,
3460
- `const key = process.argv[3];`,
3461
- `(0, eval)('"use strict";\\n' + plan); // indirect eval → real global scope, strict like the engine`,
3462
- `Object.freeze(globalThis); // the sealed sandbox freezes before dispatch`,
3463
- `let msg;`,
3464
- `try { globalThis.plan({ intent: { domain: key, directiveId: "__nomos_boot_probe__" } }); msg = "unexpected-success"; }`,
3465
- `catch (e) { msg = String((e && e.message) || e); }`,
3466
- `if (!/no directive registered/.test(msg)) { console.error(key + "\\u0000" + msg); process.exit(3); }`,
4546
+ `const os = require("node:os");`,
4547
+ `const { Worker, isMainThread, parentPort, workerData } = require("node:worker_threads");`,
4548
+ `if (!isMainThread) {`,
4549
+ ` const plan = fs.readFileSync(workerData.planPath, "utf8");`,
4550
+ ` try {`,
4551
+ ` (0, eval)('"use strict";\\n' + plan); // indirect eval → real global scope, strict like the engine`,
4552
+ ` Object.freeze(globalThis); // the sealed sandbox freezes before dispatch`,
4553
+ ` for (const [domain, directiveId] of workerData.checks) {`,
4554
+ ` let found;`,
4555
+ ` try { found = globalThis.__nomosHasDirective(domain, directiveId); }`,
4556
+ ` catch (e) { parentPort.postMessage({ domain, error: String((e && e.message) || e) }); return; }`,
4557
+ ` if (directiveId !== "" && found !== true) {`,
4558
+ ` parentPort.postMessage({ domain, error: "routed offer '" + directiveId + "' is absent from its selected plan registry" });`,
4559
+ ` return;`,
4560
+ ` }`,
4561
+ ` }`,
4562
+ ` parentPort.postMessage({ ok: true });`,
4563
+ ` } catch (e) {`,
4564
+ ` parentPort.postMessage({ domain: workerData.checks[0]?.[0] || "?", error: String((e && e.message) || e) });`,
4565
+ ` }`,
4566
+ `} else {`,
4567
+ ` const jobs = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));`,
4568
+ ` const requested = Number(process.env.NOMOS_BOOT_PROBE_CONCURRENCY || os.availableParallelism());`,
4569
+ ` const concurrency = Math.max(1, Math.min(jobs.length, Number.isFinite(requested) ? Math.floor(requested) : 1));`,
4570
+ ` let cursor = 0;`,
4571
+ ` let failure;`,
4572
+ ` const run = (job) => new Promise((resolve) => {`,
4573
+ ` const worker = new Worker(__filename, { workerData: job });`,
4574
+ ` let result;`,
4575
+ ` worker.once("message", (message) => { result = message; });`,
4576
+ ` worker.once("error", (error) => { result = { domain: job.checks[0]?.[0] || "?", error: error.message }; });`,
4577
+ ` worker.once("exit", (code) => resolve(result || { domain: job.checks[0]?.[0] || "?", error: "probe worker exited " + code + " without a result" }));`,
4578
+ ` });`,
4579
+ ` const drain = async () => {`,
4580
+ ` while (failure === undefined) {`,
4581
+ ` const index = cursor++;`,
4582
+ ` if (index >= jobs.length) return;`,
4583
+ ` const result = await run(jobs[index]);`,
4584
+ ` if (result.ok !== true) failure = result;`,
4585
+ ` }`,
4586
+ ` };`,
4587
+ ` Promise.all(Array.from({ length: concurrency }, () => drain())).then(() => {`,
4588
+ ` if (failure !== undefined) {`,
4589
+ ` console.error(failure.domain + "\\u0000" + failure.error);`,
4590
+ ` process.exitCode = 3;`,
4591
+ ` }`,
4592
+ ` }).catch((error) => { console.error("?\\u0000" + error.message); process.exitCode = 3; });`,
4593
+ ` }`,
3467
4594
  ``,
3468
4595
  ].join("\n"),
3469
4596
  "utf8",
3470
4597
  );
3471
- for (const route of planRoutes.filter((candidate) => candidate.selector.startsWith("plan:domain:"))) {
3472
- const domainKey = route.selector.slice("plan:domain:".length);
3473
- const asset = planAssets.find((candidate) => candidate.sha256 === route.planSha256)!;
4598
+ const checksByPlan = new Map<string, Set<string>>();
4599
+ const addCheck = (planSha256: string, domain: string, directiveId: string): void => {
4600
+ const checks = checksByPlan.get(planSha256) ?? new Set<string>();
4601
+ checks.add(`${domain}\0${directiveId}`);
4602
+ checksByPlan.set(planSha256, checks);
4603
+ };
4604
+ for (const route of planRoutes) {
4605
+ if (route.selector.startsWith("plan:domain:")) {
4606
+ addCheck(route.planSha256, route.selector.slice("plan:domain:".length), "");
4607
+ continue;
4608
+ }
4609
+ const match = /^plan:offer:([^:]+):(.+)$/.exec(route.selector);
4610
+ if (match !== null) addCheck(route.planSha256, match[1]!, match[2]!);
4611
+ }
4612
+ const probeJobsPath = path.join(outDir, ".nomos_boot_jobs.json");
4613
+ const probePlanPaths: string[] = [];
4614
+ const probeJobs = [...checksByPlan].map(([planSha256, encodedChecks]) => {
4615
+ const checks = [...encodedChecks].map((encoded) => {
4616
+ const separator = encoded.indexOf("\0");
4617
+ return [encoded.slice(0, separator), encoded.slice(separator + 1)] as const;
4618
+ });
4619
+ const asset = routedPlanAssets.find((candidate) => candidate.sha256 === planSha256)!;
3474
4620
  const planPath = path.join(outDir, `.nomos_boot_${planSequence++}.js`);
3475
- writeFileSync(planPath, asset.javascript, "utf8");
3476
- try {
3477
- execFileSync(process.execPath, [probePath, planPath, domainKey], {
3478
- encoding: "utf8", stdio: ["ignore", "ignore", "pipe"],
3479
- });
3480
- } catch (e) {
3481
- const stderr = String((e as { stderr?: string }).stderr ?? "").trim();
3482
- const [, msg = stderr] = stderr.includes("\u0000") ? stderr.split("\u0000") : ["?", stderr];
3483
- fail(
3484
- `domain '${domainKey}' failed its frozen-sandbox plan probe — a module initializer ` +
3485
- `touches ambient globals after the freeze (the engine would refuse its first dispatch): ${msg}`,
3486
- );
4621
+ if (sharedRuntimeProfileJavascript === undefined) {
4622
+ fail("canonical shared runtime was not compiled before the boot probe");
3487
4623
  }
4624
+ writeFileSync(planPath, sharedRuntimeProfileJavascript + asset.javascript, "utf8");
4625
+ probePlanPaths.push(planPath);
4626
+ return { planPath, checks };
4627
+ });
4628
+ writeFileSync(probeJobsPath, JSON.stringify(probeJobs), "utf8");
4629
+ try {
4630
+ execFileSync(process.execPath, [probePath, probeJobsPath], {
4631
+ encoding: "utf8", stdio: ["ignore", "ignore", "pipe"],
4632
+ });
4633
+ } catch (e) {
4634
+ const stderr = String((e as { stderr?: string }).stderr ?? "").trim();
4635
+ const [domainKey = "?", msg = stderr] = stderr.includes("\u0000") ? stderr.split("\u0000") : ["?", stderr];
4636
+ fail(
4637
+ `domain '${domainKey}' failed its selected-plan registry probe — the compiler routed an ` +
4638
+ `offer to a plan that cannot dispatch it, or a module initializer failed after the ` +
4639
+ `sandbox freeze: ${msg}`,
4640
+ );
4641
+ } finally {
4642
+ rmSync(probePath, { force: true });
4643
+ rmSync(probeJobsPath, { force: true });
4644
+ for (const planPath of probePlanPaths) rmSync(planPath, { force: true });
3488
4645
  }
3489
4646
  }
3490
4647
 
3491
- // ── 2. USD IR + the package envelope ──
3492
- const usdJson = emitUsdJsonForModules(domainModules);
3493
- const text = nativePlanPackageUsda(
4648
+ // ── 2. Canonical typed USDA closure ──
4649
+ // There is no flattened package and no JSON law intermediate. Sparse typed source opinions plus
4650
+ // compiler-owned plan/framework layers are the package; composition is deferred to LawStage.
4651
+ if (sharedRuntimeProfileJavascript === undefined) {
4652
+ fail("canonical shared runtime was not compiled");
4653
+ }
4654
+ const sharedRuntimeAsset: UsdRuntimeAsset = {
4655
+ sha256: sha256HexUtf8(sharedRuntimeProfileJavascript),
4656
+ javascript: sharedRuntimeProfileJavascript,
4657
+ };
4658
+ const lawClosure = emitLawClosure(
3494
4659
  cfg.name,
3495
- JSON.parse(usdJson),
3496
- domainModules,
3497
- planAssets,
4660
+ domainModules.map((module) => module.domain ?? module.name),
4661
+ runtimeDependencies,
4662
+ v3DomainAssets,
4663
+ sharedRuntimeAsset,
4664
+ routedPlanAssets,
3498
4665
  planRoutes,
3499
4666
  );
3500
- const pkgPath = path.join(outDir, `${cfg.name}.package.usda`);
3501
- const domainHash = sha256HexUtf8(text);
3502
-
3503
- // ── 2a. the REAL layered RELATION `.usda` set — an additive, deterministic visualization +
3504
- // cross-check artifact (usdview / `usdcat --flatten`). One sublayer per domain carrying its
3505
- // relation opinions as PRIMS + a root with `subLayers` STRONGEST-FIRST; usdcat composes the
3506
- // shared relation types, the compiler's `composeSharedRelationTypes` is the fail-closed guard
3507
- // on top. Emitted iff ANY domain declares a relation type. Never feeds the package hash.
3508
- {
3509
- const relInputs: RelationLayerInput[] = [];
3510
- for (const mod of domainModules) {
3511
- // Per-domain UNCOMPOSED relation types: emit the single module alone (single-domain ⇒ no shared
3512
- // type ⇒ its own decls, un-unioned — the layers ARE the sublayers usdcat will compose).
3513
- const one = emitUsd([{ path: `/Nomos/${mod.domain ?? mod.name}`, module: mod }]);
3514
- const types = one.layers[0]?.nomosRelations?.types;
3515
- if (types !== undefined && Object.keys(types).length > 0) {
3516
- relInputs.push({ domain: mod.domain ?? mod.name, types });
3517
- }
3518
- }
3519
- if (relInputs.length > 0) {
3520
- const emit = emitLayeredRelationUsda(relInputs, cfg.name);
3521
- const relDir = path.join(outDir, `${cfg.name}.relation-layers`);
3522
- mkdirSync(relDir, { recursive: true });
3523
- for (const f of emit.files) writeFileSync(path.join(relDir, f.name), f.text, "utf8");
3524
- writeFileSync(path.join(relDir, "root.usda"), emit.root, "utf8");
3525
- }
4667
+ // The composed USDA closure is the law. Generated clients, lifecycle rows and
4668
+ // compatibility metadata all bind this identity, never the retired flattened envelope.
4669
+ const domainHash = lawClosure.identity;
4670
+ const lawClosureAssetsPath = path.join(outDir, `${cfg.name}.law`);
4671
+ const lawUsdzPath = path.join(outDir, `${cfg.name}.law.usdz`);
4672
+ rmSync(path.join(outDir, `${cfg.name}.law-closure.usda`), { force: true });
4673
+ rmSync(path.join(outDir, `${cfg.name}.law-closure.assets`), { recursive: true, force: true });
4674
+ rmSync(lawClosureAssetsPath, { recursive: true, force: true });
4675
+ mkdirSync(lawClosureAssetsPath, { recursive: true });
4676
+ for (const asset of lawClosure.assets) {
4677
+ const assetPath = path.join(lawClosureAssetsPath, asset.logicalPath);
4678
+ mkdirSync(path.dirname(assetPath), { recursive: true });
4679
+ writeFileSync(assetPath, asset.bytes);
3526
4680
  }
3527
-
3528
- // ── 2b. REQUIRED LAYERED emission — the executable envelope above retains the
3529
- // canonical resolved law, while these are the authored/composable source layers. The
3530
- // homomorphism is ASSERTED fail-closed on every compile: the
3531
- // per-module layer stack must resolve to the same canonical law embedded
3532
- // in the package, or compilation refuses.
3533
- let layeredRootPath: string | undefined;
3534
- let layeredFileCount = 0;
4681
+ const rootAsset = lawClosure.assets.find((asset) => asset.logicalPath === lawClosure.rootLogicalPath);
4682
+ if (rootAsset === undefined) fail("v3 closure: root.usda is missing from the emitted law stage");
4683
+ const lawUsdz = usdzPack([
4684
+ [`layers/${rootAsset.logicalPath}`, rootAsset.bytes],
4685
+ ...lawClosure.assets
4686
+ .filter((asset) => asset !== rootAsset)
4687
+ .map((asset) => [`layers/${asset.logicalPath}`, asset.bytes] as const),
4688
+ ]);
4689
+ writeFileSync(lawUsdzPath, lawUsdz);
4690
+ const layeredRootPath = path.join(lawClosureAssetsPath, lawClosure.rootLogicalPath);
4691
+ const closureAssetCount = lawClosure.assets.length;
3535
4692
  let usdcatFlattenVerified = false;
3536
- let composedUsdIdentity: string | undefined;
3537
- {
3538
- const safe = (s: string) => s.replace(/[^A-Za-z0-9._-]/g, "_");
3539
- const layersDir = path.join(outDir, "layers");
3540
- mkdirSync(layersDir, { recursive: true });
3541
- const allLayers: UsdLayer[] = [];
3542
- const relPaths: string[] = [];
3543
- const pendingWrites: { path: string; text: string }[] = [];
3544
- const canonicalLayers = new Map(
3545
- (JSON.parse(usdJson) as { layers: UsdLayer[] }).layers.map((layer) => [layer.path, layer] as const),
3546
- );
3547
- for (const ld of layeredDomains) {
3548
- const authored = emitLayeredDomain(ld.key, ld.inputs, { applyBusinessCorrectionLayer: false });
3549
- const canonicalLayer = canonicalLayers.get(`/Nomos/${ld.key}`);
3550
- // Authored layers carry structural declarations (aggregate/directive/query prims).
3551
- // Domain-level `nomos:*` values are the compiler's closure over the complete package
3552
- // (relation namespaces, read projections, invariant gates, contracts and language
3553
- // consequences), so placing partial versions on source layers lets weaker generated
3554
- // facts show through. Strip those derived attributes from source layers and append one
3555
- // explicit strongest closure layer. This is ordinary OpenUSD composition: source
3556
- // opinions remain inspectable and the compiler-owned overlay is the one canonical law.
3557
- const structuralLayers = authored.map((emitted, index) => {
3558
- const { path: layerPath, prims, queries, deriveds, combineds } = emitted.layer;
3559
- const layer: UsdLayer = {
3560
- path: layerPath,
3561
- prims,
3562
- ...(queries !== undefined ? { queries } : {}),
3563
- ...(deriveds !== undefined ? { deriveds } : {}),
3564
- ...(combineds !== undefined ? { combineds } : {}),
3565
- };
3566
- return {
3567
- ...emitted,
3568
- layer,
3569
- usda: usdaLayerText(ld.key, emitted.name, layer, ld.inputs[index]!.module, new Set()),
3570
- };
3571
- });
3572
- const closureLayers = canonicalLayer === undefined
3573
- ? []
3574
- : (() => {
3575
- const layer: UsdLayer = canonicalLayer;
3576
- const name = "__nomos_package_closure";
3577
- const canonicalModule = domainModules.find((module) =>
3578
- (module.domain ?? module.name) === ld.key);
3579
- return [{
3580
- name,
3581
- layer,
3582
- usda: usdaLayerText(ld.key, name, layer, canonicalModule, new Set()),
3583
- }];
3584
- })();
3585
- const emittedLayers = [...structuralLayers, ...closureLayers];
3586
- for (const emitted of emittedLayers) {
3587
- const base = `${safe(ld.key)}--${safe(emitted.name)}`;
3588
- pendingWrites.push({ path: path.join(layersDir, `${base}.usda`), text: emitted.usda });
3589
- pendingWrites.push({
3590
- path: path.join(layersDir, `${base}.layer.json`),
3591
- text: JSON.stringify(emitted.layer) + "\n",
3592
- });
3593
- allLayers.push(emitted.layer);
3594
- relPaths.push(`layers/${base}.usda`);
3595
- }
3596
- }
3597
- const flat = flattenLayeredUsd(allLayers);
3598
- composedUsdIdentity = sha256HexUtf8(canonicalJson(flat));
3599
- const canonicalLaw = JSON.parse(usdJson);
3600
- if (canonicalJson(flat) !== canonicalJson(canonicalLaw)) {
3601
- const difference = firstLawDifference(canonicalLaw, flat) ?? "canonical law differs";
4693
+ let usdcatFlattenCached = false;
4694
+ const composedUsdIdentity = domainHash;
4695
+ const usdcat = resolveUsdcat();
4696
+ if (usdcat === undefined) {
4697
+ if (requireUsdcat) {
3602
4698
  fail(
3603
- `layered emission does not flatten to the canonical composed IR — the module fold and ` +
3604
- `the layer-stack fold DISAGREE for this package (usually a re-declared directive ` +
3605
- `silently dropping an omit-when-empty reads/emits boundary, or a re-declared ` +
3606
- `aggregate dropping a field: USD shows the weaker opinion through; the module fold ` +
3607
- `does not). First difference: ${difference}. Refusing to emit the package.`,
4699
+ "stock OpenUSD `usdcat` is required for this compile but was not found. " +
4700
+ "Install OpenUSD or set NOMOS_USDCAT to the executable path.",
3608
4701
  );
3609
4702
  }
3610
- for (const w of pendingWrites) writeFileSync(w.path, w.text, "utf8");
3611
- layeredRootPath = path.join(outDir, `${cfg.name}.layered.usda`);
3612
- writeFileSync(layeredRootPath, layeredRootUsda(relPaths), "utf8");
3613
- layeredFileCount = relPaths.length;
3614
-
3615
- const usdcat = resolveUsdcat();
3616
- if (usdcat === undefined) {
3617
- if (requireUsdcat) {
3618
- fail(
3619
- "stock OpenUSD `usdcat` is required for this compile but was not found. " +
3620
- "Install OpenUSD or set NOMOS_USDCAT to the executable path.",
3621
- );
3622
- }
4703
+ } else {
4704
+ const usdcatProofPath = path.join(outDir, `.${cfg.name}.usdcat-proof.usda`);
4705
+ // Retired cache encoding: proof metadata follows the same inspectable USDA
4706
+ // vocabulary as the closure it witnesses.
4707
+ rmSync(path.join(outDir, `.${cfg.name}.usdcat-proof.json`), { force: true });
4708
+ const proofKey = createHash("sha256")
4709
+ .update("nomos-usda-v3-load\0")
4710
+ .update(domainHash)
4711
+ .update("\0")
4712
+ .update(String(usdcat))
4713
+ .digest("hex");
4714
+ const proofCodec = await import("../compile_proof.mjs" as string) as {
4715
+ readKeyProofUsda(file: string, format: string): string | undefined;
4716
+ writeKeyProofUsda(file: string, format: string, key: string): void;
4717
+ };
4718
+ const priorProofKey = proofCodec.readKeyProofUsda(
4719
+ usdcatProofPath,
4720
+ "nomos.usdcat-load-proof.v1",
4721
+ );
4722
+ if (priorProofKey === proofKey) {
4723
+ usdcatFlattenVerified = true;
4724
+ usdcatFlattenCached = true;
3623
4725
  } else {
3624
- let flattenedText: string;
3625
4726
  try {
3626
- flattenedText = execFileSync(usdcat, ["--flatten", layeredRootPath], {
4727
+ execFileSync(usdcat, ["--loadOnly", layeredRootPath], {
3627
4728
  encoding: "utf8",
3628
- stdio: ["ignore", "pipe", "pipe"],
4729
+ stdio: ["ignore", "ignore", "pipe"],
3629
4730
  maxBuffer: 256 * 1024 * 1024,
3630
4731
  });
3631
4732
  } catch (error) {
3632
4733
  const stderr = String((error as { stderr?: string }).stderr ?? "").trim();
3633
- fail(`stock OpenUSD refused the generated layer stack: ${stderr || String(error)}`);
3634
- }
3635
- const openUsdLaw = parseUsdaDocument(flattenedText);
3636
- if (canonicalJson(openUsdLaw) !== canonicalJson(canonicalLaw)) {
3637
- const difference = firstLawDifference(canonicalLaw, openUsdLaw) ?? "canonical law differs";
3638
- fail(
3639
- `stock OpenUSD and Nomos resolve different effective law. First difference: ${difference}. ` +
3640
- `Refusing to emit the package.`,
3641
- );
4734
+ rmSync(lawUsdzPath, { force: true });
4735
+ fail(`stock OpenUSD refused the canonical typed USDA closure: ${stderr || String(error)}`);
3642
4736
  }
4737
+ proofCodec.writeKeyProofUsda(
4738
+ usdcatProofPath,
4739
+ "nomos.usdcat-load-proof.v1",
4740
+ proofKey,
4741
+ );
3643
4742
  usdcatFlattenVerified = true;
3644
4743
  }
3645
4744
  }
3646
- // The executable package is the COMMITTED result of composition. Do not leave
3647
- // a newly-written package behind when either Nomos or stock OpenUSD rejects its
3648
- // source stack; rejected layer artifacts remain useful diagnostics, not runnable law.
3649
- writeFileSync(pkgPath, text, "utf8");
3650
4745
 
3651
4746
  // ── 2c. THE PUBLISHED OPENUSD PACKAGE ASSET ──
3652
4747
  // `rootPart` is the explicit package/application boundary from the OpenUSD composition ADR.
@@ -3661,7 +4756,7 @@ async function main(): Promise<void> {
3661
4756
  packageIdentity: composedUsdIdentity!,
3662
4757
  domains: presentationDomainModules.map((module) => module.domain ?? module.name),
3663
4758
  root: cfg.rootPart,
3664
- lawAsset: `./${cfg.name}.layered.usda`,
4759
+ lawAsset: `./${cfg.name}.law/root.usda`,
3665
4760
  });
3666
4761
  packageAssetPath = path.join(outDir, `${cfg.name}.asset.usda`);
3667
4762
  const candidateAssetPath = path.join(outDir, `.${cfg.name}.asset.candidate.usda`);
@@ -3680,13 +4775,16 @@ async function main(): Promise<void> {
3680
4775
  }
3681
4776
  }
3682
4777
 
3683
- // ── 3. the IDENTITY emit (for the summary's per-domain identityHash) ──
3684
- // The admission identity is carried INSIDE the package USD IR (emitUsd) + the committed
3685
- // nomos.stable-ids.json lockfile; the deploy/cloud/runtime contract uses neither a read-manifest nor an
3686
- // identity-manifest sidecar. The old domain_manifests.json / domain_identity_*.json build files were
3687
- // local diagnostics the contract ignored — REMOVED (they only confused /build consumers). We still
3688
- // compute the identity to surface its per-domain hash in the summary below.
3689
- const identity = buildIdentity(domainModules);
4778
+ // ── 3. typed per-domain identity (diagnostic/client compatibility metadata) ──
4779
+ // A domain's identity is the digest of its canonical sparse USDA layer. Do not rebuild the retired
4780
+ // canonical JSON manifest merely to print or embed a second, competing identity.
4781
+ const identityHashes: Record<string, string> = {};
4782
+ for (const module of domainModules) {
4783
+ const hash = typedDomainIdentityByName.get(module.name);
4784
+ if (hash === undefined) fail(`typed USDA identity is missing for domain '${module.name}'`);
4785
+ identityHashes[module.name] = hash;
4786
+ identityHashes[module.domain ?? module.name] = hash;
4787
+ }
3690
4788
 
3691
4789
  // ── 3a. the CLIENT/LAW COMPATIBILITY MANIFEST (§5, architecture/plug_and_play_dx.md).
3692
4790
  // A DIAGNOSIS surface, NEVER a gate: the shape-compat arc (#72) established that clients
@@ -3705,7 +4803,7 @@ async function main(): Promise<void> {
3705
4803
  const compatDomains: NomosCompat["domains"] = {};
3706
4804
  for (const m of presentationDomainModules) {
3707
4805
  const key = m.domain ?? m.name;
3708
- const identityHash = identity.hashes[key];
4806
+ const identityHash = identityHashes[key];
3709
4807
  compatDomains[key] = identityHash === undefined ? {} : { identityHash };
3710
4808
  }
3711
4809
  // RELEASE COHERENCE — the kernel capabilities this law REQUIRES (compound-key queries +
@@ -3717,12 +4815,6 @@ async function main(): Promise<void> {
3717
4815
  ...(m.surfacedResolvers ? { surfacedResolvers: m.surfacedResolvers } : {}),
3718
4816
  ...(m.aggregates ? { aggregates: m.aggregates } : {}),
3719
4817
  }));
3720
- try {
3721
- const parsed = JSON.parse(usdJson) as { layers?: Record<string, unknown>[] };
3722
- for (const layer of parsed.layers ?? []) probes.push({ layer });
3723
- } catch {
3724
- /* shape-derived features (compoundKeys) still apply from the queries above */
3725
- }
3726
4818
  return requiredKernelFeatures(probes);
3727
4819
  })();
3728
4820
  const nomosCompat: NomosCompat = {
@@ -3734,34 +4826,24 @@ async function main(): Promise<void> {
3734
4826
  requiresKernel,
3735
4827
  };
3736
4828
 
3737
- // ── 3b. the OpenAPI spec — THE PACKAGE'S SELF-DESCRIPTION (the host's runtime routing table;
3738
- // architecture/githolon_host_contract.md "The Self-Describing API"). Each domain emits its fragment
3739
- // from its OWN zod (z.toJSONSchema); the package's spec is the law-blind merge. Deploy STAGES it like
3740
- // the read overlay; the workspace's full spec is the runtime merge of installed packages' specs
3741
- // (currentLaw); the host ingests it and builds its router from it — no domain/directive name in host
3742
- // source. Self-describing for the whole web ecosystem (Swagger/codegen/Hono/agent tool-calling).
4829
+ // ── 3b. public OpenAPI diagnostic/codegen projection. This JSON document is not law, install input,
4830
+ // a runtime route table, or host authority. Kernel execution and current-law description derive from
4831
+ // the installed typed USDA stage; this projection exists only for Swagger, client generators and agent
4832
+ // tooling that consume OpenAPI.
3743
4833
  const publicOpenapi = mergeOpenApiFragments(
3744
4834
  presentationDomainModules.map((m) => generateOpenApiFragment(m, { lawHash: domainHash })),
3745
4835
  { title: `${cfg.name} (nomos package)`, lawHash: domainHash },
3746
4836
  );
3747
- const runtimeOpenapi = mergeOpenApiFragments(
3748
- domainModules.map((m) => generateOpenApiFragment(m, { lawHash: domainHash, includeFrameworkRoutes: true })),
3749
- { title: `${cfg.name} (nomos package)`, lawHash: domainHash },
3750
- );
3751
4837
  const openapiPath = path.join(outDir, `${cfg.name}.openapi.json`);
3752
4838
  writeFileSync(openapiPath, JSON.stringify(publicOpenapi, null, 2) + "\n", "utf8");
3753
4839
 
3754
- // ── 4. the one-call deploy body (package only) ──
3755
- // SELF-DESCRIBING: `summary` states what the (otherwise hex-opaque) package
3756
- // contains — purely derived from the inputs (no timestamps), ignored by the
3757
- // deploy endpoint, and mirrored as build/<name>.summary.txt for a human or
3758
- // agent who simply cats the build output.
4840
+ // ── 4. human build summary (diagnostic only; never deployment input) ──
3759
4841
  const summary = {
3760
4842
  package: cfg.name,
3761
- // Binds the deploy body to the published package asset consumed by an application stage. This is the
3762
- // content identity of the composed source layers; domainHash below is the runtime package-law hash.
4843
+ // Binds the summary to the published package asset consumed by an application stage.
3763
4844
  sourceIdentity: composedUsdIdentity!,
3764
4845
  domainHash,
4846
+ lawClosureIdentity: lawClosure.identity,
3765
4847
  domains: presentationDomainModules.map((m) => ({
3766
4848
  domain: m.domain ?? m.name,
3767
4849
  aggregates: m.aggregates.map((a) => a.id),
@@ -3769,29 +4851,9 @@ async function main(): Promise<void> {
3769
4851
  queries: (m.queries ?? []).map((q) => q.id),
3770
4852
  counts: (m.counts ?? []).map((c) => c.id),
3771
4853
  sums: (m.sums ?? []).map((s) => s.id),
3772
- identityHash: identity.hashes[m.domain ?? m.name],
4854
+ identityHash: identityHashes[m.domain ?? m.name],
3773
4855
  })),
3774
4856
  };
3775
- const deployBody = {
3776
- summary,
3777
- packageUsda: text,
3778
- // The capability lowering (unhashed sidecar; omit-when-empty — see the
3779
- // collector's doc above). The worker stages it on the manifest overlay:
3780
- // the deploy-time binding warning + the executor's scan map read it there.
3781
- ...(capabilityLowerings.length > 0 ? { capabilities: capabilityLowerings } : {}),
3782
- // THE STRUCTURAL OFFER (#72): the per-domain interface, staged on the manifest overlay so
3783
- // `connect()` pulls the workspace's installed shapes and negotiates by structural subtyping.
3784
- interface: packageInterface,
3785
- // THE SELF-DESCRIBING API: this package's OpenAPI spec (law-derived). The worker stages it on the
3786
- // manifest overlay; a read serves the workspace's merged spec; the host builds its router from it.
3787
- openapi: runtimeOpenapi,
3788
- // THE CLIENT/LAW COMPAT MANIFEST (§5): a DIAGNOSIS surface (law hash + interface hash + generator
3789
- // provenance) the connect-time classifier reads. Frontend-only — the package bytes above ignore it.
3790
- nomosCompat,
3791
- };
3792
- const deployPath = path.join(outDir, `${cfg.name}.deploy.json`);
3793
- writeFileSync(deployPath, JSON.stringify(deployBody) + "\n", "utf8");
3794
-
3795
4857
  // ── 4b. the GENERATED PROOF — a runnable, domain-shaped e2e synthesized from the
3796
4858
  // law itself (codegen_proof.ts): reshape the law, recompile, the proof
3797
4859
  // reshapes itself — no test rewriting. NEVER FATAL: a domain the
@@ -3840,14 +4902,16 @@ async function main(): Promise<void> {
3840
4902
 
3841
4903
  const summaryTxt = [
3842
4904
  `${cfg.name} — compiled Nomos domain package`,
3843
- `law (domainHash): ${domainHash} # sha256(${cfg.name}.package.usda) — content-addressed`,
4905
+ `law (domainHash): ${domainHash} # composed typed USDA closure identity`,
4906
+ `law transport: ${path.basename(lawUsdzPath)} # lossless one-file USDZ envelope; not canonical custody`,
4907
+ `canonical law: ${path.basename(lawClosureAssetsPath)}/ # exact logically named, human-readable USDA`,
3844
4908
  ...summary.domains.flatMap((d) => [
3845
4909
  `domain '${d.domain}'${d.identityHash ? ` (identity ${d.identityHash.slice(0, 12)}…)` : " (EXCLUDED from identity manifest)"}`,
3846
4910
  ` aggregates: ${d.aggregates.join(", ") || "—"}`,
3847
4911
  ` directives: ${d.directives.join(", ") || "—"}`,
3848
4912
  ` queries: ${d.queries.join(", ") || "—"} counts: ${d.counts.join(", ") || "—"} sums: ${d.sums.join(", ") || "—"}`,
3849
4913
  ]),
3850
- `deploy: POST ${cfg.name}.deploy.json to /v2/workspaces/<ws>/domains (or: githolon deploy <ws>)`,
4914
+ `deploy: githolon deploy <ws> --file ${cfg.name}.law.usdz`,
3851
4915
  packageAssetPath !== undefined
3852
4916
  ? `package asset: ${path.basename(packageAssetPath)} — the real USD package root consumed by application composition`
3853
4917
  : `package asset: NOT EMITTED — declare rootPart: { name, kind: "component" | "assembly" }`,
@@ -3877,10 +4941,9 @@ async function main(): Promise<void> {
3877
4941
  const temporaryClientPath = path.join(outDir, `.${cfg.name}.client.source.ts`);
3878
4942
  const temporaryRuntimeTypesPath = path.join(outDir, `.${cfg.name}.application-runtime.d.ts`);
3879
4943
  const temporaryDeclarationPath = temporaryClientPath.replace(/\.ts$/, ".d.ts");
3880
- writeFileSync(temporaryClientPath, clientSource, "utf8");
3881
- writeFileSync(temporaryRuntimeTypesPath, [
4944
+ const runtimeTypesSource = [
3882
4945
  `export declare function nomosInternalOpenGeneratedApplication<T extends object>(`,
3883
- ` options: { cloud: string; application: string; signIn: (request?: { readonly deviceChallenge?: string }) => Promise<string> },`,
4946
+ ` options: { cloud: string; application: string; signIn: (request?: { readonly deviceChallenge?: string }) => Promise<string>; lawHash: string },`,
3884
4947
  ` bind: (holon: unknown, user: { readonly identityId: string; readonly identityAddress: string }) => T,`,
3885
4948
  `): Promise<T & {`,
3886
4949
  ` currentStatus(): Promise<any>;`,
@@ -3900,11 +4963,48 @@ async function main(): Promise<void> {
3900
4963
  ` policies: readonly { readonly domain: string; readonly domainHash: string; readonly query: string; readonly redeem: string; readonly personField: string; readonly roleField: string; readonly scopeField: string }[],`,
3901
4964
  `): Promise<void>;`,
3902
4965
  ``,
3903
- ].join("\n"), "utf8");
4966
+ ].join("\n");
4967
+ // Declaration generation used to ask TypeScript to rebuild and reduce the same 800 KiB public type graph
4968
+ // on every law compile, even when an implementation-only plan edit left that graph byte-identical. Cache
4969
+ // the result by the exact generated source, runtime declaration boundary, TypeScript version and pruning
4970
+ // algorithm. The installed law hash is normalized because it changes runtime compatibility identity but
4971
+ // has type `string` in every public declaration. A typed USDA proof binds the cached declaration bytes;
4972
+ // there is no JSON cache manifest and no cached file can become runtime or custody authority.
4973
+ const declarationProofCodec = await import("../compile_proof.mjs" as string) as {
4974
+ readKeyProofUsda(file: string, format: string): string | undefined;
4975
+ writeKeyProofUsda(file: string, format: string, key: string): void;
4976
+ };
4977
+ const declarationProofFormat = "nomos.typescript-declaration-proof.v1";
4978
+ const normalizedClientSource = clientSource.replaceAll(domainHash, "<nomos-composed-law-identity>");
4979
+ const declarationInputIdentity = createHash("sha256")
4980
+ .update(`${declarationProofFormat}\0`)
4981
+ .update(normalizedClientSource)
4982
+ .update("\0")
4983
+ .update(runtimeTypesSource)
4984
+ .update("\0")
4985
+ .update(ts.version)
4986
+ .update("\0")
4987
+ .update(readFileSync(fileURLToPath(import.meta.url)))
4988
+ .digest("hex");
4989
+ const declarationCacheDir = path.join(cfgDir, "node_modules", ".cache", "nomos", "typescript-declarations");
4990
+ const declarationCachePath = path.join(declarationCacheDir, `${declarationInputIdentity}.d.mts`);
4991
+ const declarationCacheProofPath = path.join(declarationCacheDir, `${declarationInputIdentity}.proof.usda`);
4992
+ const declarationProofKey = (bytes: Buffer): string => createHash("sha256")
4993
+ .update(`${declarationProofFormat}\0${declarationInputIdentity}\0`)
4994
+ .update(bytes)
4995
+ .digest("hex");
4996
+ const cachedDeclaration = existsSync(declarationCachePath) ? readFileSync(declarationCachePath) : undefined;
4997
+ const declarationCacheHit = cachedDeclaration !== undefined &&
4998
+ declarationProofCodec.readKeyProofUsda(declarationCacheProofPath, declarationProofFormat) ===
4999
+ declarationProofKey(cachedDeclaration);
5000
+ if (declarationCacheHit) copyFileSync(declarationCachePath, clientDeclarationPath);
3904
5001
  try {
3905
- const typescriptEntry = createRequire(import.meta.url).resolve("typescript/lib/typescript.js");
3906
- const ts = (await import(pathToFileURL(typescriptEntry).href)) as typeof import("typescript");
3907
- const program = ts.createProgram(
5002
+ if (!declarationCacheHit) {
5003
+ writeFileSync(temporaryClientPath, clientSource, "utf8");
5004
+ writeFileSync(temporaryRuntimeTypesPath, runtimeTypesSource, "utf8");
5005
+ const typescriptEntry = createRequire(import.meta.url).resolve("typescript/lib/typescript.js");
5006
+ const ts = (await import(pathToFileURL(typescriptEntry).href)) as typeof import("typescript");
5007
+ const program = ts.createProgram(
3908
5008
  [temporaryClientPath, temporaryRuntimeTypesPath],
3909
5009
  {
3910
5010
  target: ts.ScriptTarget.ES2022,
@@ -4069,8 +5169,18 @@ async function main(): Promise<void> {
4069
5169
  .map((statement) => printer.printNode(ts.EmitHint.Unspecified, statement, declarationFile)),
4070
5170
  "",
4071
5171
  ].join("\n");
4072
- writeFileSync(temporaryDeclarationPath, declarationSource, "utf8");
4073
- renameSync(temporaryDeclarationPath, clientDeclarationPath);
5172
+ writeFileSync(temporaryDeclarationPath, declarationSource, "utf8");
5173
+ renameSync(temporaryDeclarationPath, clientDeclarationPath);
5174
+ mkdirSync(declarationCacheDir, { recursive: true });
5175
+ const declarationCacheTemporaryPath = `${declarationCachePath}.tmp-${process.pid}`;
5176
+ copyFileSync(clientDeclarationPath, declarationCacheTemporaryPath);
5177
+ renameSync(declarationCacheTemporaryPath, declarationCachePath);
5178
+ declarationProofCodec.writeKeyProofUsda(
5179
+ declarationCacheProofPath,
5180
+ declarationProofFormat,
5181
+ declarationProofKey(readFileSync(declarationCachePath)),
5182
+ );
5183
+ }
4074
5184
  await esbuildMod.build({
4075
5185
  stdin: {
4076
5186
  contents: clientSource,
@@ -4159,7 +5269,7 @@ async function main(): Promise<void> {
4159
5269
  if (dartPackages !== undefined) {
4160
5270
  const r = emitDartPackages(
4161
5271
  presentationDomainModules, dartOut, domainHash, packageInterface, dartPackages,
4162
- dartNames, nomosCompat, cfg.name, deployPath,
5272
+ dartNames, nomosCompat, cfg.name, lawUsdzPath,
4163
5273
  );
4164
5274
  dartFiles = r.written;
4165
5275
  dartCursedHints = r.cursed;
@@ -4228,6 +5338,10 @@ async function main(): Promise<void> {
4228
5338
  // Package generation must not depend on which SDKs happen to be installed on the compiler's
4229
5339
  // machine. Write every source-dogfood override first; the analyzer loop may legitimately stop
4230
5340
  // at the first missing `dart`, but that must not leave later solve roots pinned to hosted code.
5341
+ const writeTextIfChanged = (file: string, text: string): void => {
5342
+ if (existsSync(file) && readFileSync(file, "utf8") === text) return;
5343
+ writeFileSync(file, text, "utf8");
5344
+ };
4231
5345
  for (const pkg of pkgDirs) {
4232
5346
  if (localDart) {
4233
5347
  const spec = readFileSync(path.join(pkg, "pubspec.yaml"), "utf8");
@@ -4248,27 +5362,124 @@ async function main(): Promise<void> {
4248
5362
  // so every local nomos_client solve root must also seat the matching local Flutter host even
4249
5363
  // when its own pubspec does not name nomos_flutter directly.
4250
5364
  if ((usesFlutter || usesClient) && localDart.flutter) lines.push(` nomos_flutter:`, ` path: ${localDart.flutter}`);
4251
- if (lines.length > 1) writeFileSync(path.join(pkg, "pubspec_overrides.yaml"), lines.join("\n") + "\n", "utf8");
5365
+ if (lines.length > 1) {
5366
+ writeTextIfChanged(path.join(pkg, "pubspec_overrides.yaml"), lines.join("\n") + "\n");
5367
+ }
4252
5368
  }
4253
5369
  }
5370
+ // `dart pub get` is a dependency-resolution operation, not a generated-source validator. Re-running
5371
+ // it for every package on every compile bought no additional proof: the analyzer consumes the exact
5372
+ // package_config produced from these pub inputs. Dart deliberately preserves package_config's mtime
5373
+ // when the resolution is unchanged, so timestamps cannot establish that relationship. Bind the exact
5374
+ // input and output bytes in a disposable USDA proof instead. The Dart-owned package_config may be JSON
5375
+ // internally, but it is third-party tooling output, never Nomos law or a Nomos manifest.
5376
+ const dartResolutionProof = await import("../compile_proof.mjs" as string) as {
5377
+ readKeyProofUsda(file: string, format: string): string | undefined;
5378
+ writeKeyProofUsda(file: string, format: string, key: string): void;
5379
+ };
5380
+ const dartResolutionFormat = "nomos.dart-package-resolution-proof.v1";
5381
+ const dartResolutionProofPath = (pkg: string): string =>
5382
+ path.join(pkg, ".dart_tool", ".nomos-pub-resolution-proof.usda");
5383
+ const dartResolutionKey = (pkg: string): string | undefined => {
5384
+ const files = [
5385
+ "pubspec.yaml",
5386
+ "pubspec.lock",
5387
+ "pubspec_overrides.yaml",
5388
+ path.join(".dart_tool", "package_config.json"),
5389
+ ].map((name) => ({ name, file: path.join(pkg, name) }));
5390
+ if (files.some(({ file }) => !existsSync(file))) return undefined;
5391
+ const hash = createHash("sha256").update(`${dartResolutionFormat}\0`);
5392
+ for (const { name, file } of files) hash.update(name).update("\0").update(readFileSync(file)).update("\0");
5393
+ return hash.digest("hex");
5394
+ };
5395
+ const dartPackageResolutionCurrent = (pkg: string): boolean => {
5396
+ const key = dartResolutionKey(pkg);
5397
+ return key !== undefined &&
5398
+ dartResolutionProof.readKeyProofUsda(dartResolutionProofPath(pkg), dartResolutionFormat) === key;
5399
+ };
4254
5400
  let dartMissing = false;
4255
5401
  let unresolved: string | undefined;
4256
5402
  for (const pkg of pkgDirs) {
5403
+ if (dartPackageResolutionCurrent(pkg)) continue;
4257
5404
  try {
4258
5405
  execFileSync("dart", ["pub", "get"], { cwd: pkg, stdio: "pipe", encoding: "utf8" });
5406
+ const key = dartResolutionKey(pkg);
5407
+ if (key !== undefined) {
5408
+ dartResolutionProof.writeKeyProofUsda(dartResolutionProofPath(pkg), dartResolutionFormat, key);
5409
+ }
4259
5410
  } catch (e) {
4260
5411
  const err = e as { code?: string; stdout?: string; stderr?: string };
4261
5412
  if (err.code === "ENOENT") { dartMissing = true; break; }
4262
5413
  unresolved = `${path.relative(process.cwd(), pkg)}: ${`${err.stdout ?? ""}${err.stderr ?? ""}`.trim().split("\n").slice(-3).join(" ")}`;
4263
5414
  }
4264
5415
  }
4265
- // Only analyze when EVERY package's deps resolved: `dart analyze` against an unresolved package reports
4266
- // every framework symbol (NomosRef, …) as undefined — a meaningless flood, not a real defect. An
4267
- // unresolved dep is already surfaced as UNVERIFIED below (e.g. a fixture pinning an as-yet-unpublished
4268
- // nomos_client — it verifies for real the moment that version lands).
5416
+ // The analyzer is a real codegen gate, but an identical generated Dart graph against identical
5417
+ // resolved packages and framework sources does not become more valid by launching another analyzer
5418
+ // process. Content-address that verdict as USDA. Any generated type/client change, pub resolution,
5419
+ // local dogfood runtime change, or Dart SDK version change produces a different key and runs the real
5420
+ // analyzer before the proof is written.
5421
+ const collectDartAnalysisFiles = (root: string): string[] => {
5422
+ const found: string[] = [];
5423
+ const visit = (directory: string): void => {
5424
+ if (!existsSync(directory)) return;
5425
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
5426
+ if (entry.name === ".dart_tool" || entry.name === "build") continue;
5427
+ const file = path.join(directory, entry.name);
5428
+ if (entry.isDirectory()) visit(file);
5429
+ else if (entry.isFile() && (entry.name.endsWith(".dart") || entry.name === "pubspec.yaml")) found.push(file);
5430
+ }
5431
+ };
5432
+ visit(root);
5433
+ return found.sort();
5434
+ };
5435
+ let dartAnalysisKey: string | undefined;
5436
+ let dartAnalysisCached = false;
4269
5437
  if (!dartMissing && unresolved === undefined && inPackage.length > 0) {
4270
5438
  try {
4271
- execFileSync("dart", ["analyze", "--no-fatal-warnings", ...inPackage], { stdio: "pipe", encoding: "utf8" });
5439
+ const dartVersion = execFileSync("dart", ["--version"], { stdio: "pipe", encoding: "utf8" }).trim();
5440
+ const inputs = new Map<string, string>();
5441
+ for (const file of [...inPackage].sort()) {
5442
+ inputs.set(`generated/${path.relative(dOut, file).replaceAll(path.sep, "/")}`, file);
5443
+ }
5444
+ for (const pkg of [...pkgDirs].sort()) {
5445
+ for (const relative of [
5446
+ "pubspec.yaml",
5447
+ "pubspec.lock",
5448
+ "pubspec_overrides.yaml",
5449
+ path.join(".dart_tool", "package_config.json"),
5450
+ ]) {
5451
+ const file = path.join(pkg, relative);
5452
+ if (existsSync(file)) {
5453
+ inputs.set(`resolution/${path.relative(dOut, pkg).replaceAll(path.sep, "/")}/${relative.replaceAll(path.sep, "/")}`, file);
5454
+ }
5455
+ }
5456
+ }
5457
+ if (localDart !== undefined) {
5458
+ for (const [role, root] of Object.entries(localDart)) {
5459
+ for (const file of collectDartAnalysisFiles(root)) {
5460
+ inputs.set(`framework/${role}/${path.relative(root, file).replaceAll(path.sep, "/")}`, file);
5461
+ }
5462
+ }
5463
+ }
5464
+ const hash = createHash("sha256")
5465
+ .update("nomos.dart-analysis-proof.v1\0")
5466
+ .update(dartVersion)
5467
+ .update("\0");
5468
+ for (const [logical, file] of [...inputs].sort(([left], [right]) => left.localeCompare(right))) {
5469
+ hash.update(logical).update("\0").update(readFileSync(file)).update("\0");
5470
+ }
5471
+ dartAnalysisKey = hash.digest("hex");
5472
+ const proofDir = path.join(cfgDir, "node_modules", ".cache", "nomos", "dart-analysis");
5473
+ const proofPath = path.join(proofDir, `${dartAnalysisKey}.proof.usda`);
5474
+ dartAnalysisCached = dartResolutionProof.readKeyProofUsda(
5475
+ proofPath,
5476
+ "nomos.dart-analysis-proof.v1",
5477
+ ) === dartAnalysisKey;
5478
+ if (!dartAnalysisCached) {
5479
+ execFileSync("dart", ["analyze", "--no-fatal-warnings", ...inPackage], { stdio: "pipe", encoding: "utf8" });
5480
+ mkdirSync(proofDir, { recursive: true });
5481
+ dartResolutionProof.writeKeyProofUsda(proofPath, "nomos.dart-analysis-proof.v1", dartAnalysisKey);
5482
+ }
4272
5483
  } catch (e) {
4273
5484
  const err = e as { code?: string; stdout?: string; stderr?: string };
4274
5485
  if (err.code === "ENOENT") { dartMissing = true; }
@@ -4278,6 +5489,10 @@ async function main(): Promise<void> {
4278
5489
  }
4279
5490
  }
4280
5491
  }
5492
+ // Only analyze when EVERY package's deps resolved: `dart analyze` against an unresolved package reports
5493
+ // every framework symbol (NomosRef, …) as undefined — a meaningless flood, not a real defect. An
5494
+ // unresolved dep is already surfaced as UNVERIFIED below (e.g. a fixture pinning an as-yet-unpublished
5495
+ // nomos_client — it verifies for real the moment that version lands).
4281
5496
  if (dartMissing) {
4282
5497
  // No `dart` on PATH — we genuinely cannot verify. Say so LOUDLY (never a `✓`), so a broken client is
4283
5498
  // NOT reported as a clean compile; installing dart turns this into a real gate.
@@ -4285,7 +5500,7 @@ async function main(): Promise<void> {
4285
5500
  } else if (unresolved !== undefined) {
4286
5501
  console.log(` dart ⚠ UNVERIFIED — \`dart pub get\` failed, so analysis is incomplete (${unresolved})`);
4287
5502
  } else if (inPackage.length > 0) {
4288
- console.log(` dart ✓ generated Dart analyzes clean (compile-valid ⇒ compilable)`);
5503
+ console.log(` dart ✓ generated Dart analyzes clean (compile-valid ⇒ compilable)${dartAnalysisCached ? " — content-addressed USDA proof reused" : ""}`);
4289
5504
  } else if (emittedDart.length > 0) {
4290
5505
  console.log(` dart — vendored (no pub package) emit — runtime package imports are unverifiable without a pubspec; analysis skipped (use dart.packages for a fully-gated pub package)`);
4291
5506
  }
@@ -4295,34 +5510,40 @@ async function main(): Promise<void> {
4295
5510
  // ── summary ──
4296
5511
  const rel = (p: string) => path.relative(process.cwd(), p);
4297
5512
  console.log(`nomos-compile: ${cfg.name}`);
4298
- console.log(` package ${rel(pkgPath)} (${text.length} bytes)`);
5513
+ console.log(` law ${rel(lawUsdzPath)} (${lawUsdz.byteLength} transport bytes)`);
4299
5514
  console.log(` domainHash ${domainHash}`);
4300
- if (layeredRootPath !== undefined) {
4301
- console.log(
4302
- ` layered ${rel(layeredRootPath)} (${layeredFileCount} module layer(s) in build/layers/ — Nomos flatten resolves the canonical law)`,
4303
- );
4304
- console.log(
4305
- usdcatFlattenVerified
4306
- ? ` usdcat ✓ stock OpenUSD --flatten resolves the same canonical law`
4307
- : ` usdcat ⚠ unavailable — set NOMOS_REQUIRE_USDCAT=1 in release CI to make this proof mandatory`,
4308
- );
4309
- }
5515
+ console.log(` custody ${rel(layeredRootPath)} (${closureAssetCount} exact named USDA/MJS asset(s))`);
5516
+ console.log(
5517
+ usdcatFlattenVerified
5518
+ ? ` usdcat ✓ stock OpenUSD loads the canonical typed USDA closure${usdcatFlattenCached ? " (content-addressed proof reused)" : ""}`
5519
+ : ` usdcat ⚠ unavailable — set NOMOS_REQUIRE_USDCAT=1 in release CI to make this proof mandatory`,
5520
+ );
4310
5521
  {
4311
5522
  const sum = (pick: (m: (typeof domainModules)[number]) => readonly unknown[] | undefined) =>
4312
5523
  presentationDomainModules.reduce((n, m) => n + (pick(m)?.length ?? 0), 0);
4313
5524
  console.log(` reads ${sum((m) => m.queries)} quer(y/ies), ${sum((m) => m.counts)} count(s), ${sum((m) => m.sums)} sum(s) — declared (routed via the openapi)`);
4314
5525
  }
5526
+ console.log(
5527
+ ` lowering workspace-invariant reads: ${rustWorkspaceInvariantReadIds.length} Rust, ` +
5528
+ `${javascriptWorkspaceInvariantReadIds.length} deterministic JavaScript fallback`,
5529
+ );
5530
+ if (javascriptWorkspaceInvariantReadIds.length > 0) {
5531
+ const shown = javascriptWorkspaceInvariantReadIds.slice(0, 8);
5532
+ const remainder = javascriptWorkspaceInvariantReadIds.length - shown.length;
5533
+ console.log(
5534
+ ` JS fallback: ${shown.join(", ")}${remainder > 0 ? ` (+${remainder} more)` : ""}; ` +
5535
+ `legal tenant code, measurable via NOMOS_PROFILE_PLANS=1`,
5536
+ );
5537
+ }
4315
5538
  // SECONDARY-CREATE AUTO-DECLARE (secondary_ensure.ts): surface every type the compiler sanctioned
4316
5539
  // from a plan's deterministic secondary `ensure(...)` — the id-mint-gate DX made visible (the author
4317
5540
  // wrote `ensure(B, …)`; the compiler declared `Ensure → B` so the deterministic id admits).
4318
5541
  for (const m of presentationDomainModules) {
4319
5542
  }
4320
5543
  const presentedIdentityNames = new Set(presentationDomainModules.map((m) => m.domain ?? m.name));
4321
- const presentedHashes = Object.entries(identity.hashes).filter(([domain]) => presentedIdentityNames.has(domain));
4322
- const presentedExcluded = identity.excluded.filter((entry) => presentedIdentityNames.has(entry.domain));
4323
- console.log(` identity ${presentedHashes.length} business domain(s) (private Nomos lifecycle law included but not exposed)${presentedExcluded.length ? ` — ${presentedExcluded.length} EXCLUDED (palette gap)` : ""}`);
5544
+ const presentedHashes = Object.entries(identityHashes).filter(([domain]) => presentedIdentityNames.has(domain));
5545
+ console.log(` identity ${presentedHashes.length} business domain(s) (private Nomos lifecycle law included but not exposed)`);
4324
5546
  for (const [dom, h] of presentedHashes) console.log(` ${dom.padEnd(20)} ${h}`);
4325
- for (const ex of presentedExcluded) console.log(` EXCLUDED ${ex.domain}: ${ex.reason}`);
4326
5547
  // STABLE IDS (#58): the minted/carried identity surface + every INFERRED rename —
4327
5548
  // the silent lane made visible (if an inference is wrong — you meant remove+add —
4328
5549
  // split the edit into two compiles, or answer the evolve gate with a disposition).
@@ -4343,7 +5564,7 @@ async function main(): Promise<void> {
4343
5564
  }
4344
5565
  console.log(` client ${rel(clientPath)} (typed TS client — payloads, read models, query accessors)`);
4345
5566
  for (const note of recursiveNotes) console.log(` note ${note}`);
4346
- console.log(` openapi ${rel(openapiPath)} (${Object.keys(publicOpenapi.paths).length} operation(s) — the public business API; private framework routes ride only in the deploy manifest)`);
5567
+ console.log(` openapi ${rel(openapiPath)} (${Object.keys(publicOpenapi.paths).length} operation(s) — public diagnostic sidecar; runtime self-description comes from installed USDA)`);
4347
5568
  if (proofPath !== undefined) {
4348
5569
  console.log(` proof ${rel(proofPath)} (a runnable e2e GENERATED from your law — run: githolon proof)`);
4349
5570
  } else {
@@ -4359,7 +5580,7 @@ async function main(): Promise<void> {
4359
5580
  // gate's refusal vocabulary, unified into ONE report. `githolon check` runs the same pass;
4360
5581
  // compile runs it implicitly and REFUSES on any refuse-level finding (a statically-provable
4361
5582
  // runtime refusal shipped as law). Warns/notes are printed but never block the compile.
4362
- const allCheckFindings = checkDomainModules(domainModules);
5583
+ const allCheckFindings = authorTimeFindings;
4363
5584
  const isFrameworkFinding = (finding: (typeof allCheckFindings)[number]) =>
4364
5585
  finding.domain === APPLICATION_HOME_DOMAIN || finding.directiveId?.startsWith("nomos_internal_") === true;
4365
5586
  const checkFindings = allCheckFindings.filter((finding) => !isFrameworkFinding(finding));
@@ -4410,7 +5631,7 @@ async function main(): Promise<void> {
4410
5631
  console.log(` dart ${rel(dartOut)}${path.sep} (${dartFiles.length} domain file(s) + ${supportText} — typed Dart for Flutter)`);
4411
5632
  }
4412
5633
  }
4413
- console.log(` deploy ${rel(deployPath)}`);
5634
+ console.log(` deploy githolon deploy <workspace> --file ${rel(lawUsdzPath)}`);
4414
5635
  console.log(``);
4415
5636
  console.log(`your business path:`);
4416
5637
  if (proofPath !== undefined) {
@@ -4425,6 +5646,53 @@ async function main(): Promise<void> {
4425
5646
  }
4426
5647
 
4427
5648
  if (analyzeFlag) runDartAnalyze(dartOut);
5649
+
5650
+ // The unchanged lane is itself USDA: a disposable content proof under
5651
+ // node_modules/.cache relates authored/compiler inputs to every generated
5652
+ // artifact. It is not another law manifest and carries no semantic opinions.
5653
+ const compileProofPath = process.env.NOMOS_COMPILE_PROOF_PATH;
5654
+ const compileArgumentIdentity = process.env.NOMOS_COMPILE_ARGUMENT_IDENTITY;
5655
+ if (
5656
+ process.exitCode !== 1 &&
5657
+ compileProofPath !== undefined &&
5658
+ compileArgumentIdentity !== undefined
5659
+ ) {
5660
+ const proof = await import("../compile_proof.mjs" as string) as {
5661
+ compileInputs(options: {
5662
+ cwd: string;
5663
+ dslDir: string;
5664
+ configPath: string;
5665
+ argumentIdentity: string;
5666
+ }): unknown;
5667
+ generatedOutputFiles(outDir: string, additional: readonly string[]): readonly { path: string; sha256: string }[];
5668
+ writeCompileProof(options: {
5669
+ proofPath: string;
5670
+ inputs: unknown;
5671
+ outputs: readonly { path: string; sha256: string }[];
5672
+ name: string;
5673
+ domainHash: string;
5674
+ law: string;
5675
+ }): void;
5676
+ };
5677
+ const inputs = proof.compileInputs({
5678
+ cwd: process.cwd(),
5679
+ dslDir: DSL_DIR,
5680
+ configPath,
5681
+ argumentIdentity: compileArgumentIdentity,
5682
+ });
5683
+ const outputs = proof.generatedOutputFiles(outDir, [
5684
+ lockPath,
5685
+ ...dartFiles,
5686
+ ]);
5687
+ proof.writeCompileProof({
5688
+ proofPath: compileProofPath,
5689
+ inputs,
5690
+ outputs,
5691
+ name: cfg.name,
5692
+ domainHash,
5693
+ law: lawUsdzPath,
5694
+ });
5695
+ }
4428
5696
  }
4429
5697
 
4430
5698
  /**