@githolon/dsl 0.102.4 → 0.103.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) 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 +7 -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 +5504 -1236
  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 +196 -89
  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/adv_realm_interference_test.dart +7 -6
  15. package/dart/test/cloud_bound_topology_test.dart +3 -3
  16. package/dart/test/cluster_births_test.dart +4 -3
  17. package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
  18. package/dart/test/cluster_local_autosync_test.dart +3 -2
  19. package/dart/test/cluster_scale_test.dart +3 -2
  20. package/dart/test/dlq_session_surface_test.dart +7 -6
  21. package/dart/test/first_app_gauntlet_test.dart +14 -21
  22. package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
  23. package/dart/test/four_layer_nested_topology_test.dart +4 -4
  24. package/dart/test/harness_phase_timeout_test.dart +2 -2
  25. package/dart/test/local_sync_inert_test.dart +1 -1
  26. package/dart/test/parent_signer_recovery_test.dart +2 -2
  27. package/dart/test/sealing_client_test.dart +4 -3
  28. package/dart/test/session_bound_test.dart +8 -7
  29. package/dart/test/test_harness_test.dart +13 -12
  30. package/dart/test/vo_read_typing_test.dart +4 -3
  31. package/dart/test/watch_after_offer_stall_test.dart +4 -3
  32. package/dart/test/webview_bridge_test.dart +102 -5
  33. package/dart_types/CHANGELOG.md +5 -0
  34. package/dart_types/lib/nomos_types.dart +9 -0
  35. package/dart_types/lib/src/nomos_ref.dart +20 -0
  36. package/dart_types/lib/src/optional_ref.dart +85 -0
  37. package/dart_types/lib/src/sha256_digest.dart +41 -0
  38. package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
  39. package/dart_types/pubspec.yaml +1 -1
  40. package/dart_types/test/optional_ref_test.dart +44 -0
  41. package/dart_types/test/sha256_digest_test.dart +18 -0
  42. package/package.json +9 -5
  43. package/runtime/generated-application-host.js +5365 -1105
  44. package/src/aggregate.ts +18 -0
  45. package/src/application_composition.ts +6 -32
  46. package/src/attested_core.ts +6 -3
  47. package/src/birth_meter.ts +1 -0
  48. package/src/birth_meter_ensure.ts +28 -2
  49. package/src/births.ts +1 -1
  50. package/src/build_package.ts +162 -538
  51. package/src/business_birth.ts +19 -3
  52. package/src/canonical_json.ts +21 -0
  53. package/src/check_package_main.ts +76 -35
  54. package/src/clear_directive.ts +19 -8
  55. package/src/codegen_dart.ts +305 -22
  56. package/src/codegen_dart_application.ts +844 -292
  57. package/src/codegen_dart_client.ts +1084 -407
  58. package/src/codegen_dart_scenario.ts +12 -12
  59. package/src/codegen_proof.ts +169 -39
  60. package/src/codegen_provider_dart.ts +17 -1
  61. package/src/codegen_ts.ts +298 -106
  62. package/src/compat_manifest.ts +2 -2
  63. package/src/compat_package_main.ts +8 -12
  64. package/src/compile_package_main.ts +1866 -597
  65. package/src/compose.ts +1 -1
  66. package/src/directive.ts +208 -15
  67. package/src/engine_entry.ts +165 -111
  68. package/src/evolve_preview.ts +43 -81
  69. package/src/extremum.ts +1 -1
  70. package/src/fields.ts +183 -4
  71. package/src/flow_gate.ts +75 -2
  72. package/src/framework/application_home.ts +2 -0
  73. package/src/framework/auth_provider_law.ts +2 -2
  74. package/src/framework/birth_meter.ts +7 -19
  75. package/src/framework/bootstrap.ts +0 -2
  76. package/src/framework/business_corrections.ts +9 -5
  77. package/src/framework/domain_lifecycle.ts +15 -29
  78. package/src/framework/encryption_scope.ts +2 -1
  79. package/src/framework/framework_aggregates.ts +2 -0
  80. package/src/framework/genesis.ts +16 -14
  81. package/src/framework/home.ts +20 -14
  82. package/src/framework/owned_custody.ts +9 -3
  83. package/src/framework/platform_admin.ts +3 -3
  84. package/src/framework/relations.ts +21 -13
  85. package/src/framework/signing_identity.ts +45 -40
  86. package/src/framework/workspace_invariant.ts +245 -0
  87. package/src/framework/workspaces.ts +23 -29
  88. package/src/index.ts +36 -16
  89. package/src/intent.ts +178 -6
  90. package/src/law_check.ts +32 -11
  91. package/src/law_graph.ts +2902 -0
  92. package/src/law_graph_usda.ts +1799 -0
  93. package/src/law_identity.ts +435 -0
  94. package/src/law_value_type.ts +230 -0
  95. package/src/lsp_server.ts +61 -31
  96. package/src/ops.ts +7 -0
  97. package/src/package_config.ts +141 -0
  98. package/src/package_dependencies.ts +108 -0
  99. package/src/package_dependency_usage.ts +150 -0
  100. package/src/package_source_imports.ts +71 -0
  101. package/src/plan_source_slice.ts +335 -0
  102. package/src/point_guard.ts +192 -0
  103. package/src/precondition.ts +44 -9
  104. package/src/read_projection_namespace.ts +1 -1
  105. package/src/runtime_zod.ts +44 -0
  106. package/src/secondary_ensure.ts +67 -14
  107. package/src/shape_compat.ts +1 -1
  108. package/src/stable_id_identity_bridge.ts +149 -0
  109. package/src/stable_id_lockfile.ts +278 -0
  110. package/src/stable_ids.ts +7 -2
  111. package/src/stable_ids_for_module.ts +348 -0
  112. package/src/stable_ids_types.ts +1 -1
  113. package/src/sum.ts +1 -1
  114. package/src/usd_state.ts +1 -1
  115. package/src/usda_relationship_integrity.ts +66 -0
  116. package/src/usda_text.ts +18 -0
  117. package/src/usdz.ts +78 -0
  118. package/src/value_object.ts +95 -0
  119. package/src/wire.ts +19 -0
  120. package/src/wire_encode.ts +22 -0
  121. package/src/workspace_sharding.ts +52 -36
  122. package/src/workspace_sharing.ts +28 -4
  123. package/src/zod_introspection.ts +1 -1
  124. package/dart/test/cluster_attested_test.dart +0 -201
  125. package/src/compile_engine.ts +0 -595
  126. package/src/manifest.ts +0 -1476
  127. package/src/read_projection_composition.ts +0 -117
  128. package/src/read_projection_manifest.ts +0 -115
  129. package/src/relation_composition.ts +0 -286
  130. package/src/usd.ts +0 -1967
  131. 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,31 +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
- import { emitUsd, type UsdLayer } from "./usd.js";
156
- import { emitLayeredRelationUsda, type RelationLayerInput } from "./relation_composition.js";
142
+ import {
143
+ identityRegistryFromStableIdLockfile,
144
+ parseStableIdLockfile,
145
+ serializeStableIdLockfile,
146
+ type StableIdLockfile,
147
+ } from "./stable_id_lockfile.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";
157
151
  import { assertNoDartBarrelCollisions, type BarrelExportedFile } from "./dart_barrel_guard.js";
158
152
  import { generateTsClient, tsApplicationName, tsClientFactoryName, recursiveLazyTypes } from "./codegen_ts.js";
159
153
  import { generateOpenApiFragment, mergeOpenApiFragments } from "./codegen_openapi.js";
160
154
  import { domainInterface, type DomainInterface } from "./shape_compat.js";
161
155
  import { generateTsProof, proofLegsIndex } from "./codegen_proof.js";
156
+ import { usdzPack } from "./usdz.js";
162
157
  import { checkDomainModules, formatFindings, hasRefusal } from "./law_check.js";
163
158
  import type { NomosCompat } from "./compat_manifest.js";
164
159
  import { requiredKernelFeatures, type FeatureProbeModule } from "./kernel_capabilities.js";
@@ -171,10 +166,55 @@ import {
171
166
  packageAssetUsda,
172
167
  type PackageRootPart,
173
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";
174
185
 
175
186
  const DSL_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
176
187
 
177
- 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 {
178
218
  readonly key: string;
179
219
  readonly name?: string;
180
220
  readonly modules: readonly string[];
@@ -218,8 +258,7 @@ interface DomainConfig {
218
258
 
219
259
  type DartSupportImport = "vendored" | "package";
220
260
 
221
- interface PackageConfig {
222
- readonly name: string;
261
+ interface PackageConfig extends NomosPackageConfig {
223
262
  readonly domains: readonly DomainConfig[];
224
263
  /** The one OpenUSD model root this independently published package exports. */
225
264
  readonly rootPart?: PackageRootPart;
@@ -318,6 +357,229 @@ function fail(msg: string): never {
318
357
  process.exit(1);
319
358
  }
320
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
+
321
583
  /** Locate stock OpenUSD's usdcat without making it a runtime dependency. */
322
584
  function resolveUsdcat(): string | undefined {
323
585
  const candidates = [process.env.NOMOS_USDCAT, "/usr/bin/usdcat", "usdcat"]
@@ -546,6 +808,12 @@ const DART_SUPPORT_CORE_SRC = [
546
808
  "foreign_ref.dart",
547
809
  "activity.dart",
548
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",
549
817
  // Branded identity wrappers (NomosPrincipal/NomosDeviceSecret) — the generated cross-workspace birth
550
818
  // methods type `actor`/`authorSecret` as these (not bare strings), so both must vendor. Pure data.
551
819
  "principal.dart",
@@ -1032,7 +1300,7 @@ function emitDart(
1032
1300
  `import '${VENDORED_TYPES_SHIM}'`,
1033
1301
  )
1034
1302
  .replace(
1035
- /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,
1036
1304
  `import '${VENDORED_TYPES_SHIM}'`,
1037
1305
  );
1038
1306
  writeFileSync(path.join(genDir, "src", f), src, "utf8");
@@ -1242,6 +1510,9 @@ function emitDart(
1242
1510
  } catch (e) {
1243
1511
  fail((e as Error).message);
1244
1512
  }
1513
+ const sha256DigestExport = [...sourceByFile.values()].some((source) => source.includes("Sha256Digest"))
1514
+ ? ", Sha256Digest"
1515
+ : "";
1245
1516
  const barrelLines = [
1246
1517
  "// GENERATED by nomos-compile — the package's public entrypoint. DO NOT EDIT.",
1247
1518
  "// Regenerated on every compile; generated implementation lives under src/generated/.",
@@ -1265,8 +1536,8 @@ function emitDart(
1265
1536
  // The generated clients import their runtime support directly. Keeping it out of the public barrel is
1266
1537
  // the surface firewall: a tenant import receives business types and application APIs, never raw runtime.
1267
1538
  supportImport === "vendored"
1268
- ? `export '${genRel}/src/${VENDORED_TYPES_SHIM}' show NomosRef;`
1269
- : `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};`,
1270
1541
  ...(contractsImportUri !== undefined
1271
1542
  ? tenantDartExport(`${genRel}/${CONTRACTS_FILE}.dart`, sourceByFile.get(CONTRACTS_FILE) ?? "")
1272
1543
  : []),
@@ -1305,6 +1576,9 @@ const dartTypesImportFor = (generatedSource: string): string => {
1305
1576
  ...(generatedSource.includes("NmsInternalNomosRefs.")
1306
1577
  ? [`import 'package:nomos_types/src/nomos_ref.dart' show NmsInternalNomosRefs;`]
1307
1578
  : []),
1579
+ ...(generatedSource.includes("NmsInternalVersionedNomosRefs.")
1580
+ ? [`import 'package:nomos_types/src/versioned_nomos_ref.dart' show NmsInternalVersionedNomosRefs;`]
1581
+ : []),
1308
1582
  ...(generatedSource.includes("NmsInternalForeignRefs.")
1309
1583
  ? [`import 'package:nomos_types/src/foreign_ref.dart' show NmsInternalForeignRefs;`]
1310
1584
  : []),
@@ -1762,6 +2036,34 @@ function reconcilePubspec(
1762
2036
  return true;
1763
2037
  }
1764
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
+
1765
2067
  /**
1766
2068
  * THE LAYERED-PACKAGES EMIT (`dart.packages`) — three sibling pub packages under `dartOut`:
1767
2069
  * `<types>/` (pure data, depends only on `nomos_types`), `<client>/` (effectful runtime clients,
@@ -1782,7 +2084,7 @@ function emitDartPackages(
1782
2084
  names: Readonly<Record<string, string>> | undefined,
1783
2085
  compat?: NomosCompat,
1784
2086
  describePackageName = packages.client,
1785
- deployJsonPath?: string,
2087
+ lawUsdzPath?: string,
1786
2088
  ): { written: string[]; cursed: string[] } {
1787
2089
  for (const [role, name] of Object.entries(packages)) {
1788
2090
  if (typeof name !== "string" || !isPubPackageName(name)) {
@@ -1837,6 +2139,7 @@ function emitDartPackages(
1837
2139
  !m[1]!.startsWith("dart:") &&
1838
2140
  m[1] !== "package:nomos_types/nomos_types.dart" &&
1839
2141
  m[1] !== "package:nomos_types/src/nomos_ref.dart" &&
2142
+ m[1] !== "package:nomos_types/src/versioned_nomos_ref.dart" &&
1840
2143
  m[1] !== "package:nomos_types/src/foreign_ref.dart" &&
1841
2144
  m[1] !== "package:nomos_types/src/invitation.dart" &&
1842
2145
  m[1] !== `${FRAMEWORK_FILE}.dart` &&
@@ -1967,11 +2270,14 @@ function emitDartPackages(
1967
2270
  );
1968
2271
  const businessSharedFile = "business_shared.dart";
1969
2272
  const businessSharedPath = path.join(typesGen, businessSharedFile);
2273
+ const sha256DigestExport = [...typesSources.values()].some((source) => source.includes("Sha256Digest"))
2274
+ ? ", Sha256Digest"
2275
+ : "";
1970
2276
  writeFileSync(
1971
2277
  businessSharedPath,
1972
2278
  [
1973
2279
  "// GENERATED by nomos-compile — shared business values and activity; no runtime machinery.",
1974
- `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;`,
1975
2281
  ...(sharedBusinessTypeNames.length === 0
1976
2282
  ? []
1977
2283
  : [`export '${FRAMEWORK_FILE}.dart' show ${sharedBusinessTypeNames.join(", ")};`]),
@@ -2199,11 +2505,11 @@ function emitDartPackages(
2199
2505
  // ── the TEST package (optional) — generated business scenarios, test-only dependency ──
2200
2506
  if (packages.test !== undefined) {
2201
2507
  const testGen = freshGenDir(packages.test);
2202
- if (deployJsonPath === undefined) fail("dart.packages.test requires the compiled application package");
2203
- const embeddedDeployName = `${describePackageName}.deploy.json`;
2204
- const embeddedDeployPath = path.join(testGen, embeddedDeployName);
2205
- copyFileSync(deployJsonPath, embeddedDeployPath);
2206
- 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);
2207
2513
  const scenarioSurfaces = perDomain.length > 1
2208
2514
  ? (() => {
2209
2515
  const topology = resolveApplicationTopology(modules);
@@ -2236,7 +2542,7 @@ function emitDartPackages(
2236
2542
  dd.modForDart,
2237
2543
  packages.client,
2238
2544
  packages.test,
2239
- embeddedDeployName,
2545
+ embeddedLawName,
2240
2546
  {
2241
2547
  surfaceName,
2242
2548
  applicationFile,
@@ -2279,11 +2585,15 @@ function emitDartPackages(
2279
2585
  ` test: ^1.25.0`,
2280
2586
  `flutter:`,
2281
2587
  ` assets:`,
2282
- ` - lib/${genRel}/${embeddedDeployName}`,
2588
+ ` - lib/${genRel}/${embeddedLawName}`,
2283
2589
  ``,
2284
2590
  ].join("\n"),
2285
2591
  { nomos_client: NOMOS_CLIENT_DEP },
2286
2592
  );
2593
+ reconcileGeneratedLawAsset(
2594
+ pkgDir(packages.test),
2595
+ `lib/${genRel}/${embeddedLawName}`,
2596
+ );
2287
2597
  }
2288
2598
 
2289
2599
  // ── the COMPAT package (optional, MIGRATION-ONLY) — pure `dart.names` typedefs over the types package ──
@@ -2360,20 +2670,26 @@ function emitDartPackages(
2360
2670
 
2361
2671
  async function main(): Promise<void> {
2362
2672
  const argv = process.argv.slice(2);
2363
- const supportedFlags = new Set(["--require-usdcat", "--compact", "--analyze"]);
2673
+ const supportedFlags = new Set(["--require-usdcat", "--compact", "--analyze", "--preflight"]);
2364
2674
  for (const flag of argv.filter((arg) => arg.startsWith("--"))) {
2365
2675
  if (supportedFlags.has(flag)) continue;
2366
2676
  if (flag === "--layered") {
2367
- 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");
2368
2678
  }
2369
2679
  fail(`unknown compile flag '${flag}'`);
2370
2680
  }
2371
2681
  const requireUsdcat = argv.includes("--require-usdcat") || process.env.NOMOS_REQUIRE_USDCAT === "1";
2372
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";
2373
2689
  // §"Shipped (1–4)" follow-up: `githolon compile --analyze` (or NOMOS_ANALYZE=1) runs
2374
2690
  // `dart analyze` on the generated Dart package after emission and fails the compile on red.
2375
2691
  const analyzeFlag = argv.includes("--analyze") || process.env.NOMOS_ANALYZE === "1";
2376
- const configArg = argv.find((a) => !a.startsWith("--")) ?? "nomos.package.mjs";
2692
+ const configArg = argv.find((a) => !a.startsWith("--")) ?? "nomos.project.ts";
2377
2693
  const configPath = path.resolve(process.cwd(), configArg);
2378
2694
  if (!existsSync(configPath)) fail(`config not found: ${configPath}`);
2379
2695
  const cfgDir = path.dirname(configPath);
@@ -2416,26 +2732,33 @@ async function main(): Promise<void> {
2416
2732
  fail(`application composition refused: ${(error as Error).message}`);
2417
2733
  }
2418
2734
  }
2419
- const cfg = configValue as PackageConfig | undefined;
2420
- if (!cfg || typeof cfg.name !== "string" || !Array.isArray(cfg.domains) || cfg.domains.length === 0) {
2421
- 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}`);
2422
2743
  }
2423
2744
  if (Object.prototype.hasOwnProperty.call(cfg, "layered")) {
2424
- 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");
2425
2746
  }
2426
- // ANY readable name works ("tool-library", "tool_library", "Tool Library"):
2427
- // artifact FILENAMES keep it verbatim; generated SYMBOLS normalize it
2428
- // (toolLibraryClient, TOOL_LIBRARY_DOMAIN_HASH — see codegen_ts.ts).
2429
- if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(cfg.name)) {
2430
- 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}`);
2431
2753
  }
2432
-
2433
- // EVERY GENERATED APPLICATION IS A COMPLETE PRODUCT ENTRYPOINT. The compiler privately appends the
2434
- // authority bootstrap law that turns verified credential evidence into durable device authorship. Tenant
2435
- // config never names this domain and presentation filters it back out: a package cannot emit the generated
2436
- // application facade while omitting the private machinery needed to open it. The deliberately obscure key
2437
- // reduces collision likelihood, but collision handling is still fail-closed: never reinterpret a tenant's
2438
- // 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.
2439
2762
  if (cfg.domains.some((d) => d.key === APPLICATION_HOME_DOMAIN)) {
2440
2763
  fail(
2441
2764
  `domain key '${APPLICATION_HOME_DOMAIN}' is reserved by Nomos for private application authority law; ` +
@@ -2444,14 +2767,32 @@ async function main(): Promise<void> {
2444
2767
  }
2445
2768
  const domainsToCompile: DomainConfig[] = [
2446
2769
  ...cfg.domains,
2447
- {
2448
- key: APPLICATION_HOME_DOMAIN,
2449
- modules: [path.join(DSL_DIR, "src", "framework", "application_home.ts")],
2450
- },
2770
+ ...(cfg.rootPart === undefined
2771
+ ? []
2772
+ : [{
2773
+ key: APPLICATION_HOME_DOMAIN,
2774
+ modules: [path.join(DSL_DIR, "src", "framework", "application_home.ts")],
2775
+ }]),
2451
2776
  ];
2452
2777
 
2453
2778
  const outDir = path.resolve(cfgDir, cfg.outDir ?? "build");
2454
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
+ }
2455
2796
  // The package-wide executable lane no longer exists. Remove its ignored build artifact on every
2456
2797
  // native compile so a developer cannot accidentally inspect or execute stale bytes as canonical law.
2457
2798
  rmSync(path.join(outDir, ".nomos_lump.js"), { force: true });
@@ -2466,7 +2807,30 @@ async function main(): Promise<void> {
2466
2807
  // Every executable is an independently addressable lazy plan asset. There is no package-wide
2467
2808
  // eager/monolithic execution mode: keeping that alternative would let it become canonical again.
2468
2809
  const entryDomainSourceByKey = new Map<string, string>();
2810
+ const entryPreloadSourceByKey = new Map<string, string>();
2469
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>();
2470
2834
  const routingInput: Record<string, Mod[]> = {};
2471
2835
  const domainModules: DomainModule[] = [];
2472
2836
  // THE SELF-DESCRIBE DECLS (G1): per domain key, the FINISHED plain read/maintenance decls the
@@ -2475,26 +2839,61 @@ async function main(): Promise<void> {
2475
2839
  // pass finished decls through), serialized into the generated entry as `openapiDecls`, so the
2476
2840
  // kernel-run describe reproduces THIS compile's `<name>.openapi.json` byte-for-byte.
2477
2841
  const openapiDeclsByDomain: Record<string, unknown> = {};
2478
- const layeredDomains: { key: string; inputs: LayeredModuleInput[] }[] = [];
2479
- /**
2480
- * THE CAPABILITY LOWERING (capability_marketplace.md §4 — the deploy-warning
2481
- * prerequisite + the executor's task→capability→domain map). Rides the
2482
- * UNHASHED deploy sidecar only (a top-level deploy-body key, like
2483
- * `dispositions`) — never the read manifest the wasm parses, never any
2484
- * hash-bearing artifact: declaring a capability moves no identity hash, and
2485
- * capability-free deploy bodies stay byte-identical (omit-when-empty).
2486
- */
2487
- const capabilityLowerings: {
2488
- capability: string;
2489
- domain: string;
2490
- aggregateId: string;
2491
- obligation: string;
2492
- complete: string;
2493
- fail: string;
2494
- block: string;
2495
- deadLetter: string;
2496
- tasksQueryId: string | null;
2497
- }[] = [];
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
+ };
2498
2897
 
2499
2898
  for (const d of domainsToCompile) {
2500
2899
  if (typeof d.key !== "string" || !Array.isArray(d.modules) || d.modules.length === 0) {
@@ -2502,12 +2901,48 @@ async function main(): Promise<void> {
2502
2901
  }
2503
2902
  const mods: Mod[] = [];
2504
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
+ }>();
2505
2909
  for (const rel of d.modules) {
2506
2910
  const abs = path.resolve(cfgDir, rel);
2507
2911
  if (!existsSync(abs)) fail(`domain '${d.key}': module not found: ${abs}`);
2508
2912
  const m = (await import(pathToFileURL(abs).href)) as Mod;
2509
2913
  mods.push(m);
2510
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
+ }
2511
2946
  }
2512
2947
  let merged: Mod = {};
2513
2948
  for (const m of mods) merged = { ...merged, ...m };
@@ -2622,12 +3057,20 @@ async function main(): Promise<void> {
2622
3057
 
2623
3058
  const domainSource = ` ${JSON.stringify(d.key)}: [${sourceExprs.join(", ")}],`;
2624
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
+ }
2625
3068
  routingInput[d.key] = mods;
2626
3069
  // ── THE AUX INVARIANT-ROUTING KEY (#47, sharding slice 7 — the invariant-gate
2627
3070
  // flame pass): in a LAZY lump, the derived sharding law registers a SECOND
2628
3071
  // time under its own domain key, and the ROUTING table points the sharding
2629
3072
  // workspace-invariant ids at THAT key only. A per-write homing-invariant
2630
- // dispatch (`workspaceInvariantReads`/`workspaceInvariant`) then forces ONLY
3073
+ // assertion dispatch then forces ONLY
2631
3074
  // the small framework module — never the tenant domain's whole zod/module
2632
3075
  // graph (the measured ~30 ms-per-dispatch floor on co2 estate_structures).
2633
3076
  // Directive dispatch is untouched (the sharding directives stay registered
@@ -2652,6 +3095,37 @@ async function main(): Promise<void> {
2652
3095
  ...discover<QueryDecl>(merged, isQueryDecl),
2653
3096
  ...resolveNamed<QueryDecl>(merged, d.queries, "queries"),
2654
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
+ }
2655
3129
  // METERED BIRTH (slice A): a domain declaring ≥1 `.births()` directive gets the meter's declared
2656
3130
  // queries appended (dedup by id) so the openapi self-describe decls + read routing carry them —
2657
3131
  // the same auto-ensure `composeDomainModule` applies to the manifest half.
@@ -2745,22 +3219,6 @@ async function main(): Promise<void> {
2745
3219
  const capByAggregate = new Map<string, ImpureCapabilityDecl>();
2746
3220
  for (const c of [...discoveredCapabilities, ...namedCapabilities]) capByAggregate.set(c.aggregate.id, c);
2747
3221
  const impureCapabilities = [...capByAggregate.values()];
2748
- for (const c of impureCapabilities) {
2749
- const meta = c as unknown as { name?: unknown; capability?: unknown; tasks?: { id?: unknown } };
2750
- const cap = typeof meta.name === "string" ? meta.name : typeof meta.capability === "string" ? meta.capability : null;
2751
- if (cap === null) continue; // a binding-free quartet lowers nothing (omit-when-absent)
2752
- capabilityLowerings.push({
2753
- capability: cap,
2754
- domain: d.key,
2755
- aggregateId: c.aggregate.id,
2756
- obligation: c.obligation.id,
2757
- complete: c.complete.id,
2758
- fail: c.fail.id,
2759
- block: c.block.id,
2760
- deadLetter: c.deadLetter.id,
2761
- tasksQueryId: typeof meta.tasks?.id === "string" ? meta.tasks.id : null,
2762
- });
2763
- }
2764
3222
  const extraAggregates = resolveNamed<AggregateHandle>(merged, d.extraAggregates, "extraAggregates");
2765
3223
 
2766
3224
  // Dart-codegen hints (frontend-only — never law; see DomainConfig):
@@ -2808,6 +3266,54 @@ async function main(): Promise<void> {
2808
3266
  // no-path / ambiguous / cross-home / unroutable law is a NAMED COMPILE
2809
3267
  // ERROR here — never a domain silently recorded as identity-excluded.
2810
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);
2811
3317
 
2812
3318
  // ── THE SELF-DESCRIBE DECLS (G1): the FINISHED plain read/maintenance decls the lump's
2813
3319
  // `describe` reconstructs the openapi fragment from. Directives + aggregates are read from
@@ -2880,169 +3386,88 @@ async function main(): Promise<void> {
2880
3386
  }
2881
3387
  }
2882
3388
 
2883
- // ── ONE USD LAYER PER MODULE (Φ applied per module, not once over the
2884
- // composed fold) — the layered emission `usd_layers.ts` proves flattens back
2885
- // to the EXACT canonical IR (asserted below, fail-closed). Per-module decls
2886
- // are discovered the same way as above, on each module's OWN exports; config
2887
- // export-names resolve on whichever module actually exports them; config
2888
- // `extraAggregates` ride the FIRST (weakest) layer, prelude-style. Compiler-
2889
- // discovered reads are seated beside the aggregate schema they read.
2890
- {
2891
- const usedStems = new Set<string>();
2892
- let inputs: LayeredModuleInput[] = mods.map((m, i) => {
2893
- const authoredPath = d.modules[i];
2894
- let stem = authoredPath === undefined
2895
- ? `nomos-derived-${i + 1}`
2896
- : path.basename(authoredPath).replace(/\.[^.]*$/, "");
2897
- for (let n = 2; usedStems.has(stem); n++) stem = `${stem}-${n}`;
2898
- usedStems.add(stem);
2899
-
2900
- const presentNames = (names: readonly string[] | undefined) =>
2901
- (names ?? []).filter((n) => (m as Record<string, unknown>)[n] !== undefined);
2902
- const layerQueries = [
2903
- ...discover<QueryDecl>(m, isQueryDecl),
2904
- ...resolveNamed<QueryDecl>(m, presentNames(d.queries), "queries"),
2905
- ];
2906
- const layerDeriveds = [
2907
- ...discover<DerivedDecl>(m, isDerivedDecl),
2908
- ...resolveNamed<DerivedDecl>(m, presentNames(d.deriveds), "deriveds"),
2909
- ];
2910
- const layerCombineds = [
2911
- ...discover<CombinedDecl>(m, isCombinedDecl),
2912
- ...resolveNamed<CombinedDecl>(m, presentNames(d.combineds), "combineds"),
2913
- ];
2914
- // Lower the module through the SAME compiler-owned expansion seam as the
2915
- // canonical fold. A hand-built `{aggregates,directives}` approximation silently
2916
- // dropped relation, invariant, attestation, flow and projection opinions, so the
2917
- // pretty USD stack was not the law. Single-module composition retains those
2918
- // generated opinions in the layer where their authored cause lives.
2919
- const module = composeDomainModule({
2920
- name: d.name ?? d.key,
2921
- domain: d.key,
2922
- modules: [m],
2923
- ...(i === 0 && extraAggregates.length > 0 ? { extraAggregates } : {}),
2924
- // Compiler-synthesized/domain-wide declarations have no honest authored
2925
- // module owner. Seat them once in the weakest prelude layer so USD and
2926
- // Nomos both see one contribution instead of duplicating them per source file.
2927
- ...(i === 0 && (composed.workspaceInvariants?.length ?? 0) > 0
2928
- ? { workspaceInvariants: composed.workspaceInvariants }
2929
- : {}),
2930
- ...(i === 0 && workspaceTypes.length > 0 ? { workspaceTypes } : {}),
2931
- ...(i === 0 && impureCapabilities.length > 0 ? { impureCapabilities } : {}),
2932
- ...(layerQueries.length > 0 ? { queries: layerQueries } : {}),
2933
- ...(layerDeriveds.length > 0 ? { deriveds: layerDeriveds } : {}),
2934
- ...(layerCombineds.length > 0 ? { combineds: layerCombineds } : {}),
2935
- });
2936
- return { name: stem, module };
2937
- });
2938
- if (inputs[0] !== undefined) {
2939
- // Work on shallow module copies: package-owned declarations usually belong
2940
- // in the weakest prelude, but a read recipe belongs beside the aggregate
2941
- // schema it reads. Putting a compiler-added query in an unrelated prelude
2942
- // makes that individual USD layer ill-typed even though the whole stack has
2943
- // the target in a stronger layer.
2944
- inputs = inputs.map((input) => ({
2945
- ...input,
2946
- module: { ...input.module } as DomainModule,
2947
- }));
2948
- // Config-named and compiler-derived reads are package opinions rather than
2949
- // exports of one source file. Seat only declarations missing from every
2950
- // authored layer, once. Reads sit beside their returned aggregate; all other
2951
- // domain-wide declarations sit in the weakest prelude.
2952
- for (const field of [
2953
- "queries",
2954
- "counts",
2955
- "spatials",
2956
- "sums",
2957
- "mins",
2958
- "maxes",
2959
- "deriveds",
2960
- "combineds",
2961
- "workspaceTypes",
2962
- "workspaceInvariants",
2963
- "orderedReads",
2964
- "exists_",
2965
- ] as const) {
2966
- const canonical = (composed as unknown as Record<string, unknown>)[field];
2967
- if (!Array.isArray(canonical)) continue;
2968
- // A package-level language may be declared in a different source module from the
2969
- // aggregate its generated read indexes (workspaceSharing is the canonical case).
2970
- // Single-module composition then discovers the read in the language module even though
2971
- // that layer cannot type it. Move every targeted declaration beside its aggregate schema
2972
- // before filling genuinely missing package opinions. This changes no composed law: one
2973
- // declaration id still contributes once; it merely gives every authored USD layer a
2974
- // self-contained, valid read projection.
2975
- for (const value of canonical) {
2976
- if (value === null || typeof value !== "object") continue;
2977
- const id = (value as { id?: unknown }).id;
2978
- if (typeof id !== "string") continue;
2979
- const declarationTarget = field === "queries"
2980
- ? (value as { returns?: unknown }).returns
2981
- : (["counts", "spatials", "sums", "mins", "maxes", "deriveds", "combineds"] as const)
2982
- .includes(field as never)
2983
- ? (value as { of?: unknown }).of
2984
- : undefined;
2985
- if (typeof declarationTarget !== "string") continue;
2986
- const ownerIndex = inputs.findIndex((input) =>
2987
- input.module.aggregates.some((aggregate) => aggregate.id === declarationTarget));
2988
- if (ownerIndex < 0) continue;
2989
- for (const [inputIndex, input] of inputs.entries()) {
2990
- if (inputIndex === ownerIndex) continue;
2991
- const module = input.module as DomainModule & Record<string, unknown>;
2992
- const existing = module[field];
2993
- if (!Array.isArray(existing)) continue;
2994
- (module as Record<string, unknown>)[field] = existing.filter((candidate) =>
2995
- candidate === null || typeof candidate !== "object" ||
2996
- (candidate as { id?: unknown }).id !== id);
2997
- }
2998
- const owner = inputs[ownerIndex]!.module as DomainModule & Record<string, unknown>;
2999
- const ownerValues = Array.isArray(owner[field]) ? owner[field] as unknown[] : [];
3000
- if (!ownerValues.some((candidate) =>
3001
- candidate !== null && typeof candidate === "object" &&
3002
- (candidate as { id?: unknown }).id === id)) {
3003
- (owner as Record<string, unknown>)[field] = [...ownerValues, value];
3004
- }
3005
- }
3006
- const present = new Set(inputs.flatMap((input) => {
3007
- const values = (input.module as unknown as Record<string, unknown>)[field];
3008
- return Array.isArray(values)
3009
- ? values.flatMap((value) => value !== null && typeof value === "object" && typeof (value as { id?: unknown }).id === "string"
3010
- ? [(value as { id: string }).id]
3011
- : [])
3012
- : [];
3013
- }));
3014
- const missing = canonical.filter((value) =>
3015
- value !== null && typeof value === "object" && typeof (value as { id?: unknown }).id === "string"
3016
- ? !present.has((value as { id: string }).id)
3017
- : true);
3018
- for (const value of missing) {
3019
- const declarationTarget = value !== null && typeof value === "object"
3020
- ? field === "queries"
3021
- ? (value as { returns?: unknown }).returns
3022
- : (["counts", "spatials", "sums", "mins", "maxes", "deriveds", "combineds"] as const).includes(field as never)
3023
- ? (value as { of?: unknown }).of
3024
- : undefined
3025
- : undefined;
3026
- const ownerIndex = typeof declarationTarget === "string"
3027
- ? inputs.findIndex((input) => input.module.aggregates.some((aggregate) => aggregate.id === declarationTarget))
3028
- : -1;
3029
- const targetIndex = ownerIndex >= 0 ? ownerIndex : 0;
3030
- const target = inputs[targetIndex]!.module as DomainModule & Record<string, unknown>;
3031
- const existing = target[field];
3032
- target[field] = [...(Array.isArray(existing) ? existing : []), value];
3033
- }
3034
- }
3035
- }
3036
- layeredDomains.push({ key: d.key, inputs });
3037
- }
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;
3038
3405
  }
3039
3406
 
3040
3407
  // Presentation is a separate concern from executable law. The private application/home domain remains
3041
3408
  // in `domainModules` for hashing, routing, self-description, replay and static checking, but generated
3042
3409
  // tenant clients/proofs/interfaces contain business vocabulary only.
3043
- 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(
3044
3413
  (mod) => (mod.domain ?? mod.name) !== APPLICATION_HOME_DOMAIN,
3045
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
+ }
3046
3471
 
3047
3472
  // ── STABLE-ID CONTINUITY (#58 — names are labels, identity is minted) ──
3048
3473
  // Derive each composed domain's stable ids from the PRIOR compile: the committed
@@ -3053,20 +3478,22 @@ async function main(): Promise<void> {
3053
3478
  // at first appearance (a leftover disappearance is THE EVOLVE GATE's typed question
3054
3479
  // at deploy — answered in the upgrade intent's disposition). The resolved continuity
3055
3480
  // is attached to the module so the canonical manifest + USD-IR carry it.
3056
- interface StableIdLock {
3057
- v: 1;
3058
- domains: Record<string, { stableIds: StableIds; schemas: Record<string, WireSchema> }>;
3059
- }
3060
3481
  const lockPath = path.join(cfgDir, "nomos.stable-ids.json");
3061
- const priorLock: StableIdLock | undefined = existsSync(lockPath)
3062
- ? (JSON.parse(readFileSync(lockPath, "utf8")) as StableIdLock)
3482
+ const priorLock: StableIdLockfile | undefined = existsSync(lockPath)
3483
+ ? parseStableIdLockfile(JSON.parse(readFileSync(lockPath, "utf8")))
3063
3484
  : undefined;
3064
3485
  const inferredRenames: { domain: string; renames: InferredRename[] }[] = [];
3486
+ let nextIdentity = identityRegistryFromStableIdLockfile(priorLock);
3487
+ const typedLawGraphs = new Map<string, LawGraph>();
3065
3488
  // The lock is project-wide, not package-local. Several package configs commonly live beside one
3066
3489
  // application stage; compiling one must not erase the stable identity of domains owned by its siblings.
3067
3490
  // Current package domains are replaced below, while absent domains remain only as continuity metadata
3068
3491
  // and never leak into this package's interface, law, clients or deployment.
3069
- const nextLock: StableIdLock = { v: 1, domains: { ...(priorLock?.domains ?? {}) } };
3492
+ const nextLock: StableIdLockfile = {
3493
+ v: 1,
3494
+ domains: { ...(priorLock?.domains ?? {}) },
3495
+ identity: nextIdentity,
3496
+ };
3070
3497
  for (const mod of domainModules) {
3071
3498
  const identityName = mod.name;
3072
3499
  // The CURRENT canonical aggregate schemas (a pre-continuity manifest pass —
@@ -3078,23 +3505,18 @@ async function main(): Promise<void> {
3078
3505
  global?: boolean;
3079
3506
  }[];
3080
3507
  try {
3081
- const preManifest = domainManifest(mod);
3082
- currentAggs = preManifest.aggregates.map((a) => ({
3083
- id: a.id,
3084
- 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,
3085
3513
  // One framework aggregate may be composed into many independently compiled
3086
3514
  // domain packages. Give that shared row one global stable-id scope so the
3087
3515
  // installed read manifest sees the same field contract from every package.
3088
- global: mod.aggregates.some(
3089
- (candidate) => candidate.id === a.id && !isNamespacedAggregate(candidate),
3090
- ),
3091
- // The CURRENT field kinds (off the pre-continuity stableIds emission) — the
3092
- // pair rule's kind axis ("rename + add a field" in one release still infers).
3093
- kinds: Object.fromEntries(
3094
- Object.entries(preManifest.stableIds?.aggregates[a.id]?.fields ?? {}).map(
3095
- ([f, v]) => [f, v.kind ?? ""] as [string, string],
3096
- ),
3097
- ),
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])),
3098
3520
  }));
3099
3521
  } catch {
3100
3522
  // A palette-gap domain has no canonical identity (recorded-and-excluded later);
@@ -3110,20 +3532,41 @@ async function main(): Promise<void> {
3110
3532
  prior = locked.stableIds;
3111
3533
  priorSchemas = locked.schemas;
3112
3534
  }
3113
- const { continuity, inferred } = deriveStableIdContinuity(
3535
+ const proposed = deriveStableIdContinuity(
3114
3536
  identityName,
3115
3537
  currentAggs,
3116
3538
  prior,
3117
3539
  priorSchemas,
3118
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);
3119
3568
  mod.stableIdContinuity = continuity;
3120
- // The layered emission's per-module layers must carry the SAME sids (the flatten
3121
- // homomorphism is byte-asserted), so the continuity rides every layer input too.
3122
- for (const ld of layeredDomains) {
3123
- if (ld.key === (mod.domain ?? mod.name)) {
3124
- for (const inp of ld.inputs) inp.module.stableIdContinuity = continuity;
3125
- }
3126
- }
3569
+ graphModule.stableIdContinuity = continuity;
3127
3570
  if (inferred.length > 0) inferredRenames.push({ domain: identityName, renames: inferred });
3128
3571
  // ── THE fromBirth EVOLVE GUARD (dart_codegen_parity.md): birth-presence may be declared at a
3129
3572
  // field's FIRST appearance (fresh sid — every row of the type is born under the invariant) or
@@ -3157,7 +3600,7 @@ async function main(): Promise<void> {
3157
3600
  // The lockfile records the EMITTED surface (sids + lineage + kinds — the next
3158
3601
  // compile's pair rule reads the kinds), not the bare continuity.
3159
3602
  nextLock.domains[identityName] = {
3160
- stableIds: domainManifest(mod).stableIds ?? continuity,
3603
+ stableIds: stableIdsForModule(mod),
3161
3604
  schemas: Object.fromEntries(currentAggs.map((a) => [a.id, a.schema])),
3162
3605
  };
3163
3606
  }
@@ -3165,7 +3608,7 @@ async function main(): Promise<void> {
3165
3608
  // identity across clean clones). Existing project-domain order is retained, so once sibling
3166
3609
  // packages are registered, recompiling them in any order rewrites the same bytes without a
3167
3610
  // giant formatting-only diff in an established lock.
3168
- writeFileSync(lockPath, JSON.stringify(nextLock, null, 2) + "\n", "utf8");
3611
+ writeFileSync(lockPath, serializeStableIdLockfile(nextLock), "utf8");
3169
3612
 
3170
3613
  // ── THE STRUCTURAL INTERFACE (#72) — directives (payload shapes) + aggregates (read
3171
3614
  // shapes, sid-keyed), keyed by DOMAIN. The SAME object is (a) baked into the generated
@@ -3176,6 +3619,34 @@ async function main(): Promise<void> {
3176
3619
  for (const mod of presentationDomainModules) {
3177
3620
  const key = mod.domain ?? mod.name;
3178
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
+ }
3179
3650
  const prior = packageInterface[key];
3180
3651
  packageInterface[key] = prior
3181
3652
  ? {
@@ -3205,6 +3676,24 @@ async function main(): Promise<void> {
3205
3676
  }
3206
3677
  }
3207
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
+
3208
3697
  // ── reports ──
3209
3698
  const entryReports: string[] = [];
3210
3699
  for (const [reportId, ref] of Object.entries(cfg.reports ?? {})) {
@@ -3224,6 +3713,18 @@ async function main(): Promise<void> {
3224
3713
  // law whose invariant declarations are inconsistent refuses to COMPILE, and the
3225
3714
  // table can never disagree with what a full force would register).
3226
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
+ );
3227
3728
  // AMBIENT-SLOT SEEDING (lazy boot only): zod v4 writes
3228
3729
  // `globalThis.__zod_global{Config,Registry}` at MODULE INIT. The sealed sandbox
3229
3730
  // freezes globalThis after the lump's top level — an eager lump's zod init ran
@@ -3240,7 +3741,10 @@ async function main(): Promise<void> {
3240
3741
  const zodSeedImports: string[] = [];
3241
3742
  const zodSeedNames: string[] = [];
3242
3743
  {
3243
- 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]
3244
3748
  .map((dir) => {
3245
3749
  try {
3246
3750
  return createRequire(pathToFileURL(path.join(dir, "noop.js"))).resolve("zod/package.json");
@@ -3281,14 +3785,12 @@ async function main(): Promise<void> {
3281
3785
  })
3282
3786
  .find((p): p is string => p !== undefined);
3283
3787
  if (!esbuildPkgJson) fail(`esbuild not found — reinstall @githolon/dsl (esbuild is one of its dependencies)`);
3284
- const esbuildPkg = JSON.parse(readFileSync(esbuildPkgJson, "utf8")) as { bin?: string | Record<string, string> };
3285
- const esbuildBinRel = typeof esbuildPkg.bin === "string" ? esbuildPkg.bin : esbuildPkg.bin?.["esbuild"];
3286
- // Exec the bin DIRECTLY (not via `node`): esbuild's postinstall replaces the
3287
- // JS shim with the platform-native binary, which only an exec can run.
3288
- const esbuildBin = path.join(path.dirname(esbuildPkgJson), esbuildBinRel ?? "bin/esbuild");
3289
3788
  const esbuildMod = (await import(pathToFileURL(path.join(path.dirname(esbuildPkgJson), "lib", "main.js")).href)) as typeof import("esbuild");
3290
3789
 
3291
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");
3292
3794
 
3293
3795
  // ── CONTENT-ADDRESSED PLAN ASSETS ─────────────────────────────────────────────
3294
3796
  // The ledger package no longer executes the package-wide compatibility lump above. Build the
@@ -3298,85 +3800,668 @@ async function main(): Promise<void> {
3298
3800
  const planAssets: UsdPlanAsset[] = [];
3299
3801
  const planRoutes: UsdPlanRoute[] = [];
3300
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
+ ]));
3301
3836
  let planSequence = 0;
3302
3837
  const orderedDomainKeys = [...entryDomainSourceByKey.keys()];
3303
- 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[]) => {
3304
3846
  const wanted = new Set(domainKeys);
3305
- 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 => {
3306
3882
  return [
3307
3883
  `// AUTO-GENERATED Nomos plan asset from ${path.basename(configPath)} — do not edit.`,
3308
3884
  `declare function require(id: string): Record<string, unknown>;`,
3309
- ...zodSeedImports,
3885
+ ...(sharedRuntime ? [] : zodSeedImports),
3310
3886
  `import { registerEngine } from "@githolon/dsl/engine-entry";`,
3311
3887
  ``,
3312
3888
  `registerEngine({`,
3313
3889
  ` domains: {`,
3314
3890
  ...domainKeys.map((key) => {
3315
- const source = entryDomainSourceByKey.get(key);
3891
+ const source = sourceOverrides.get(key) ?? entryDomainSourceByKey.get(key);
3316
3892
  if (source === undefined) throw new Error(`missing generated entry source for '${key}'`);
3317
3893
  return source;
3318
3894
  }),
3319
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
+ })() : []),
3320
3903
  ...(includeReports && entryReports.length > 0 ? [` reports: {`, ...entryReports, ` },`] : []),
3321
- ` routing: ${JSON.stringify(routing)},`,
3322
- ...(zodSeedNames.length > 0 ? [` seeds: [${zodSeedNames.join(", ")}],`] : []),
3323
- ...(Object.keys(decls).length > 0 ? [` openapiDecls: ${JSON.stringify(decls)},`] : []),
3324
- ` 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)`)},`] : []),
3325
3911
  `});`,
3326
- ...domainKeys.flatMap((key) => {
3327
- const install = entryMintInstallByKey.get(key);
3328
- return install === undefined ? [] : [install];
3329
- }),
3912
+ ...(includeMintInstalls
3913
+ ? domainKeys.flatMap((key) => {
3914
+ const install = entryMintInstallByKey.get(key);
3915
+ return install === undefined ? [] : [install];
3916
+ })
3917
+ : []),
3330
3918
  ``,
3331
3919
  ].join("\n");
3332
3920
  };
3333
- 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> => {
3334
4041
  const ordered = orderedDomainKeys.filter((key) => domainKeys.includes(key));
3335
- 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
+ });
3336
4049
  const hit = planBySignature.get(signature);
3337
4050
  if (hit !== undefined) return hit;
3338
- const sourcePath = path.join(outDir, `.nomos_plan_${planSequence++}.ts`);
3339
- writeFileSync(sourcePath, planEntrySource(ordered, includeReports), "utf8");
3340
- const args = [
3341
- sourcePath,
3342
- "--bundle", "--format=iife", "--target=es2020", "--platform=neutral", "--charset=utf8",
3343
- "--define:__nmsRuntimeLaw=true",
3344
- ...(compact ? ["--minify"] : []),
3345
- ];
3346
- let bundled = execFileSync(esbuildBin, args, {
3347
- encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], cwd: cfgDir, maxBuffer: 256 * 1024 * 1024,
3348
- }).trimEnd() + "\n";
3349
- bundled = canonicalizeEngineJavascript(bundled);
3350
- if (!/globalThis\.plan\s*=/.test(bundled) && !/globalThis\["plan"\]\s*=/.test(bundled)) {
3351
- 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;
3352
4148
  }
3353
- 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
+ };
3354
4157
  planBySignature.set(signature, asset);
4158
+ planByHash.set(sha256, asset);
3355
4159
  planAssets.push(asset);
3356
4160
  return asset;
3357
4161
  };
3358
- const routePlan = (selector: string, domainKeys: readonly string[], includeReports = false): void => {
3359
- 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);
4169
+ planRoutes.push({ selector, planSha256: asset.sha256 });
4170
+ };
4171
+ const routeAsset = (selector: string, asset: UsdPlanAsset): void => {
3360
4172
  planRoutes.push({ selector, planSha256: asset.sha256 });
3361
4173
  };
3362
- for (const key of orderedDomainKeys) routePlan(`plan:domain:${key}`, [key]);
3363
- routePlan("describe", orderedDomainKeys);
3364
- for (const reportId of Object.keys(cfg.reports ?? {})) routePlan(`report:report:${reportId}`, [], true);
3365
- const routeTable = (
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 (
3366
4393
  prefix: string,
3367
4394
  table: Readonly<Record<string, readonly string[]>> | undefined,
3368
- ): void => {
3369
- 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);
3370
4397
  };
3371
- routeTable("report:derive:", routing.derivedOf);
3372
- routeTable("report:combine:", routing.combinedOf);
3373
- routeTable("report:aggregateInvariant:", routing.aggregateInvariantOf);
3374
- routeTable("report:invariant:", routing.relationOf);
3375
- routeTable("report:workspaceInvariantReads:", routing.workspaceInvariantOf);
3376
- routeTable("report:workspaceInvariant:", routing.workspaceInvariantOf);
3377
- const workspaceInvariantDomains = [...new Set(Object.values(routing.workspaceInvariantOf ?? {}).flat())];
3378
- if (workspaceInvariantDomains.length > 0) {
3379
- 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)`);
3380
4465
  }
3381
4466
 
3382
4467
  // ── 1a. THE CAPTURED-READ GATE (#57, lifted-when-declared since #58) ──
@@ -3404,7 +4489,7 @@ async function main(): Promise<void> {
3404
4489
  ((d as { declaredForeignReads?: unknown[] }).declaredForeignReads ?? []).length > 0,
3405
4490
  ),
3406
4491
  );
3407
- 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) {
3408
4493
  fail(
3409
4494
  `this law references read() — the CAPTURED-READ lane — but NO directive declares a ` +
3410
4495
  `captured-read query, so the sealed engine would provide no nomos.read and every ` +
@@ -3445,207 +4530,218 @@ async function main(): Promise<void> {
3445
4530
  // fresh frozen context, then force every domain — and refuse to package on any
3446
4531
  // failure that is not the expected unknown-directive refusal.
3447
4532
  {
3448
- // A CHILD process: the probe must freeze a REAL global object (a `vm` context
3449
- // global refuses Object.freeze), and the lump must run STRICT (the engine
3450
- // evals it with the STRICT flag — sloppy mode would silently no-op the very
3451
- // 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.
3452
4540
  const probePath = path.join(outDir, ".nomos_boot_probe.cjs");
3453
4541
  writeFileSync(
3454
4542
  probePath,
3455
4543
  [
3456
4544
  `// AUTO-GENERATED by nomos-compile — the frozen-sandbox lazy-boot probe.`,
3457
4545
  `const fs = require("node:fs");`,
3458
- `const plan = fs.readFileSync(process.argv[2], "utf8");`,
3459
- `const key = process.argv[3];`,
3460
- `(0, eval)('"use strict";\\n' + plan); // indirect eval → real global scope, strict like the engine`,
3461
- `Object.freeze(globalThis); // the sealed sandbox freezes before dispatch`,
3462
- `let msg;`,
3463
- `try { globalThis.plan({ intent: { domain: key, directiveId: "__nomos_boot_probe__" } }); msg = "unexpected-success"; }`,
3464
- `catch (e) { msg = String((e && e.message) || e); }`,
3465
- `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
+ ` }`,
3466
4594
  ``,
3467
4595
  ].join("\n"),
3468
4596
  "utf8",
3469
4597
  );
3470
- for (const route of planRoutes.filter((candidate) => candidate.selector.startsWith("plan:domain:"))) {
3471
- const domainKey = route.selector.slice("plan:domain:".length);
3472
- 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)!;
3473
4620
  const planPath = path.join(outDir, `.nomos_boot_${planSequence++}.js`);
3474
- writeFileSync(planPath, asset.javascript, "utf8");
3475
- try {
3476
- execFileSync(process.execPath, [probePath, planPath, domainKey], {
3477
- encoding: "utf8", stdio: ["ignore", "ignore", "pipe"],
3478
- });
3479
- } catch (e) {
3480
- const stderr = String((e as { stderr?: string }).stderr ?? "").trim();
3481
- const [, msg = stderr] = stderr.includes("\u0000") ? stderr.split("\u0000") : ["?", stderr];
3482
- fail(
3483
- `domain '${domainKey}' failed its frozen-sandbox plan probe — a module initializer ` +
3484
- `touches ambient globals after the freeze (the engine would refuse its first dispatch): ${msg}`,
3485
- );
4621
+ if (sharedRuntimeProfileJavascript === undefined) {
4622
+ fail("canonical shared runtime was not compiled before the boot probe");
3486
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 });
3487
4645
  }
3488
4646
  }
3489
4647
 
3490
- // ── 2. USD IR + the package envelope ──
3491
- const usdJson = emitUsdJsonForModules(domainModules);
3492
- 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(
3493
4659
  cfg.name,
3494
- JSON.parse(usdJson),
3495
- domainModules,
3496
- planAssets,
4660
+ domainModules.map((module) => module.domain ?? module.name),
4661
+ runtimeDependencies,
4662
+ v3DomainAssets,
4663
+ sharedRuntimeAsset,
4664
+ routedPlanAssets,
3497
4665
  planRoutes,
3498
4666
  );
3499
- const pkgPath = path.join(outDir, `${cfg.name}.package.usda`);
3500
- const domainHash = sha256HexUtf8(text);
3501
-
3502
- // ── 2a. the REAL layered RELATION `.usda` set — an additive, deterministic visualization +
3503
- // cross-check artifact (usdview / `usdcat --flatten`). One sublayer per domain carrying its
3504
- // relation opinions as PRIMS + a root with `subLayers` STRONGEST-FIRST; usdcat composes the
3505
- // shared relation types, the compiler's `composeSharedRelationTypes` is the fail-closed guard
3506
- // on top. Emitted iff ANY domain declares a relation type. Never feeds the package hash.
3507
- {
3508
- const relInputs: RelationLayerInput[] = [];
3509
- for (const mod of domainModules) {
3510
- // Per-domain UNCOMPOSED relation types: emit the single module alone (single-domain ⇒ no shared
3511
- // type ⇒ its own decls, un-unioned — the layers ARE the sublayers usdcat will compose).
3512
- const one = emitUsd([{ path: `/Nomos/${mod.domain ?? mod.name}`, module: mod }]);
3513
- const types = one.layers[0]?.nomosRelations?.types;
3514
- if (types !== undefined && Object.keys(types).length > 0) {
3515
- relInputs.push({ domain: mod.domain ?? mod.name, types });
3516
- }
3517
- }
3518
- if (relInputs.length > 0) {
3519
- const emit = emitLayeredRelationUsda(relInputs, cfg.name);
3520
- const relDir = path.join(outDir, `${cfg.name}.relation-layers`);
3521
- mkdirSync(relDir, { recursive: true });
3522
- for (const f of emit.files) writeFileSync(path.join(relDir, f.name), f.text, "utf8");
3523
- writeFileSync(path.join(relDir, "root.usda"), emit.root, "utf8");
3524
- }
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);
3525
4680
  }
3526
-
3527
- // ── 2b. REQUIRED LAYERED emission — the executable envelope above retains the
3528
- // canonical resolved law, while these are the authored/composable source layers. The
3529
- // homomorphism is ASSERTED fail-closed on every compile: the
3530
- // per-module layer stack must resolve to the same canonical law embedded
3531
- // in the package, or compilation refuses.
3532
- let layeredRootPath: string | undefined;
3533
- 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;
3534
4692
  let usdcatFlattenVerified = false;
3535
- let composedUsdIdentity: string | undefined;
3536
- {
3537
- const safe = (s: string) => s.replace(/[^A-Za-z0-9._-]/g, "_");
3538
- const layersDir = path.join(outDir, "layers");
3539
- mkdirSync(layersDir, { recursive: true });
3540
- const allLayers: UsdLayer[] = [];
3541
- const relPaths: string[] = [];
3542
- const pendingWrites: { path: string; text: string }[] = [];
3543
- const canonicalLayers = new Map(
3544
- (JSON.parse(usdJson) as { layers: UsdLayer[] }).layers.map((layer) => [layer.path, layer] as const),
3545
- );
3546
- for (const ld of layeredDomains) {
3547
- const authored = emitLayeredDomain(ld.key, ld.inputs, { applyBusinessCorrectionLayer: false });
3548
- const canonicalLayer = canonicalLayers.get(`/Nomos/${ld.key}`);
3549
- // Authored layers carry structural declarations (aggregate/directive/query prims).
3550
- // Domain-level `nomos:*` values are the compiler's closure over the complete package
3551
- // (relation namespaces, read projections, invariant gates, contracts and language
3552
- // consequences), so placing partial versions on source layers lets weaker generated
3553
- // facts show through. Strip those derived attributes from source layers and append one
3554
- // explicit strongest closure layer. This is ordinary OpenUSD composition: source
3555
- // opinions remain inspectable and the compiler-owned overlay is the one canonical law.
3556
- const structuralLayers = authored.map((emitted, index) => {
3557
- const { path: layerPath, prims, queries, deriveds, combineds } = emitted.layer;
3558
- const layer: UsdLayer = {
3559
- path: layerPath,
3560
- prims,
3561
- ...(queries !== undefined ? { queries } : {}),
3562
- ...(deriveds !== undefined ? { deriveds } : {}),
3563
- ...(combineds !== undefined ? { combineds } : {}),
3564
- };
3565
- return {
3566
- ...emitted,
3567
- layer,
3568
- usda: usdaLayerText(ld.key, emitted.name, layer, ld.inputs[index]!.module, new Set()),
3569
- };
3570
- });
3571
- const closureLayers = canonicalLayer === undefined
3572
- ? []
3573
- : (() => {
3574
- const layer: UsdLayer = canonicalLayer;
3575
- const name = "__nomos_package_closure";
3576
- const canonicalModule = domainModules.find((module) =>
3577
- (module.domain ?? module.name) === ld.key);
3578
- return [{
3579
- name,
3580
- layer,
3581
- usda: usdaLayerText(ld.key, name, layer, canonicalModule, new Set()),
3582
- }];
3583
- })();
3584
- const emittedLayers = [...structuralLayers, ...closureLayers];
3585
- for (const emitted of emittedLayers) {
3586
- const base = `${safe(ld.key)}--${safe(emitted.name)}`;
3587
- pendingWrites.push({ path: path.join(layersDir, `${base}.usda`), text: emitted.usda });
3588
- pendingWrites.push({
3589
- path: path.join(layersDir, `${base}.layer.json`),
3590
- text: JSON.stringify(emitted.layer) + "\n",
3591
- });
3592
- allLayers.push(emitted.layer);
3593
- relPaths.push(`layers/${base}.usda`);
3594
- }
3595
- }
3596
- const flat = flattenLayeredUsd(allLayers);
3597
- composedUsdIdentity = sha256HexUtf8(canonicalJson(flat));
3598
- const canonicalLaw = JSON.parse(usdJson);
3599
- if (canonicalJson(flat) !== canonicalJson(canonicalLaw)) {
3600
- 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) {
3601
4698
  fail(
3602
- `layered emission does not flatten to the canonical composed IR — the module fold and ` +
3603
- `the layer-stack fold DISAGREE for this package (usually a re-declared directive ` +
3604
- `silently dropping an omit-when-empty reads/emits boundary, or a re-declared ` +
3605
- `aggregate dropping a field: USD shows the weaker opinion through; the module fold ` +
3606
- `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.",
3607
4701
  );
3608
4702
  }
3609
- for (const w of pendingWrites) writeFileSync(w.path, w.text, "utf8");
3610
- layeredRootPath = path.join(outDir, `${cfg.name}.layered.usda`);
3611
- writeFileSync(layeredRootPath, layeredRootUsda(relPaths), "utf8");
3612
- layeredFileCount = relPaths.length;
3613
-
3614
- const usdcat = resolveUsdcat();
3615
- if (usdcat === undefined) {
3616
- if (requireUsdcat) {
3617
- fail(
3618
- "stock OpenUSD `usdcat` is required for this compile but was not found. " +
3619
- "Install OpenUSD or set NOMOS_USDCAT to the executable path.",
3620
- );
3621
- }
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;
3622
4725
  } else {
3623
- let flattenedText: string;
3624
4726
  try {
3625
- flattenedText = execFileSync(usdcat, ["--flatten", layeredRootPath], {
4727
+ execFileSync(usdcat, ["--loadOnly", layeredRootPath], {
3626
4728
  encoding: "utf8",
3627
- stdio: ["ignore", "pipe", "pipe"],
4729
+ stdio: ["ignore", "ignore", "pipe"],
3628
4730
  maxBuffer: 256 * 1024 * 1024,
3629
4731
  });
3630
4732
  } catch (error) {
3631
4733
  const stderr = String((error as { stderr?: string }).stderr ?? "").trim();
3632
- fail(`stock OpenUSD refused the generated layer stack: ${stderr || String(error)}`);
3633
- }
3634
- const openUsdLaw = parseUsdaDocument(flattenedText);
3635
- if (canonicalJson(openUsdLaw) !== canonicalJson(canonicalLaw)) {
3636
- const difference = firstLawDifference(canonicalLaw, openUsdLaw) ?? "canonical law differs";
3637
- fail(
3638
- `stock OpenUSD and Nomos resolve different effective law. First difference: ${difference}. ` +
3639
- `Refusing to emit the package.`,
3640
- );
4734
+ rmSync(lawUsdzPath, { force: true });
4735
+ fail(`stock OpenUSD refused the canonical typed USDA closure: ${stderr || String(error)}`);
3641
4736
  }
4737
+ proofCodec.writeKeyProofUsda(
4738
+ usdcatProofPath,
4739
+ "nomos.usdcat-load-proof.v1",
4740
+ proofKey,
4741
+ );
3642
4742
  usdcatFlattenVerified = true;
3643
4743
  }
3644
4744
  }
3645
- // The executable package is the COMMITTED result of composition. Do not leave
3646
- // a newly-written package behind when either Nomos or stock OpenUSD rejects its
3647
- // source stack; rejected layer artifacts remain useful diagnostics, not runnable law.
3648
- writeFileSync(pkgPath, text, "utf8");
3649
4745
 
3650
4746
  // ── 2c. THE PUBLISHED OPENUSD PACKAGE ASSET ──
3651
4747
  // `rootPart` is the explicit package/application boundary from the OpenUSD composition ADR.
@@ -3660,7 +4756,7 @@ async function main(): Promise<void> {
3660
4756
  packageIdentity: composedUsdIdentity!,
3661
4757
  domains: presentationDomainModules.map((module) => module.domain ?? module.name),
3662
4758
  root: cfg.rootPart,
3663
- lawAsset: `./${cfg.name}.layered.usda`,
4759
+ lawAsset: `./${cfg.name}.law/root.usda`,
3664
4760
  });
3665
4761
  packageAssetPath = path.join(outDir, `${cfg.name}.asset.usda`);
3666
4762
  const candidateAssetPath = path.join(outDir, `.${cfg.name}.asset.candidate.usda`);
@@ -3679,13 +4775,16 @@ async function main(): Promise<void> {
3679
4775
  }
3680
4776
  }
3681
4777
 
3682
- // ── 3. the IDENTITY emit (for the summary's per-domain identityHash) ──
3683
- // The admission identity is carried INSIDE the package USD IR (emitUsd) + the committed
3684
- // nomos.stable-ids.json lockfile; the deploy/cloud/runtime contract uses neither a read-manifest nor an
3685
- // identity-manifest sidecar. The old domain_manifests.json / domain_identity_*.json build files were
3686
- // local diagnostics the contract ignored — REMOVED (they only confused /build consumers). We still
3687
- // compute the identity to surface its per-domain hash in the summary below.
3688
- 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
+ }
3689
4788
 
3690
4789
  // ── 3a. the CLIENT/LAW COMPATIBILITY MANIFEST (§5, architecture/plug_and_play_dx.md).
3691
4790
  // A DIAGNOSIS surface, NEVER a gate: the shape-compat arc (#72) established that clients
@@ -3704,7 +4803,7 @@ async function main(): Promise<void> {
3704
4803
  const compatDomains: NomosCompat["domains"] = {};
3705
4804
  for (const m of presentationDomainModules) {
3706
4805
  const key = m.domain ?? m.name;
3707
- const identityHash = identity.hashes[key];
4806
+ const identityHash = identityHashes[key];
3708
4807
  compatDomains[key] = identityHash === undefined ? {} : { identityHash };
3709
4808
  }
3710
4809
  // RELEASE COHERENCE — the kernel capabilities this law REQUIRES (compound-key queries +
@@ -3716,12 +4815,6 @@ async function main(): Promise<void> {
3716
4815
  ...(m.surfacedResolvers ? { surfacedResolvers: m.surfacedResolvers } : {}),
3717
4816
  ...(m.aggregates ? { aggregates: m.aggregates } : {}),
3718
4817
  }));
3719
- try {
3720
- const parsed = JSON.parse(usdJson) as { layers?: Record<string, unknown>[] };
3721
- for (const layer of parsed.layers ?? []) probes.push({ layer });
3722
- } catch {
3723
- /* shape-derived features (compoundKeys) still apply from the queries above */
3724
- }
3725
4818
  return requiredKernelFeatures(probes);
3726
4819
  })();
3727
4820
  const nomosCompat: NomosCompat = {
@@ -3733,34 +4826,24 @@ async function main(): Promise<void> {
3733
4826
  requiresKernel,
3734
4827
  };
3735
4828
 
3736
- // ── 3b. the OpenAPI spec — THE PACKAGE'S SELF-DESCRIPTION (the host's runtime routing table;
3737
- // architecture/githolon_host_contract.md "The Self-Describing API"). Each domain emits its fragment
3738
- // from its OWN zod (z.toJSONSchema); the package's spec is the law-blind merge. Deploy STAGES it like
3739
- // the read overlay; the workspace's full spec is the runtime merge of installed packages' specs
3740
- // (currentLaw); the host ingests it and builds its router from it — no domain/directive name in host
3741
- // 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.
3742
4833
  const publicOpenapi = mergeOpenApiFragments(
3743
4834
  presentationDomainModules.map((m) => generateOpenApiFragment(m, { lawHash: domainHash })),
3744
4835
  { title: `${cfg.name} (nomos package)`, lawHash: domainHash },
3745
4836
  );
3746
- const runtimeOpenapi = mergeOpenApiFragments(
3747
- domainModules.map((m) => generateOpenApiFragment(m, { lawHash: domainHash, includeFrameworkRoutes: true })),
3748
- { title: `${cfg.name} (nomos package)`, lawHash: domainHash },
3749
- );
3750
4837
  const openapiPath = path.join(outDir, `${cfg.name}.openapi.json`);
3751
4838
  writeFileSync(openapiPath, JSON.stringify(publicOpenapi, null, 2) + "\n", "utf8");
3752
4839
 
3753
- // ── 4. the one-call deploy body (package only) ──
3754
- // SELF-DESCRIBING: `summary` states what the (otherwise hex-opaque) package
3755
- // contains — purely derived from the inputs (no timestamps), ignored by the
3756
- // deploy endpoint, and mirrored as build/<name>.summary.txt for a human or
3757
- // agent who simply cats the build output.
4840
+ // ── 4. human build summary (diagnostic only; never deployment input) ──
3758
4841
  const summary = {
3759
4842
  package: cfg.name,
3760
- // Binds the deploy body to the published package asset consumed by an application stage. This is the
3761
- // 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.
3762
4844
  sourceIdentity: composedUsdIdentity!,
3763
4845
  domainHash,
4846
+ lawClosureIdentity: lawClosure.identity,
3764
4847
  domains: presentationDomainModules.map((m) => ({
3765
4848
  domain: m.domain ?? m.name,
3766
4849
  aggregates: m.aggregates.map((a) => a.id),
@@ -3768,29 +4851,9 @@ async function main(): Promise<void> {
3768
4851
  queries: (m.queries ?? []).map((q) => q.id),
3769
4852
  counts: (m.counts ?? []).map((c) => c.id),
3770
4853
  sums: (m.sums ?? []).map((s) => s.id),
3771
- identityHash: identity.hashes[m.domain ?? m.name],
4854
+ identityHash: identityHashes[m.domain ?? m.name],
3772
4855
  })),
3773
4856
  };
3774
- const deployBody = {
3775
- summary,
3776
- packageUsda: text,
3777
- // The capability lowering (unhashed sidecar; omit-when-empty — see the
3778
- // collector's doc above). The worker stages it on the manifest overlay:
3779
- // the deploy-time binding warning + the executor's scan map read it there.
3780
- ...(capabilityLowerings.length > 0 ? { capabilities: capabilityLowerings } : {}),
3781
- // THE STRUCTURAL OFFER (#72): the per-domain interface, staged on the manifest overlay so
3782
- // `connect()` pulls the workspace's installed shapes and negotiates by structural subtyping.
3783
- interface: packageInterface,
3784
- // THE SELF-DESCRIBING API: this package's OpenAPI spec (law-derived). The worker stages it on the
3785
- // manifest overlay; a read serves the workspace's merged spec; the host builds its router from it.
3786
- openapi: runtimeOpenapi,
3787
- // THE CLIENT/LAW COMPAT MANIFEST (§5): a DIAGNOSIS surface (law hash + interface hash + generator
3788
- // provenance) the connect-time classifier reads. Frontend-only — the package bytes above ignore it.
3789
- nomosCompat,
3790
- };
3791
- const deployPath = path.join(outDir, `${cfg.name}.deploy.json`);
3792
- writeFileSync(deployPath, JSON.stringify(deployBody) + "\n", "utf8");
3793
-
3794
4857
  // ── 4b. the GENERATED PROOF — a runnable, domain-shaped e2e synthesized from the
3795
4858
  // law itself (codegen_proof.ts): reshape the law, recompile, the proof
3796
4859
  // reshapes itself — no test rewriting. NEVER FATAL: a domain the
@@ -3839,14 +4902,16 @@ async function main(): Promise<void> {
3839
4902
 
3840
4903
  const summaryTxt = [
3841
4904
  `${cfg.name} — compiled Nomos domain package`,
3842
- `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`,
3843
4908
  ...summary.domains.flatMap((d) => [
3844
4909
  `domain '${d.domain}'${d.identityHash ? ` (identity ${d.identityHash.slice(0, 12)}…)` : " (EXCLUDED from identity manifest)"}`,
3845
4910
  ` aggregates: ${d.aggregates.join(", ") || "—"}`,
3846
4911
  ` directives: ${d.directives.join(", ") || "—"}`,
3847
4912
  ` queries: ${d.queries.join(", ") || "—"} counts: ${d.counts.join(", ") || "—"} sums: ${d.sums.join(", ") || "—"}`,
3848
4913
  ]),
3849
- `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`,
3850
4915
  packageAssetPath !== undefined
3851
4916
  ? `package asset: ${path.basename(packageAssetPath)} — the real USD package root consumed by application composition`
3852
4917
  : `package asset: NOT EMITTED — declare rootPart: { name, kind: "component" | "assembly" }`,
@@ -3876,10 +4941,9 @@ async function main(): Promise<void> {
3876
4941
  const temporaryClientPath = path.join(outDir, `.${cfg.name}.client.source.ts`);
3877
4942
  const temporaryRuntimeTypesPath = path.join(outDir, `.${cfg.name}.application-runtime.d.ts`);
3878
4943
  const temporaryDeclarationPath = temporaryClientPath.replace(/\.ts$/, ".d.ts");
3879
- writeFileSync(temporaryClientPath, clientSource, "utf8");
3880
- writeFileSync(temporaryRuntimeTypesPath, [
4944
+ const runtimeTypesSource = [
3881
4945
  `export declare function nomosInternalOpenGeneratedApplication<T extends object>(`,
3882
- ` 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 },`,
3883
4947
  ` bind: (holon: unknown, user: { readonly identityId: string; readonly identityAddress: string }) => T,`,
3884
4948
  `): Promise<T & {`,
3885
4949
  ` currentStatus(): Promise<any>;`,
@@ -3899,11 +4963,48 @@ async function main(): Promise<void> {
3899
4963
  ` policies: readonly { readonly domain: string; readonly domainHash: string; readonly query: string; readonly redeem: string; readonly personField: string; readonly roleField: string; readonly scopeField: string }[],`,
3900
4964
  `): Promise<void>;`,
3901
4965
  ``,
3902
- ].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);
3903
5001
  try {
3904
- const typescriptEntry = createRequire(import.meta.url).resolve("typescript/lib/typescript.js");
3905
- const ts = (await import(pathToFileURL(typescriptEntry).href)) as typeof import("typescript");
3906
- 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(
3907
5008
  [temporaryClientPath, temporaryRuntimeTypesPath],
3908
5009
  {
3909
5010
  target: ts.ScriptTarget.ES2022,
@@ -4068,8 +5169,18 @@ async function main(): Promise<void> {
4068
5169
  .map((statement) => printer.printNode(ts.EmitHint.Unspecified, statement, declarationFile)),
4069
5170
  "",
4070
5171
  ].join("\n");
4071
- writeFileSync(temporaryDeclarationPath, declarationSource, "utf8");
4072
- 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
+ }
4073
5184
  await esbuildMod.build({
4074
5185
  stdin: {
4075
5186
  contents: clientSource,
@@ -4158,7 +5269,7 @@ async function main(): Promise<void> {
4158
5269
  if (dartPackages !== undefined) {
4159
5270
  const r = emitDartPackages(
4160
5271
  presentationDomainModules, dartOut, domainHash, packageInterface, dartPackages,
4161
- dartNames, nomosCompat, cfg.name, deployPath,
5272
+ dartNames, nomosCompat, cfg.name, lawUsdzPath,
4162
5273
  );
4163
5274
  dartFiles = r.written;
4164
5275
  dartCursedHints = r.cursed;
@@ -4227,6 +5338,10 @@ async function main(): Promise<void> {
4227
5338
  // Package generation must not depend on which SDKs happen to be installed on the compiler's
4228
5339
  // machine. Write every source-dogfood override first; the analyzer loop may legitimately stop
4229
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
+ };
4230
5345
  for (const pkg of pkgDirs) {
4231
5346
  if (localDart) {
4232
5347
  const spec = readFileSync(path.join(pkg, "pubspec.yaml"), "utf8");
@@ -4247,27 +5362,124 @@ async function main(): Promise<void> {
4247
5362
  // so every local nomos_client solve root must also seat the matching local Flutter host even
4248
5363
  // when its own pubspec does not name nomos_flutter directly.
4249
5364
  if ((usesFlutter || usesClient) && localDart.flutter) lines.push(` nomos_flutter:`, ` path: ${localDart.flutter}`);
4250
- 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
+ }
4251
5368
  }
4252
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
+ };
4253
5400
  let dartMissing = false;
4254
5401
  let unresolved: string | undefined;
4255
5402
  for (const pkg of pkgDirs) {
5403
+ if (dartPackageResolutionCurrent(pkg)) continue;
4256
5404
  try {
4257
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
+ }
4258
5410
  } catch (e) {
4259
5411
  const err = e as { code?: string; stdout?: string; stderr?: string };
4260
5412
  if (err.code === "ENOENT") { dartMissing = true; break; }
4261
5413
  unresolved = `${path.relative(process.cwd(), pkg)}: ${`${err.stdout ?? ""}${err.stderr ?? ""}`.trim().split("\n").slice(-3).join(" ")}`;
4262
5414
  }
4263
5415
  }
4264
- // Only analyze when EVERY package's deps resolved: `dart analyze` against an unresolved package reports
4265
- // every framework symbol (NomosRef, …) as undefined — a meaningless flood, not a real defect. An
4266
- // unresolved dep is already surfaced as UNVERIFIED below (e.g. a fixture pinning an as-yet-unpublished
4267
- // 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;
4268
5437
  if (!dartMissing && unresolved === undefined && inPackage.length > 0) {
4269
5438
  try {
4270
- 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
+ }
4271
5483
  } catch (e) {
4272
5484
  const err = e as { code?: string; stdout?: string; stderr?: string };
4273
5485
  if (err.code === "ENOENT") { dartMissing = true; }
@@ -4277,6 +5489,10 @@ async function main(): Promise<void> {
4277
5489
  }
4278
5490
  }
4279
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).
4280
5496
  if (dartMissing) {
4281
5497
  // No `dart` on PATH — we genuinely cannot verify. Say so LOUDLY (never a `✓`), so a broken client is
4282
5498
  // NOT reported as a clean compile; installing dart turns this into a real gate.
@@ -4284,7 +5500,7 @@ async function main(): Promise<void> {
4284
5500
  } else if (unresolved !== undefined) {
4285
5501
  console.log(` dart ⚠ UNVERIFIED — \`dart pub get\` failed, so analysis is incomplete (${unresolved})`);
4286
5502
  } else if (inPackage.length > 0) {
4287
- 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" : ""}`);
4288
5504
  } else if (emittedDart.length > 0) {
4289
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)`);
4290
5506
  }
@@ -4294,34 +5510,40 @@ async function main(): Promise<void> {
4294
5510
  // ── summary ──
4295
5511
  const rel = (p: string) => path.relative(process.cwd(), p);
4296
5512
  console.log(`nomos-compile: ${cfg.name}`);
4297
- console.log(` package ${rel(pkgPath)} (${text.length} bytes)`);
5513
+ console.log(` law ${rel(lawUsdzPath)} (${lawUsdz.byteLength} transport bytes)`);
4298
5514
  console.log(` domainHash ${domainHash}`);
4299
- if (layeredRootPath !== undefined) {
4300
- console.log(
4301
- ` layered ${rel(layeredRootPath)} (${layeredFileCount} module layer(s) in build/layers/ — Nomos flatten resolves the canonical law)`,
4302
- );
4303
- console.log(
4304
- usdcatFlattenVerified
4305
- ? ` usdcat ✓ stock OpenUSD --flatten resolves the same canonical law`
4306
- : ` usdcat ⚠ unavailable — set NOMOS_REQUIRE_USDCAT=1 in release CI to make this proof mandatory`,
4307
- );
4308
- }
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
+ );
4309
5521
  {
4310
5522
  const sum = (pick: (m: (typeof domainModules)[number]) => readonly unknown[] | undefined) =>
4311
5523
  presentationDomainModules.reduce((n, m) => n + (pick(m)?.length ?? 0), 0);
4312
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)`);
4313
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
+ }
4314
5538
  // SECONDARY-CREATE AUTO-DECLARE (secondary_ensure.ts): surface every type the compiler sanctioned
4315
5539
  // from a plan's deterministic secondary `ensure(...)` — the id-mint-gate DX made visible (the author
4316
5540
  // wrote `ensure(B, …)`; the compiler declared `Ensure → B` so the deterministic id admits).
4317
5541
  for (const m of presentationDomainModules) {
4318
5542
  }
4319
5543
  const presentedIdentityNames = new Set(presentationDomainModules.map((m) => m.domain ?? m.name));
4320
- const presentedHashes = Object.entries(identity.hashes).filter(([domain]) => presentedIdentityNames.has(domain));
4321
- const presentedExcluded = identity.excluded.filter((entry) => presentedIdentityNames.has(entry.domain));
4322
- 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)`);
4323
5546
  for (const [dom, h] of presentedHashes) console.log(` ${dom.padEnd(20)} ${h}`);
4324
- for (const ex of presentedExcluded) console.log(` EXCLUDED ${ex.domain}: ${ex.reason}`);
4325
5547
  // STABLE IDS (#58): the minted/carried identity surface + every INFERRED rename —
4326
5548
  // the silent lane made visible (if an inference is wrong — you meant remove+add —
4327
5549
  // split the edit into two compiles, or answer the evolve gate with a disposition).
@@ -4342,7 +5564,7 @@ async function main(): Promise<void> {
4342
5564
  }
4343
5565
  console.log(` client ${rel(clientPath)} (typed TS client — payloads, read models, query accessors)`);
4344
5566
  for (const note of recursiveNotes) console.log(` note ${note}`);
4345
- 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)`);
4346
5568
  if (proofPath !== undefined) {
4347
5569
  console.log(` proof ${rel(proofPath)} (a runnable e2e GENERATED from your law — run: githolon proof)`);
4348
5570
  } else {
@@ -4358,7 +5580,7 @@ async function main(): Promise<void> {
4358
5580
  // gate's refusal vocabulary, unified into ONE report. `githolon check` runs the same pass;
4359
5581
  // compile runs it implicitly and REFUSES on any refuse-level finding (a statically-provable
4360
5582
  // runtime refusal shipped as law). Warns/notes are printed but never block the compile.
4361
- const allCheckFindings = checkDomainModules(domainModules);
5583
+ const allCheckFindings = authorTimeFindings;
4362
5584
  const isFrameworkFinding = (finding: (typeof allCheckFindings)[number]) =>
4363
5585
  finding.domain === APPLICATION_HOME_DOMAIN || finding.directiveId?.startsWith("nomos_internal_") === true;
4364
5586
  const checkFindings = allCheckFindings.filter((finding) => !isFrameworkFinding(finding));
@@ -4409,7 +5631,7 @@ async function main(): Promise<void> {
4409
5631
  console.log(` dart ${rel(dartOut)}${path.sep} (${dartFiles.length} domain file(s) + ${supportText} — typed Dart for Flutter)`);
4410
5632
  }
4411
5633
  }
4412
- console.log(` deploy ${rel(deployPath)}`);
5634
+ console.log(` deploy githolon deploy <workspace> --file ${rel(lawUsdzPath)}`);
4413
5635
  console.log(``);
4414
5636
  console.log(`your business path:`);
4415
5637
  if (proofPath !== undefined) {
@@ -4424,6 +5646,53 @@ async function main(): Promise<void> {
4424
5646
  }
4425
5647
 
4426
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
+ }
4427
5696
  }
4428
5697
 
4429
5698
  /**