@githolon/dsl 0.102.4 → 0.103.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/compile_package.mjs +43 -2
  2. package/compile_proof.d.mts +47 -0
  3. package/compile_proof.mjs +343 -0
  4. package/dart/CHANGELOG.md +7 -0
  5. package/dart/lib/src/dev_cloud.dart +8 -8
  6. package/dart/lib/src/nms_generated_runtime.dart +2 -0
  7. package/dart/lib/src/runner/node-entry.mjs +5504 -1236
  8. package/dart/lib/src/runner/offline-install.mjs +4578 -528
  9. package/dart/lib/src/test_harness.dart +206 -152
  10. package/dart/lib/src/webview_bridge.dart +196 -89
  11. package/dart/pubspec.lock +1 -1
  12. package/dart/pubspec.yaml +2 -2
  13. package/dart/test/acquire_session_credentials_test.dart +4 -3
  14. package/dart/test/adv_realm_interference_test.dart +7 -6
  15. package/dart/test/cloud_bound_topology_test.dart +3 -3
  16. package/dart/test/cluster_births_test.dart +4 -3
  17. package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
  18. package/dart/test/cluster_local_autosync_test.dart +3 -2
  19. package/dart/test/cluster_scale_test.dart +3 -2
  20. package/dart/test/dlq_session_surface_test.dart +7 -6
  21. package/dart/test/first_app_gauntlet_test.dart +14 -21
  22. package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
  23. package/dart/test/four_layer_nested_topology_test.dart +4 -4
  24. package/dart/test/harness_phase_timeout_test.dart +2 -2
  25. package/dart/test/local_sync_inert_test.dart +1 -1
  26. package/dart/test/parent_signer_recovery_test.dart +2 -2
  27. package/dart/test/sealing_client_test.dart +4 -3
  28. package/dart/test/session_bound_test.dart +8 -7
  29. package/dart/test/test_harness_test.dart +13 -12
  30. package/dart/test/vo_read_typing_test.dart +4 -3
  31. package/dart/test/watch_after_offer_stall_test.dart +4 -3
  32. package/dart/test/webview_bridge_test.dart +102 -5
  33. package/dart_types/CHANGELOG.md +5 -0
  34. package/dart_types/lib/nomos_types.dart +9 -0
  35. package/dart_types/lib/src/nomos_ref.dart +20 -0
  36. package/dart_types/lib/src/optional_ref.dart +85 -0
  37. package/dart_types/lib/src/sha256_digest.dart +41 -0
  38. package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
  39. package/dart_types/pubspec.yaml +1 -1
  40. package/dart_types/test/optional_ref_test.dart +44 -0
  41. package/dart_types/test/sha256_digest_test.dart +18 -0
  42. package/package.json +9 -5
  43. package/runtime/generated-application-host.js +5365 -1105
  44. package/src/aggregate.ts +18 -0
  45. package/src/application_composition.ts +6 -32
  46. package/src/attested_core.ts +6 -3
  47. package/src/birth_meter.ts +1 -0
  48. package/src/birth_meter_ensure.ts +28 -2
  49. package/src/births.ts +1 -1
  50. package/src/build_package.ts +162 -538
  51. package/src/business_birth.ts +19 -3
  52. package/src/canonical_json.ts +21 -0
  53. package/src/check_package_main.ts +76 -35
  54. package/src/clear_directive.ts +19 -8
  55. package/src/codegen_dart.ts +305 -22
  56. package/src/codegen_dart_application.ts +844 -292
  57. package/src/codegen_dart_client.ts +1084 -407
  58. package/src/codegen_dart_scenario.ts +12 -12
  59. package/src/codegen_proof.ts +169 -39
  60. package/src/codegen_provider_dart.ts +17 -1
  61. package/src/codegen_ts.ts +298 -106
  62. package/src/compat_manifest.ts +2 -2
  63. package/src/compat_package_main.ts +8 -12
  64. package/src/compile_package_main.ts +1866 -597
  65. package/src/compose.ts +1 -1
  66. package/src/directive.ts +208 -15
  67. package/src/engine_entry.ts +165 -111
  68. package/src/evolve_preview.ts +43 -81
  69. package/src/extremum.ts +1 -1
  70. package/src/fields.ts +183 -4
  71. package/src/flow_gate.ts +75 -2
  72. package/src/framework/application_home.ts +2 -0
  73. package/src/framework/auth_provider_law.ts +2 -2
  74. package/src/framework/birth_meter.ts +7 -19
  75. package/src/framework/bootstrap.ts +0 -2
  76. package/src/framework/business_corrections.ts +9 -5
  77. package/src/framework/domain_lifecycle.ts +15 -29
  78. package/src/framework/encryption_scope.ts +2 -1
  79. package/src/framework/framework_aggregates.ts +2 -0
  80. package/src/framework/genesis.ts +16 -14
  81. package/src/framework/home.ts +20 -14
  82. package/src/framework/owned_custody.ts +9 -3
  83. package/src/framework/platform_admin.ts +3 -3
  84. package/src/framework/relations.ts +21 -13
  85. package/src/framework/signing_identity.ts +45 -40
  86. package/src/framework/workspace_invariant.ts +245 -0
  87. package/src/framework/workspaces.ts +23 -29
  88. package/src/index.ts +36 -16
  89. package/src/intent.ts +178 -6
  90. package/src/law_check.ts +32 -11
  91. package/src/law_graph.ts +2902 -0
  92. package/src/law_graph_usda.ts +1799 -0
  93. package/src/law_identity.ts +435 -0
  94. package/src/law_value_type.ts +230 -0
  95. package/src/lsp_server.ts +61 -31
  96. package/src/ops.ts +7 -0
  97. package/src/package_config.ts +141 -0
  98. package/src/package_dependencies.ts +108 -0
  99. package/src/package_dependency_usage.ts +150 -0
  100. package/src/package_source_imports.ts +71 -0
  101. package/src/plan_source_slice.ts +335 -0
  102. package/src/point_guard.ts +192 -0
  103. package/src/precondition.ts +44 -9
  104. package/src/read_projection_namespace.ts +1 -1
  105. package/src/runtime_zod.ts +44 -0
  106. package/src/secondary_ensure.ts +67 -14
  107. package/src/shape_compat.ts +1 -1
  108. package/src/stable_id_identity_bridge.ts +149 -0
  109. package/src/stable_id_lockfile.ts +278 -0
  110. package/src/stable_ids.ts +7 -2
  111. package/src/stable_ids_for_module.ts +348 -0
  112. package/src/stable_ids_types.ts +1 -1
  113. package/src/sum.ts +1 -1
  114. package/src/usd_state.ts +1 -1
  115. package/src/usda_relationship_integrity.ts +66 -0
  116. package/src/usda_text.ts +18 -0
  117. package/src/usdz.ts +78 -0
  118. package/src/value_object.ts +95 -0
  119. package/src/wire.ts +19 -0
  120. package/src/wire_encode.ts +22 -0
  121. package/src/workspace_sharding.ts +52 -36
  122. package/src/workspace_sharing.ts +28 -4
  123. package/src/zod_introspection.ts +1 -1
  124. package/dart/test/cluster_attested_test.dart +0 -201
  125. package/src/compile_engine.ts +0 -595
  126. package/src/manifest.ts +0 -1476
  127. package/src/read_projection_composition.ts +0 -117
  128. package/src/read_projection_manifest.ts +0 -115
  129. package/src/relation_composition.ts +0 -286
  130. package/src/usd.ts +0 -1967
  131. package/src/usd_layers.ts +0 -1776
package/src/usd_layers.ts DELETED
@@ -1,1776 +0,0 @@
1
- // NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
2
- // remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
3
- // wasm32-wasip1 artifact {kernel · projection · embedded
4
- // QuickJS engine} on V8 + WASI-shim, byte-identical everywhere. V8 = portability; the one
5
- // wasm = determinism. No native, no wasmtime, no 2nd artifact, no domain-JS on bare V8.
6
- // If a file isn't this / hosting this / authoring for this / proving this — it's gone.
7
-
8
- /**
9
- * USD LAYERED EMISSION + the flatten-equivalence fold — the composability spike.
10
- *
11
- * THE THESIS (the monoid homomorphism): Nomos's module fold and USD's layer stack
12
- * are the SAME non-commutative monoid. `composeDomainModule` spread-merges a
13
- * domain's ordered module list (later wins — a left-biased fold over export bags);
14
- * a USD layer stack resolves the strongest opinion per attribute (stronger layer
15
- * wins — a left-biased fold over opinion maps). This module makes the
16
- * correspondence MECHANICAL:
17
- *
18
- * * `emitLayeredDomain` lowers each authored MODULE into its own `UsdLayer`
19
- * (and a real `.usda` text layer standard USD tooling opens), instead of
20
- * lowering only the composed result.
21
- * * `flattenLayeredUsd` is the ordered opinion-map fold over those layers —
22
- * and the proof obligation is CANONICAL LAW EQUALITY:
23
- *
24
- * canonicalJson(flattenLayeredUsd(layers)) === canonicalJson(emitUsd([composed]))
25
- *
26
- * i.e. Φ(fold_modules(A, B)) == fold_layers(Φ(A), Φ(B)) — flatten-after-lower
27
- * equals lower-after-compose. `nomos-compile` ASSERTS this fail-closed on every
28
- * compile (never assumed); release builds can additionally require stock OpenUSD's
29
- * `usdcat --flatten` to resolve the same canonical law.
30
- *
31
- * WHERE THE TWO FOLDS COULD DIVERGE (the lemma, stated honestly): USD composes
32
- * PER-ATTRIBUTE (a weaker layer's attribute shows through a stronger prim that is
33
- * silent about it), while the module fold replaces a re-exported declaration
34
- * WHOLESALE. The two coincide exactly when every re-declaration is TOTAL over its
35
- * attribute set — which the DSL guarantees structurally for aggregates
36
- * (`aggregate()` takes the full field record; there is no partial declaration) and
37
- * for a directive's required attributes (target/marker/requires/payload). The gap
38
- * is omit-when-empty boundaries (`reads`/`emits`) on a RE-declared directive:
39
- * "omitted" means "none" to the module fold but "no opinion" (show-through) to
40
- * USD. The compile-time canonical-equality assertion fail-closes that case rather than
41
- * silently shipping either answer.
42
- *
43
- * STATE vs LAW (why this is sound): law composition is this non-commutative
44
- * left-biased fold (order is meaning — the layer stack); STATE merge is the
45
- * commutative semilattice world (Lww/AddWins/MapOf merge drivers — order-free by
46
- * construction). The domainHash is deliberately NON-homomorphic: it is sha256 over
47
- * the COMPOSED canonical bytes, so identity lives on the flattened law, never on
48
- * any decomposition of it. Every package therefore emits its authored layer stack;
49
- * the executable `.package.usda` envelope carries that resolved law and its hash.
50
- *
51
- * LIVRPS note: the module list maps onto the SUBLAYER arc only (local opinions per
52
- * layer, stronger-over-weaker). The reference/variant arcs of `usd.ts` are the
53
- * typed MONOTONIC governance lane (`mergeUsdPrim`, fail-closed rules) — a
54
- * different, stricter composition than the opinion fold here, on purpose: a tenant
55
- * re-authoring its own module stack is opinion strength; a foreign layer composing
56
- * over deployed law is a monotonic move.
57
- *
58
- * BUILD-TIME ONLY (Buffer for lossless UTF-8 identifier tokens): reached via `@githolon/dsl/usd-layers`, never
59
- * the runtime barrel.
60
- */
61
- import type { AggregateHandle } from "./aggregate.js";
62
- import { createHash } from "node:crypto";
63
- import { READ_PROJECTION_DELIMITER } from "./read_projection_namespace.js";
64
- import type { Field } from "./fields.js";
65
- import type { DomainModule } from "./codegen_dart.js";
66
- import type { WireDriver, WireRefMode, WireSchema } from "./wire.js";
67
- import {
68
- emitUsd,
69
- type UsdCombined,
70
- type UsdDerived,
71
- type UsdDocument,
72
- type UsdLayer,
73
- type UsdPrim,
74
- type UsdQuery,
75
- } from "./usd.js";
76
- import { composeSharedRelationTypes } from "./relation_composition.js";
77
- import { withBusinessCorrectionLayer } from "./framework/business_corrections.js";
78
-
79
- /** The layer-file format marker (customLayerData `nomos:format`). */
80
- export const NOMOS_USD_LAYER_FORMAT = "nomos.usd-layer.v1";
81
- /** The layered-root format marker — the subLayers document. */
82
- export const NOMOS_USD_LAYERED_PACKAGE_FORMAT = "nomos.usd-layered-package.v1";
83
- /** The self-contained ledger/runtime stage: native law plus indexed executable plan assets. */
84
- export const NOMOS_USD_PLAN_PACKAGE_FORMAT = "nomos.openusd-plan-package.v2";
85
-
86
- /** Append generated lines without turning a large law into function arguments. */
87
- function appendLines(target: string[], lines: readonly string[]): void {
88
- for (const line of lines) target.push(line);
89
- }
90
-
91
- // ── small codecs ───────────────────────────────────────────────────────────────────
92
-
93
- /** Escape a string for a double-quoted usda string literal. */
94
- export function usdaString(s: string): string {
95
- return JSON.stringify(s);
96
- }
97
-
98
- /** A usda `string[]` literal. */
99
- export function usdaStringArray(items: readonly string[]): string {
100
- return `[${items.map(usdaString).join(", ")}]`;
101
- }
102
-
103
- /**
104
- * A `WireDriver` as a READABLE usda token: unit drivers are their bare serde
105
- * string (`"Lww"`, `"AddWins"`, `"LwwCausal"`, …); `MapOf` nests as `MapOf(<inner>)`;
106
- * the EXPLICIT TIME-AUTHORITY `LwwBy` carries its principal as `LwwBy(<principal>)`.
107
- * Readable on purpose — the usdcat/usdview tree is the visual proof, so the merge law
108
- * (and the time-authority) of every field must be legible in it (hex would bury the point).
109
- */
110
- export function driverToken(driver: WireDriver): string {
111
- if (typeof driver === "string") return driver;
112
- if ("LwwBy" in driver) return `LwwBy(${driver.LwwBy})`;
113
- // `Surfaced(<inner>)` — the user-resolvable posture stays legible in the tree too.
114
- if ("Surfaced" in driver) return `Surfaced(${driverToken(driver.Surfaced)})`;
115
- // `Attributed(<inner>)` — attributed reads (field_provenance.md), legible in the tree.
116
- if ("Attributed" in driver) return `Attributed(${driverToken(driver.Attributed)})`;
117
- return `MapOf(${driverToken(driver.MapOf)})`;
118
- }
119
-
120
- /** Inverse of {@link driverToken} — fail-closed on a malformed token. */
121
- export function parseDriverToken(token: string): WireDriver {
122
- if (token.startsWith("MapOf(") && token.endsWith(")")) {
123
- return { MapOf: parseDriverToken(token.slice("MapOf(".length, -1)) };
124
- }
125
- if (token.startsWith("Surfaced(") && token.endsWith(")")) {
126
- return { Surfaced: parseDriverToken(token.slice("Surfaced(".length, -1)) };
127
- }
128
- if (token.startsWith("Attributed(") && token.endsWith(")")) {
129
- return { Attributed: parseDriverToken(token.slice("Attributed(".length, -1)) };
130
- }
131
- // `LwwBy(<principal>)` — the principal is an opaque non-empty string (uids carry
132
- // hyphens, so it is NOT the bare-identifier shape the unit-driver branch validates).
133
- if (token.startsWith("LwwBy(") && token.endsWith(")")) {
134
- const principal = token.slice("LwwBy(".length, -1);
135
- if (principal.length === 0) {
136
- throw new Error(`usd-layers: LwwBy driver token names no time-authority principal`);
137
- }
138
- return { LwwBy: principal };
139
- }
140
- if (!/^[A-Za-z][A-Za-z0-9]*$/.test(token)) {
141
- throw new Error(`usd-layers: malformed driver token '${token}'`);
142
- }
143
- return token as WireDriver;
144
- }
145
-
146
- /** A valid USD prim-name identifier for an arbitrary id (raw id rides `nomos:id`). */
147
- export function primName(id: string): string {
148
- const sanitized = id.replace(/[^A-Za-z0-9_]/g, "_");
149
- return /^[A-Za-z_]/.test(sanitized) ? sanitized : `_${sanitized}`;
150
- }
151
-
152
- /** Lossless USD identifier for an arbitrary business field label. */
153
- export function fieldToken(field: string): string {
154
- return `f${Buffer.from(field, "utf8").toString("hex")}`;
155
- }
156
-
157
- /** Inverse of {@link fieldToken}. */
158
- export function parseFieldToken(token: string): string {
159
- if (!/^f(?:[0-9a-f]{2})+$/.test(token)) throw new Error(`usd-layers: malformed field token '${token}'`);
160
- return Buffer.from(token.slice(1), "hex").toString("utf8");
161
- }
162
-
163
- /** Canonical JSON text used only to content-stamp a native USD value tree. */
164
- function stableValueJson(value: unknown): string {
165
- if (value === null || typeof value !== "object") return JSON.stringify(value);
166
- if (Array.isArray(value)) return `[${value.map(stableValueJson).join(",")}]`;
167
- return `{${Object.keys(value as Record<string, unknown>).sort().map((key) =>
168
- `${JSON.stringify(key)}:${stableValueJson((value as Record<string, unknown>)[key])}`).join(",")}}`;
169
- }
170
-
171
- /**
172
- * Encode an arbitrary law value as an ordinary USD prim tree. No JSON text is carried. Every node
173
- * bears the root content stamp so a stronger layer replaces the whole value: weaker descendants
174
- * may show through structurally under USD composition, but the reader ignores their old stamp.
175
- */
176
- export function nativeValueLines(name: string, value: unknown, indent: string): string[] {
177
- const hash = createHash("sha256").update(stableValueJson(value)).digest("hex");
178
- const keyHex = (key: string): string => Buffer.from(key, "utf8").toString("hex");
179
- const scalarLine = (nodeValue: unknown, attr: string, at: string): string => {
180
- if (nodeValue === null) return `${at}custom bool nomos:zv:${attr} = true`;
181
- if (typeof nodeValue === "string") return `${at}custom string nomos:sv:${attr} = ${usdaString(nodeValue)}`;
182
- if (typeof nodeValue === "boolean") return `${at}custom bool nomos:bv:${attr} = ${nodeValue ? "true" : "false"}`;
183
- if (typeof nodeValue === "number" && Number.isFinite(nodeValue)) {
184
- const usdType = Number.isSafeInteger(nodeValue) && !Object.is(nodeValue, -0) ? "int64" : "double";
185
- return `${at}custom ${usdType} nomos:nv:${attr} = ${Object.is(nodeValue, -0) ? "-0" : String(nodeValue)}`;
186
- }
187
- throw new Error(`usd-layers: '${name}' contains a non-JSON value (${typeof nodeValue})`);
188
- };
189
- const emitContainer = (nodeName: string, nodeValue: unknown, at: string): string[] => {
190
- const isArray = Array.isArray(nodeValue);
191
- const isObject = nodeValue !== null && typeof nodeValue === "object" && !isArray;
192
- const lines = [
193
- `${at}def Scope ${usdaString(nodeName)}`,
194
- `${at}{`,
195
- `${at} custom string nomos:containerKind = ${usdaString(isArray ? "array" : isObject ? "object" : "scalar")}`,
196
- ];
197
- const entries: readonly [string | undefined, unknown][] = isArray
198
- ? nodeValue.map((child) => [undefined, child] as const)
199
- : isObject
200
- // The content hash canonicalizes object keys. The physical tree MUST use the same order:
201
- // two layers carrying the same object with different JS insertion order otherwise share a
202
- // root hash but compose incompatible positional children under that root.
203
- ? Object.entries(nodeValue as Record<string, unknown>).sort(([a], [b]) => a.localeCompare(b))
204
- : [[undefined, nodeValue]];
205
- entries.forEach(([key, child], index) => {
206
- const position = `i${String(index).padStart(6, "0")}`;
207
- const identity = key === undefined ? position : `${position}:k${keyHex(key)}`;
208
- if (child !== null && typeof child === "object") {
209
- lines.push("");
210
- appendLines(lines, emitContainer(identity.replace(/:/g, "_"), child, `${at} `));
211
- } else {
212
- lines.push(scalarLine(child, identity, `${at} `));
213
- }
214
- });
215
- lines.push(`${at}}`);
216
- return lines;
217
- };
218
- const rootName = `__nms_${primName(name)}_${hash}`;
219
- const lines = [`${indent}custom string nomos:value:${name} = ${usdaString(hash)}`, ""];
220
- appendLines(lines, emitContainer(rootName, value, indent));
221
- return lines;
222
- }
223
-
224
- /** Field→driver map with keys sorted (the canonical schema shape `manifest.ts` emits). */
225
- function sortedSchema(schema: WireSchema): WireSchema {
226
- const out: WireSchema = {};
227
- for (const k of Object.keys(schema).sort()) out[k] = schema[k]!;
228
- return out;
229
- }
230
-
231
- function domainLayerAttrLines(domain: string, layer: UsdLayer, indent: string): string[] {
232
- const attrs = [
233
- `${indent}custom string nomos:kind = "domain"`,
234
- `${indent}custom string nomos:domain = ${usdaString(domain)}`,
235
- ];
236
- const values: readonly [string, unknown][] = [
237
- ["identityManifest", layer.nomosIdentityManifest], ["readProjection", layer.nomosReadProjection],
238
- ["stableIds", layer.nomosStableIds], ["interface", layer.nomosInterface], ["readGate", layer.nomosReadGate],
239
- ["invariantGate", layer.nomosInvariantGate], ["readVisibility", layer.nomosReadVisibility],
240
- ["encryption", layer.nomosEncryption], ["capabilities", layer.nomosCapabilities],
241
- ["attributedGate", layer.nomosAttributedGate], ["relations", layer.nomosRelations],
242
- ["relationNamespace", layer.nomosRelationNamespace], ["readProjectionNamespace", layer.nomosReadProjectionNamespace],
243
- ["relationGate", layer.nomosRelationGate], ["attestationGate", layer.nomosAttestationGate],
244
- ["delegationGate", layer.nomosDelegationGate], ["attestedReadGate", layer.nomosAttestedReadGate],
245
- ["birthGate", layer.nomosBirthGate], ["birthRecordGate", layer.nomosBirthRecordGate],
246
- ["parentAttestedBirth", layer.nomosParentAttestedBirth], ["verifiedFirstEnroll", layer.nomosVerifiedFirstEnroll],
247
- ["eraCertGate", layer.nomosEraCertGate], ["valueObjects", layer.nomosValueObjects],
248
- ["contracts", layer.nomosContracts], ["foreignRefs", layer.nomosForeignRefs], ["flowGate", layer.nomosFlowGate],
249
- ];
250
- for (const [name, value] of values) {
251
- if (value === undefined) continue;
252
- attrs.push("");
253
- appendLines(attrs, nativeValueLines(name, value, indent));
254
- }
255
- return attrs;
256
- }
257
-
258
- // ── per-module layer emission ────────────────────────────────────────────────────────
259
-
260
- /** One authored module of a domain's ordered module list (weak → strong). */
261
- export interface LayeredModuleInput {
262
- /** The layer stem (artifact basename) — usually the module file's basename. */
263
- readonly name: string;
264
- /** The module lowered ALONE (its own aggregates/directives/declared reads). */
265
- readonly module: DomainModule;
266
- }
267
-
268
- /** One emitted module layer: the IR layer + its standalone `.usda` text. */
269
- export interface EmittedModuleLayer {
270
- readonly name: string;
271
- /** The module's `UsdLayer` (path = `/Nomos/<domain>`) — the layer JSON artifact. */
272
- readonly layer: UsdLayer;
273
- /** Real usda text standard USD tooling opens (usdcat/usdview/pxr). */
274
- readonly usda: string;
275
- }
276
-
277
- /**
278
- * Lower ONE domain's ordered module list into per-module USD layers (weak → strong,
279
- * module order). Each module goes through the SAME `emitUsd` lowering the canonical
280
- * single-doc emission uses — Φ is one function, applied per module instead of once
281
- * over the composed module. Prim paths already defined by a WEAKER module of the
282
- * same stack are emitted with the USD `over` specifier (the stronger layer's
283
- * opinions compose over the weaker definition); first definitions are `def`s.
284
- */
285
- export function emitLayeredDomain(
286
- domain: string,
287
- modules: readonly LayeredModuleInput[],
288
- options: { readonly applyBusinessCorrectionLayer?: boolean } = {},
289
- ): EmittedModuleLayer[] {
290
- if (modules.length === 0) {
291
- throw new Error(`usd-layers: domain '${domain}' has no modules to layer`);
292
- }
293
- // Raw callers need the cross-module correction prelude. The package compiler,
294
- // however, passes modules already lowered through composeDomainModule; re-inferring
295
- // the prelude here would turn framework-expanded capability directives into public
296
- // business verbs and invent law that the canonical fold never declared.
297
- const normalizedModules = options.applyBusinessCorrectionLayer === false
298
- ? modules.map((entry) => entry.module)
299
- : withBusinessCorrectionLayer(modules.map((entry) => entry.module));
300
- const out: EmittedModuleLayer[] = [];
301
- const seenPaths = new Set<string>();
302
- for (const [index, m] of modules.entries()) {
303
- const module = normalizedModules[index]!;
304
- const doc = emitUsd([{ path: `/Nomos/${domain}`, module }]);
305
- const layer = doc.layers[0]!;
306
- const overPaths = new Set<string>();
307
- for (const prim of layer.prims) {
308
- if (seenPaths.has(prim.path)) overPaths.add(prim.path);
309
- }
310
- out.push({
311
- name: m.name,
312
- layer,
313
- usda: usdaLayerText(domain, m.name, layer, module, overPaths),
314
- });
315
- for (const prim of layer.prims) seenPaths.add(prim.path);
316
- }
317
- return out;
318
- }
319
-
320
- /** The ref/hasMany decoration for one aggregate (USD relationships — visual arcs). */
321
- function relationLines(
322
- domainScope: string,
323
- agg: AggregateHandle | undefined,
324
- indent: string,
325
- ): string[] {
326
- if (agg === undefined) return [];
327
- const lines: string[] = [];
328
- for (const [name, field] of Object.entries(agg.fields as Record<string, Field>).sort(
329
- ([a], [b]) => (a < b ? -1 : a > b ? 1 : 0),
330
- )) {
331
- if (field.kind === "ref" && field.refAggregateId !== undefined) {
332
- lines.push(
333
- `${indent}custom rel nomos:ref:${name} = <${domainScope}/${primName(field.refAggregateId)}>`,
334
- );
335
- }
336
- }
337
- for (const [name, field] of Object.entries(agg.hasMany as Record<string, Field>).sort(
338
- ([a], [b]) => (a < b ? -1 : a > b ? 1 : 0),
339
- )) {
340
- if (field.refAggregateId !== undefined) {
341
- lines.push(
342
- `${indent}custom rel nomos:hasMany:${name} = <${domainScope}/${primName(field.refAggregateId)}>`,
343
- );
344
- if (field.viaField !== undefined) {
345
- lines.push(
346
- `${indent}custom string nomos:hasMany:${name}:via = ${usdaString(field.viaField)}`,
347
- );
348
- }
349
- }
350
- }
351
- return lines;
352
- }
353
-
354
- /**
355
- * One module layer as REAL `.usda` text: `/Nomos/<domain>` scopes, one prim per
356
- * aggregate (field→merge-driver as readable `nomos:driver:<field>` attributes,
357
- * `t.ref`/`t.hasMany` as USD relationships), one prim per directive, one prim per
358
- * declared query/derived/combined. Structured values are ordinary stamped USD prim trees,
359
- * so the round-trip is byte-faithful without a hidden JSON authority. Re-touched prim paths use
360
- * the `over` specifier; everything carries `nomos:id` so the sanitized prim name
361
- * never loses the wire id.
362
- */
363
- export function usdaLayerText(
364
- domain: string,
365
- moduleName: string,
366
- layer: UsdLayer,
367
- module: DomainModule | undefined,
368
- overPaths: ReadonlySet<string>,
369
- ): string {
370
- const domainScope = `/Nomos/${primName(domain)}`;
371
- const aggById = new Map<string, AggregateHandle>();
372
- for (const a of (module?.aggregates ?? []) as AggregateHandle[]) aggById.set(a.id, a);
373
-
374
- const ind2 = " ";
375
- const ind3 = " ";
376
- const body: string[] = [];
377
-
378
- for (const prim of layer.prims) {
379
- const id = prim.path.slice(prim.path.lastIndexOf("/") + 1);
380
- const spec = overPaths.has(prim.path) ? "over" : "def";
381
- body.push(`${ind2}${spec} ${usdaString(primName(id))}`, `${ind2}{`);
382
- if (prim.kind === "aggregate") {
383
- body.push(
384
- `${ind3}custom string nomos:kind = "aggregate"`,
385
- `${ind3}custom string nomos:id = ${usdaString(prim.type)}`,
386
- );
387
- for (const [field, driver] of Object.entries(prim.schema)) {
388
- body.push(`${ind3}custom string nomos:driver:${fieldToken(field)} = ${usdaString(driverToken(driver))}`);
389
- }
390
- appendLines(body, relationLines(domainScope, aggById.get(prim.type), ind3));
391
- } else {
392
- body.push(
393
- `${ind3}custom string nomos:kind = "directive"`,
394
- `${ind3}custom string nomos:id = ${usdaString(id)}`,
395
- `${ind3}custom string nomos:target = ${usdaString(prim.target)}`,
396
- `${ind3}custom string nomos:marker = ${usdaString(prim.marker)}`,
397
- `${ind3}custom string[] nomos:requires = ${usdaStringArray(prim.requires)}`,
398
- );
399
- if (prim.reads !== undefined) {
400
- body.push(`${ind3}custom string[] nomos:reads = ${usdaStringArray(prim.reads)}`);
401
- }
402
- if (prim.emits !== undefined) {
403
- body.push("");
404
- appendLines(body, nativeValueLines("emits", prim.emits, ind3));
405
- }
406
- if (prim.secondaryOutputs !== undefined) {
407
- body.push("");
408
- appendLines(body, nativeValueLines("secondaryOutputs", prim.secondaryOutputs, ind3));
409
- }
410
- if (prim.payloadJsonSchema !== undefined) {
411
- body.push("");
412
- appendLines(body, nativeValueLines("payloadJsonSchema", prim.payloadJsonSchema, ind3));
413
- }
414
- }
415
- body.push(`${ind2}}`, ``);
416
- }
417
-
418
- for (const q of layer.queries ?? []) {
419
- body.push(
420
- `${ind2}def ${usdaString(primName(q.id))}`,
421
- `${ind2}{`,
422
- `${ind3}custom string nomos:kind = "query"`,
423
- `${ind3}custom string nomos:id = ${usdaString(q.id)}`,
424
- `${ind3}custom string[] nomos:key = ${usdaStringArray(q.key)}`,
425
- `${ind3}custom string nomos:returns = ${usdaString(q.returns)}`,
426
- `${ind2}}`,
427
- ``,
428
- );
429
- }
430
- for (const d of layer.deriveds ?? []) {
431
- body.push(
432
- `${ind2}def ${usdaString(primName(d.id))}`,
433
- `${ind2}{`,
434
- `${ind3}custom string nomos:kind = "derived"`,
435
- `${ind3}custom string nomos:id = ${usdaString(d.id)}`,
436
- `${ind3}custom string nomos:of = ${usdaString(d.of)}`,
437
- ``,
438
- );
439
- appendLines(body, nativeValueLines("returns", d.returns, ind3));
440
- body.push(
441
- `${ind2}}`,
442
- ``,
443
- );
444
- }
445
- for (const c of layer.combineds ?? []) {
446
- body.push(
447
- `${ind2}def ${usdaString(primName(c.id))}`,
448
- `${ind2}{`,
449
- `${ind3}custom string nomos:kind = "combined"`,
450
- `${ind3}custom string nomos:id = ${usdaString(c.id)}`,
451
- `${ind3}custom string nomos:of = ${usdaString(c.of)}`,
452
- `${ind3}custom string nomos:refField = ${usdaString(c.refField)}`,
453
- `${ind3}custom string nomos:readsFrom = ${usdaString(c.reads)}`,
454
- ``,
455
- );
456
- appendLines(body, nativeValueLines("returns", c.returns, ind3));
457
- body.push(
458
- `${ind2}}`,
459
- ``,
460
- );
461
- }
462
- // Drop the trailing blank line inside the scope.
463
- if (body[body.length - 1] === "") body.pop();
464
-
465
- const lines = [
466
- `#usda 1.0`,
467
- `(`,
468
- ` customLayerData = {`,
469
- ` string "nomos:format" = ${usdaString(NOMOS_USD_LAYER_FORMAT)}`,
470
- ` string "nomos:domain" = ${usdaString(domain)}`,
471
- ` string "nomos:module" = ${usdaString(moduleName)}`,
472
- ` }`,
473
- `)`,
474
- ``,
475
- `def Scope "Nomos"`,
476
- `{`,
477
- ` def Scope ${usdaString(primName(domain))}`,
478
- ` {`,
479
- ];
480
- // Stable ids + holon-owned manifests: native layer-level law values.
481
- appendLines(lines, domainLayerAttrLines(domain, layer, " "));
482
- lines.push(``);
483
- appendLines(lines, body);
484
- lines.push(
485
- ` }`,
486
- `}`,
487
- ``,
488
- );
489
- return lines.join("\n");
490
- }
491
-
492
- /**
493
- * The layered ROOT document: a `#usda 1.0` whose subLayers stack the module layers.
494
- * INPUT order is Nomos module order (weak → strong, later wins); USD's subLayers
495
- * list is STRONGEST-FIRST, so the list is emitted REVERSED. Same monoid, opposite
496
- * notation — the reversal IS the correspondence, recorded in the doc string.
497
- */
498
- export function layeredRootUsda(
499
- sublayerRelPathsWeakToStrong: readonly string[],
500
- composed?: UsdDocument,
501
- ): string {
502
- const strongestFirst = [...sublayerRelPathsWeakToStrong].reverse();
503
- const composedDomainOpinions: string[] = [];
504
- if (composed !== undefined && composed.layers.length > 0) {
505
- composedDomainOpinions.push(``, `over "Nomos"`, `{`);
506
- for (const layer of composed.layers) {
507
- const domain = layer.path.slice(layer.path.lastIndexOf("/") + 1);
508
- composedDomainOpinions.push(` over ${usdaString(primName(domain))}`, ` {`);
509
- appendLines(composedDomainOpinions, domainLayerAttrLines(domain, layer, " "));
510
- composedDomainOpinions.push(` }`);
511
- }
512
- composedDomainOpinions.push(`}`, ``);
513
- }
514
- const lines = [
515
- `#usda 1.0`,
516
- `(`,
517
- ` doc = """Nomos layered domain package. subLayers are STRONGEST-FIRST (USD layer-stack`,
518
- `order); the Nomos module list composes weak-to-strong (later wins) — the same`,
519
- `left-biased fold, opposite list notation. Flattening this stage (usdcat --flatten)`,
520
- `yields the SAME effective law as the flattened .package.usda emission."""`,
521
- ` customLayerData = {`,
522
- ` string "nomos:format" = ${usdaString(NOMOS_USD_LAYERED_PACKAGE_FORMAT)}`,
523
- ` }`,
524
- ` subLayers = [`,
525
- ];
526
- strongestFirst.forEach((p, index) => {
527
- lines.push(` @${p}@${index < strongestFirst.length - 1 ? "," : ""}`);
528
- });
529
- lines.push(
530
- ` ]`,
531
- `)`,
532
- );
533
- appendLines(lines, composedDomainOpinions);
534
- lines.push(``);
535
- return lines.join("\n");
536
- }
537
-
538
- export interface UsdPlanAsset {
539
- readonly sha256: string;
540
- readonly javascript: string;
541
- }
542
-
543
- export interface UsdPlanRoute {
544
- readonly selector: string;
545
- readonly planSha256: string;
546
- }
547
-
548
- /**
549
- * Emit the one self-contained stage stored on the ledger. Law remains ordinary USD prims and
550
- * attributes; executable implementation details are separate, content-addressed plan assets.
551
- * The route index is data, not another config format, and therefore composes/travels with the law.
552
- */
553
- export function nativePlanPackageUsda(
554
- packageName: string,
555
- document: UsdDocument,
556
- modules: readonly DomainModule[],
557
- plans: readonly UsdPlanAsset[],
558
- routes: readonly UsdPlanRoute[],
559
- ): string {
560
- const modulesByDomain = new Map(modules.map((module) => [module.domain ?? module.name, module] as const));
561
- const planByHash = new Map<string, UsdPlanAsset>();
562
- for (const plan of plans) {
563
- const actual = createHash("sha256").update(plan.javascript, "utf8").digest("hex");
564
- if (actual !== plan.sha256) {
565
- throw new Error(`usd-layers: plan '${plan.sha256}' content hashes to '${actual}'`);
566
- }
567
- planByHash.set(plan.sha256, plan);
568
- }
569
- for (const route of routes) {
570
- if (!planByHash.has(route.planSha256)) {
571
- throw new Error(`usd-layers: route '${route.selector}' names missing plan '${route.planSha256}'`);
572
- }
573
- }
574
-
575
- const domainBlocks: string[] = [];
576
- for (const layer of document.layers) {
577
- const domain = layer.path.slice(layer.path.lastIndexOf("/") + 1);
578
- const module = modulesByDomain.get(domain);
579
- const lines = usdaLayerText(domain, "composed", layer, module, new Set()).split("\n");
580
- const marker = ` def Scope ${usdaString(primName(domain))}`;
581
- const start = lines.indexOf(marker);
582
- if (start < 0) throw new Error(`usd-layers: could not extract native domain '${domain}'`);
583
- let balance = 0;
584
- let opened = false;
585
- let end = -1;
586
- for (let index = start; index < lines.length; index += 1) {
587
- const line = lines[index]!;
588
- for (const char of line) {
589
- if (char === "{") { balance += 1; opened = true; }
590
- if (char === "}") balance -= 1;
591
- }
592
- if (opened && balance === 0) { end = index; break; }
593
- }
594
- if (end < 0) throw new Error(`usd-layers: native domain '${domain}' has unbalanced scopes`);
595
- appendLines(domainBlocks, lines.slice(start, end + 1));
596
- domainBlocks.push("");
597
- }
598
- if (domainBlocks[domainBlocks.length - 1] === "") domainBlocks.pop();
599
-
600
- const sortedPlans = [...planByHash.values()].sort((a, b) => a.sha256.localeCompare(b.sha256));
601
- const sortedRoutes = [...routes].sort((a, b) => a.selector.localeCompare(b.selector));
602
- const runtime: string[] = [
603
- `def Scope "__NomosRuntime_7e9c"`,
604
- `{`,
605
- ` custom string nomos:kind = "runtime"`,
606
- ` custom string nomos:format = ${usdaString(NOMOS_USD_PLAN_PACKAGE_FORMAT)}`,
607
- ` custom string nomos:package = ${usdaString(packageName)}`,
608
- ``,
609
- ` def Scope "Routes"`,
610
- ` {`,
611
- ];
612
- sortedRoutes.forEach((route, index) => runtime.push(
613
- ` def Scope "r${String(index).padStart(6, "0")}"`,
614
- ` {`,
615
- ` custom string nomos:selector = ${usdaString(route.selector)}`,
616
- ` custom string nomos:plan = ${usdaString(route.planSha256)}`,
617
- ` }`,
618
- ``,
619
- ));
620
- if (runtime[runtime.length - 1] === "") runtime.pop();
621
- runtime.push(` }`, ``, ` def Scope "Plans"`, ` {`);
622
- sortedPlans.forEach((plan) => runtime.push(
623
- ` def Scope ${usdaString(`p_${plan.sha256}`)}`,
624
- ` {`,
625
- ` custom string nomos:sha256 = ${usdaString(plan.sha256)}`,
626
- ` custom string nomos:language = "javascript"`,
627
- ` custom string nomos:source = ${usdaString(plan.javascript)}`,
628
- ` }`,
629
- ``,
630
- ));
631
- if (runtime[runtime.length - 1] === "") runtime.pop();
632
- runtime.push(` }`, `}`);
633
-
634
- const lines = [
635
- `#usda 1.0`,
636
- `(`,
637
- ` customLayerData = {`,
638
- ` string "nomos:format" = ${usdaString(NOMOS_USD_PLAN_PACKAGE_FORMAT)}`,
639
- ` }`,
640
- `)`,
641
- ``,
642
- `def Scope "Nomos"`,
643
- `{`,
644
- ];
645
- appendLines(lines, domainBlocks);
646
- lines.push(
647
- `}`,
648
- ``,
649
- );
650
- appendLines(lines, runtime);
651
- lines.push(``);
652
- return lines.join("\n");
653
- }
654
-
655
- // ── the ordered opinion-map fold (flatten) ───────────────────────────────────────────
656
-
657
- /** Strongest-opinion-per-attribute merge of two prims at one path (over = stronger). */
658
- function foldOpinionPrim(base: UsdPrim, over: UsdPrim): UsdPrim {
659
- if (base.kind !== over.kind) {
660
- throw new Error(
661
- `usd-layers: opinion fold conflict at ${over.path}: a ${over.kind} prim cannot ` +
662
- `override a ${base.kind} prim (structural conflict, fail-closed).`,
663
- );
664
- }
665
- if (base.kind === "aggregate" && over.kind === "aggregate") {
666
- // Per-attribute: the stronger layer's field opinions win; weaker fields show
667
- // through. (Coincides with the module fold's wholesale replacement exactly when
668
- // the redeclaration is total — which `aggregate()` makes structural.)
669
- return {
670
- kind: "aggregate",
671
- path: over.path,
672
- type: over.type,
673
- schema: sortedSchema({ ...base.schema, ...over.schema }),
674
- };
675
- }
676
- if (base.kind === "directive" && over.kind === "directive") {
677
- // Per-attribute show-through, rebuilt in the canonical prim key order so the
678
- // flattened bytes match `encodeModuleToPrims` exactly.
679
- const reads = over.reads ?? base.reads;
680
- const emits = over.emits ?? base.emits;
681
- const secondaryOutputs = over.secondaryOutputs ?? base.secondaryOutputs;
682
- const payloadJsonSchema = over.payloadJsonSchema ?? base.payloadJsonSchema;
683
- return {
684
- kind: "directive",
685
- path: over.path,
686
- target: over.target,
687
- marker: over.marker,
688
- requires: over.requires,
689
- ...(reads !== undefined ? { reads } : {}),
690
- ...(emits !== undefined ? { emits } : {}),
691
- ...(secondaryOutputs !== undefined ? { secondaryOutputs } : {}),
692
- ...(payloadJsonSchema !== undefined ? { payloadJsonSchema } : {}),
693
- };
694
- }
695
- throw new Error(`usd-layers: unhandled prim kind at ${over.path}.`);
696
- }
697
-
698
- /** Fold id-keyed layer read declarations (later layer wins per id), sorted by id. */
699
- function foldById<T extends { readonly id: string }>(
700
- lists: readonly (readonly T[] | undefined)[],
701
- ): T[] | undefined {
702
- const byId = new Map<string, T>();
703
- for (const list of lists) for (const item of list ?? []) byId.set(item.id, item);
704
- if (byId.size === 0) return undefined;
705
- return [...byId.keys()].sort().map((id) => byId.get(id)!);
706
- }
707
-
708
- function foldByKey<T>(
709
- lists: readonly (readonly T[] | undefined)[],
710
- keyOf: (item: T) => string,
711
- ): T[] | undefined {
712
- const byKey = new Map<string, T>();
713
- for (const list of lists) for (const item of list ?? []) byKey.set(keyOf(item), item);
714
- if (byKey.size === 0) return undefined;
715
- return [...byKey.keys()].sort().map((key) => byKey.get(key)!);
716
- }
717
-
718
- function foldRecord<T>(
719
- records: readonly (Readonly<Record<string, T>> | undefined)[],
720
- ): Record<string, T> | undefined {
721
- const byKey = new Map<string, T>();
722
- for (const record of records) {
723
- if (record === undefined) continue;
724
- for (const [key, value] of Object.entries(record)) byKey.set(key, value);
725
- }
726
- if (byKey.size === 0) return undefined;
727
- const out: Record<string, T> = {};
728
- for (const key of [...byKey.keys()].sort()) out[key] = byKey.get(key)!;
729
- return out;
730
- }
731
-
732
- function foldIdentityManifest(
733
- stack: readonly UsdLayer[],
734
- stableIds: UsdLayer["nomosStableIds"],
735
- ): UsdLayer["nomosIdentityManifest"] {
736
- const manifests = stack
737
- .map((l) => l.nomosIdentityManifest)
738
- .filter((m): m is NonNullable<UsdLayer["nomosIdentityManifest"]> => m !== undefined);
739
- if (manifests.length === 0) return undefined;
740
- const domain = manifests[manifests.length - 1]!.domain;
741
- const workspaceInvariants = foldById(manifests.map((m) => m.workspaceInvariants));
742
- const queries = foldById(manifests.map((m) => m.queries));
743
- const counts = foldById(manifests.map((m) => m.counts));
744
- const spatials = foldById(manifests.map((m) => m.spatials));
745
- const sums = foldById(manifests.map((m) => m.sums));
746
- const deriveds = foldById(manifests.map((m) => m.deriveds));
747
- const combineds = foldById(manifests.map((m) => m.combineds));
748
- const mins = foldById(manifests.map((m) => m.mins));
749
- const maxes = foldById(manifests.map((m) => m.maxes));
750
- const orderedReads = foldById(manifests.map((m) => m.orderedReads));
751
- const workspaceTypes = foldById(manifests.map((m) => m.workspaceTypes));
752
- const homes = foldRecord(manifests.map((m) => m.homes));
753
- const routes = foldByKey(manifests.map((m) => m.routes), (r) => r.directive);
754
- const aggregatesById = new Map<string, NonNullable<UsdLayer["nomosIdentityManifest"]>["aggregates"][number]>();
755
- for (const manifest of manifests) {
756
- for (const aggregate of manifest.aggregates) aggregatesById.set(aggregate.id, aggregate);
757
- }
758
- // Aggregate namespace qualification happens after the canonical manifest has
759
- // sorted its bare business labels. Preserve that order here: sorting the final
760
- // `co2.Asset` spelling would incorrectly move every framework aggregate ahead
761
- // of the tenant model even though the laws are otherwise identical.
762
- const identityLabel = (id: string): string => id.slice(id.lastIndexOf(".") + 1);
763
- const aggregates = [...aggregatesById.values()].sort((a, b) => {
764
- const aLabel = identityLabel(a.id);
765
- const bLabel = identityLabel(b.id);
766
- return aLabel !== bLabel
767
- ? aLabel < bLabel ? -1 : 1
768
- : a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
769
- });
770
- return {
771
- domain,
772
- aggregates,
773
- directives: foldById(manifests.map((m) => m.directives)) ?? [],
774
- ...(stableIds !== undefined ? { stableIds } : {}),
775
- ...(workspaceInvariants !== undefined ? { workspaceInvariants } : {}),
776
- ...(queries !== undefined ? { queries } : {}),
777
- ...(counts !== undefined ? { counts } : {}),
778
- ...(spatials !== undefined ? { spatials } : {}),
779
- ...(sums !== undefined ? { sums } : {}),
780
- ...(deriveds !== undefined ? { deriveds } : {}),
781
- ...(combineds !== undefined ? { combineds } : {}),
782
- ...(mins !== undefined ? { mins } : {}),
783
- ...(maxes !== undefined ? { maxes } : {}),
784
- ...(orderedReads !== undefined ? { orderedReads } : {}),
785
- ...(workspaceTypes !== undefined ? { workspaceTypes } : {}),
786
- ...(homes !== undefined ? { homes } : {}),
787
- ...(routes !== undefined ? { routes } : {}),
788
- };
789
- }
790
-
791
- function foldReadProjection(stack: readonly UsdLayer[]): UsdLayer["nomosReadProjection"] {
792
- const projections = stack
793
- .map((l) => l.nomosReadProjection)
794
- .filter((p): p is NonNullable<UsdLayer["nomosReadProjection"]> => p !== undefined);
795
- if (projections.length === 0) return undefined;
796
- const aggregateFieldKinds = foldRecord(projections.map((p) => p.aggregateFieldKinds)) ?? {};
797
- const sums = foldById(projections.map((p) => p.sums));
798
- const mins = foldById(projections.map((p) => p.mins));
799
- const maxes = foldById(projections.map((p) => p.maxes));
800
- const orderedReads = foldById(projections.map((p) => p.orderedReads));
801
- return {
802
- aggregateFieldKinds,
803
- queries: foldById(projections.map((p) => p.queries)) ?? [],
804
- counts: foldById(projections.map((p) => p.counts)) ?? [],
805
- spatials: foldById(projections.map((p) => p.spatials)) ?? [],
806
- deriveds: foldById(projections.map((p) => p.deriveds)) ?? [],
807
- combineds: foldById(projections.map((p) => p.combineds)) ?? [],
808
- ...(sums !== undefined ? { sums } : {}),
809
- ...(mins !== undefined ? { mins } : {}),
810
- ...(maxes !== undefined ? { maxes } : {}),
811
- ...(orderedReads !== undefined ? { orderedReads } : {}),
812
- };
813
- }
814
-
815
- /**
816
- * THE ORDERED OPINION-MAP FOLD: flatten module layers (weak → strong within each
817
- * stage path) into the effective single-layer-per-domain document. Strongest
818
- * opinion per attribute; layer-level read declarations fold by id (later wins).
819
- * Output layers/prims sorted by path — the SAME canonical shape `emitUsd` emits, so
820
- * `JSON.stringify(flattenLayeredUsd(layers))` is directly byte-comparable with
821
- * `emitUsdJsonForModules([composedModule, ...])`. THAT byte-equality is the
822
- * homomorphism: flatten-after-lower == lower-after-compose.
823
- */
824
- export function flattenLayeredUsd(layers: readonly UsdLayer[]): UsdDocument {
825
- const groups = new Map<string, UsdLayer[]>();
826
- for (const layer of layers) {
827
- const group = groups.get(layer.path);
828
- if (group === undefined) groups.set(layer.path, [layer]);
829
- else group.push(layer);
830
- }
831
-
832
- const out: UsdLayer[] = [...groups.keys()].sort().map((path) => {
833
- const stack = groups.get(path)!;
834
- const prims = new Map<string, UsdPrim>();
835
- for (const layer of stack) {
836
- for (const prim of layer.prims) {
837
- const existing = prims.get(prim.path);
838
- prims.set(prim.path, existing === undefined ? prim : foldOpinionPrim(existing, prim));
839
- }
840
- }
841
- const queries = foldById<UsdQuery>(stack.map((l) => l.queries));
842
- const deriveds = foldById<UsdDerived>(stack.map((l) => l.deriveds));
843
- const combineds = foldById<UsdCombined>(stack.map((l) => l.combineds));
844
- // STABLE IDS (#58): fold per-module stableIds — domain sid from the latest
845
- // declaring layer (they coincide by construction: one domain, one mint scope),
846
- // aggregates merged later-wins per CURRENT name, SORTED keys (byte-determinism
847
- // against the composed emission's sorted-aggregate insertion order).
848
- let stableIds: UsdLayer["nomosStableIds"];
849
- {
850
- const aggs = new Map<string, NonNullable<UsdLayer["nomosStableIds"]>["aggregates"][string]>();
851
- let domainSid: string | undefined;
852
- for (const l of stack) {
853
- if (l.nomosStableIds === undefined) continue;
854
- domainSid = l.nomosStableIds.domain;
855
- for (const [name, a] of Object.entries(l.nomosStableIds.aggregates)) aggs.set(name, a);
856
- }
857
- if (domainSid !== undefined) {
858
- const aggregates: NonNullable<UsdLayer["nomosStableIds"]>["aggregates"] = {};
859
- for (const k of [...aggs.keys()].sort()) aggregates[k] = aggs.get(k)!;
860
- stableIds = { v: 1, domain: domainSid, aggregates };
861
- }
862
- }
863
- // THE STRUCTURAL INTERFACE (#72): fold per-module interfaces — directives, aggregates and their
864
- // declared cross-workspace read edges merge later-wins, with sorted keys (matching the composed
865
- // emission's sorted-key `domainInterface`). `foreignReads` is optional only at the final boundary;
866
- // dropping it while folding makes a layered package describe a weaker law than its module fold.
867
- let iface: UsdLayer["nomosInterface"];
868
- {
869
- type Dir = NonNullable<UsdLayer["nomosInterface"]>["directives"][string];
870
- type Agg = NonNullable<UsdLayer["nomosInterface"]>["aggregates"][string];
871
- type ForeignReads = NonNullable<NonNullable<UsdLayer["nomosInterface"]>["foreignReads"]>[string];
872
- const dirs = new Map<string, Dir>();
873
- const aggsI = new Map<string, Agg>();
874
- const foreignReads = new Map<string, ForeignReads>();
875
- let domainI: string | undefined;
876
- let domainSidI: string | undefined;
877
- for (const l of stack) {
878
- if (l.nomosInterface === undefined) continue;
879
- domainI = l.nomosInterface.domain;
880
- domainSidI = l.nomosInterface.domainSid;
881
- for (const [id, spec] of Object.entries(l.nomosInterface.directives)) dirs.set(id, spec);
882
- for (const [sid, spec] of Object.entries(l.nomosInterface.aggregates)) aggsI.set(sid, spec);
883
- for (const [id, edges] of Object.entries(l.nomosInterface.foreignReads ?? {})) foreignReads.set(id, edges);
884
- }
885
- if (domainI !== undefined) {
886
- const directivesRec: Record<string, Dir> = {};
887
- for (const k of [...dirs.keys()].sort()) directivesRec[k] = dirs.get(k)!;
888
- const aggregatesRec: Record<string, Agg> = {};
889
- for (const k of [...aggsI.keys()].sort()) aggregatesRec[k] = aggsI.get(k)!;
890
- const foreignReadsRec: Record<string, ForeignReads> = {};
891
- for (const k of [...foreignReads.keys()].sort()) foreignReadsRec[k] = foreignReads.get(k)!;
892
- iface = {
893
- v: 1,
894
- domain: domainI,
895
- ...(domainSidI !== undefined ? { domainSid: domainSidI } : {}),
896
- directives: directivesRec,
897
- aggregates: aggregatesRec,
898
- ...(foreignReads.size > 0 ? { foreignReads: foreignReadsRec } : {}),
899
- };
900
- }
901
- }
902
- const identityManifest = foldIdentityManifest(stack, stableIds);
903
- const readProjection = foldReadProjection(stack);
904
- // The captured-read gate (#58): queries + per-directive read sets merged
905
- // later-wins, SORTED keys (matching the composed emission).
906
- let readGate: UsdLayer["nomosReadGate"];
907
- {
908
- const q = new Map<string, { key: string[]; returns: string }>();
909
- const d = new Map<string, string[]>();
910
- for (const l of stack) {
911
- if (l.nomosReadGate === undefined) continue;
912
- for (const [id, spec] of Object.entries(l.nomosReadGate.queries)) q.set(id, spec);
913
- for (const [id, reads] of Object.entries(l.nomosReadGate.directives)) d.set(id, reads);
914
- }
915
- if (d.size > 0) {
916
- const queriesRec: Record<string, { key: string[]; returns: string }> = {};
917
- for (const k of [...q.keys()].sort()) queriesRec[k] = q.get(k)!;
918
- const directivesRec: Record<string, string[]> = {};
919
- for (const k of [...d.keys()].sort()) directivesRec[k] = d.get(k)!;
920
- readGate = { v: 1, queries: queriesRec, directives: directivesRec };
921
- }
922
- }
923
- // THE INVARIANT GATE: aggregate invariant presence is a set; named workspace
924
- // invariants are later-wins by id. Both are sorted exactly as the canonical emit.
925
- let invariantGate: UsdLayer["nomosInvariantGate"];
926
- {
927
- const aggregates = new Set<string>();
928
- const workspaceInvariants = new Map<string, { id: string; on: string }>();
929
- for (const layer of stack) {
930
- if (layer.nomosInvariantGate === undefined) continue;
931
- for (const aggregate of layer.nomosInvariantGate.aggregates ?? []) aggregates.add(aggregate);
932
- for (const invariant of layer.nomosInvariantGate.workspaceInvariants ?? []) {
933
- workspaceInvariants.set(invariant.id, invariant);
934
- }
935
- }
936
- if (aggregates.size > 0 || workspaceInvariants.size > 0) {
937
- invariantGate = {
938
- v: 1,
939
- ...(aggregates.size > 0 ? { aggregates: [...aggregates].sort() } : {}),
940
- ...(workspaceInvariants.size > 0
941
- ? { workspaceInvariants: [...workspaceInvariants.values()].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0) }
942
- : {}),
943
- };
944
- }
945
- }
946
- // The read-visibility gate (slice 5.1): per-aggregate required-read caps merged
947
- // later-wins, SORTED keys (matching the composed emission). Omit-when-empty.
948
- let readVisibility: UsdLayer["nomosReadVisibility"];
949
- {
950
- const a = new Map<string, { requires: string }>();
951
- for (const l of stack) {
952
- if (l.nomosReadVisibility === undefined) continue;
953
- for (const [type, spec] of Object.entries(l.nomosReadVisibility.aggregates)) a.set(type, spec);
954
- }
955
- if (a.size > 0) {
956
- const aggregatesRec: Record<string, { requires: string }> = {};
957
- for (const k of [...a.keys()].sort()) aggregatesRec[k] = a.get(k)!;
958
- readVisibility = { v: 1, aggregates: aggregatesRec };
959
- }
960
- }
961
- // The encryption gate (slice 2): per-aggregate encrypted-field sets merged later-wins,
962
- // SORTED keys (matching the composed emission). Omit-when-empty.
963
- let encryption: UsdLayer["nomosEncryption"];
964
- {
965
- const a = new Map<string, { scope: string; fields: string[]; delegatable: false }>();
966
- for (const l of stack) {
967
- if (l.nomosEncryption === undefined) continue;
968
- for (const [type, spec] of Object.entries(l.nomosEncryption.aggregates)) a.set(type, spec);
969
- }
970
- if (a.size > 0) {
971
- const aggregatesRec: Record<string, { scope: string; fields: string[]; delegatable: false }> = {};
972
- for (const k of [...a.keys()].sort()) aggregatesRec[k] = a.get(k)!;
973
- encryption = { v: 1, aggregates: aggregatesRec };
974
- }
975
- }
976
- // DECLARED IMPURE CAPABILITIES: compiler composition dedupes by task aggregate;
977
- // mirror that exact rule here, with the stronger layer winning and canonical
978
- // aggregate-id order. Omit-when-empty (the capability era rule).
979
- let capabilities: UsdLayer["nomosCapabilities"];
980
- {
981
- type Capability = NonNullable<UsdLayer["nomosCapabilities"]>["capabilities"][number];
982
- const byAggregate = new Map<string, Capability>();
983
- for (const layer of stack) {
984
- for (const capability of layer.nomosCapabilities?.capabilities ?? []) {
985
- byAggregate.set(capability.aggregateId, capability);
986
- }
987
- }
988
- if (byAggregate.size > 0) {
989
- capabilities = {
990
- v: 1,
991
- capabilities: [...byAggregate.values()].sort((a, b) =>
992
- a.aggregateId < b.aggregateId ? -1 : a.aggregateId > b.aggregateId ? 1 : 0,
993
- ),
994
- };
995
- }
996
- }
997
- // THE ATTRIBUTED-READS GATE (field_provenance.md): per-aggregate attributed-field
998
- // sets merged later-wins, SORTED keys (matching the composed emission). Omit-when-empty.
999
- let attributedGate: UsdLayer["nomosAttributedGate"];
1000
- {
1001
- const a = new Map<string, string[]>();
1002
- for (const l of stack) {
1003
- if (l.nomosAttributedGate === undefined) continue;
1004
- for (const [type, fields] of Object.entries(l.nomosAttributedGate.aggregates)) a.set(type, fields);
1005
- }
1006
- if (a.size > 0) {
1007
- const aggregatesRec: Record<string, string[]> = {};
1008
- for (const k of [...a.keys()].sort()) aggregatesRec[k] = a.get(k)!;
1009
- attributedGate = { v: 1, aggregates: aggregatesRec };
1010
- }
1011
- }
1012
- // THE RELATION SCHEMA (P1): per-type relation defs merged later-wins across the stack,
1013
- // SORTED keys (matching the composed emission). Omit-when-empty (the nomosRelations era rule).
1014
- let relations: UsdLayer["nomosRelations"];
1015
- {
1016
- const t = new Map<string, NonNullable<UsdLayer["nomosRelations"]>["types"][string]>();
1017
- for (const l of stack) {
1018
- if (l.nomosRelations === undefined) continue;
1019
- for (const [type, spec] of Object.entries(l.nomosRelations.types)) {
1020
- // RELATION-NAMESPACING SAFETY: after qualification, two domains' same bare type key under
1021
- // DISTINCT ids (`estate__workspace` vs `home__workspace`) and never reach here as the same key.
1022
- // A collision on the SAME canonical id can now only be a genuinely SHARED id (two layers
1023
- // contributing the same global type, or a future cross-domain shared type). Silent last-wins
1024
- // would DROP one layer's relations — so a collision on a key whose spec DIFFERS is a HARD ERROR
1025
- // (loud, not last-wins). An identical re-contribution (framework dedup) is fine.
1026
- const prior = t.get(type);
1027
- if (prior !== undefined && JSON.stringify(prior) !== JSON.stringify(spec)) {
1028
- throw new Error(
1029
- `nomosRelations: relation type "${type}" is contributed with DIFFERING definitions by two ` +
1030
- `layers — the qualified ids collide. A shared relation type must be identical across ` +
1031
- `domains (or namespaced per-domain). Refusing to silently last-wins.`,
1032
- );
1033
- }
1034
- t.set(type, spec);
1035
- }
1036
- }
1037
- if (t.size > 0) {
1038
- const typesRec: NonNullable<UsdLayer["nomosRelations"]>["types"] = {};
1039
- for (const k of [...t.keys()].sort()) typesRec[k] = t.get(k)!;
1040
- relations = { v: 1, types: typesRec };
1041
- }
1042
- }
1043
- // THE RELATION-NAMESPACE ERA KEY (relation-namespacing): the SORTED union of every layer's qualified
1044
- // type ids. Present iff ANY layer namespaced ≥1 type; omit-when-empty (byte-identical for global-only
1045
- // / relation-free packages). `delimiter` is the ratified colon-free `"__"`.
1046
- let relationNamespace: UsdLayer["nomosRelationNamespace"];
1047
- {
1048
- const ns = new Set<string>();
1049
- for (const l of stack) {
1050
- if (l.nomosRelationNamespace === undefined) continue;
1051
- for (const id of l.nomosRelationNamespace.types) ns.add(id);
1052
- }
1053
- if (ns.size > 0) relationNamespace = { v: 1, delimiter: "__", types: [...ns].sort() };
1054
- }
1055
- // THE READ-PROJECTION NAMESPACE ERA KEY (read-projection namespacing): the SORTED union of every
1056
- // layer's qualified aggregate type names. Present iff ANY layer namespaced >=1 aggregate;
1057
- // omit-when-empty (byte-identical for global-only packages). Delimiter is the ratified "__".
1058
- let readProjectionNamespace: UsdLayer["nomosReadProjectionNamespace"];
1059
- {
1060
- const ns = new Set<string>();
1061
- for (const l of stack) {
1062
- if (l.nomosReadProjectionNamespace === undefined) continue;
1063
- for (const id of l.nomosReadProjectionNamespace.types) ns.add(id);
1064
- }
1065
- if (ns.size > 0) readProjectionNamespace = { v: 1, delimiter: READ_PROJECTION_DELIMITER, types: [...ns].sort() };
1066
- }
1067
- // THE RELATION GATE (P4): directiveId → {relation, object} merged later-wins, SORTED. Omit-when-empty.
1068
- let relationGate: UsdLayer["nomosRelationGate"];
1069
- {
1070
- const r = new Map<string, { relation: string; object: string }>();
1071
- for (const l of stack) {
1072
- if (l.nomosRelationGate === undefined) continue;
1073
- for (const [dir, spec] of Object.entries(l.nomosRelationGate.requires)) r.set(dir, spec);
1074
- }
1075
- if (r.size > 0) {
1076
- const requiresRec: Record<string, { relation: string; object: string }> = {};
1077
- for (const k of [...r.keys()].sort()) requiresRec[k] = r.get(k)!;
1078
- relationGate = { v: 1, requires: requiresRec };
1079
- }
1080
- }
1081
- // THE ATTESTATION GATE (home_holons §3/§5): directiveId → owner-source merged later-wins across layers,
1082
- // SORTED keys. Omit-when-empty (era rule).
1083
- let attestationGate: UsdLayer["nomosAttestationGate"];
1084
- {
1085
- type AttSrc = NonNullable<UsdLayer["nomosAttestationGate"]>["directives"][string];
1086
- const a = new Map<string, AttSrc>();
1087
- for (const l of stack) {
1088
- if (l.nomosAttestationGate === undefined) continue;
1089
- for (const [id, src] of Object.entries(l.nomosAttestationGate.directives)) a.set(id, src);
1090
- }
1091
- if (a.size > 0) {
1092
- const rec: Record<string, AttSrc> = {};
1093
- for (const k of [...a.keys()].sort()) rec[k] = a.get(k)!;
1094
- attestationGate = { v: 1, directives: rec };
1095
- }
1096
- }
1097
- // THE DELEGATED-AUTHORITY GATE (delegated_authority_births.md): directiveId → delegation spec merged
1098
- // later-wins across layers, SORTED keys. Omit-when-empty (era rule).
1099
- let delegationGate: UsdLayer["nomosDelegationGate"];
1100
- {
1101
- type DelSrc = NonNullable<UsdLayer["nomosDelegationGate"]>["directives"][string];
1102
- const a = new Map<string, DelSrc>();
1103
- for (const l of stack) {
1104
- if (l.nomosDelegationGate === undefined) continue;
1105
- for (const [id, src] of Object.entries(l.nomosDelegationGate.directives)) a.set(id, src);
1106
- }
1107
- if (a.size > 0) {
1108
- const rec: Record<string, DelSrc> = {};
1109
- for (const k of [...a.keys()].sort()) rec[k] = a.get(k)!;
1110
- delegationGate = { v: 1, directives: rec };
1111
- }
1112
- }
1113
- // THE ATTESTED-READ GATE (slice B): sources + per-directive edges merged later-wins across
1114
- // layers, SORTED keys (matching the composed emission). Omit-when-empty (era rule).
1115
- let attestedReadGate: UsdLayer["nomosAttestedReadGate"];
1116
- {
1117
- type Gate = NonNullable<UsdLayer["nomosAttestedReadGate"]>;
1118
- type SrcSpec = Gate["sources"][string];
1119
- type PolSpec = NonNullable<Gate["policies"]>[string];
1120
- type EdgeList = Gate["directives"][string];
1121
- const srcs = new Map<string, SrcSpec>();
1122
- const pols = new Map<string, PolSpec>();
1123
- const dirs = new Map<string, EdgeList>();
1124
- for (const l of stack) {
1125
- if (l.nomosAttestedReadGate === undefined) continue;
1126
- for (const [ws, spec] of Object.entries(l.nomosAttestedReadGate.sources)) srcs.set(ws, spec);
1127
- for (const [id, spec] of Object.entries(l.nomosAttestedReadGate.policies ?? {})) pols.set(id, spec);
1128
- for (const [id, edges] of Object.entries(l.nomosAttestedReadGate.directives)) dirs.set(id, edges);
1129
- }
1130
- if (dirs.size > 0) {
1131
- const sourcesRec: Record<string, SrcSpec> = {};
1132
- for (const k of [...srcs.keys()].sort()) sourcesRec[k] = srcs.get(k)!;
1133
- const policiesRec: Record<string, PolSpec> = {};
1134
- for (const k of [...pols.keys()].sort()) policiesRec[k] = pols.get(k)!;
1135
- const directivesRec: Record<string, EdgeList> = {};
1136
- for (const k of [...dirs.keys()].sort()) directivesRec[k] = dirs.get(k)!;
1137
- attestedReadGate = {
1138
- v: 1,
1139
- sources: sourcesRec,
1140
- ...(Object.keys(policiesRec).length > 0 ? { policies: policiesRec } : {}),
1141
- directives: directivesRec,
1142
- };
1143
- }
1144
- }
1145
- // THE BIRTH GATE (G3): the SORTED union of every layer's `.births()` directive ids. Omit-when-empty
1146
- // (era rule). The set is the contract; order is incidental (sorted for byte-determinism).
1147
- let birthGate: UsdLayer["nomosBirthGate"];
1148
- {
1149
- const b = new Set<string>();
1150
- for (const l of stack) {
1151
- if (l.nomosBirthGate === undefined) continue;
1152
- for (const id of l.nomosBirthGate.directives) b.add(id);
1153
- }
1154
- if (b.size > 0) birthGate = { v: 1, directives: [...b].sort() };
1155
- }
1156
- // THE BIRTH-RECORD GATE (metered birth, A1): present iff ANY layer declares it (once declared,
1157
- // declared). Carry the FIRST layer's aggregate/prefix verbatim (the framework constants — do NOT
1158
- // reconstruct). Omit-when-absent (era rule).
1159
- let birthRecordGate: UsdLayer["nomosBirthRecordGate"];
1160
- for (const l of stack) {
1161
- if (l.nomosBirthRecordGate !== undefined) {
1162
- birthRecordGate = l.nomosBirthRecordGate;
1163
- break;
1164
- }
1165
- }
1166
- // THE PEER-SOVEREIGN ROOT-OF-TRUST: the era key is present iff ANY layer declares it (once required,
1167
- // required). Carry the FIRST layer's declared owner/cert locators verbatim (preserve them — do NOT
1168
- // reconstruct {v:1}, which would drop the locators the kernel reads). Omit-when-absent (era rule).
1169
- let parentAttestedBirth: UsdLayer["nomosParentAttestedBirth"];
1170
- for (const l of stack) {
1171
- if (l.nomosParentAttestedBirth !== undefined) {
1172
- parentAttestedBirth = l.nomosParentAttestedBirth;
1173
- break;
1174
- }
1175
- }
1176
- // THE KEYLESS VERIFIED-FIRST-DEVICE GATE: directiveId → payload field mapping, merged later-wins across
1177
- // layers, SORTED keys (mirrors the attestation gate). Omit-when-empty (era rule).
1178
- let verifiedFirstEnroll: UsdLayer["nomosVerifiedFirstEnroll"];
1179
- {
1180
- type VfeSrc = NonNullable<UsdLayer["nomosVerifiedFirstEnroll"]>["directives"][string];
1181
- const v = new Map<string, VfeSrc>();
1182
- for (const l of stack) {
1183
- if (l.nomosVerifiedFirstEnroll === undefined) continue;
1184
- for (const [id, src] of Object.entries(l.nomosVerifiedFirstEnroll.directives)) v.set(id, src);
1185
- }
1186
- if (v.size > 0) {
1187
- const rec: Record<string, VfeSrc> = {};
1188
- for (const k of [...v.keys()].sort()) rec[k] = v.get(k)!;
1189
- verifiedFirstEnroll = { v: 1, directives: rec };
1190
- }
1191
- }
1192
- // LAWFUL RECOVERY: the single `nomosEraCertGate` (later-wins across the stack — one certifyEra verb).
1193
- let eraCertGate: UsdLayer["nomosEraCertGate"];
1194
- for (const l of stack) {
1195
- if (l.nomosEraCertGate !== undefined) eraCertGate = l.nomosEraCertGate;
1196
- }
1197
- // FIRST-CLASS VALUE OBJECTS (+ the VO↔field usage pointers): descriptors merged
1198
- // later-wins per NAME, usages later-wins per (aggregate, field), both SORTED —
1199
- // so the layered fold stays byte-homomorphic with `emitUsd`'s composed emission.
1200
- // Omit-when-empty (the era rule); `usages` omitted when none (0.33.0 tolerance).
1201
- let valueObjects: UsdLayer["nomosValueObjects"];
1202
- {
1203
- type VoDesc = NonNullable<UsdLayer["nomosValueObjects"]>["valueObjects"][number];
1204
- type VoUse = NonNullable<NonNullable<UsdLayer["nomosValueObjects"]>["usages"]>[number];
1205
- const byName = new Map<string, VoDesc>();
1206
- const byField = new Map<string, VoUse>();
1207
- for (const l of stack) {
1208
- if (l.nomosValueObjects === undefined) continue;
1209
- for (const d of l.nomosValueObjects.valueObjects) byName.set(d.name, d);
1210
- for (const u of l.nomosValueObjects.usages ?? []) byField.set(`${u.aggregate}\u0000${u.field}`, u);
1211
- }
1212
- if (byName.size > 0) {
1213
- const descs = [...byName.values()].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
1214
- const uses = [...byField.values()].sort((a, b) =>
1215
- a.aggregate !== b.aggregate ? (a.aggregate < b.aggregate ? -1 : 1) : a.field < b.field ? -1 : a.field > b.field ? 1 : 0,
1216
- );
1217
- valueObjects = { v: 1, valueObjects: descs, ...(uses.length > 0 ? { usages: uses } : {}) };
1218
- }
1219
- }
1220
- let foreignRefs: UsdLayer["nomosForeignRefs"];
1221
- for (const l of stack) { if (l.nomosForeignRefs !== undefined) foreignRefs = l.nomosForeignRefs; }
1222
- // DOMAIN CONTRACTS: union the per-layer contract descriptors by id (later-wins on the shape),
1223
- // sorted by id — byte-identical to the composed emit (which sorts contracts by id). implementedBy
1224
- // is unioned across layers so a contract declared in one module and implemented in a same-layer
1225
- // aggregate stays intact. Omit-when-empty.
1226
- let contracts: UsdLayer["nomosContracts"];
1227
- {
1228
- type C = NonNullable<UsdLayer["nomosContracts"]>["contracts"][number];
1229
- const byId = new Map<string, C>();
1230
- const implById = new Map<string, Set<string>>();
1231
- for (const l of stack) {
1232
- if (l.nomosContracts === undefined) continue;
1233
- for (const c of l.nomosContracts.contracts) {
1234
- byId.set(c.id, c);
1235
- const s = implById.get(c.id) ?? new Set<string>();
1236
- for (const a of c.implementedBy) s.add(a);
1237
- implById.set(c.id, s);
1238
- }
1239
- }
1240
- if (byId.size > 0) {
1241
- contracts = {
1242
- v: 1,
1243
- contracts: [...byId.keys()].sort().map((id) => ({
1244
- ...byId.get(id)!,
1245
- implementedBy: [...(implById.get(id) ?? [])].sort(),
1246
- })),
1247
- };
1248
- }
1249
- }
1250
- // THE FLOW GATE: a stronger directive replaces the weaker declaration wholesale, so its
1251
- // harvested endorsement flows must do the same (including replacing them with none). Other
1252
- // flow rows union by their stable site. Both halves retain emitUsd's canonical sort order.
1253
- let flowGate: UsdLayer["nomosFlowGate"];
1254
- {
1255
- type Endorse = NonNullable<NonNullable<UsdLayer["nomosFlowGate"]>["endorse"]>[number];
1256
- type Declassify = NonNullable<NonNullable<UsdLayer["nomosFlowGate"]>["declassify"]>[number];
1257
- const endorseByDirective = new Map<string, Endorse[]>();
1258
- const declassifyBySite = new Map<string, Declassify>();
1259
- for (const layer of stack) {
1260
- const touchedDirectives = new Set(
1261
- layer.nomosIdentityManifest?.directives.map((directive) => directive.id) ?? [],
1262
- );
1263
- for (const directiveId of touchedDirectives) endorseByDirective.delete(directiveId);
1264
- for (const row of layer.nomosFlowGate?.endorse ?? []) {
1265
- const rows = endorseByDirective.get(row.directiveId) ?? [];
1266
- rows.push(row);
1267
- endorseByDirective.set(row.directiveId, rows);
1268
- }
1269
- for (const row of layer.nomosFlowGate?.declassify ?? []) declassifyBySite.set(row.site, row);
1270
- }
1271
- const declassify = [...declassifyBySite.values()].sort((a, b) =>
1272
- a.site < b.site ? -1 : a.site > b.site ? 1 : 0,
1273
- );
1274
- const endorse = [...endorseByDirective.values()].flat().sort((a, b) =>
1275
- a.directiveId !== b.directiveId ? (a.directiveId < b.directiveId ? -1 : 1)
1276
- : a.sink !== b.sink ? (a.sink < b.sink ? -1 : 1)
1277
- : a.facet < b.facet ? -1 : a.facet > b.facet ? 1 : 0,
1278
- );
1279
- if (declassify.length > 0 || endorse.length > 0) {
1280
- flowGate = {
1281
- v: 1,
1282
- ...(declassify.length > 0 ? { declassify } : {}),
1283
- ...(endorse.length > 0 ? { endorse } : {}),
1284
- };
1285
- }
1286
- }
1287
- return {
1288
- path,
1289
- prims: [...prims.keys()].sort().map((p) => prims.get(p)!),
1290
- ...(identityManifest !== undefined ? { nomosIdentityManifest: identityManifest } : {}),
1291
- ...(readProjection !== undefined ? { nomosReadProjection: readProjection } : {}),
1292
- ...(queries !== undefined ? { queries } : {}),
1293
- ...(deriveds !== undefined ? { deriveds } : {}),
1294
- ...(combineds !== undefined ? { combineds } : {}),
1295
- ...(stableIds !== undefined ? { nomosStableIds: stableIds } : {}),
1296
- ...(iface !== undefined ? { nomosInterface: iface } : {}),
1297
- ...(readGate !== undefined ? { nomosReadGate: readGate } : {}),
1298
- ...(invariantGate !== undefined ? { nomosInvariantGate: invariantGate } : {}),
1299
- ...(readVisibility !== undefined ? { nomosReadVisibility: readVisibility } : {}),
1300
- ...(encryption !== undefined ? { nomosEncryption: encryption } : {}),
1301
- ...(capabilities !== undefined ? { nomosCapabilities: capabilities } : {}),
1302
- ...(attributedGate !== undefined ? { nomosAttributedGate: attributedGate } : {}),
1303
- ...(relations !== undefined ? { nomosRelations: relations } : {}),
1304
- ...(relationNamespace !== undefined ? { nomosRelationNamespace: relationNamespace } : {}),
1305
- ...(readProjectionNamespace !== undefined ? { nomosReadProjectionNamespace: readProjectionNamespace } : {}),
1306
- ...(relationGate !== undefined ? { nomosRelationGate: relationGate } : {}),
1307
- ...(attestationGate !== undefined ? { nomosAttestationGate: attestationGate } : {}),
1308
- ...(delegationGate !== undefined ? { nomosDelegationGate: delegationGate } : {}),
1309
- ...(attestedReadGate !== undefined ? { nomosAttestedReadGate: attestedReadGate } : {}),
1310
- ...(birthGate !== undefined ? { nomosBirthGate: birthGate } : {}),
1311
- ...(birthRecordGate !== undefined ? { nomosBirthRecordGate: birthRecordGate } : {}),
1312
- ...(parentAttestedBirth !== undefined ? { nomosParentAttestedBirth: parentAttestedBirth } : {}),
1313
- ...(verifiedFirstEnroll !== undefined ? { nomosVerifiedFirstEnroll: verifiedFirstEnroll } : {}),
1314
- ...(eraCertGate !== undefined ? { nomosEraCertGate: eraCertGate } : {}),
1315
- ...(valueObjects !== undefined ? { nomosValueObjects: valueObjects } : {}),
1316
- ...(foreignRefs !== undefined ? { nomosForeignRefs: foreignRefs } : {}),
1317
- ...(contracts !== undefined ? { nomosContracts: contracts } : {}),
1318
- ...(flowGate !== undefined ? { nomosFlowGate: flowGate } : {}),
1319
- };
1320
- });
1321
-
1322
- // Apply the SAME cross-layer shared-relation composition + guard the composed emission
1323
- // (`usd.ts::emitUsd`) applies, so the layered fold stays byte-homomorphic with it (a divergent
1324
- // shared global type refuses identically on both paths).
1325
- return { layers: composeSharedRelationTypes(out) };
1326
- }
1327
-
1328
- // ── usda → IR (the round-trip parser for the pxr/usdcat conformance lane) ──────────
1329
-
1330
- interface ParsedPrim {
1331
- /** Sanitized prim path, e.g. `/Nomos/guestbook/GuestbookEntry`. */
1332
- readonly path: string;
1333
- readonly attrs: Map<string, string | string[] | number | boolean>;
1334
- }
1335
-
1336
- const ATTR_STRING = /^custom\s+string\s+([\w:]+)\s*=\s*"((?:[^"\\]|\\.)*)"$/;
1337
- const ATTR_STRING_ARRAY = /^custom\s+string\[\]\s+([\w:]+)\s*=\s*\[(.*)\]$/;
1338
- const ATTR_NUMBER = /^custom\s+(?:double|int64)\s+([\w:]+)\s*=\s*(-?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)$/;
1339
- const ATTR_BOOL = /^custom\s+bool\s+([\w:]+)\s*=\s*(true|false|0|1)$/;
1340
- const PRIM_HEADER = /^(?:def|over|class)(?:\s+\w+)?\s+"([^"]+)"/;
1341
-
1342
- function unescapeUsda(s: string): string {
1343
- try {
1344
- return JSON.parse(`"${s}"`) as string;
1345
- } catch {
1346
- throw new Error("usd-layers: invalid escaped USDA string");
1347
- }
1348
- }
1349
-
1350
- function parseStringArrayItems(inner: string): string[] {
1351
- const items: string[] = [];
1352
- const re = /"((?:[^"\\]|\\.)*)"/g;
1353
- let m: RegExpExecArray | null;
1354
- while ((m = re.exec(inner)) !== null) items.push(unescapeUsda(m[1]!));
1355
- return items;
1356
- }
1357
-
1358
- /**
1359
- * A deliberately SMALL usda reader for the documents THIS module emits (and their
1360
- * usdcat-flattened images): prim tree + `custom string` / `custom string[]`
1361
- * attributes. Relationships and unknown lines are ignored (decoration). Not a
1362
- * general USD parser — the conformance lane's inverse image, nothing more.
1363
- */
1364
- function parseUsdaPrims(text: string): ParsedPrim[] {
1365
- const prims: ParsedPrim[] = [];
1366
- const stack: ParsedPrim[] = [];
1367
- let pending: string | undefined;
1368
- let inLayerMeta = false;
1369
-
1370
- for (const raw of text.split("\n")) {
1371
- const line = raw.trim();
1372
- if (stack.length === 0 && pending === undefined) {
1373
- if (line === "(") {
1374
- inLayerMeta = true;
1375
- continue;
1376
- }
1377
- if (inLayerMeta) {
1378
- if (line === ")") inLayerMeta = false;
1379
- continue;
1380
- }
1381
- }
1382
- const header = PRIM_HEADER.exec(line);
1383
- if (header !== null) {
1384
- pending = header[1]!;
1385
- // Same-line `{` (not emitted here, but tolerated).
1386
- if (!line.endsWith("{")) continue;
1387
- }
1388
- if (line === "{" || (header !== null && line.endsWith("{"))) {
1389
- const parentPath = stack.length > 0 ? stack[stack.length - 1]!.path : "";
1390
- const prim: ParsedPrim = {
1391
- path: `${parentPath}/${pending ?? "?"}`,
1392
- attrs: new Map(),
1393
- };
1394
- pending = undefined;
1395
- stack.push(prim);
1396
- prims.push(prim);
1397
- continue;
1398
- }
1399
- if (line === "}") {
1400
- stack.pop();
1401
- continue;
1402
- }
1403
- if (stack.length === 0) continue;
1404
- const top = stack[stack.length - 1]!;
1405
- const arr = ATTR_STRING_ARRAY.exec(line);
1406
- if (arr !== null) {
1407
- top.attrs.set(arr[1]!, parseStringArrayItems(arr[2]!));
1408
- continue;
1409
- }
1410
- const str = ATTR_STRING.exec(line);
1411
- if (str !== null) {
1412
- top.attrs.set(str[1]!, unescapeUsda(str[2]!));
1413
- continue;
1414
- }
1415
- const number = ATTR_NUMBER.exec(line);
1416
- if (number !== null) {
1417
- top.attrs.set(number[1]!, Number(number[2]!));
1418
- continue;
1419
- }
1420
- const bool = ATTR_BOOL.exec(line);
1421
- if (bool !== null) top.attrs.set(bool[1]!, bool[2] === "true" || bool[2] === "1");
1422
- }
1423
- return prims;
1424
- }
1425
-
1426
- /** Decode one content-addressed native USD value tree immediately beneath `parentPath`. */
1427
- function nativeValueAt(prims: readonly ParsedPrim[], parentPath: string, name: string): unknown | undefined {
1428
- const depth = (path: string) => path.split("/").length;
1429
- const parent = prims.find((prim) => prim.path === parentPath);
1430
- const hash = parent?.attrs.get(`nomos:value:${name}`);
1431
- if (hash === undefined) return undefined;
1432
- if (typeof hash !== "string" || !/^[0-9a-f]{64}$/.test(hash)) {
1433
- throw new Error(`usd-layers: native value '${name}' has an invalid content address`);
1434
- }
1435
- const rootPath = `${parentPath}/__nms_${primName(name)}_${hash}`;
1436
- const root = prims.find((prim) => prim.path === rootPath);
1437
- if (root === undefined) {
1438
- throw new Error(`usd-layers: native value '${name}' points to missing content '${hash}'`);
1439
- }
1440
- const descendants = prims.filter((prim) => prim.path === rootPath || prim.path.startsWith(`${rootPath}/`));
1441
- const directChildren = (path: string): ParsedPrim[] => descendants.filter((candidate) =>
1442
- candidate.path.startsWith(`${path}/`) && depth(candidate.path) === depth(path) + 1);
1443
- const decodeKey = (hex: string, path: string): string => {
1444
- if (!/^(?:[0-9a-f]{2})*$/.test(hex)) {
1445
- throw new Error(`usd-layers: native object child '${path}' has an invalid key`);
1446
- }
1447
- return Buffer.from(hex, "hex").toString("utf8");
1448
- };
1449
- type NativeEntry = { readonly index: number; readonly key?: string; readonly value: unknown };
1450
- const decode = (prim: ParsedPrim): unknown => {
1451
- const kind = prim.attrs.get("nomos:containerKind");
1452
- if (kind !== "scalar" && kind !== "array" && kind !== "object") {
1453
- throw new Error(`usd-layers: native value container '${prim.path}' has no valid kind`);
1454
- }
1455
- const entries: NativeEntry[] = [];
1456
- for (const [attr, value] of prim.attrs) {
1457
- const match = /^nomos:(sv|bv|nv|zv):i(\d{6})(?::k([0-9a-f]*))?$/.exec(attr);
1458
- if (match === null) continue;
1459
- const scalarKind = match[1]!;
1460
- const key = match[3] === undefined ? undefined : decodeKey(match[3], prim.path);
1461
- const scalar = scalarKind === "zv" ? null : value;
1462
- if ((scalarKind === "sv" && typeof scalar !== "string") ||
1463
- (scalarKind === "bv" && typeof scalar !== "boolean") ||
1464
- (scalarKind === "nv" && typeof scalar !== "number") ||
1465
- (scalarKind === "zv" && value !== true)) {
1466
- throw new Error(`usd-layers: native scalar '${attr}' has the wrong USD type`);
1467
- }
1468
- entries.push({ index: Number(match[2]!), ...(key === undefined ? {} : { key }), value: scalar });
1469
- }
1470
- for (const child of directChildren(prim.path)) {
1471
- const childName = child.path.slice(child.path.lastIndexOf("/") + 1);
1472
- const match = /^i(\d{6})(?:_k([0-9a-f]*))?$/.exec(childName);
1473
- if (match === null) {
1474
- throw new Error(`usd-layers: native child '${child.path}' has an invalid position`);
1475
- }
1476
- const key = match[2] === undefined ? undefined : decodeKey(match[2], child.path);
1477
- entries.push({ index: Number(match[1]!), ...(key === undefined ? {} : { key }), value: decode(child) });
1478
- }
1479
- entries.sort((a, b) => a.index - b.index);
1480
- if (entries.some((entry, index) => entry.index !== index)) {
1481
- throw new Error(`usd-layers: native value container '${prim.path}' has missing or duplicate positions`);
1482
- }
1483
- if (kind === "scalar") {
1484
- if (entries.length !== 1 || entries[0]!.key !== undefined) {
1485
- throw new Error(`usd-layers: native scalar container '${prim.path}' must contain exactly one unkeyed value`);
1486
- }
1487
- return entries[0]!.value;
1488
- }
1489
- if (kind === "array") {
1490
- if (entries.some((entry) => entry.key !== undefined)) {
1491
- throw new Error(`usd-layers: native array container '${prim.path}' contains an object key`);
1492
- }
1493
- return entries.map((entry) => entry.value);
1494
- }
1495
- {
1496
- const out: Record<string, unknown> = {};
1497
- for (const entry of entries) {
1498
- if (entry.key === undefined) throw new Error(`usd-layers: native object container '${prim.path}' contains an unkeyed value`);
1499
- out[entry.key] = entry.value;
1500
- }
1501
- return out;
1502
- }
1503
- };
1504
- return decode(root);
1505
- }
1506
-
1507
- function attrString(prim: ParsedPrim, name: string): string {
1508
- const v = prim.attrs.get(name);
1509
- if (typeof v !== "string") {
1510
- throw new Error(`usd-layers: prim ${prim.path} is missing string attribute ${name}`);
1511
- }
1512
- return v;
1513
- }
1514
-
1515
- function attrStringArray(prim: ParsedPrim, name: string): string[] {
1516
- const v = prim.attrs.get(name);
1517
- if (!Array.isArray(v)) {
1518
- throw new Error(`usd-layers: prim ${prim.path} is missing string[] attribute ${name}`);
1519
- }
1520
- return v;
1521
- }
1522
-
1523
- /**
1524
- * Reconstruct the IR `UsdDocument` from usda text this module emitted — either ONE
1525
- * module layer or a usdcat-FLATTENED stage. Domains are identified by their scope
1526
- * prim (`nomos:kind = "domain"`); every child prim rebuilds from its `nomos:*`
1527
- * attributes into the canonical prim shape (so canonicalJson comparison with
1528
- * `emitUsd` / `flattenLayeredUsd` compares law rather than USD formatting).
1529
- */
1530
- export function parseUsdaDocument(text: string): UsdDocument {
1531
- const prims = parseUsdaPrims(text);
1532
- const domainByScope = new Map<string, string>();
1533
- const identityManifestByDomain = new Map<string, UsdLayer["nomosIdentityManifest"]>();
1534
- const readProjectionByDomain = new Map<string, UsdLayer["nomosReadProjection"]>();
1535
- const stableIdsByDomain = new Map<string, UsdLayer["nomosStableIds"]>();
1536
- const interfaceByDomain = new Map<string, UsdLayer["nomosInterface"]>();
1537
- const readGateByDomain = new Map<string, UsdLayer["nomosReadGate"]>();
1538
- const invariantGateByDomain = new Map<string, UsdLayer["nomosInvariantGate"]>();
1539
- const readVisibilityByDomain = new Map<string, UsdLayer["nomosReadVisibility"]>();
1540
- const encryptionByDomain = new Map<string, UsdLayer["nomosEncryption"]>();
1541
- const capabilitiesByDomain = new Map<string, UsdLayer["nomosCapabilities"]>();
1542
- const attributedGateByDomain = new Map<string, UsdLayer["nomosAttributedGate"]>();
1543
- const relationsByDomain = new Map<string, UsdLayer["nomosRelations"]>();
1544
- const relationGateByDomain = new Map<string, UsdLayer["nomosRelationGate"]>();
1545
- const relationNamespaceByDomain = new Map<string, UsdLayer["nomosRelationNamespace"]>();
1546
- const readProjectionNamespaceByDomain = new Map<string, UsdLayer["nomosReadProjectionNamespace"]>();
1547
- const attestationGateByDomain = new Map<string, UsdLayer["nomosAttestationGate"]>();
1548
- const delegationGateByDomain = new Map<string, UsdLayer["nomosDelegationGate"]>();
1549
- const attestedReadGateByDomain = new Map<string, UsdLayer["nomosAttestedReadGate"]>();
1550
- const birthGateByDomain = new Map<string, UsdLayer["nomosBirthGate"]>();
1551
- const birthRecordGateByDomain = new Map<string, UsdLayer["nomosBirthRecordGate"]>();
1552
- const parentAttestedBirthByDomain = new Map<string, UsdLayer["nomosParentAttestedBirth"]>();
1553
- const verifiedFirstEnrollByDomain = new Map<string, UsdLayer["nomosVerifiedFirstEnroll"]>();
1554
- const eraCertGateByDomain = new Map<string, UsdLayer["nomosEraCertGate"]>();
1555
- const valueObjectsByDomain = new Map<string, UsdLayer["nomosValueObjects"]>();
1556
- const foreignRefsByDomain = new Map<string, UsdLayer["nomosForeignRefs"]>();
1557
- const contractsByDomain = new Map<string, UsdLayer["nomosContracts"]>();
1558
- const flowGateByDomain = new Map<string, UsdLayer["nomosFlowGate"]>();
1559
- for (const p of prims) {
1560
- if (p.attrs.get("nomos:kind") === "domain") {
1561
- const domain = attrString(p, "nomos:domain");
1562
- domainByScope.set(p.path, domain);
1563
- // Structured law is native USD; no embedded serialization has an alternate vote.
1564
- const native = (name: string): unknown | undefined => nativeValueAt(prims, p.path, name);
1565
- const set = <T>(name: string, target: Map<string, T>): void => {
1566
- const value = native(name);
1567
- if (value !== undefined) target.set(domain, value as T);
1568
- };
1569
- set("identityManifest", identityManifestByDomain); set("readProjection", readProjectionByDomain);
1570
- set("stableIds", stableIdsByDomain); set("interface", interfaceByDomain); set("readGate", readGateByDomain);
1571
- set("invariantGate", invariantGateByDomain); set("readVisibility", readVisibilityByDomain);
1572
- set("encryption", encryptionByDomain); set("capabilities", capabilitiesByDomain);
1573
- set("attributedGate", attributedGateByDomain); set("relations", relationsByDomain);
1574
- set("relationGate", relationGateByDomain); set("relationNamespace", relationNamespaceByDomain);
1575
- set("readProjectionNamespace", readProjectionNamespaceByDomain); set("attestationGate", attestationGateByDomain);
1576
- set("delegationGate", delegationGateByDomain); set("attestedReadGate", attestedReadGateByDomain);
1577
- set("birthGate", birthGateByDomain); set("birthRecordGate", birthRecordGateByDomain);
1578
- set("parentAttestedBirth", parentAttestedBirthByDomain); set("verifiedFirstEnroll", verifiedFirstEnrollByDomain);
1579
- set("eraCertGate", eraCertGateByDomain); set("valueObjects", valueObjectsByDomain);
1580
- set("foreignRefs", foreignRefsByDomain); set("contracts", contractsByDomain); set("flowGate", flowGateByDomain);
1581
- }
1582
- }
1583
-
1584
- interface Bucket {
1585
- prims: UsdPrim[];
1586
- queries: UsdQuery[];
1587
- deriveds: UsdDerived[];
1588
- combineds: UsdCombined[];
1589
- }
1590
- const buckets = new Map<string, Bucket>();
1591
- const bucketFor = (domain: string): Bucket => {
1592
- let b = buckets.get(domain);
1593
- if (b === undefined) {
1594
- b = { prims: [], queries: [], deriveds: [], combineds: [] };
1595
- buckets.set(domain, b);
1596
- }
1597
- return b;
1598
- };
1599
-
1600
- for (const p of prims) {
1601
- const kind = p.attrs.get("nomos:kind");
1602
- if (typeof kind !== "string" || kind === "domain") continue;
1603
- // A self-contained runtime package also carries the obscure framework-owned plan index at a
1604
- // separate root. It is USD, but it is not domain law and must not be projected into the IR.
1605
- if (!p.path.startsWith("/Nomos/")) continue;
1606
- const scopePath = p.path.slice(0, p.path.lastIndexOf("/"));
1607
- const domain = domainByScope.get(scopePath);
1608
- if (domain === undefined) {
1609
- throw new Error(`usd-layers: prim ${p.path} has no enclosing nomos domain scope`);
1610
- }
1611
- const b = bucketFor(domain);
1612
- const id = attrString(p, "nomos:id");
1613
- const irPath = `/Nomos/${domain}/${id}`;
1614
-
1615
- if (kind === "aggregate") {
1616
- const schema: WireSchema = {};
1617
- for (const [name, value] of p.attrs) {
1618
- if (name.startsWith("nomos:driver:") && typeof value === "string") {
1619
- schema[parseFieldToken(name.slice("nomos:driver:".length))] = parseDriverToken(value);
1620
- }
1621
- }
1622
- b.prims.push({ kind: "aggregate", path: irPath, type: id, schema: sortedSchema(schema) });
1623
- } else if (kind === "directive") {
1624
- const reads = p.attrs.has("nomos:reads") ? attrStringArray(p, "nomos:reads") : undefined;
1625
- const nativeEmits = nativeValueAt(prims, p.path, "emits");
1626
- const nativeSecondaryOutputs = nativeValueAt(prims, p.path, "secondaryOutputs");
1627
- const nativePayloadJsonSchema = nativeValueAt(prims, p.path, "payloadJsonSchema");
1628
- const emits = nativeEmits as Record<string, { max?: number }> | undefined;
1629
- const secondaryOutputs = nativeSecondaryOutputs as { target: string; marker: WireRefMode }[] | undefined;
1630
- const payloadJsonSchema = nativePayloadJsonSchema;
1631
- b.prims.push({
1632
- kind: "directive",
1633
- path: irPath,
1634
- target: attrString(p, "nomos:target"),
1635
- marker: attrString(p, "nomos:marker") as WireRefMode,
1636
- requires: attrStringArray(p, "nomos:requires"),
1637
- ...(reads !== undefined ? { reads } : {}),
1638
- ...(emits !== undefined ? { emits } : {}),
1639
- ...(secondaryOutputs !== undefined ? { secondaryOutputs } : {}),
1640
- ...(payloadJsonSchema !== undefined ? { payloadJsonSchema } : {}),
1641
- });
1642
- } else if (kind === "query") {
1643
- b.queries.push({
1644
- id,
1645
- key: attrStringArray(p, "nomos:key"),
1646
- returns: attrString(p, "nomos:returns"),
1647
- });
1648
- } else if (kind === "derived") {
1649
- const nativeReturns = nativeValueAt(prims, p.path, "returns");
1650
- b.deriveds.push({
1651
- id,
1652
- of: attrString(p, "nomos:of"),
1653
- returns: nativeReturns as UsdDerived["returns"],
1654
- });
1655
- } else if (kind === "combined") {
1656
- const nativeReturns = nativeValueAt(prims, p.path, "returns");
1657
- b.combineds.push({
1658
- id,
1659
- of: attrString(p, "nomos:of"),
1660
- refField: attrString(p, "nomos:refField"),
1661
- reads: attrString(p, "nomos:readsFrom"),
1662
- returns: nativeReturns as UsdCombined["returns"],
1663
- });
1664
- } else {
1665
- throw new Error(`usd-layers: prim ${p.path} has unknown nomos:kind '${kind}'`);
1666
- }
1667
- }
1668
-
1669
- const byId = <T extends { id: string }>(a: T, b: T) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
1670
- const byPath = (a: UsdPrim, b: UsdPrim) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
1671
- const layers: UsdLayer[] = [...buckets.keys()]
1672
- .map((domain) => {
1673
- const b = buckets.get(domain)!;
1674
- const identity = identityManifestByDomain.get(domain);
1675
- const readProjection = readProjectionByDomain.get(domain);
1676
- const sids = stableIdsByDomain.get(domain);
1677
- const iface = interfaceByDomain.get(domain);
1678
- const rg = readGateByDomain.get(domain);
1679
- const invariantGate = invariantGateByDomain.get(domain);
1680
- const rv = readVisibilityByDomain.get(domain);
1681
- const enc = encryptionByDomain.get(domain);
1682
- const capabilities = capabilitiesByDomain.get(domain);
1683
- const attrGate = attributedGateByDomain.get(domain);
1684
- const rel = relationsByDomain.get(domain);
1685
- const relGate = relationGateByDomain.get(domain);
1686
- const relNs = relationNamespaceByDomain.get(domain);
1687
- const rpNs = readProjectionNamespaceByDomain.get(domain);
1688
- const attGate = attestationGateByDomain.get(domain);
1689
- const delGate = delegationGateByDomain.get(domain);
1690
- const attReadGate = attestedReadGateByDomain.get(domain);
1691
- const birthGate = birthGateByDomain.get(domain);
1692
- const birthRecordGate = birthRecordGateByDomain.get(domain);
1693
- const parentAttestedBirth = parentAttestedBirthByDomain.get(domain);
1694
- const verifiedFirstEnroll = verifiedFirstEnrollByDomain.get(domain);
1695
- const eraCertGate = eraCertGateByDomain.get(domain);
1696
- const valueObjects = valueObjectsByDomain.get(domain);
1697
- const foreignRefs2 = foreignRefsByDomain.get(domain);
1698
- const contracts2 = contractsByDomain.get(domain);
1699
- const flowGate2 = flowGateByDomain.get(domain);
1700
- return {
1701
- path: `/Nomos/${domain}`,
1702
- prims: [...b.prims].sort(byPath),
1703
- ...(identity !== undefined ? { nomosIdentityManifest: identity } : {}),
1704
- ...(readProjection !== undefined ? { nomosReadProjection: readProjection } : {}),
1705
- ...(b.queries.length > 0 ? { queries: [...b.queries].sort(byId) } : {}),
1706
- ...(b.deriveds.length > 0 ? { deriveds: [...b.deriveds].sort(byId) } : {}),
1707
- ...(b.combineds.length > 0 ? { combineds: [...b.combineds].sort(byId) } : {}),
1708
- ...(sids !== undefined ? { nomosStableIds: sids } : {}),
1709
- ...(iface !== undefined ? { nomosInterface: iface } : {}),
1710
- ...(rg !== undefined ? { nomosReadGate: rg } : {}),
1711
- ...(invariantGate !== undefined ? { nomosInvariantGate: invariantGate } : {}),
1712
- ...(rv !== undefined ? { nomosReadVisibility: rv } : {}),
1713
- ...(enc !== undefined ? { nomosEncryption: enc } : {}),
1714
- ...(capabilities !== undefined ? { nomosCapabilities: capabilities } : {}),
1715
- ...(attrGate !== undefined ? { nomosAttributedGate: attrGate } : {}),
1716
- ...(rel !== undefined ? { nomosRelations: rel } : {}),
1717
- ...(relNs !== undefined ? { nomosRelationNamespace: relNs } : {}),
1718
- ...(rpNs !== undefined ? { nomosReadProjectionNamespace: rpNs } : {}),
1719
- ...(relGate !== undefined ? { nomosRelationGate: relGate } : {}),
1720
- ...(attGate !== undefined ? { nomosAttestationGate: attGate } : {}),
1721
- ...(delGate !== undefined ? { nomosDelegationGate: delGate } : {}),
1722
- ...(attReadGate !== undefined ? { nomosAttestedReadGate: attReadGate } : {}),
1723
- ...(birthGate !== undefined ? { nomosBirthGate: birthGate } : {}),
1724
- ...(birthRecordGate !== undefined ? { nomosBirthRecordGate: birthRecordGate } : {}),
1725
- ...(parentAttestedBirth !== undefined ? { nomosParentAttestedBirth: parentAttestedBirth } : {}),
1726
- ...(verifiedFirstEnroll !== undefined ? { nomosVerifiedFirstEnroll: verifiedFirstEnroll } : {}),
1727
- ...(eraCertGate !== undefined ? { nomosEraCertGate: eraCertGate } : {}),
1728
- ...(valueObjects !== undefined ? { nomosValueObjects: valueObjects } : {}),
1729
- ...(foreignRefs2 !== undefined ? { nomosForeignRefs: foreignRefs2 } : {}),
1730
- ...(contracts2 !== undefined ? { nomosContracts: contracts2 } : {}),
1731
- ...(flowGate2 !== undefined ? { nomosFlowGate: flowGate2 } : {}),
1732
- };
1733
- })
1734
- .sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
1735
- return { layers };
1736
- }
1737
-
1738
- // ── the honest TS-rendered tree (usdcat-equivalent visual) ──────────────────────────
1739
-
1740
- /**
1741
- * The usdcat-equivalent prim tree as plain text — the visual proof for environments
1742
- * without USD tooling (and the muted-layer diff's render). Honest about what it is:
1743
- * a rendering of the IR document, not a pxr traversal.
1744
- */
1745
- export function renderUsdTree(doc: UsdDocument): string {
1746
- const lines: string[] = [];
1747
- for (const layer of doc.layers) {
1748
- lines.push(layer.path);
1749
- for (const prim of layer.prims) {
1750
- const name = prim.path.slice(prim.path.lastIndexOf("/") + 1);
1751
- if (prim.kind === "aggregate") {
1752
- lines.push(` ${name} [aggregate]`);
1753
- for (const [field, driver] of Object.entries(prim.schema)) {
1754
- lines.push(` .${field} : ${driverToken(driver)}`);
1755
- }
1756
- } else {
1757
- const reads = prim.reads !== undefined ? ` reads [${prim.reads.join(", ")}]` : "";
1758
- lines.push(
1759
- ` ${name} [directive] ${prim.marker} -> ${prim.target} requires [${prim.requires.join(", ")}]${reads}`,
1760
- );
1761
- }
1762
- }
1763
- for (const q of layer.queries ?? []) {
1764
- lines.push(` ${q.id} [query] key (${q.key.join(", ")}) -> ${q.returns}`);
1765
- }
1766
- for (const d of layer.deriveds ?? []) {
1767
- lines.push(` ${d.id} [derived] of ${d.of} -> ${d.returns.type}${d.returns.nullable ? "?" : ""}`);
1768
- }
1769
- for (const c of layer.combineds ?? []) {
1770
- lines.push(
1771
- ` ${c.id} [combined] of ${c.of} via .${c.refField} reads ${c.reads} -> ${c.returns.type}${c.returns.nullable ? "?" : ""}`,
1772
- );
1773
- }
1774
- }
1775
- return lines.join("\n") + "\n";
1776
- }