@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
@@ -29,12 +29,12 @@
29
29
  * generated, so they stay reproducible from the domain definitions.
30
30
  */
31
31
  import { z } from "zod";
32
- import { voMetaOf, collectPayloadValueObjectDescriptors, idOfMetaOf, idOfTargetKindOf, brandedIdMetaOf, NOMOS_UNIT_BRAND } from "./value_object.js";
32
+ import { voMetaOf, collectPayloadValueObjectDescriptors, idOfMetaOf, idOfTargetKindOf, versionedRefOfMetaOf, brandedIdMetaOf, NOMOS_UNIT_BRAND } from "./value_object.js";
33
33
  import { recursiveTypeBaseName, zodUnwrapLazy } from "./zod_lazy.js";
34
34
  import type { AggregateHandle } from "./aggregate.js";
35
35
  import type { WorkspaceInvariantDecl } from "./framework/workspace_invariant.js";
36
36
  import type { RelationTypeDecl } from "./relation_schema.js";
37
- import type { Field, FieldKind, ValueObjectDescriptor, ValueObjectFieldDescriptor } from "./fields.js";
37
+ import { isSha256DigestSchema, type Field, type FieldKind, type ValueObjectDescriptor, type ValueObjectFieldDescriptor } from "./fields.js";
38
38
  import { admitsNull, nullMetaOf, nullDocLine } from "./null_meta.js";
39
39
  import type { Directive } from "./directive.js";
40
40
  import { executeDirectiveToIntent } from "./wire_encode.js";
@@ -84,6 +84,7 @@ export type ToJsonWrap =
84
84
  | "voList" // a list of value objects — emit `.map((e) => e.toJson()).toList()`.
85
85
  | "wrapped" // a standalone branded-id or unit extension type — emit `.value`.
86
86
  | "ref" // an aggregate-specific NomosRef<T> — emit its `.id` wire scalar.
87
+ | "versionedRef" // a publication pin + stable series — emit the atomic address pair.
87
88
  | "recipient"; // a framework-created Recipient<User> — emit its private delivery address.
88
89
 
89
90
  export interface PayloadFieldSpec {
@@ -91,10 +92,20 @@ export interface PayloadFieldSpec {
91
92
  dartType: string;
92
93
  /** Aggregate wire type when this field is a NomosRef<T>. */
93
94
  referenceType?: string;
95
+ /** Versioned publication/series metadata for payload decode and framework codegen. */
96
+ versionedRefTarget?: string;
97
+ versionedRefSeries?: string;
98
+ versionedRefCurrentField?: string;
99
+ versionedRefTargetInput?: string;
100
+ versionedRefSeriesInput?: string;
101
+ versionedRefTargetWireType?: string;
102
+ versionedRefSeriesWireType?: string;
94
103
  /** for enums: the Dart enum type name + raw values. */
95
104
  enumValues?: string[];
96
105
  /** how this field serialises into `nmsInternalToPayloadJson()`. */
97
106
  toJson: ToJsonWrap;
107
+ /** Wire scalar under a nominal wrapper. Keeps string semantics distinct from numeric units. */
108
+ wrappedScalarKind?: "string" | "number";
98
109
  /**
99
110
  * Whether the payload field is OPTIONAL (Zod `.optional()`/`.default()`). Optional
100
111
  * fields are nullable in Dart and OMITTED from `nmsInternalToPayloadJson()` when null — so the
@@ -118,6 +129,60 @@ export interface DartBusinessRecordInput {
118
129
  readonly dartType: string;
119
130
  }
120
131
 
132
+ /**
133
+ * A payload NomosRef whose workspace locator is carried by the sibling field of
134
+ * a declared foreignRef output. The public action accepts the one atomic typed
135
+ * reference; codegen derives both wire fields so tenant code cannot split them.
136
+ */
137
+ export interface DartForeignReferenceInput {
138
+ readonly idField: string;
139
+ readonly workspaceField: string;
140
+ readonly optional: boolean;
141
+ }
142
+
143
+ export function dartForeignReferenceInputs(
144
+ directive: AnyDirective,
145
+ aggregates: readonly AggregateHandle[],
146
+ ): DartForeignReferenceInput[] {
147
+ const specs = introspectPayload(directive.payloadSchema as z.ZodTypeAny, new Map());
148
+ const payloadNames = new Set(specs.map((spec) => spec.name));
149
+ const recordInputIds = new Set((directive.businessRecordInputs ?? []).map((input) => input.field));
150
+ const outputIds = new Set([
151
+ directive.aggregateId,
152
+ ...(directive.secondaryOutputs ?? []).map((output) => output.aggregateId),
153
+ ]);
154
+ const outputs = aggregates.filter((aggregate) => outputIds.has(aggregate.id));
155
+
156
+ return specs.flatMap((spec) => {
157
+ if (spec.toJson !== "ref" || recordInputIds.has(spec.name)) return [];
158
+ const matches = outputs.flatMap((aggregate) => {
159
+ const field = aggregate.fields[spec.name] as Field | undefined;
160
+ if (
161
+ field?.kind !== "ref" ||
162
+ field.foreignRef === undefined ||
163
+ (spec.referenceType !== undefined && field.foreignRef.targetAggregateId !== spec.referenceType)
164
+ ) {
165
+ return [];
166
+ }
167
+ return [field.foreignRef.workspaceField];
168
+ });
169
+ const workspaceFields = [...new Set(matches)];
170
+ if (workspaceFields.length === 0) return [];
171
+ if (workspaceFields.length > 1) {
172
+ throw new Error(
173
+ `${directive.id}: foreign reference payload '${spec.name}' maps to more than one workspace field`,
174
+ );
175
+ }
176
+ const workspaceField = workspaceFields[0]!;
177
+ if (!payloadNames.has(workspaceField)) {
178
+ throw new Error(
179
+ `${directive.id}: foreign reference payload '${spec.name}' must carry its declared locator '${workspaceField}'`,
180
+ );
181
+ }
182
+ return [{ idField: spec.name, workspaceField, optional: spec.optional }];
183
+ });
184
+ }
185
+
121
186
  /**
122
187
  * Resolve `.fromRecord(field)` against the directive's target aggregate. The
123
188
  * aggregate owns the foreign-reference pairing, so authors name only the
@@ -378,6 +443,31 @@ function introspectField(
378
443
  enumDartTypes: Map<string, string>,
379
444
  lazyNames?: Map<z.ZodTypeAny, string>,
380
445
  ): Omit<PayloadFieldSpec, "optional"> {
446
+ const versioned = versionedRefOfMetaOf(zt);
447
+ if (versioned !== undefined) {
448
+ const target = versioned.targetKind === "contract"
449
+ ? contractTypeName(versioned.targetAggregateType)
450
+ : concreteClassName(versioned.targetAggregateType);
451
+ const series = versioned.seriesKind === "contract"
452
+ ? contractTypeName(versioned.seriesAggregateType)
453
+ : concreteClassName(versioned.seriesAggregateType);
454
+ return {
455
+ name,
456
+ dartType: `VersionedNomosRef<${target}, ${series}>`,
457
+ toJson: "versionedRef",
458
+ versionedRefTarget: target,
459
+ versionedRefSeries: series,
460
+ versionedRefCurrentField: versioned.currentField,
461
+ // The pinned version normally comes from the stable series' compiled
462
+ // current pointer, so it already carries the public contract type.
463
+ versionedRefTargetInput: target,
464
+ versionedRefSeriesInput: versioned.seriesKind === "contract"
465
+ ? concreteClassName(versioned.seriesAggregateType)
466
+ : series,
467
+ versionedRefTargetWireType: versioned.targetAggregateType,
468
+ versionedRefSeriesWireType: versioned.seriesAggregateType,
469
+ };
470
+ }
381
471
  if (recipientMetaOf(zt) !== undefined) {
382
472
  return { name, dartType: "Recipient<User>", toJson: "recipient" };
383
473
  }
@@ -425,10 +515,13 @@ function introspectField(
425
515
  toJson: "ref",
426
516
  };
427
517
  }
518
+ if (isSha256DigestSchema(zt)) {
519
+ return { name, dartType: "Sha256Digest", toJson: "wrapped", wrappedScalarKind: "string" };
520
+ }
428
521
  // brandedId("SiteId"): a standalone nominal string → the generated extension type.
429
522
  const brand = brandedIdMetaOf(zt);
430
523
  if (brand !== undefined) {
431
- return { name, dartType: brand, toJson: "wrapped" };
524
+ return { name, dartType: brand, toJson: "wrapped", wrappedScalarKind: "string" };
432
525
  }
433
526
  return { name, dartType: "String", toJson: "scalar" };
434
527
  }
@@ -442,7 +535,7 @@ function introspectField(
442
535
  {
443
536
  const unitName = (zt as unknown as Record<symbol, unknown>)[NOMOS_UNIT_BRAND];
444
537
  if (typeof unitName === "string") {
445
- return { name, dartType: unitName, toJson: "wrapped" };
538
+ return { name, dartType: unitName, toJson: "wrapped", wrappedScalarKind: "number" };
446
539
  }
447
540
  }
448
541
  return {
@@ -531,6 +624,8 @@ interface AggFieldSpec {
531
624
  wireName: string;
532
625
  kind: MaterializedFieldKind;
533
626
  dartType: string;
627
+ /** A canonical semantic string wrapper whose wire representation remains String. */
628
+ semanticString?: "sha256";
534
629
  enumValues?: string[];
535
630
  /** For an own-id string field represented as `NomosRef<TAggregate>`. */
536
631
  valueClass?: string;
@@ -561,6 +656,16 @@ interface AggFieldSpec {
561
656
  * WORKSPACE id (the locator's `workspace`, dynamic per row). `undefined` for a non-foreignRef field.
562
657
  */
563
658
  foreignRefWorkspaceField?: string;
659
+ /** A first-class atomic custody address (`t.nomosRef`). */
660
+ nomosRefTarget?: string;
661
+ /** The target aggregate's domain-qualified wire type. */
662
+ nomosRefAggregateType?: string;
663
+ /** A publication pin + stable series (`t.versionedForeignRef`). */
664
+ versionedRefTarget?: string;
665
+ /** The stable series' generated Dart class. */
666
+ versionedRefSeries?: string;
667
+ /** Compiled field on the series which carries its current publication. */
668
+ versionedRefCurrentField?: string;
564
669
  /**
565
670
  * Whether the field is OPTIONAL on a folded aggregate — derived from the DSL field
566
671
  * schema (GAP 1). A `set`/`map` is a COLLECTION: it is ALWAYS empty-defaultable
@@ -737,6 +842,9 @@ function readKeyParamSpec(
737
842
  if (fieldSpec.kind === "int") {
738
843
  return { dartType: "int", valueExpr: paramName };
739
844
  }
845
+ if (fieldSpec.semanticString === "sha256") {
846
+ return { dartType: "Sha256Digest", valueExpr: `${paramName}.value` };
847
+ }
740
848
  return { dartType: "String", valueExpr: paramName };
741
849
  }
742
850
 
@@ -845,8 +953,13 @@ function aggFieldSpec(
845
953
  name,
846
954
  wireName,
847
955
  kind: field.kind,
848
- dartType: valueClass === undefined ? "String" : `NomosRef<${valueClass}>`,
956
+ dartType: valueClass === undefined
957
+ ? isSha256DigestSchema(field.zod as z.ZodTypeAny) ? "Sha256Digest" : "String"
958
+ : `NomosRef<${valueClass}>`,
849
959
  optional,
960
+ ...(valueClass === undefined && isSha256DigestSchema(field.zod as z.ZodTypeAny)
961
+ ? { semanticString: "sha256" as const }
962
+ : {}),
850
963
  ...(valueClass !== undefined && valueAggregateType !== undefined
851
964
  ? { valueClass, valueAggregateType }
852
965
  : {}),
@@ -876,6 +989,40 @@ function aggFieldSpec(
876
989
  ...(field.jsonShape !== undefined ? { jsonShape: field.jsonShape } : {}),
877
990
  };
878
991
  case "ref": {
992
+ if (field.versionedForeignRef !== undefined) {
993
+ const version = field.versionedForeignRef.targetIsContract === true
994
+ ? contractTypeName(field.versionedForeignRef.targetAggregateId)
995
+ : refIdClassForAggregate(field.versionedForeignRef.targetAggregateId);
996
+ const series = field.versionedForeignRef.seriesIsContract === true
997
+ ? contractTypeName(field.versionedForeignRef.seriesAggregateId)
998
+ : refIdClassForAggregate(field.versionedForeignRef.seriesAggregateId);
999
+ return {
1000
+ name,
1001
+ wireName,
1002
+ kind: field.kind,
1003
+ dartType: `VersionedNomosRef<${version}, ${series}>`,
1004
+ optional,
1005
+ versionedRefTarget: version,
1006
+ versionedRefSeries: series,
1007
+ versionedRefCurrentField: field.versionedForeignRef.currentField,
1008
+ };
1009
+ }
1010
+ if (field.nomosRef !== undefined) {
1011
+ const target = field.nomosRef.targetIsContract === true
1012
+ ? contractTypeName(field.nomosRef.targetAggregateId)
1013
+ : refIdClassForAggregate(field.nomosRef.targetAggregateId);
1014
+ return {
1015
+ name,
1016
+ wireName,
1017
+ kind: field.kind,
1018
+ dartType: `NomosRef<${target}>`,
1019
+ optional,
1020
+ nomosRefTarget: target,
1021
+ nomosRefAggregateType: aggregateWireTypeForAggregate(
1022
+ field.nomosRef.targetAggregateId,
1023
+ ),
1024
+ };
1025
+ }
879
1026
  // A `t.foreignRef` field is a TYPED CROSS-WORKSPACE LOCATOR (not an atomic FK): surface it as
880
1027
  // `ForeignRef<Target>` (the shared `nomos_types` runtime type) — the raw stored id + the sibling
881
1028
  // workspace field give the app `resolve`/`watch`/`attest` on demand, without cloning the other holon.
@@ -1708,7 +1855,7 @@ function dartTypeOfVoField(f: ValueObjectFieldDescriptor, voName: string, fieldN
1708
1855
  case "string":
1709
1856
  return f.idOf !== undefined
1710
1857
  ? `NomosRef<${f.idOfContract === true ? contractTypeName(f.idOf) : concreteClassName(f.idOf)}>`
1711
- : f.brand ?? "String"; // a brandedId(...) → the generated extension-type wrapper
1858
+ : f.digest === "sha256" ? "Sha256Digest" : f.brand ?? "String";
1712
1859
  case "number":
1713
1860
  // a UNIT-tagged number is the generated zero-cost extension type; `.int()` is Dart int.
1714
1861
  if (f.unit !== undefined) return f.unit;
@@ -1745,6 +1892,7 @@ function voFromExpr(
1745
1892
  const target = f.idOfContract === true ? contractTypeName(f.idOf) : concreteClassName(f.idOf);
1746
1893
  return `NmsInternalNomosRefs.create<${target}>(workspace: '', type: '${f.idOf}', id: ${expr} as String)`;
1747
1894
  }
1895
+ if (f.digest === "sha256") return `Sha256Digest(${expr} as String)`;
1748
1896
  return f.brand !== undefined ? `${f.brand}(${expr} as String)` : `${expr} as String`;
1749
1897
  case "enum":
1750
1898
  // TOLERANT typed decode — an unknown wire value becomes the sentinel case.
@@ -1777,6 +1925,7 @@ function voToJsonExpr(f: ValueObjectFieldDescriptor, expr: string, depth = 0): s
1777
1925
  return f.unit !== undefined ? `${expr}.value` : expr;
1778
1926
  case "string":
1779
1927
  if (f.idOf !== undefined) return `NmsInternalNomosRefs.id(${expr})`;
1928
+ if (f.digest === "sha256") return `${expr}.value`;
1780
1929
  return f.brand !== undefined ? `${expr}.value` : expr; // a brandedId extension type → its wire string
1781
1930
  case "boolean":
1782
1931
  case "literal":
@@ -2759,6 +2908,26 @@ function foreignRefExpr(s: AggFieldSpec, aggregate: string): string {
2759
2908
  : build(idReader);
2760
2909
  }
2761
2910
 
2911
+ function atomicNomosRefExpr(s: AggFieldSpec): string {
2912
+ const target = s.nomosRefTarget as string;
2913
+ const raw = `data['${s.wireName}']`;
2914
+ return s.optional
2915
+ ? `() { final v = ${raw}; return v == null ? null : NmsInternalNomosRefs.fromJson<${target}>(v); }()`
2916
+ : `NmsInternalNomosRefs.fromJson<${target}>(${raw})`;
2917
+ }
2918
+
2919
+ function versionedNomosRefExpr(s: AggFieldSpec): string {
2920
+ const version = s.versionedRefTarget as string;
2921
+ const series = s.versionedRefSeries as string;
2922
+ const currentField = JSON.stringify(s.versionedRefCurrentField);
2923
+ const build = (value: string): string =>
2924
+ `() { final m = (${value} as Map).cast<String, Object?>(); return NmsInternalVersionedNomosRefs.create<${version}, ${series}>(pinned: NmsInternalNomosRefs.fromJson<${version}>(m['pinned']), series: NmsInternalNomosRefs.fromJson<${series}>(m['series']), currentField: ${currentField}); }()`;
2925
+ const raw = `data['${s.wireName}']`;
2926
+ return s.optional
2927
+ ? `() { final v = ${raw}; return v == null ? null : ${build("v")}; }()`
2928
+ : build(raw);
2929
+ }
2930
+
2762
2931
  function parseField(s: AggFieldSpec, aggregate: string): string {
2763
2932
  // A `.optional()` scalar reads through the `…OrNull` tolerant reader (returns null
2764
2933
  // when the flat `data` lacks it — a freshly-folded aggregate need not have folded
@@ -2767,6 +2936,8 @@ function parseField(s: AggFieldSpec, aggregate: string): string {
2767
2936
  const opt = s.optional;
2768
2937
  switch (s.kind) {
2769
2938
  case "ref": {
2939
+ if (s.versionedRefTarget !== undefined) return versionedNomosRefExpr(s);
2940
+ if (s.nomosRefTarget !== undefined) return atomicNomosRefExpr(s);
2770
2941
  // A `t.foreignRef` field decodes to the shared `ForeignRef<Target>` locator (raw id + sibling
2771
2942
  // workspace field + the target `fromJson`), not a same-workspace NomosRef.
2772
2943
  if (s.foreignRefTarget !== undefined) return foreignRefExpr(s, aggregate);
@@ -2794,9 +2965,17 @@ function parseField(s: AggFieldSpec, aggregate: string): string {
2794
2965
  ? `() { final v = ${reader}; return v == null ? null : NmsInternalNomosRefs.create<${s.valueClass}>(workspace: _location, type: '${s.valueAggregateType}', id: v); }()`
2795
2966
  : `NmsInternalNomosRefs.create<${s.valueClass}>(workspace: _location, type: '${s.valueAggregateType}', id: ${reader})`;
2796
2967
  }
2797
- return opt
2798
- ? `readStrOrNull(data, '${s.wireName}')`
2799
- : `readStr(data, '${aggregate}', '${s.wireName}')`;
2968
+ {
2969
+ const reader = opt
2970
+ ? `readStrOrNull(data, '${s.wireName}')`
2971
+ : `readStr(data, '${aggregate}', '${s.wireName}')`;
2972
+ if (s.semanticString === "sha256") {
2973
+ return opt
2974
+ ? `() { final v = ${reader}; return v == null ? null : Sha256Digest(v); }()`
2975
+ : `Sha256Digest(${reader})`;
2976
+ }
2977
+ return reader;
2978
+ }
2800
2979
  case "json":
2801
2980
  // A NAMED VALUE OBJECT reads the projection's decoded object and wraps it in its
2802
2981
  // generated class (the EvidenceRef pattern). A plain `json`-kind field is typed
@@ -2855,6 +3034,15 @@ function parseField(s: AggFieldSpec, aggregate: string): string {
2855
3034
  function slotDecodeCallback(s: AggFieldSpec, mode: "aggregate" | "readModel"): string {
2856
3035
  switch (s.kind) {
2857
3036
  case "ref":
3037
+ if (s.versionedRefTarget !== undefined) {
3038
+ const version = s.versionedRefTarget;
3039
+ const series = s.versionedRefSeries as string;
3040
+ const currentField = JSON.stringify(s.versionedRefCurrentField);
3041
+ return `(v) { final m = (v as Map).cast<String, Object?>(); return NmsInternalVersionedNomosRefs.create<${version}, ${series}>(pinned: NmsInternalNomosRefs.fromJson<${version}>(m['pinned']), series: NmsInternalNomosRefs.fromJson<${series}>(m['series']), currentField: ${currentField}); }`;
3042
+ }
3043
+ if (s.nomosRefTarget !== undefined) {
3044
+ return `(v) => NmsInternalNomosRefs.fromJson<${s.nomosRefTarget}>(v)`;
3045
+ }
2858
3046
  // A null-admitting `t.foreignRef` (exotic): the sibling workspace field is not reachable from this
2859
3047
  // single leaf, so the locator carries the raw id with an empty workspace (resolves to not-found until
2860
3048
  // read through the normal field path). A plain ref keeps its typed-id decode.
@@ -2867,8 +3055,11 @@ function slotDecodeCallback(s: AggFieldSpec, mode: "aggregate" | "readModel"): s
2867
3055
  : `(v) => v as String`;
2868
3056
  case "position":
2869
3057
  case "string":
2870
- return s.valueClass !== undefined
2871
- ? `(v) => NmsInternalNomosRefs.create<${s.valueClass}>(workspace: _location, type: '${s.valueAggregateType}', id: v as String)`
3058
+ if (s.valueClass !== undefined) {
3059
+ return `(v) => NmsInternalNomosRefs.create<${s.valueClass}>(workspace: _location, type: '${s.valueAggregateType}', id: v as String)`;
3060
+ }
3061
+ return s.semanticString === "sha256"
3062
+ ? `(v) => Sha256Digest(v as String)`
2872
3063
  : `(v) => v as String`;
2873
3064
  case "json":
2874
3065
  if (s.voClass !== undefined) {
@@ -2919,6 +3110,7 @@ export function readModelClassName(aggregateType: string): string {
2919
3110
  function readModelDartType(s: AggFieldSpec): string {
2920
3111
  switch (s.kind) {
2921
3112
  case "ref":
3113
+ if (s.versionedRefTarget !== undefined || s.nomosRefTarget !== undefined) return s.dartType;
2922
3114
  // A `t.foreignRef` field surfaces as the typed cross-workspace `ForeignRef<Target>` locator; a plain
2923
3115
  // same-workspace ref stays `NomosRef<Target>` (or a bare String when the target is unknown).
2924
3116
  if (s.foreignRefTarget !== undefined) return `ForeignRef<${s.foreignRefTarget}>`;
@@ -2936,7 +3128,7 @@ function readModelDartType(s: AggFieldSpec): string {
2936
3128
  // so these labels are unreachable at runtime — present for FieldKind exhaustiveness.
2937
3129
  case "position":
2938
3130
  case "string":
2939
- return s.valueClass === undefined ? "String" : `NomosRef<${s.valueClass}>`;
3131
+ return s.valueClass === undefined ? s.dartType : `NomosRef<${s.valueClass}>`;
2940
3132
  case "counter":
2941
3133
  case "int":
2942
3134
  return "int";
@@ -2984,6 +3176,8 @@ function parseReadModelField(s: AggFieldSpec, aggregate: string): string {
2984
3176
  const opt = s.optional;
2985
3177
  switch (s.kind) {
2986
3178
  case "ref": {
3179
+ if (s.versionedRefTarget !== undefined) return versionedNomosRefExpr(s);
3180
+ if (s.nomosRefTarget !== undefined) return atomicNomosRefExpr(s);
2987
3181
  // A `t.foreignRef` field decodes to the shared `ForeignRef<Target>` locator (same as the aggregate
2988
3182
  // class); a plain same-workspace ref stays `NomosRef<Target>`.
2989
3183
  if (s.foreignRefTarget !== undefined) return foreignRefExpr(s, aggregate);
@@ -3005,9 +3199,17 @@ function parseReadModelField(s: AggFieldSpec, aggregate: string): string {
3005
3199
  ? `() { final v = ${reader}; return v == null ? null : NmsInternalNomosRefs.create<${s.valueClass}>(workspace: _location, type: '${s.valueAggregateType}', id: v); }()`
3006
3200
  : `NmsInternalNomosRefs.create<${s.valueClass}>(workspace: _location, type: '${s.valueAggregateType}', id: ${reader})`;
3007
3201
  }
3008
- return opt
3009
- ? `readStrOrNull(data, '${s.wireName}')`
3010
- : `readStr(data, '${aggregate}', '${s.wireName}')`;
3202
+ {
3203
+ const reader = opt
3204
+ ? `readStrOrNull(data, '${s.wireName}')`
3205
+ : `readStr(data, '${aggregate}', '${s.wireName}')`;
3206
+ if (s.semanticString === "sha256") {
3207
+ return opt
3208
+ ? `() { final v = ${reader}; return v == null ? null : Sha256Digest(v); }()`
3209
+ : `Sha256Digest(${reader})`;
3210
+ }
3211
+ return reader;
3212
+ }
3011
3213
  case "json":
3012
3214
  // An OBJECT-asserted leaf (`t.jsonObject()`, e.g. `location`): the read engine has
3013
3215
  // decoded the stored JSON into a structured object — surface it as a STRICT
@@ -3216,12 +3418,21 @@ function emitDirective(
3216
3418
  const className = dartActionInputClassName(d);
3217
3419
  const specs = introspectPayload(d.payloadSchema as z.ZodTypeAny, enumDartTypes, lazyNames);
3218
3420
  const recordInputs = dartBusinessRecordInputs(d, aggregates);
3421
+ const foreignReferenceInputs = dartForeignReferenceInputs(d, aggregates);
3422
+ const foreignReferenceIds = new Set(foreignReferenceInputs.map((input) => input.idField));
3219
3423
  const principalField = d.businessPrincipalField;
3220
3424
  const derivedFields = new Set([
3221
3425
  ...recordInputs.flatMap((input) => [input.idField, input.workspaceField]),
3426
+ ...foreignReferenceInputs.map((input) => input.workspaceField),
3222
3427
  ...(principalField === undefined ? [] : [principalField]),
3223
3428
  ]);
3224
3429
  const publicSpecs = specs.filter((spec) => !derivedFields.has(spec.name));
3430
+ // Optional first-class relationships are OPTIONAL on the wire but REQUIRED
3431
+ // choices at the application boundary. A plain nullable constructor argument
3432
+ // makes an accidentally forgotten relationship indistinguishable from a
3433
+ // deliberate omission. The generated sum type keeps those meanings apart.
3434
+ const isOptionalReferenceChoice = (spec: PayloadFieldSpec): boolean =>
3435
+ spec.optional && spec.toJson === "ref";
3225
3436
  const internalPayloadArguments = [
3226
3437
  ...(principalField === undefined ? [] : ["required String nmsInternalPrincipal"]),
3227
3438
  ...(recordInputs.length === 0
@@ -3242,6 +3453,8 @@ function emitDirective(
3242
3453
  .map((s) =>
3243
3454
  s.nullAdmits === true
3244
3455
  ? ` final NomosSlot<${stripNullable(s.dartType)}> ${s.name};`
3456
+ : isOptionalReferenceChoice(s)
3457
+ ? ` final NomosOptionalRef<${s.dartType.replace(/^NomosRef<(.*)>$/, "$1")}> ${s.name};`
3245
3458
  : ` final ${s.dartType}${s.optional ? "?" : ""} ${s.name};`,
3246
3459
  ),
3247
3460
  ...recordInputs.map((input) => ` final ${input.dartType} ${input.argument};`),
@@ -3253,6 +3466,8 @@ function emitDirective(
3253
3466
  // an optional field defaults to null; a required field is `required`.
3254
3467
  s.nullAdmits === true
3255
3468
  ? ` this.${s.name} = const NomosSlot.omitted(),`
3469
+ : isOptionalReferenceChoice(s)
3470
+ ? ` required this.${s.name},`
3256
3471
  : ` ${s.optional ? "" : "required "}this.${s.name},`,
3257
3472
  ),
3258
3473
  ...recordInputs.map((input) => ` required this.${input.argument},`),
@@ -3270,10 +3485,20 @@ function emitDirective(
3270
3485
  // fields are written only when present (mirrors the engine's Zod: ops are emitted
3271
3486
  // only for present fields).
3272
3487
  const requiredEntries = specs
3273
- .filter((s) => !derivedFields.has(s.name) && !s.optional && s.nullAdmits !== true)
3488
+ .filter((s) =>
3489
+ !derivedFields.has(s.name) &&
3490
+ !foreignReferenceIds.has(s.name) &&
3491
+ !s.optional &&
3492
+ s.nullAdmits !== true
3493
+ )
3274
3494
  .map((s) => ` '${s.name}': ${toJsonExpr(s, s.name)},`);
3275
3495
  const optionalLines = specs
3276
- .filter((s) => !derivedFields.has(s.name) && s.optional && s.nullAdmits !== true)
3496
+ .filter((s) =>
3497
+ !derivedFields.has(s.name) &&
3498
+ s.optional &&
3499
+ s.nullAdmits !== true &&
3500
+ !isOptionalReferenceChoice(s)
3501
+ )
3277
3502
  .map(
3278
3503
  (s) =>
3279
3504
  ` if (${s.name} != null) '${s.name}': ${toJsonExpr(s, `${s.name}!`)},`,
@@ -3284,7 +3509,57 @@ function emitDirective(
3284
3509
  const slotWriteLines = slotSpecs.map(
3285
3510
  (s) => ` ${s.name}.writeTo(data, '${s.name}', (v) => ${toJsonExpr(s, "v")});`,
3286
3511
  );
3287
- const hasSlots = slotSpecs.length > 0;
3512
+ const optionalReferenceSpecs = specs.filter(
3513
+ (s) =>
3514
+ !derivedFields.has(s.name) &&
3515
+ !foreignReferenceIds.has(s.name) &&
3516
+ isOptionalReferenceChoice(s),
3517
+ );
3518
+ const optionalReferenceWriteLines = optionalReferenceSpecs.map(
3519
+ (s) => ` ${s.name}.writeTo(data, '${s.name}', (reference) => NmsInternalNomosRefs.id(reference));`,
3520
+ );
3521
+ const requiredForeignReferenceInputs = foreignReferenceInputs.filter((input) => !input.optional);
3522
+ const requiredWorkspaceSources = new Map<string, DartForeignReferenceInput>();
3523
+ const requiredForeignReferenceEntries = requiredForeignReferenceInputs.flatMap((input) => {
3524
+ const first = requiredWorkspaceSources.get(input.workspaceField);
3525
+ if (first === undefined) requiredWorkspaceSources.set(input.workspaceField, input);
3526
+ return [
3527
+ ...(first === undefined
3528
+ ? [` '${input.workspaceField}': NmsInternalNomosRefs.workspace(${input.idField}),`]
3529
+ : []),
3530
+ ` '${input.idField}': NmsInternalNomosRefs.id(${input.idField}),`,
3531
+ ];
3532
+ });
3533
+ const requiredForeignReferenceValidationLines = requiredForeignReferenceInputs.flatMap((input) => {
3534
+ const first = requiredWorkspaceSources.get(input.workspaceField)!;
3535
+ if (first.idField === input.idField) return [];
3536
+ return [
3537
+ ` if (NmsInternalNomosRefs.workspace(${input.idField}) != NmsInternalNomosRefs.workspace(${first.idField})) {`,
3538
+ ` throw ArgumentError('Foreign references for ${input.workspaceField} must belong to the same workspace.');`,
3539
+ ` }`,
3540
+ ];
3541
+ });
3542
+ const optionalForeignReferenceWriteLines = foreignReferenceInputs
3543
+ .filter((input) => input.optional)
3544
+ .flatMap((input) => [
3545
+ ` ${input.idField}.when(`,
3546
+ ` absent: () {},`,
3547
+ ` present: (reference) {`,
3548
+ ` final workspace = NmsInternalNomosRefs.workspace(reference);`,
3549
+ ` final existingWorkspace = data['${input.workspaceField}'];`,
3550
+ ` if (existingWorkspace != null && existingWorkspace != workspace) {`,
3551
+ ` throw ArgumentError('Foreign references for ${input.workspaceField} must belong to the same workspace.');`,
3552
+ ` }`,
3553
+ ` data['${input.workspaceField}'] = workspace;`,
3554
+ ` data['${input.idField}'] = NmsInternalNomosRefs.id(reference);`,
3555
+ ` },`,
3556
+ ` );`,
3557
+ ]);
3558
+ const hasPostWrites =
3559
+ slotSpecs.length > 0 ||
3560
+ optionalReferenceSpecs.length > 0 ||
3561
+ requiredForeignReferenceValidationLines.length > 0 ||
3562
+ optionalForeignReferenceWriteLines.length > 0;
3288
3563
 
3289
3564
  const docFirst = directiveDoc(d);
3290
3565
 
@@ -3302,11 +3577,12 @@ function emitDirective(
3302
3577
  ``,
3303
3578
  ` /// Nomos implementation seam used only by the generated application package.`,
3304
3579
  ` /// Tenant code should pass this object to the generated domain application instead.`,
3305
- ...(hasSlots
3580
+ ...(hasPostWrites
3306
3581
  ? [
3307
3582
  ` Map<String, Object?> nmsInternalToPayloadJson(${internalPayloadSignature}) {`,
3308
3583
  ` final data = <String, Object?>{`,
3309
3584
  ...requiredEntries,
3585
+ ...requiredForeignReferenceEntries,
3310
3586
  ...optionalLines,
3311
3587
  ...(principalField === undefined ? [] : [` '${principalField}': nmsInternalPrincipal,`]),
3312
3588
  ...recordInputs.flatMap((input) => [
@@ -3314,13 +3590,17 @@ function emitDirective(
3314
3590
  ` '${input.idField}': nmsInternalIdOf(${input.argument}),`,
3315
3591
  ]),
3316
3592
  ` };`,
3593
+ ...requiredForeignReferenceValidationLines,
3317
3594
  ...slotWriteLines,
3595
+ ...optionalReferenceWriteLines,
3596
+ ...optionalForeignReferenceWriteLines,
3318
3597
  ` return data;`,
3319
3598
  ` }`,
3320
3599
  ]
3321
3600
  : [
3322
3601
  ` Map<String, Object?> nmsInternalToPayloadJson(${internalPayloadSignature}) => <String, Object?>{`,
3323
3602
  ...requiredEntries,
3603
+ ...requiredForeignReferenceEntries,
3324
3604
  ...optionalLines,
3325
3605
  ...(principalField === undefined ? [] : [` '${principalField}': nmsInternalPrincipal,`]),
3326
3606
  ...recordInputs.flatMap((input) => [
@@ -3350,6 +3630,8 @@ export function toJsonExpr(spec: PayloadFieldSpec, accessor: string): string {
3350
3630
  return `${accessor}.value`;
3351
3631
  case "ref":
3352
3632
  return `NmsInternalNomosRefs.id(${accessor})`;
3633
+ case "versionedRef":
3634
+ return `{'pinned': NmsInternalNomosRefs.toJson(${accessor}.pinned), 'series': NmsInternalNomosRefs.toJson(${accessor}.series)}`;
3353
3635
  case "recipient":
3354
3636
  return `NmsInternalRecipients.address(${accessor})`;
3355
3637
  }
@@ -3413,6 +3695,7 @@ export function synthesizeRecursivePayloadTypes(
3413
3695
  }
3414
3696
  switch (zodTypeName(zt)) {
3415
3697
  case "ZodString": {
3698
+ if (isSha256DigestSchema(zt)) return { type: "string", digest: "sha256" };
3416
3699
  const b = brandedIdMetaOf(zt);
3417
3700
  if (b !== undefined) return { type: "string", brand: b };
3418
3701
  const idAgg = idOfMetaOf(zt);
@@ -4026,7 +4309,7 @@ function buildProbePayload(
4026
4309
  zt = zodInnerType(zt);
4027
4310
  }
4028
4311
  const sentinel = fieldBySentinel.get(name);
4029
- out[name] = sentinel !== undefined && zodTypeName(zt) === "ZodString"
4312
+ out[name] = sentinel !== undefined && zodTypeName(zt) === "ZodString" && !isSha256DigestSchema(zt)
4030
4313
  ? sentinel
4031
4314
  : sampleFromZod(zt);
4032
4315
  }
@@ -4041,7 +4324,7 @@ function sampleFromZod(zt: z.ZodTypeAny): unknown {
4041
4324
  }
4042
4325
  switch (zodTypeName(t)) {
4043
4326
  case "ZodString":
4044
- return "x";
4327
+ return isSha256DigestSchema(t) ? "0".repeat(64) : "x";
4045
4328
  case "ZodNumber":
4046
4329
  return 1;
4047
4330
  case "ZodBoolean":
@@ -4506,7 +4789,7 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
4506
4789
  try {
4507
4790
  for (const spec of introspectPayload(schema as z.ZodTypeAny, new Map())) {
4508
4791
  // A wrapped spec whose brand a VO descriptor already registered is a brandedId, not a unit.
4509
- if (spec.toJson === "wrapped" && !spec.dartType.endsWith("Id") && !brandNames.has(spec.dartType)) {
4792
+ if (spec.toJson === "wrapped" && spec.wrappedScalarKind === "number") {
4510
4793
  unitNames.add(spec.dartType);
4511
4794
  }
4512
4795
  }