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