@githolon/dsl 0.101.5 → 0.102.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 (120) hide show
  1. package/dart/CHANGELOG.md +13 -0
  2. package/dart/lib/nomos_client.dart +3 -52
  3. package/dart/lib/src/dev_cloud.dart +5 -0
  4. package/dart/lib/src/home.dart +10 -15
  5. package/dart/lib/src/nms_conformance_runtime.dart +12 -0
  6. package/dart/lib/src/nms_generated_runtime.dart +29 -0
  7. package/dart/lib/src/nms_test_environment.dart +8 -0
  8. package/dart/lib/src/node_runner_transport.dart +56 -18
  9. package/dart/lib/src/provider.dart +5 -1
  10. package/dart/lib/src/record_origin.dart +55 -0
  11. package/dart/lib/src/runner/node-entry.mjs +9105 -0
  12. package/dart/lib/src/runner/offline-install.mjs +4729 -0
  13. package/dart/lib/src/session_bound.dart +23 -10
  14. package/dart/lib/src/source_trust.dart +51 -0
  15. package/dart/lib/src/test_harness.dart +490 -58
  16. package/dart/lib/src/webview_bridge.dart +167 -58
  17. package/dart/lib/src/workspace_name.dart +7 -1
  18. package/dart/pubspec.lock +3 -3
  19. package/dart/pubspec.yaml +4 -2
  20. package/dart/test/acquire_session_credentials_test.dart +34 -17
  21. package/dart/test/acquire_session_test.dart +109 -24
  22. package/dart/test/adv_realm_interference_test.dart +227 -99
  23. package/dart/test/bundle_version_pairing_test.dart +1 -1
  24. package/dart/test/cloud_bound_topology_test.dart +1 -1
  25. package/dart/test/cluster_attested_test.dart +52 -35
  26. package/dart/test/cluster_births_test.dart +5 -2
  27. package/dart/test/cluster_device_keyed_acceptance_test.dart +5 -2
  28. package/dart/test/cluster_local_autosync_test.dart +5 -2
  29. package/dart/test/cluster_scale_test.dart +5 -2
  30. package/dart/test/compat_test.dart +1 -1
  31. package/dart/test/dlq_session_surface_test.dart +2 -1
  32. package/dart/test/errors_test.dart +1 -1
  33. package/dart/test/first_app_gauntlet_test.dart +78 -51
  34. package/dart/test/four_layer_nested_topology_test.dart +1 -1
  35. package/dart/test/harness_phase_timeout_test.dart +1 -1
  36. package/dart/test/home_parity_test.dart +9 -5
  37. package/dart/test/local_sync_inert_test.dart +22 -14
  38. package/dart/test/node_runner_transport_test.dart +9 -5
  39. package/dart/test/null_slot_test.dart +1 -1
  40. package/dart/test/parent_signer_recovery_test.dart +5 -2
  41. package/dart/test/schema_validation_test.dart +1 -1
  42. package/dart/test/sealing_client_test.dart +2 -1
  43. package/dart/test/session_bound_test.dart +92 -119
  44. package/dart/test/stale_bundle_test.dart +1 -1
  45. package/dart/test/test_harness_test.dart +77 -22
  46. package/dart/test/vo_read_typing_test.dart +32 -14
  47. package/dart/test/watch_after_offer_stall_test.dart +2 -1
  48. package/dart/test/webview_bridge_test.dart +17 -4
  49. package/dart/test/wire_test.dart +1 -1
  50. package/dart_types/CHANGELOG.md +8 -0
  51. package/dart_types/lib/nomos_types.dart +7 -6
  52. package/dart_types/lib/src/activity.dart +23 -13
  53. package/dart_types/lib/src/foreign_ref.dart +61 -112
  54. package/dart_types/lib/src/invitation.dart +96 -0
  55. package/dart_types/lib/src/nomos_ref.dart +97 -59
  56. package/dart_types/pubspec.yaml +1 -1
  57. package/dart_types/test/surfaced_test.dart +125 -19
  58. package/package.json +7 -3
  59. package/runtime/generated-application-host.js +8851 -0
  60. package/src/activity.ts +2 -2
  61. package/src/aggregate.ts +24 -1
  62. package/src/application_composition.ts +282 -0
  63. package/src/application_topology.ts +151 -0
  64. package/src/attested_core.ts +2 -2
  65. package/src/authoring_context.ts +9 -0
  66. package/src/birth_meter_ensure.ts +1 -1
  67. package/src/births.ts +53 -5
  68. package/src/build_package.ts +79 -71
  69. package/src/business_birth.ts +93 -10
  70. package/src/clear_directive.ts +3 -1
  71. package/src/codegen_dart.ts +369 -420
  72. package/src/codegen_dart_application.ts +1508 -0
  73. package/src/codegen_dart_client.ts +156 -95
  74. package/src/codegen_dart_scenario.ts +299 -0
  75. package/src/codegen_openapi.ts +8 -3
  76. package/src/codegen_proof.ts +1113 -1137
  77. package/src/codegen_provider_dart.ts +12 -1
  78. package/src/codegen_ts.ts +1176 -496
  79. package/src/compat_package_main.ts +22 -10
  80. package/src/compile_package_main.ts +1536 -337
  81. package/src/contract.ts +10 -4
  82. package/src/dart_barrel_guard.ts +5 -1
  83. package/src/describe_model.ts +13 -5
  84. package/src/directive.ts +244 -5
  85. package/src/engine_entry.ts +56 -13
  86. package/src/engine_javascript.ts +31 -0
  87. package/src/evolve_preview.ts +3 -12
  88. package/src/framework/application_home.ts +24 -10
  89. package/src/framework/capability.ts +15 -1
  90. package/src/framework/domain_lifecycle.ts +8 -0
  91. package/src/framework/framework_aggregates.ts +2 -2
  92. package/src/framework/genesis.ts +11 -1
  93. package/src/framework/home.ts +178 -75
  94. package/src/framework/impure_capability.ts +24 -7
  95. package/src/framework/relations.ts +13 -5
  96. package/src/framework/workspaces.ts +13 -12
  97. package/src/generated_client_names.ts +42 -3
  98. package/src/idof_lint.ts +65 -13
  99. package/src/index.ts +46 -12
  100. package/src/intent.ts +587 -0
  101. package/src/law_check.ts +64 -16
  102. package/src/precondition.ts +16 -4
  103. package/src/read_projection_namespace.ts +2 -7
  104. package/src/recipient.ts +33 -0
  105. package/src/relation_schema.ts +93 -0
  106. package/src/secondary_ensure.ts +11 -2
  107. package/src/shape_compat.ts +11 -1
  108. package/src/usd.ts +30 -8
  109. package/src/usd_layers.ts +447 -246
  110. package/src/usd_state.ts +1 -1
  111. package/src/value_object.ts +49 -13
  112. package/src/wire_encode.ts +124 -26
  113. package/src/workspace_sharing.ts +269 -0
  114. package/src/zod_introspection.ts +84 -0
  115. package/dart/example/nomos_secure_key_store.dart +0 -82
  116. package/dart/example/nomos_webview_transport.dart +0 -63
  117. package/dart/lib/nomos_headless.dart +0 -50
  118. package/dart/lib/testing.dart +0 -14
  119. package/dart/test/co2_product_gauntlet_test.dart +0 -244
  120. package/src/framework/identity.ts +0 -537
package/src/usd_layers.ts CHANGED
@@ -19,13 +19,14 @@
19
19
  * (and a real `.usda` text layer standard USD tooling opens), instead of
20
20
  * lowering only the composed result.
21
21
  * * `flattenLayeredUsd` is the ordered opinion-map fold over those layers —
22
- * and the proof obligation is BYTE-EQUALITY:
22
+ * and the proof obligation is CANONICAL LAW EQUALITY:
23
23
  *
24
- * JSON.stringify(flattenLayeredUsd(layers)) === emitUsdJsonForModules([composed])
24
+ * canonicalJson(flattenLayeredUsd(layers)) === canonicalJson(emitUsd([composed]))
25
25
  *
26
26
  * i.e. Φ(fold_modules(A, B)) == fold_layers(Φ(A), Φ(B)) — flatten-after-lower
27
- * equals lower-after-compose, byte for byte. `nomos-compile --layered`
28
- * ASSERTS this fail-closed on every layered compile (never assumed).
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.
29
30
  *
30
31
  * WHERE THE TWO FOLDS COULD DIVERGE (the lemma, stated honestly): USD composes
31
32
  * PER-ATTRIBUTE (a weaker layer's attribute shows through a stronger prim that is
@@ -36,7 +37,7 @@
36
37
  * for a directive's required attributes (target/marker/requires/payload). The gap
37
38
  * is omit-when-empty boundaries (`reads`/`emits`) on a RE-declared directive:
38
39
  * "omitted" means "none" to the module fold but "no opinion" (show-through) to
39
- * USD. The compile-time byte-equality assertion fail-closes that case rather than
40
+ * USD. The compile-time canonical-equality assertion fail-closes that case rather than
40
41
  * silently shipping either answer.
41
42
  *
42
43
  * STATE vs LAW (why this is sound): law composition is this non-commutative
@@ -44,8 +45,8 @@
44
45
  * commutative semilattice world (Lww/AddWins/MapOf merge drivers — order-free by
45
46
  * construction). The domainHash is deliberately NON-homomorphic: it is sha256 over
46
47
  * the COMPOSED canonical bytes, so identity lives on the flattened law, never on
47
- * any decomposition of it. Layered emission is therefore OPT-IN and additive: the
48
- * flattened `.package.usda` emission and its hash do not move by one byte.
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.
49
50
  *
50
51
  * LIVRPS note: the module list maps onto the SUBLAYER arc only (local opinions per
51
52
  * layer, stronger-over-weaker). The reference/variant arcs of `usd.ts` are the
@@ -54,10 +55,11 @@
54
55
  * re-authoring its own module stack is opinion strength; a foreign layer composing
55
56
  * over deployed law is a monotonic move.
56
57
  *
57
- * BUILD-TIME ONLY (Buffer for hex): reached via `@githolon/dsl/usd-layers`, never
58
+ * BUILD-TIME ONLY (Buffer for lossless UTF-8 identifier tokens): reached via `@githolon/dsl/usd-layers`, never
58
59
  * the runtime barrel.
59
60
  */
60
61
  import type { AggregateHandle } from "./aggregate.js";
62
+ import { createHash } from "node:crypto";
61
63
  import { READ_PROJECTION_DELIMITER } from "./read_projection_namespace.js";
62
64
  import type { Field } from "./fields.js";
63
65
  import type { DomainModule } from "./codegen_dart.js";
@@ -78,21 +80,14 @@ import { withBusinessCorrectionLayer } from "./framework/business_corrections.js
78
80
  export const NOMOS_USD_LAYER_FORMAT = "nomos.usd-layer.v1";
79
81
  /** The layered-root format marker — the subLayers document. */
80
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";
81
85
 
82
86
  // ── small codecs ───────────────────────────────────────────────────────────────────
83
87
 
84
- /** Lowercase hex of the UTF-8 bytes (same convention as the package envelope). */
85
- function hexUtf8(text: string): string {
86
- return Buffer.from(text, "utf8").toString("hex");
87
- }
88
-
89
- function unhexUtf8(hex: string): string {
90
- return Buffer.from(hex, "hex").toString("utf8");
91
- }
92
-
93
88
  /** Escape a string for a double-quoted usda string literal. */
94
89
  export function usdaString(s: string): string {
95
- return `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
90
+ return JSON.stringify(s);
96
91
  }
97
92
 
98
93
  /** A usda `string[]` literal. */
@@ -149,6 +144,79 @@ export function primName(id: string): string {
149
144
  return /^[A-Za-z_]/.test(sanitized) ? sanitized : `_${sanitized}`;
150
145
  }
151
146
 
147
+ /** Lossless USD identifier for an arbitrary business field label. */
148
+ export function fieldToken(field: string): string {
149
+ return `f${Buffer.from(field, "utf8").toString("hex")}`;
150
+ }
151
+
152
+ /** Inverse of {@link fieldToken}. */
153
+ export function parseFieldToken(token: string): string {
154
+ if (!/^f(?:[0-9a-f]{2})+$/.test(token)) throw new Error(`usd-layers: malformed field token '${token}'`);
155
+ return Buffer.from(token.slice(1), "hex").toString("utf8");
156
+ }
157
+
158
+ /** Canonical JSON text used only to content-stamp a native USD value tree. */
159
+ function stableValueJson(value: unknown): string {
160
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
161
+ if (Array.isArray(value)) return `[${value.map(stableValueJson).join(",")}]`;
162
+ return `{${Object.keys(value as Record<string, unknown>).sort().map((key) =>
163
+ `${JSON.stringify(key)}:${stableValueJson((value as Record<string, unknown>)[key])}`).join(",")}}`;
164
+ }
165
+
166
+ /**
167
+ * Encode an arbitrary law value as an ordinary USD prim tree. No JSON text is carried. Every node
168
+ * bears the root content stamp so a stronger layer replaces the whole value: weaker descendants
169
+ * may show through structurally under USD composition, but the reader ignores their old stamp.
170
+ */
171
+ export function nativeValueLines(name: string, value: unknown, indent: string): string[] {
172
+ const hash = createHash("sha256").update(stableValueJson(value)).digest("hex");
173
+ const keyHex = (key: string): string => Buffer.from(key, "utf8").toString("hex");
174
+ const scalarLine = (nodeValue: unknown, attr: string, at: string): string => {
175
+ if (nodeValue === null) return `${at}custom bool nomos:zv:${attr} = true`;
176
+ if (typeof nodeValue === "string") return `${at}custom string nomos:sv:${attr} = ${usdaString(nodeValue)}`;
177
+ if (typeof nodeValue === "boolean") return `${at}custom bool nomos:bv:${attr} = ${nodeValue ? "true" : "false"}`;
178
+ if (typeof nodeValue === "number" && Number.isFinite(nodeValue)) {
179
+ const usdType = Number.isSafeInteger(nodeValue) && !Object.is(nodeValue, -0) ? "int64" : "double";
180
+ return `${at}custom ${usdType} nomos:nv:${attr} = ${Object.is(nodeValue, -0) ? "-0" : String(nodeValue)}`;
181
+ }
182
+ throw new Error(`usd-layers: '${name}' contains a non-JSON value (${typeof nodeValue})`);
183
+ };
184
+ const emitContainer = (nodeName: string, nodeValue: unknown, at: string): string[] => {
185
+ const isArray = Array.isArray(nodeValue);
186
+ const isObject = nodeValue !== null && typeof nodeValue === "object" && !isArray;
187
+ const lines = [
188
+ `${at}def Scope ${usdaString(nodeName)}`,
189
+ `${at}{`,
190
+ `${at} custom string nomos:containerKind = ${usdaString(isArray ? "array" : isObject ? "object" : "scalar")}`,
191
+ ];
192
+ const entries: readonly [string | undefined, unknown][] = isArray
193
+ ? nodeValue.map((child) => [undefined, child] as const)
194
+ : isObject
195
+ // The content hash canonicalizes object keys. The physical tree MUST use the same order:
196
+ // two layers carrying the same object with different JS insertion order otherwise share a
197
+ // root hash but compose incompatible positional children under that root.
198
+ ? Object.entries(nodeValue as Record<string, unknown>).sort(([a], [b]) => a.localeCompare(b))
199
+ : [[undefined, nodeValue]];
200
+ entries.forEach(([key, child], index) => {
201
+ const position = `i${String(index).padStart(6, "0")}`;
202
+ const identity = key === undefined ? position : `${position}:k${keyHex(key)}`;
203
+ if (child !== null && typeof child === "object") {
204
+ lines.push("", ...emitContainer(identity.replace(/:/g, "_"), child, `${at} `));
205
+ } else {
206
+ lines.push(scalarLine(child, identity, `${at} `));
207
+ }
208
+ });
209
+ lines.push(`${at}}`);
210
+ return lines;
211
+ };
212
+ const rootName = `__nms_${primName(name)}_${hash}`;
213
+ return [
214
+ `${indent}custom string nomos:value:${name} = ${usdaString(hash)}`,
215
+ "",
216
+ ...emitContainer(rootName, value, indent),
217
+ ];
218
+ }
219
+
152
220
  /** Field→driver map with keys sorted (the canonical schema shape `manifest.ts` emits). */
153
221
  function sortedSchema(schema: WireSchema): WireSchema {
154
222
  const out: WireSchema = {};
@@ -157,82 +225,26 @@ function sortedSchema(schema: WireSchema): WireSchema {
157
225
  }
158
226
 
159
227
  function domainLayerAttrLines(domain: string, layer: UsdLayer, indent: string): string[] {
160
- return [
228
+ const attrs = [
161
229
  `${indent}custom string nomos:kind = "domain"`,
162
230
  `${indent}custom string nomos:domain = ${usdaString(domain)}`,
163
- ...(layer.nomosIdentityManifest !== undefined
164
- ? [`${indent}custom string nomos:identityManifestHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosIdentityManifest)))}`]
165
- : []),
166
- ...(layer.nomosReadProjection !== undefined
167
- ? [`${indent}custom string nomos:readProjectionHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosReadProjection)))}`]
168
- : []),
169
- ...(layer.nomosStableIds !== undefined
170
- ? [`${indent}custom string nomos:stableIdsHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosStableIds)))}`]
171
- : []),
172
- ...(layer.nomosInterface !== undefined
173
- ? [`${indent}custom string nomos:interfaceHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosInterface)))}`]
174
- : []),
175
- ...(layer.nomosReadGate !== undefined
176
- ? [`${indent}custom string nomos:readGateHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosReadGate)))}`]
177
- : []),
178
- ...(layer.nomosReadVisibility !== undefined
179
- ? [`${indent}custom string nomos:readVisibilityHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosReadVisibility)))}`]
180
- : []),
181
- ...(layer.nomosEncryption !== undefined
182
- ? [`${indent}custom string nomos:encryptionHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosEncryption)))}`]
183
- : []),
184
- ...(layer.nomosAttributedGate !== undefined
185
- ? [`${indent}custom string nomos:attributedGateHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosAttributedGate)))}`]
186
- : []),
187
- ...(layer.nomosRelations !== undefined
188
- ? [`${indent}custom string nomos:relationsHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosRelations)))}`]
189
- : []),
190
- ...(layer.nomosRelationNamespace !== undefined
191
- ? [`${indent}custom string nomos:relationNamespaceHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosRelationNamespace)))}`]
192
- : []),
193
- ...(layer.nomosReadProjectionNamespace !== undefined
194
- ? [`${indent}custom string nomos:readProjectionNamespaceHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosReadProjectionNamespace)))}`]
195
- : []),
196
- ...(layer.nomosRelationGate !== undefined
197
- ? [`${indent}custom string nomos:relationGateHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosRelationGate)))}`]
198
- : []),
199
- ...(layer.nomosAttestationGate !== undefined
200
- ? [`${indent}custom string nomos:attestationGateHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosAttestationGate)))}`]
201
- : []),
202
- ...(layer.nomosDelegationGate !== undefined
203
- ? [`${indent}custom string nomos:delegationGateHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosDelegationGate)))}`]
204
- : []),
205
- ...(layer.nomosAttestedReadGate !== undefined
206
- ? [`${indent}custom string nomos:attestedReadGateHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosAttestedReadGate)))}`]
207
- : []),
208
- ...(layer.nomosBirthGate !== undefined
209
- ? [`${indent}custom string nomos:birthGateHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosBirthGate)))}`]
210
- : []),
211
- ...(layer.nomosBirthRecordGate !== undefined
212
- ? [`${indent}custom string nomos:birthRecordGateHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosBirthRecordGate)))}`]
213
- : []),
214
- ...(layer.nomosParentAttestedBirth !== undefined
215
- ? [`${indent}custom string nomos:parentAttestedBirthHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosParentAttestedBirth)))}`]
216
- : []),
217
- ...(layer.nomosVerifiedFirstEnroll !== undefined
218
- ? [`${indent}custom string nomos:verifiedFirstEnrollHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosVerifiedFirstEnroll)))}`]
219
- : []),
220
- ...(layer.nomosEraCertGate !== undefined
221
- ? [`${indent}custom string nomos:eraCertGateHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosEraCertGate)))}`]
222
- : []),
223
- ...(layer.nomosValueObjects !== undefined
224
- ? [`${indent}custom string nomos:valueObjectsHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosValueObjects)))}`]
225
- : []),
226
- ...(layer.nomosContracts !== undefined
227
- ? [`${indent}custom string nomos:contractsHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosContracts)))}`]
228
- : []),
229
- ...(layer.nomosForeignRefs !== undefined
230
- ? [`${indent}custom string nomos:foreignRefsHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosForeignRefs)))}`]
231
- : []),
232
- ...(layer.nomosFlowGate !== undefined
233
- ? [`${indent}custom string nomos:flowGateHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosFlowGate)))}`]
234
- : []),
235
231
  ];
232
+ const values: readonly [string, unknown][] = [
233
+ ["identityManifest", layer.nomosIdentityManifest], ["readProjection", layer.nomosReadProjection],
234
+ ["stableIds", layer.nomosStableIds], ["interface", layer.nomosInterface], ["readGate", layer.nomosReadGate],
235
+ ["invariantGate", layer.nomosInvariantGate], ["readVisibility", layer.nomosReadVisibility],
236
+ ["encryption", layer.nomosEncryption], ["capabilities", layer.nomosCapabilities],
237
+ ["attributedGate", layer.nomosAttributedGate], ["relations", layer.nomosRelations],
238
+ ["relationNamespace", layer.nomosRelationNamespace], ["readProjectionNamespace", layer.nomosReadProjectionNamespace],
239
+ ["relationGate", layer.nomosRelationGate], ["attestationGate", layer.nomosAttestationGate],
240
+ ["delegationGate", layer.nomosDelegationGate], ["attestedReadGate", layer.nomosAttestedReadGate],
241
+ ["birthGate", layer.nomosBirthGate], ["birthRecordGate", layer.nomosBirthRecordGate],
242
+ ["parentAttestedBirth", layer.nomosParentAttestedBirth], ["verifiedFirstEnroll", layer.nomosVerifiedFirstEnroll],
243
+ ["eraCertGate", layer.nomosEraCertGate], ["valueObjects", layer.nomosValueObjects],
244
+ ["contracts", layer.nomosContracts], ["foreignRefs", layer.nomosForeignRefs], ["flowGate", layer.nomosFlowGate],
245
+ ];
246
+ for (const [name, value] of values) if (value !== undefined) attrs.push("", ...nativeValueLines(name, value, indent));
247
+ return attrs;
236
248
  }
237
249
 
238
250
  // ── per-module layer emission ────────────────────────────────────────────────────────
@@ -265,11 +277,18 @@ export interface EmittedModuleLayer {
265
277
  export function emitLayeredDomain(
266
278
  domain: string,
267
279
  modules: readonly LayeredModuleInput[],
280
+ options: { readonly applyBusinessCorrectionLayer?: boolean } = {},
268
281
  ): EmittedModuleLayer[] {
269
282
  if (modules.length === 0) {
270
283
  throw new Error(`usd-layers: domain '${domain}' has no modules to layer`);
271
284
  }
272
- const normalizedModules = withBusinessCorrectionLayer(modules.map((entry) => entry.module));
285
+ // Raw callers need the cross-module correction prelude. The package compiler,
286
+ // however, passes modules already lowered through composeDomainModule; re-inferring
287
+ // the prelude here would turn framework-expanded capability directives into public
288
+ // business verbs and invent law that the canonical fold never declared.
289
+ const normalizedModules = options.applyBusinessCorrectionLayer === false
290
+ ? modules.map((entry) => entry.module)
291
+ : withBusinessCorrectionLayer(modules.map((entry) => entry.module));
273
292
  const out: EmittedModuleLayer[] = [];
274
293
  const seenPaths = new Set<string>();
275
294
  for (const [index, m] of modules.entries()) {
@@ -328,8 +347,8 @@ function relationLines(
328
347
  * One module layer as REAL `.usda` text: `/Nomos/<domain>` scopes, one prim per
329
348
  * aggregate (field→merge-driver as readable `nomos:driver:<field>` attributes,
330
349
  * `t.ref`/`t.hasMany` as USD relationships), one prim per directive, one prim per
331
- * declared query/derived/combined. Nested JSON (payload schema / emits / returns)
332
- * rides hex-encoded so the round-trip is byte-faithful. Re-touched prim paths use
350
+ * declared query/derived/combined. Structured values are ordinary stamped USD prim trees,
351
+ * so the round-trip is byte-faithful without a hidden JSON authority. Re-touched prim paths use
333
352
  * the `over` specifier; everything carries `nomos:id` so the sanitized prim name
334
353
  * never loses the wire id.
335
354
  */
@@ -337,12 +356,12 @@ export function usdaLayerText(
337
356
  domain: string,
338
357
  moduleName: string,
339
358
  layer: UsdLayer,
340
- module: DomainModule,
359
+ module: DomainModule | undefined,
341
360
  overPaths: ReadonlySet<string>,
342
361
  ): string {
343
362
  const domainScope = `/Nomos/${primName(domain)}`;
344
363
  const aggById = new Map<string, AggregateHandle>();
345
- for (const a of module.aggregates as AggregateHandle[]) aggById.set(a.id, a);
364
+ for (const a of (module?.aggregates ?? []) as AggregateHandle[]) aggById.set(a.id, a);
346
365
 
347
366
  const ind2 = " ";
348
367
  const ind3 = " ";
@@ -358,7 +377,7 @@ export function usdaLayerText(
358
377
  `${ind3}custom string nomos:id = ${usdaString(prim.type)}`,
359
378
  );
360
379
  for (const [field, driver] of Object.entries(prim.schema)) {
361
- body.push(`${ind3}custom string nomos:driver:${field} = ${usdaString(driverToken(driver))}`);
380
+ body.push(`${ind3}custom string nomos:driver:${fieldToken(field)} = ${usdaString(driverToken(driver))}`);
362
381
  }
363
382
  body.push(...relationLines(domainScope, aggById.get(prim.type), ind3));
364
383
  } else {
@@ -373,21 +392,13 @@ export function usdaLayerText(
373
392
  body.push(`${ind3}custom string[] nomos:reads = ${usdaStringArray(prim.reads)}`);
374
393
  }
375
394
  if (prim.emits !== undefined) {
376
- body.push(
377
- `${ind3}custom string nomos:emitsHex = ${usdaString(hexUtf8(JSON.stringify(prim.emits)))}`,
378
- );
395
+ body.push("", ...nativeValueLines("emits", prim.emits, ind3));
379
396
  }
380
397
  if (prim.secondaryOutputs !== undefined) {
381
- body.push(
382
- `${ind3}custom string nomos:secondaryOutputsHex = ${usdaString(hexUtf8(JSON.stringify(prim.secondaryOutputs)))}`,
383
- );
398
+ body.push("", ...nativeValueLines("secondaryOutputs", prim.secondaryOutputs, ind3));
384
399
  }
385
400
  if (prim.payloadJsonSchema !== undefined) {
386
- body.push(
387
- `${ind3}custom string nomos:payloadJsonSchemaHex = ${usdaString(
388
- hexUtf8(JSON.stringify(prim.payloadJsonSchema)),
389
- )}`,
390
- );
401
+ body.push("", ...nativeValueLines("payloadJsonSchema", prim.payloadJsonSchema, ind3));
391
402
  }
392
403
  }
393
404
  body.push(`${ind2}}`, ``);
@@ -412,7 +423,8 @@ export function usdaLayerText(
412
423
  `${ind3}custom string nomos:kind = "derived"`,
413
424
  `${ind3}custom string nomos:id = ${usdaString(d.id)}`,
414
425
  `${ind3}custom string nomos:of = ${usdaString(d.of)}`,
415
- `${ind3}custom string nomos:returnsHex = ${usdaString(hexUtf8(JSON.stringify(d.returns)))}`,
426
+ ``,
427
+ ...nativeValueLines("returns", d.returns, ind3),
416
428
  `${ind2}}`,
417
429
  ``,
418
430
  );
@@ -426,7 +438,8 @@ export function usdaLayerText(
426
438
  `${ind3}custom string nomos:of = ${usdaString(c.of)}`,
427
439
  `${ind3}custom string nomos:refField = ${usdaString(c.refField)}`,
428
440
  `${ind3}custom string nomos:readsFrom = ${usdaString(c.reads)}`,
429
- `${ind3}custom string nomos:returnsHex = ${usdaString(hexUtf8(JSON.stringify(c.returns)))}`,
441
+ ``,
442
+ ...nativeValueLines("returns", c.returns, ind3),
430
443
  `${ind2}}`,
431
444
  ``,
432
445
  );
@@ -448,8 +461,7 @@ export function usdaLayerText(
448
461
  `{`,
449
462
  ` def Scope ${usdaString(primName(domain))}`,
450
463
  ` {`,
451
- // Stable ids + holon-owned manifests: layer-level law keys, hex-carried
452
- // so the .usda round-trips byte-faithfully to the IR layer.
464
+ // Stable ids + holon-owned manifests: native layer-level law values.
453
465
  ...domainLayerAttrLines(domain, layer, " "),
454
466
  ``,
455
467
  ...body,
@@ -510,6 +522,119 @@ export function layeredRootUsda(
510
522
  ].join("\n");
511
523
  }
512
524
 
525
+ export interface UsdPlanAsset {
526
+ readonly sha256: string;
527
+ readonly javascript: string;
528
+ }
529
+
530
+ export interface UsdPlanRoute {
531
+ readonly selector: string;
532
+ readonly planSha256: string;
533
+ }
534
+
535
+ /**
536
+ * Emit the one self-contained stage stored on the ledger. Law remains ordinary USD prims and
537
+ * attributes; executable implementation details are separate, content-addressed plan assets.
538
+ * The route index is data, not another config format, and therefore composes/travels with the law.
539
+ */
540
+ export function nativePlanPackageUsda(
541
+ packageName: string,
542
+ document: UsdDocument,
543
+ modules: readonly DomainModule[],
544
+ plans: readonly UsdPlanAsset[],
545
+ routes: readonly UsdPlanRoute[],
546
+ ): string {
547
+ const modulesByDomain = new Map(modules.map((module) => [module.domain ?? module.name, module] as const));
548
+ const planByHash = new Map<string, UsdPlanAsset>();
549
+ for (const plan of plans) {
550
+ const actual = createHash("sha256").update(plan.javascript, "utf8").digest("hex");
551
+ if (actual !== plan.sha256) {
552
+ throw new Error(`usd-layers: plan '${plan.sha256}' content hashes to '${actual}'`);
553
+ }
554
+ planByHash.set(plan.sha256, plan);
555
+ }
556
+ for (const route of routes) {
557
+ if (!planByHash.has(route.planSha256)) {
558
+ throw new Error(`usd-layers: route '${route.selector}' names missing plan '${route.planSha256}'`);
559
+ }
560
+ }
561
+
562
+ const domainBlocks: string[] = [];
563
+ for (const layer of document.layers) {
564
+ const domain = layer.path.slice(layer.path.lastIndexOf("/") + 1);
565
+ const module = modulesByDomain.get(domain);
566
+ const lines = usdaLayerText(domain, "composed", layer, module, new Set()).split("\n");
567
+ const marker = ` def Scope ${usdaString(primName(domain))}`;
568
+ const start = lines.indexOf(marker);
569
+ if (start < 0) throw new Error(`usd-layers: could not extract native domain '${domain}'`);
570
+ let balance = 0;
571
+ let opened = false;
572
+ let end = -1;
573
+ for (let index = start; index < lines.length; index += 1) {
574
+ const line = lines[index]!;
575
+ for (const char of line) {
576
+ if (char === "{") { balance += 1; opened = true; }
577
+ if (char === "}") balance -= 1;
578
+ }
579
+ if (opened && balance === 0) { end = index; break; }
580
+ }
581
+ if (end < 0) throw new Error(`usd-layers: native domain '${domain}' has unbalanced scopes`);
582
+ domainBlocks.push(...lines.slice(start, end + 1), "");
583
+ }
584
+ if (domainBlocks[domainBlocks.length - 1] === "") domainBlocks.pop();
585
+
586
+ const sortedPlans = [...planByHash.values()].sort((a, b) => a.sha256.localeCompare(b.sha256));
587
+ const sortedRoutes = [...routes].sort((a, b) => a.selector.localeCompare(b.selector));
588
+ const runtime: string[] = [
589
+ `def Scope "__NomosRuntime_7e9c"`,
590
+ `{`,
591
+ ` custom string nomos:kind = "runtime"`,
592
+ ` custom string nomos:format = ${usdaString(NOMOS_USD_PLAN_PACKAGE_FORMAT)}`,
593
+ ` custom string nomos:package = ${usdaString(packageName)}`,
594
+ ``,
595
+ ` def Scope "Routes"`,
596
+ ` {`,
597
+ ];
598
+ sortedRoutes.forEach((route, index) => runtime.push(
599
+ ` def Scope "r${String(index).padStart(6, "0")}"`,
600
+ ` {`,
601
+ ` custom string nomos:selector = ${usdaString(route.selector)}`,
602
+ ` custom string nomos:plan = ${usdaString(route.planSha256)}`,
603
+ ` }`,
604
+ ``,
605
+ ));
606
+ if (runtime[runtime.length - 1] === "") runtime.pop();
607
+ runtime.push(` }`, ``, ` def Scope "Plans"`, ` {`);
608
+ sortedPlans.forEach((plan) => runtime.push(
609
+ ` def Scope ${usdaString(`p_${plan.sha256}`)}`,
610
+ ` {`,
611
+ ` custom string nomos:sha256 = ${usdaString(plan.sha256)}`,
612
+ ` custom string nomos:language = "javascript"`,
613
+ ` custom string nomos:source = ${usdaString(plan.javascript)}`,
614
+ ` }`,
615
+ ``,
616
+ ));
617
+ if (runtime[runtime.length - 1] === "") runtime.pop();
618
+ runtime.push(` }`, `}`);
619
+
620
+ return [
621
+ `#usda 1.0`,
622
+ `(`,
623
+ ` customLayerData = {`,
624
+ ` string "nomos:format" = ${usdaString(NOMOS_USD_PLAN_PACKAGE_FORMAT)}`,
625
+ ` }`,
626
+ `)`,
627
+ ``,
628
+ `def Scope "Nomos"`,
629
+ `{`,
630
+ ...domainBlocks,
631
+ `}`,
632
+ ``,
633
+ ...runtime,
634
+ ``,
635
+ ].join("\n");
636
+ }
637
+
513
638
  // ── the ordered opinion-map fold (flatten) ───────────────────────────────────────────
514
639
 
515
640
  /** Strongest-opinion-per-attribute merge of two prims at one path (over = stronger). */
@@ -609,9 +734,25 @@ function foldIdentityManifest(
609
734
  const workspaceTypes = foldById(manifests.map((m) => m.workspaceTypes));
610
735
  const homes = foldRecord(manifests.map((m) => m.homes));
611
736
  const routes = foldByKey(manifests.map((m) => m.routes), (r) => r.directive);
737
+ const aggregatesById = new Map<string, NonNullable<UsdLayer["nomosIdentityManifest"]>["aggregates"][number]>();
738
+ for (const manifest of manifests) {
739
+ for (const aggregate of manifest.aggregates) aggregatesById.set(aggregate.id, aggregate);
740
+ }
741
+ // Aggregate namespace qualification happens after the canonical manifest has
742
+ // sorted its bare business labels. Preserve that order here: sorting the final
743
+ // `co2.Asset` spelling would incorrectly move every framework aggregate ahead
744
+ // of the tenant model even though the laws are otherwise identical.
745
+ const identityLabel = (id: string): string => id.slice(id.lastIndexOf(".") + 1);
746
+ const aggregates = [...aggregatesById.values()].sort((a, b) => {
747
+ const aLabel = identityLabel(a.id);
748
+ const bLabel = identityLabel(b.id);
749
+ return aLabel !== bLabel
750
+ ? aLabel < bLabel ? -1 : 1
751
+ : a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
752
+ });
612
753
  return {
613
754
  domain,
614
- aggregates: foldById(manifests.map((m) => m.aggregates)) ?? [],
755
+ aggregates,
615
756
  directives: foldById(manifests.map((m) => m.directives)) ?? [],
616
757
  ...(stableIds !== undefined ? { stableIds } : {}),
617
758
  ...(workspaceInvariants !== undefined ? { workspaceInvariants } : {}),
@@ -702,14 +843,18 @@ export function flattenLayeredUsd(layers: readonly UsdLayer[]): UsdDocument {
702
843
  stableIds = { v: 1, domain: domainSid, aggregates };
703
844
  }
704
845
  }
705
- // THE STRUCTURAL INTERFACE (#72): fold per-module interfaces — directives + aggregates merged
706
- // later-wins, SORTED keys (matching the composed emission's sorted-key `domainInterface`).
846
+ // THE STRUCTURAL INTERFACE (#72): fold per-module interfaces — directives, aggregates and their
847
+ // declared cross-workspace read edges merge later-wins, with sorted keys (matching the composed
848
+ // emission's sorted-key `domainInterface`). `foreignReads` is optional only at the final boundary;
849
+ // dropping it while folding makes a layered package describe a weaker law than its module fold.
707
850
  let iface: UsdLayer["nomosInterface"];
708
851
  {
709
852
  type Dir = NonNullable<UsdLayer["nomosInterface"]>["directives"][string];
710
853
  type Agg = NonNullable<UsdLayer["nomosInterface"]>["aggregates"][string];
854
+ type ForeignReads = NonNullable<NonNullable<UsdLayer["nomosInterface"]>["foreignReads"]>[string];
711
855
  const dirs = new Map<string, Dir>();
712
856
  const aggsI = new Map<string, Agg>();
857
+ const foreignReads = new Map<string, ForeignReads>();
713
858
  let domainI: string | undefined;
714
859
  let domainSidI: string | undefined;
715
860
  for (const l of stack) {
@@ -718,18 +863,22 @@ export function flattenLayeredUsd(layers: readonly UsdLayer[]): UsdDocument {
718
863
  domainSidI = l.nomosInterface.domainSid;
719
864
  for (const [id, spec] of Object.entries(l.nomosInterface.directives)) dirs.set(id, spec);
720
865
  for (const [sid, spec] of Object.entries(l.nomosInterface.aggregates)) aggsI.set(sid, spec);
866
+ for (const [id, edges] of Object.entries(l.nomosInterface.foreignReads ?? {})) foreignReads.set(id, edges);
721
867
  }
722
868
  if (domainI !== undefined) {
723
869
  const directivesRec: Record<string, Dir> = {};
724
870
  for (const k of [...dirs.keys()].sort()) directivesRec[k] = dirs.get(k)!;
725
871
  const aggregatesRec: Record<string, Agg> = {};
726
872
  for (const k of [...aggsI.keys()].sort()) aggregatesRec[k] = aggsI.get(k)!;
873
+ const foreignReadsRec: Record<string, ForeignReads> = {};
874
+ for (const k of [...foreignReads.keys()].sort()) foreignReadsRec[k] = foreignReads.get(k)!;
727
875
  iface = {
728
876
  v: 1,
729
877
  domain: domainI,
730
878
  ...(domainSidI !== undefined ? { domainSid: domainSidI } : {}),
731
879
  directives: directivesRec,
732
880
  aggregates: aggregatesRec,
881
+ ...(foreignReads.size > 0 ? { foreignReads: foreignReadsRec } : {}),
733
882
  };
734
883
  }
735
884
  }
@@ -754,6 +903,29 @@ export function flattenLayeredUsd(layers: readonly UsdLayer[]): UsdDocument {
754
903
  readGate = { v: 1, queries: queriesRec, directives: directivesRec };
755
904
  }
756
905
  }
906
+ // THE INVARIANT GATE: aggregate invariant presence is a set; named workspace
907
+ // invariants are later-wins by id. Both are sorted exactly as the canonical emit.
908
+ let invariantGate: UsdLayer["nomosInvariantGate"];
909
+ {
910
+ const aggregates = new Set<string>();
911
+ const workspaceInvariants = new Map<string, { id: string; on: string }>();
912
+ for (const layer of stack) {
913
+ if (layer.nomosInvariantGate === undefined) continue;
914
+ for (const aggregate of layer.nomosInvariantGate.aggregates ?? []) aggregates.add(aggregate);
915
+ for (const invariant of layer.nomosInvariantGate.workspaceInvariants ?? []) {
916
+ workspaceInvariants.set(invariant.id, invariant);
917
+ }
918
+ }
919
+ if (aggregates.size > 0 || workspaceInvariants.size > 0) {
920
+ invariantGate = {
921
+ v: 1,
922
+ ...(aggregates.size > 0 ? { aggregates: [...aggregates].sort() } : {}),
923
+ ...(workspaceInvariants.size > 0
924
+ ? { workspaceInvariants: [...workspaceInvariants.values()].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0) }
925
+ : {}),
926
+ };
927
+ }
928
+ }
757
929
  // The read-visibility gate (slice 5.1): per-aggregate required-read caps merged
758
930
  // later-wins, SORTED keys (matching the composed emission). Omit-when-empty.
759
931
  let readVisibility: UsdLayer["nomosReadVisibility"];
@@ -784,6 +956,27 @@ export function flattenLayeredUsd(layers: readonly UsdLayer[]): UsdDocument {
784
956
  encryption = { v: 1, aggregates: aggregatesRec };
785
957
  }
786
958
  }
959
+ // DECLARED IMPURE CAPABILITIES: compiler composition dedupes by task aggregate;
960
+ // mirror that exact rule here, with the stronger layer winning and canonical
961
+ // aggregate-id order. Omit-when-empty (the capability era rule).
962
+ let capabilities: UsdLayer["nomosCapabilities"];
963
+ {
964
+ type Capability = NonNullable<UsdLayer["nomosCapabilities"]>["capabilities"][number];
965
+ const byAggregate = new Map<string, Capability>();
966
+ for (const layer of stack) {
967
+ for (const capability of layer.nomosCapabilities?.capabilities ?? []) {
968
+ byAggregate.set(capability.aggregateId, capability);
969
+ }
970
+ }
971
+ if (byAggregate.size > 0) {
972
+ capabilities = {
973
+ v: 1,
974
+ capabilities: [...byAggregate.values()].sort((a, b) =>
975
+ a.aggregateId < b.aggregateId ? -1 : a.aggregateId > b.aggregateId ? 1 : 0,
976
+ ),
977
+ };
978
+ }
979
+ }
787
980
  // THE ATTRIBUTED-READS GATE (field_provenance.md): per-aggregate attributed-field
788
981
  // sets merged later-wins, SORTED keys (matching the composed emission). Omit-when-empty.
789
982
  let attributedGate: UsdLayer["nomosAttributedGate"];
@@ -1074,7 +1267,7 @@ export function flattenLayeredUsd(layers: readonly UsdLayer[]): UsdDocument {
1074
1267
  };
1075
1268
  }
1076
1269
  }
1077
- return {
1270
+ return {
1078
1271
  path,
1079
1272
  prims: [...prims.keys()].sort().map((p) => prims.get(p)!),
1080
1273
  ...(identityManifest !== undefined ? { nomosIdentityManifest: identityManifest } : {}),
@@ -1085,8 +1278,10 @@ return {
1085
1278
  ...(stableIds !== undefined ? { nomosStableIds: stableIds } : {}),
1086
1279
  ...(iface !== undefined ? { nomosInterface: iface } : {}),
1087
1280
  ...(readGate !== undefined ? { nomosReadGate: readGate } : {}),
1281
+ ...(invariantGate !== undefined ? { nomosInvariantGate: invariantGate } : {}),
1088
1282
  ...(readVisibility !== undefined ? { nomosReadVisibility: readVisibility } : {}),
1089
1283
  ...(encryption !== undefined ? { nomosEncryption: encryption } : {}),
1284
+ ...(capabilities !== undefined ? { nomosCapabilities: capabilities } : {}),
1090
1285
  ...(attributedGate !== undefined ? { nomosAttributedGate: attributedGate } : {}),
1091
1286
  ...(relations !== undefined ? { nomosRelations: relations } : {}),
1092
1287
  ...(relationNamespace !== undefined ? { nomosRelationNamespace: relationNamespace } : {}),
@@ -1118,15 +1313,21 @@ return {
1118
1313
  interface ParsedPrim {
1119
1314
  /** Sanitized prim path, e.g. `/Nomos/guestbook/GuestbookEntry`. */
1120
1315
  readonly path: string;
1121
- readonly attrs: Map<string, string | string[]>;
1316
+ readonly attrs: Map<string, string | string[] | number | boolean>;
1122
1317
  }
1123
1318
 
1124
1319
  const ATTR_STRING = /^custom\s+string\s+([\w:]+)\s*=\s*"((?:[^"\\]|\\.)*)"$/;
1125
1320
  const ATTR_STRING_ARRAY = /^custom\s+string\[\]\s+([\w:]+)\s*=\s*\[(.*)\]$/;
1321
+ const ATTR_NUMBER = /^custom\s+(?:double|int64)\s+([\w:]+)\s*=\s*(-?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)$/;
1322
+ const ATTR_BOOL = /^custom\s+bool\s+([\w:]+)\s*=\s*(true|false|0|1)$/;
1126
1323
  const PRIM_HEADER = /^(?:def|over|class)(?:\s+\w+)?\s+"([^"]+)"/;
1127
1324
 
1128
1325
  function unescapeUsda(s: string): string {
1129
- return s.replace(/\\(.)/g, "$1");
1326
+ try {
1327
+ return JSON.parse(`"${s}"`) as string;
1328
+ } catch {
1329
+ throw new Error("usd-layers: invalid escaped USDA string");
1330
+ }
1130
1331
  }
1131
1332
 
1132
1333
  function parseStringArrayItems(inner: string): string[] {
@@ -1194,10 +1395,98 @@ function parseUsdaPrims(text: string): ParsedPrim[] {
1194
1395
  top.attrs.set(str[1]!, unescapeUsda(str[2]!));
1195
1396
  continue;
1196
1397
  }
1398
+ const number = ATTR_NUMBER.exec(line);
1399
+ if (number !== null) {
1400
+ top.attrs.set(number[1]!, Number(number[2]!));
1401
+ continue;
1402
+ }
1403
+ const bool = ATTR_BOOL.exec(line);
1404
+ if (bool !== null) top.attrs.set(bool[1]!, bool[2] === "true" || bool[2] === "1");
1197
1405
  }
1198
1406
  return prims;
1199
1407
  }
1200
1408
 
1409
+ /** Decode one content-addressed native USD value tree immediately beneath `parentPath`. */
1410
+ function nativeValueAt(prims: readonly ParsedPrim[], parentPath: string, name: string): unknown | undefined {
1411
+ const depth = (path: string) => path.split("/").length;
1412
+ const parent = prims.find((prim) => prim.path === parentPath);
1413
+ const hash = parent?.attrs.get(`nomos:value:${name}`);
1414
+ if (hash === undefined) return undefined;
1415
+ if (typeof hash !== "string" || !/^[0-9a-f]{64}$/.test(hash)) {
1416
+ throw new Error(`usd-layers: native value '${name}' has an invalid content address`);
1417
+ }
1418
+ const rootPath = `${parentPath}/__nms_${primName(name)}_${hash}`;
1419
+ const root = prims.find((prim) => prim.path === rootPath);
1420
+ if (root === undefined) {
1421
+ throw new Error(`usd-layers: native value '${name}' points to missing content '${hash}'`);
1422
+ }
1423
+ const descendants = prims.filter((prim) => prim.path === rootPath || prim.path.startsWith(`${rootPath}/`));
1424
+ const directChildren = (path: string): ParsedPrim[] => descendants.filter((candidate) =>
1425
+ candidate.path.startsWith(`${path}/`) && depth(candidate.path) === depth(path) + 1);
1426
+ const decodeKey = (hex: string, path: string): string => {
1427
+ if (!/^(?:[0-9a-f]{2})*$/.test(hex)) {
1428
+ throw new Error(`usd-layers: native object child '${path}' has an invalid key`);
1429
+ }
1430
+ return Buffer.from(hex, "hex").toString("utf8");
1431
+ };
1432
+ type NativeEntry = { readonly index: number; readonly key?: string; readonly value: unknown };
1433
+ const decode = (prim: ParsedPrim): unknown => {
1434
+ const kind = prim.attrs.get("nomos:containerKind");
1435
+ if (kind !== "scalar" && kind !== "array" && kind !== "object") {
1436
+ throw new Error(`usd-layers: native value container '${prim.path}' has no valid kind`);
1437
+ }
1438
+ const entries: NativeEntry[] = [];
1439
+ for (const [attr, value] of prim.attrs) {
1440
+ const match = /^nomos:(sv|bv|nv|zv):i(\d{6})(?::k([0-9a-f]*))?$/.exec(attr);
1441
+ if (match === null) continue;
1442
+ const scalarKind = match[1]!;
1443
+ const key = match[3] === undefined ? undefined : decodeKey(match[3], prim.path);
1444
+ const scalar = scalarKind === "zv" ? null : value;
1445
+ if ((scalarKind === "sv" && typeof scalar !== "string") ||
1446
+ (scalarKind === "bv" && typeof scalar !== "boolean") ||
1447
+ (scalarKind === "nv" && typeof scalar !== "number") ||
1448
+ (scalarKind === "zv" && value !== true)) {
1449
+ throw new Error(`usd-layers: native scalar '${attr}' has the wrong USD type`);
1450
+ }
1451
+ entries.push({ index: Number(match[2]!), ...(key === undefined ? {} : { key }), value: scalar });
1452
+ }
1453
+ for (const child of directChildren(prim.path)) {
1454
+ const childName = child.path.slice(child.path.lastIndexOf("/") + 1);
1455
+ const match = /^i(\d{6})(?:_k([0-9a-f]*))?$/.exec(childName);
1456
+ if (match === null) {
1457
+ throw new Error(`usd-layers: native child '${child.path}' has an invalid position`);
1458
+ }
1459
+ const key = match[2] === undefined ? undefined : decodeKey(match[2], child.path);
1460
+ entries.push({ index: Number(match[1]!), ...(key === undefined ? {} : { key }), value: decode(child) });
1461
+ }
1462
+ entries.sort((a, b) => a.index - b.index);
1463
+ if (entries.some((entry, index) => entry.index !== index)) {
1464
+ throw new Error(`usd-layers: native value container '${prim.path}' has missing or duplicate positions`);
1465
+ }
1466
+ if (kind === "scalar") {
1467
+ if (entries.length !== 1 || entries[0]!.key !== undefined) {
1468
+ throw new Error(`usd-layers: native scalar container '${prim.path}' must contain exactly one unkeyed value`);
1469
+ }
1470
+ return entries[0]!.value;
1471
+ }
1472
+ if (kind === "array") {
1473
+ if (entries.some((entry) => entry.key !== undefined)) {
1474
+ throw new Error(`usd-layers: native array container '${prim.path}' contains an object key`);
1475
+ }
1476
+ return entries.map((entry) => entry.value);
1477
+ }
1478
+ {
1479
+ const out: Record<string, unknown> = {};
1480
+ for (const entry of entries) {
1481
+ if (entry.key === undefined) throw new Error(`usd-layers: native object container '${prim.path}' contains an unkeyed value`);
1482
+ out[entry.key] = entry.value;
1483
+ }
1484
+ return out;
1485
+ }
1486
+ };
1487
+ return decode(root);
1488
+ }
1489
+
1201
1490
  function attrString(prim: ParsedPrim, name: string): string {
1202
1491
  const v = prim.attrs.get(name);
1203
1492
  if (typeof v !== "string") {
@@ -1218,8 +1507,8 @@ function attrStringArray(prim: ParsedPrim, name: string): string[] {
1218
1507
  * Reconstruct the IR `UsdDocument` from usda text this module emitted — either ONE
1219
1508
  * module layer or a usdcat-FLATTENED stage. Domains are identified by their scope
1220
1509
  * prim (`nomos:kind = "domain"`); every child prim rebuilds from its `nomos:*`
1221
- * attributes into the canonical prim shape (so the result is directly comparable —
1222
- * `JSON.stringify`-byte-comparable — with `emitUsd` / `flattenLayeredUsd` output).
1510
+ * attributes into the canonical prim shape (so canonicalJson comparison with
1511
+ * `emitUsd` / `flattenLayeredUsd` compares law rather than USD formatting).
1223
1512
  */
1224
1513
  export function parseUsdaDocument(text: string): UsdDocument {
1225
1514
  const prims = parseUsdaPrims(text);
@@ -1229,8 +1518,10 @@ export function parseUsdaDocument(text: string): UsdDocument {
1229
1518
  const stableIdsByDomain = new Map<string, UsdLayer["nomosStableIds"]>();
1230
1519
  const interfaceByDomain = new Map<string, UsdLayer["nomosInterface"]>();
1231
1520
  const readGateByDomain = new Map<string, UsdLayer["nomosReadGate"]>();
1521
+ const invariantGateByDomain = new Map<string, UsdLayer["nomosInvariantGate"]>();
1232
1522
  const readVisibilityByDomain = new Map<string, UsdLayer["nomosReadVisibility"]>();
1233
1523
  const encryptionByDomain = new Map<string, UsdLayer["nomosEncryption"]>();
1524
+ const capabilitiesByDomain = new Map<string, UsdLayer["nomosCapabilities"]>();
1234
1525
  const attributedGateByDomain = new Map<string, UsdLayer["nomosAttributedGate"]>();
1235
1526
  const relationsByDomain = new Map<string, UsdLayer["nomosRelations"]>();
1236
1527
  const relationGateByDomain = new Map<string, UsdLayer["nomosRelationGate"]>();
@@ -1252,120 +1543,24 @@ export function parseUsdaDocument(text: string): UsdDocument {
1252
1543
  if (p.attrs.get("nomos:kind") === "domain") {
1253
1544
  const domain = attrString(p, "nomos:domain");
1254
1545
  domainByScope.set(p.path, domain);
1255
- // Holon-owned manifests + gates: the hex-carried layer-level law keys.
1256
- const idHex = p.attrs.get("nomos:identityManifestHex");
1257
- if (typeof idHex === "string") {
1258
- identityManifestByDomain.set(domain, JSON.parse(unhexUtf8(idHex)) as UsdLayer["nomosIdentityManifest"]);
1259
- }
1260
- const rpHex = p.attrs.get("nomos:readProjectionHex");
1261
- if (typeof rpHex === "string") {
1262
- readProjectionByDomain.set(domain, JSON.parse(unhexUtf8(rpHex)) as UsdLayer["nomosReadProjection"]);
1263
- }
1264
- const sidHex = p.attrs.get("nomos:stableIdsHex");
1265
- if (typeof sidHex === "string") {
1266
- stableIdsByDomain.set(domain, JSON.parse(unhexUtf8(sidHex)) as UsdLayer["nomosStableIds"]);
1267
- }
1268
- // THE STRUCTURAL INTERFACE (#72): the on-ledger OFFER, hex-carried per domain layer.
1269
- const ifaceHex = p.attrs.get("nomos:interfaceHex");
1270
- if (typeof ifaceHex === "string") {
1271
- interfaceByDomain.set(domain, JSON.parse(unhexUtf8(ifaceHex)) as UsdLayer["nomosInterface"]);
1272
- }
1273
- const rgHex = p.attrs.get("nomos:readGateHex");
1274
- if (typeof rgHex === "string") {
1275
- readGateByDomain.set(domain, JSON.parse(unhexUtf8(rgHex)) as UsdLayer["nomosReadGate"]);
1276
- }
1277
- // THE READ-VISIBILITY GATE (slice 5.1): the hex-carried per-aggregate cap map.
1278
- const rvHex = p.attrs.get("nomos:readVisibilityHex");
1279
- if (typeof rvHex === "string") {
1280
- readVisibilityByDomain.set(domain, JSON.parse(unhexUtf8(rvHex)) as UsdLayer["nomosReadVisibility"]);
1281
- }
1282
- // THE ENCRYPTION GATE (slice 2): the hex-carried per-aggregate encrypted-field map.
1283
- const encHex = p.attrs.get("nomos:encryptionHex");
1284
- if (typeof encHex === "string") {
1285
- encryptionByDomain.set(domain, JSON.parse(unhexUtf8(encHex)) as UsdLayer["nomosEncryption"]);
1286
- }
1287
- // THE ATTRIBUTED-READS GATE (field_provenance.md): the hex-carried per-aggregate map.
1288
- const attrHex = p.attrs.get("nomos:attributedGateHex");
1289
- if (typeof attrHex === "string") {
1290
- attributedGateByDomain.set(domain, JSON.parse(unhexUtf8(attrHex)) as UsdLayer["nomosAttributedGate"]);
1291
- }
1292
- // THE RELATION SCHEMA (P1): the hex-carried per-type relation rewrite map.
1293
- const relHex = p.attrs.get("nomos:relationsHex");
1294
- if (typeof relHex === "string") {
1295
- relationsByDomain.set(domain, JSON.parse(unhexUtf8(relHex)) as UsdLayer["nomosRelations"]);
1296
- }
1297
- // THE RELATION GATE (P4): the hex-carried directiveId → {relation, object} map.
1298
- const relGateHex = p.attrs.get("nomos:relationGateHex");
1299
- if (typeof relGateHex === "string") {
1300
- relationGateByDomain.set(domain, JSON.parse(unhexUtf8(relGateHex)) as UsdLayer["nomosRelationGate"]);
1301
- }
1302
- // THE RELATION-NAMESPACE ERA KEY (relation-namespacing): the hex-carried qualified-type witness.
1303
- const relNsHex = p.attrs.get("nomos:relationNamespaceHex");
1304
- if (typeof relNsHex === "string") {
1305
- relationNamespaceByDomain.set(domain, JSON.parse(unhexUtf8(relNsHex)) as UsdLayer["nomosRelationNamespace"]);
1306
- }
1307
- // THE READ-PROJECTION-NAMESPACE ERA KEY: the hex-carried qualified-aggregate witness.
1308
- const rpNsHex = p.attrs.get("nomos:readProjectionNamespaceHex");
1309
- if (typeof rpNsHex === "string") {
1310
- readProjectionNamespaceByDomain.set(domain, JSON.parse(unhexUtf8(rpNsHex)) as UsdLayer["nomosReadProjectionNamespace"]);
1311
- }
1312
- // THE ATTESTATION GATE (home_holons §3/§5): the hex-carried attestation-authorized directive set.
1313
- const attGateHex = p.attrs.get("nomos:attestationGateHex");
1314
- if (typeof attGateHex === "string") {
1315
- attestationGateByDomain.set(domain, JSON.parse(unhexUtf8(attGateHex)) as UsdLayer["nomosAttestationGate"]);
1316
- }
1317
- // THE DELEGATED-AUTHORITY GATE (delegated_authority_births.md): the hex-carried delegation spec.
1318
- const delGateHex = p.attrs.get("nomos:delegationGateHex");
1319
- if (typeof delGateHex === "string") {
1320
- delegationGateByDomain.set(domain, JSON.parse(unhexUtf8(delGateHex)) as UsdLayer["nomosDelegationGate"]);
1321
- }
1322
- // THE ATTESTED-READ GATE (slice B): the hex-carried trust-edge declaration.
1323
- const attReadGateHex = p.attrs.get("nomos:attestedReadGateHex");
1324
- if (typeof attReadGateHex === "string") {
1325
- attestedReadGateByDomain.set(domain, JSON.parse(unhexUtf8(attReadGateHex)) as UsdLayer["nomosAttestedReadGate"]);
1326
- }
1327
- // THE BIRTH GATE (G3): the hex-carried `.births()` directive set.
1328
- const birthGateHex = p.attrs.get("nomos:birthGateHex");
1329
- if (typeof birthGateHex === "string") {
1330
- birthGateByDomain.set(domain, JSON.parse(unhexUtf8(birthGateHex)) as UsdLayer["nomosBirthGate"]);
1331
- }
1332
- // THE BIRTH-RECORD GATE (metered birth, A1): the hex-carried record declaration.
1333
- const birthRecordGateHex = p.attrs.get("nomos:birthRecordGateHex");
1334
- if (typeof birthRecordGateHex === "string") {
1335
- birthRecordGateByDomain.set(domain, JSON.parse(unhexUtf8(birthRecordGateHex)) as UsdLayer["nomosBirthRecordGate"]);
1336
- }
1337
- // THE PEER-SOVEREIGN ROOT-OF-TRUST: the hex-carried parent-attested-birth era marker.
1338
- const pabHex = p.attrs.get("nomos:parentAttestedBirthHex");
1339
- if (typeof pabHex === "string") {
1340
- parentAttestedBirthByDomain.set(domain, JSON.parse(unhexUtf8(pabHex)) as UsdLayer["nomosParentAttestedBirth"]);
1341
- }
1342
- // THE KEYLESS VERIFIED-FIRST-DEVICE GATE: the hex-carried era marker.
1343
- const vfeHex = p.attrs.get("nomos:verifiedFirstEnrollHex");
1344
- if (typeof vfeHex === "string") {
1345
- verifiedFirstEnrollByDomain.set(domain, JSON.parse(unhexUtf8(vfeHex)) as UsdLayer["nomosVerifiedFirstEnroll"]);
1346
- }
1347
- const ecgHex = p.attrs.get("nomos:eraCertGateHex");
1348
- if (typeof ecgHex === "string") {
1349
- eraCertGateByDomain.set(domain, JSON.parse(unhexUtf8(ecgHex)) as UsdLayer["nomosEraCertGate"]);
1350
- }
1351
- // FIRST-CLASS VALUE OBJECTS: the hex-carried descriptor set (descriptive, not a gate).
1352
- const voHex = p.attrs.get("nomos:valueObjectsHex");
1353
- if (typeof voHex === "string") {
1354
- valueObjectsByDomain.set(domain, JSON.parse(unhexUtf8(voHex)) as UsdLayer["nomosValueObjects"]);
1355
- }
1356
- const frHex = p.attrs.get("nomos:foreignRefsHex");
1357
- if (typeof frHex === "string") {
1358
- foreignRefsByDomain.set(domain, JSON.parse(unhexUtf8(frHex)) as UsdLayer["nomosForeignRefs"]);
1359
- }
1360
- // DOMAIN CONTRACTS: the hex-carried public DTO descriptor set (descriptive, not a gate).
1361
- const ctHex = p.attrs.get("nomos:contractsHex");
1362
- if (typeof ctHex === "string") {
1363
- contractsByDomain.set(domain, JSON.parse(unhexUtf8(ctHex)) as UsdLayer["nomosContracts"]);
1364
- }
1365
- const flowGateHex = p.attrs.get("nomos:flowGateHex");
1366
- if (typeof flowGateHex === "string") {
1367
- flowGateByDomain.set(domain, JSON.parse(unhexUtf8(flowGateHex)) as UsdLayer["nomosFlowGate"]);
1368
- }
1546
+ // Structured law is native USD; no embedded serialization has an alternate vote.
1547
+ const native = (name: string): unknown | undefined => nativeValueAt(prims, p.path, name);
1548
+ const set = <T>(name: string, target: Map<string, T>): void => {
1549
+ const value = native(name);
1550
+ if (value !== undefined) target.set(domain, value as T);
1551
+ };
1552
+ set("identityManifest", identityManifestByDomain); set("readProjection", readProjectionByDomain);
1553
+ set("stableIds", stableIdsByDomain); set("interface", interfaceByDomain); set("readGate", readGateByDomain);
1554
+ set("invariantGate", invariantGateByDomain); set("readVisibility", readVisibilityByDomain);
1555
+ set("encryption", encryptionByDomain); set("capabilities", capabilitiesByDomain);
1556
+ set("attributedGate", attributedGateByDomain); set("relations", relationsByDomain);
1557
+ set("relationGate", relationGateByDomain); set("relationNamespace", relationNamespaceByDomain);
1558
+ set("readProjectionNamespace", readProjectionNamespaceByDomain); set("attestationGate", attestationGateByDomain);
1559
+ set("delegationGate", delegationGateByDomain); set("attestedReadGate", attestedReadGateByDomain);
1560
+ set("birthGate", birthGateByDomain); set("birthRecordGate", birthRecordGateByDomain);
1561
+ set("parentAttestedBirth", parentAttestedBirthByDomain); set("verifiedFirstEnroll", verifiedFirstEnrollByDomain);
1562
+ set("eraCertGate", eraCertGateByDomain); set("valueObjects", valueObjectsByDomain);
1563
+ set("foreignRefs", foreignRefsByDomain); set("contracts", contractsByDomain); set("flowGate", flowGateByDomain);
1369
1564
  }
1370
1565
  }
1371
1566
 
@@ -1388,6 +1583,9 @@ export function parseUsdaDocument(text: string): UsdDocument {
1388
1583
  for (const p of prims) {
1389
1584
  const kind = p.attrs.get("nomos:kind");
1390
1585
  if (typeof kind !== "string" || kind === "domain") continue;
1586
+ // A self-contained runtime package also carries the obscure framework-owned plan index at a
1587
+ // separate root. It is USD, but it is not domain law and must not be projected into the IR.
1588
+ if (!p.path.startsWith("/Nomos/")) continue;
1391
1589
  const scopePath = p.path.slice(0, p.path.lastIndexOf("/"));
1392
1590
  const domain = domainByScope.get(scopePath);
1393
1591
  if (domain === undefined) {
@@ -1401,21 +1599,18 @@ export function parseUsdaDocument(text: string): UsdDocument {
1401
1599
  const schema: WireSchema = {};
1402
1600
  for (const [name, value] of p.attrs) {
1403
1601
  if (name.startsWith("nomos:driver:") && typeof value === "string") {
1404
- schema[name.slice("nomos:driver:".length)] = parseDriverToken(value);
1602
+ schema[parseFieldToken(name.slice("nomos:driver:".length))] = parseDriverToken(value);
1405
1603
  }
1406
1604
  }
1407
1605
  b.prims.push({ kind: "aggregate", path: irPath, type: id, schema: sortedSchema(schema) });
1408
1606
  } else if (kind === "directive") {
1409
1607
  const reads = p.attrs.has("nomos:reads") ? attrStringArray(p, "nomos:reads") : undefined;
1410
- const emits = p.attrs.has("nomos:emitsHex")
1411
- ? (JSON.parse(unhexUtf8(attrString(p, "nomos:emitsHex"))) as Record<string, { max?: number }>)
1412
- : undefined;
1413
- const secondaryOutputs = p.attrs.has("nomos:secondaryOutputsHex")
1414
- ? (JSON.parse(unhexUtf8(attrString(p, "nomos:secondaryOutputsHex"))) as { target: string; marker: WireRefMode }[])
1415
- : undefined;
1416
- const payloadJsonSchema = p.attrs.has("nomos:payloadJsonSchemaHex")
1417
- ? (JSON.parse(unhexUtf8(attrString(p, "nomos:payloadJsonSchemaHex"))) as unknown)
1418
- : undefined;
1608
+ const nativeEmits = nativeValueAt(prims, p.path, "emits");
1609
+ const nativeSecondaryOutputs = nativeValueAt(prims, p.path, "secondaryOutputs");
1610
+ const nativePayloadJsonSchema = nativeValueAt(prims, p.path, "payloadJsonSchema");
1611
+ const emits = nativeEmits as Record<string, { max?: number }> | undefined;
1612
+ const secondaryOutputs = nativeSecondaryOutputs as { target: string; marker: WireRefMode }[] | undefined;
1613
+ const payloadJsonSchema = nativePayloadJsonSchema;
1419
1614
  b.prims.push({
1420
1615
  kind: "directive",
1421
1616
  path: irPath,
@@ -1434,18 +1629,20 @@ export function parseUsdaDocument(text: string): UsdDocument {
1434
1629
  returns: attrString(p, "nomos:returns"),
1435
1630
  });
1436
1631
  } else if (kind === "derived") {
1632
+ const nativeReturns = nativeValueAt(prims, p.path, "returns");
1437
1633
  b.deriveds.push({
1438
1634
  id,
1439
1635
  of: attrString(p, "nomos:of"),
1440
- returns: JSON.parse(unhexUtf8(attrString(p, "nomos:returnsHex"))) as UsdDerived["returns"],
1636
+ returns: nativeReturns as UsdDerived["returns"],
1441
1637
  });
1442
1638
  } else if (kind === "combined") {
1639
+ const nativeReturns = nativeValueAt(prims, p.path, "returns");
1443
1640
  b.combineds.push({
1444
1641
  id,
1445
1642
  of: attrString(p, "nomos:of"),
1446
1643
  refField: attrString(p, "nomos:refField"),
1447
1644
  reads: attrString(p, "nomos:readsFrom"),
1448
- returns: JSON.parse(unhexUtf8(attrString(p, "nomos:returnsHex"))) as UsdCombined["returns"],
1645
+ returns: nativeReturns as UsdCombined["returns"],
1449
1646
  });
1450
1647
  } else {
1451
1648
  throw new Error(`usd-layers: prim ${p.path} has unknown nomos:kind '${kind}'`);
@@ -1462,8 +1659,10 @@ export function parseUsdaDocument(text: string): UsdDocument {
1462
1659
  const sids = stableIdsByDomain.get(domain);
1463
1660
  const iface = interfaceByDomain.get(domain);
1464
1661
  const rg = readGateByDomain.get(domain);
1662
+ const invariantGate = invariantGateByDomain.get(domain);
1465
1663
  const rv = readVisibilityByDomain.get(domain);
1466
1664
  const enc = encryptionByDomain.get(domain);
1665
+ const capabilities = capabilitiesByDomain.get(domain);
1467
1666
  const attrGate = attributedGateByDomain.get(domain);
1468
1667
  const rel = relationsByDomain.get(domain);
1469
1668
  const relGate = relationGateByDomain.get(domain);
@@ -1492,8 +1691,10 @@ export function parseUsdaDocument(text: string): UsdDocument {
1492
1691
  ...(sids !== undefined ? { nomosStableIds: sids } : {}),
1493
1692
  ...(iface !== undefined ? { nomosInterface: iface } : {}),
1494
1693
  ...(rg !== undefined ? { nomosReadGate: rg } : {}),
1694
+ ...(invariantGate !== undefined ? { nomosInvariantGate: invariantGate } : {}),
1495
1695
  ...(rv !== undefined ? { nomosReadVisibility: rv } : {}),
1496
1696
  ...(enc !== undefined ? { nomosEncryption: enc } : {}),
1697
+ ...(capabilities !== undefined ? { nomosCapabilities: capabilities } : {}),
1497
1698
  ...(attrGate !== undefined ? { nomosAttributedGate: attrGate } : {}),
1498
1699
  ...(rel !== undefined ? { nomosRelations: rel } : {}),
1499
1700
  ...(relNs !== undefined ? { nomosRelationNamespace: relNs } : {}),