@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.
- package/compile_package.mjs +43 -2
- package/compile_proof.d.mts +47 -0
- package/compile_proof.mjs +343 -0
- package/dart/CHANGELOG.md +7 -0
- package/dart/lib/src/dev_cloud.dart +8 -8
- package/dart/lib/src/nms_generated_runtime.dart +2 -0
- package/dart/lib/src/runner/node-entry.mjs +5504 -1236
- package/dart/lib/src/runner/offline-install.mjs +4578 -528
- package/dart/lib/src/test_harness.dart +206 -152
- package/dart/lib/src/webview_bridge.dart +196 -89
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/acquire_session_credentials_test.dart +4 -3
- package/dart/test/adv_realm_interference_test.dart +7 -6
- package/dart/test/cloud_bound_topology_test.dart +3 -3
- package/dart/test/cluster_births_test.dart +4 -3
- package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
- package/dart/test/cluster_local_autosync_test.dart +3 -2
- package/dart/test/cluster_scale_test.dart +3 -2
- package/dart/test/dlq_session_surface_test.dart +7 -6
- package/dart/test/first_app_gauntlet_test.dart +14 -21
- package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
- package/dart/test/four_layer_nested_topology_test.dart +4 -4
- package/dart/test/harness_phase_timeout_test.dart +2 -2
- package/dart/test/local_sync_inert_test.dart +1 -1
- package/dart/test/parent_signer_recovery_test.dart +2 -2
- package/dart/test/sealing_client_test.dart +4 -3
- package/dart/test/session_bound_test.dart +8 -7
- package/dart/test/test_harness_test.dart +13 -12
- package/dart/test/vo_read_typing_test.dart +4 -3
- package/dart/test/watch_after_offer_stall_test.dart +4 -3
- package/dart/test/webview_bridge_test.dart +102 -5
- package/dart_types/CHANGELOG.md +5 -0
- package/dart_types/lib/nomos_types.dart +9 -0
- package/dart_types/lib/src/nomos_ref.dart +20 -0
- package/dart_types/lib/src/optional_ref.dart +85 -0
- package/dart_types/lib/src/sha256_digest.dart +41 -0
- package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/optional_ref_test.dart +44 -0
- package/dart_types/test/sha256_digest_test.dart +18 -0
- package/package.json +9 -5
- package/runtime/generated-application-host.js +5365 -1105
- package/src/aggregate.ts +18 -0
- package/src/application_composition.ts +6 -32
- package/src/attested_core.ts +6 -3
- package/src/birth_meter.ts +1 -0
- package/src/birth_meter_ensure.ts +28 -2
- package/src/births.ts +1 -1
- package/src/build_package.ts +162 -538
- package/src/business_birth.ts +19 -3
- package/src/canonical_json.ts +21 -0
- package/src/check_package_main.ts +76 -35
- package/src/clear_directive.ts +19 -8
- package/src/codegen_dart.ts +305 -22
- package/src/codegen_dart_application.ts +844 -292
- package/src/codegen_dart_client.ts +1084 -407
- package/src/codegen_dart_scenario.ts +12 -12
- package/src/codegen_proof.ts +169 -39
- package/src/codegen_provider_dart.ts +17 -1
- package/src/codegen_ts.ts +298 -106
- package/src/compat_manifest.ts +2 -2
- package/src/compat_package_main.ts +8 -12
- package/src/compile_package_main.ts +1866 -597
- package/src/compose.ts +1 -1
- package/src/directive.ts +208 -15
- package/src/engine_entry.ts +165 -111
- package/src/evolve_preview.ts +43 -81
- package/src/extremum.ts +1 -1
- package/src/fields.ts +183 -4
- package/src/flow_gate.ts +75 -2
- package/src/framework/application_home.ts +2 -0
- package/src/framework/auth_provider_law.ts +2 -2
- package/src/framework/birth_meter.ts +7 -19
- package/src/framework/bootstrap.ts +0 -2
- package/src/framework/business_corrections.ts +9 -5
- package/src/framework/domain_lifecycle.ts +15 -29
- package/src/framework/encryption_scope.ts +2 -1
- package/src/framework/framework_aggregates.ts +2 -0
- package/src/framework/genesis.ts +16 -14
- package/src/framework/home.ts +20 -14
- package/src/framework/owned_custody.ts +9 -3
- package/src/framework/platform_admin.ts +3 -3
- package/src/framework/relations.ts +21 -13
- package/src/framework/signing_identity.ts +45 -40
- package/src/framework/workspace_invariant.ts +245 -0
- package/src/framework/workspaces.ts +23 -29
- package/src/index.ts +36 -16
- package/src/intent.ts +178 -6
- package/src/law_check.ts +32 -11
- package/src/law_graph.ts +2902 -0
- package/src/law_graph_usda.ts +1799 -0
- package/src/law_identity.ts +435 -0
- package/src/law_value_type.ts +230 -0
- package/src/lsp_server.ts +61 -31
- package/src/ops.ts +7 -0
- package/src/package_config.ts +141 -0
- package/src/package_dependencies.ts +108 -0
- package/src/package_dependency_usage.ts +150 -0
- package/src/package_source_imports.ts +71 -0
- package/src/plan_source_slice.ts +335 -0
- package/src/point_guard.ts +192 -0
- package/src/precondition.ts +44 -9
- package/src/read_projection_namespace.ts +1 -1
- package/src/runtime_zod.ts +44 -0
- package/src/secondary_ensure.ts +67 -14
- package/src/shape_compat.ts +1 -1
- package/src/stable_id_identity_bridge.ts +149 -0
- package/src/stable_id_lockfile.ts +278 -0
- package/src/stable_ids.ts +7 -2
- package/src/stable_ids_for_module.ts +348 -0
- package/src/stable_ids_types.ts +1 -1
- package/src/sum.ts +1 -1
- package/src/usd_state.ts +1 -1
- package/src/usda_relationship_integrity.ts +66 -0
- package/src/usda_text.ts +18 -0
- package/src/usdz.ts +78 -0
- package/src/value_object.ts +95 -0
- package/src/wire.ts +19 -0
- package/src/wire_encode.ts +22 -0
- package/src/workspace_sharding.ts +52 -36
- package/src/workspace_sharing.ts +28 -4
- package/src/zod_introspection.ts +1 -1
- package/dart/test/cluster_attested_test.dart +0 -201
- package/src/compile_engine.ts +0 -595
- package/src/manifest.ts +0 -1476
- package/src/read_projection_composition.ts +0 -117
- package/src/read_projection_manifest.ts +0 -115
- package/src/relation_composition.ts +0 -286
- package/src/usd.ts +0 -1967
- 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
|
|
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.
|
|
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.
|
|
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>.
|
|
32
|
-
*
|
|
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
|
-
|
|
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 {
|
|
156
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
2203
|
-
const
|
|
2204
|
-
const
|
|
2205
|
-
copyFileSync(
|
|
2206
|
-
written.push(
|
|
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
|
-
|
|
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}/${
|
|
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
|
|
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.
|
|
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
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
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.
|
|
2745
|
+
fail("nomos.project.ts no longer accepts 'layered': every law closure is layered, with no alternate compile lane");
|
|
2425
2746
|
}
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
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
|
-
//
|
|
2434
|
-
//
|
|
2435
|
-
//
|
|
2436
|
-
//
|
|
2437
|
-
//
|
|
2438
|
-
//
|
|
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
|
-
|
|
2449
|
-
|
|
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
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
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
|
|
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
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
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
|
|
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:
|
|
3062
|
-
? (JSON.parse(readFileSync(lockPath, "utf8"))
|
|
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:
|
|
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
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
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:
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
3322
|
-
...(
|
|
3323
|
-
|
|
3324
|
-
|
|
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
|
-
...
|
|
3327
|
-
|
|
3328
|
-
|
|
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
|
|
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({
|
|
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
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
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 = {
|
|
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 = (
|
|
3359
|
-
|
|
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
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
const
|
|
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
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
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 (
|
|
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
|
-
//
|
|
3449
|
-
// global refuses Object.freeze), and the lump
|
|
3450
|
-
// evals it with the STRICT flag — sloppy mode
|
|
3451
|
-
// frozen-global writes the probe exists to
|
|
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
|
|
3459
|
-
`const
|
|
3460
|
-
`
|
|
3461
|
-
`
|
|
3462
|
-
`
|
|
3463
|
-
`
|
|
3464
|
-
`
|
|
3465
|
-
`
|
|
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
|
-
|
|
3471
|
-
|
|
3472
|
-
const
|
|
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
|
-
|
|
3475
|
-
|
|
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.
|
|
3491
|
-
|
|
3492
|
-
|
|
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
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
4660
|
+
domainModules.map((module) => module.domain ?? module.name),
|
|
4661
|
+
runtimeDependencies,
|
|
4662
|
+
v3DomainAssets,
|
|
4663
|
+
sharedRuntimeAsset,
|
|
4664
|
+
routedPlanAssets,
|
|
3497
4665
|
planRoutes,
|
|
3498
4666
|
);
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
{
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
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
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
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
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
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
|
-
|
|
3603
|
-
|
|
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
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
const
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
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
|
-
|
|
4727
|
+
execFileSync(usdcat, ["--loadOnly", layeredRootPath], {
|
|
3626
4728
|
encoding: "utf8",
|
|
3627
|
-
stdio: ["ignore", "
|
|
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
|
-
|
|
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}.
|
|
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.
|
|
3683
|
-
//
|
|
3684
|
-
//
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
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 =
|
|
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.
|
|
3737
|
-
//
|
|
3738
|
-
//
|
|
3739
|
-
//
|
|
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.
|
|
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
|
|
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:
|
|
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} #
|
|
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:
|
|
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
|
-
|
|
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")
|
|
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
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
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
|
-
|
|
4072
|
-
|
|
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,
|
|
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)
|
|
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
|
-
//
|
|
4265
|
-
//
|
|
4266
|
-
//
|
|
4267
|
-
//
|
|
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", ["
|
|
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(`
|
|
5513
|
+
console.log(` law ${rel(lawUsdzPath)} (${lawUsdz.byteLength} transport bytes)`);
|
|
4298
5514
|
console.log(` domainHash ${domainHash}`);
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
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(
|
|
4321
|
-
|
|
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) —
|
|
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 =
|
|
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(
|
|
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
|
/**
|