@githolon/dsl 0.102.4 → 0.103.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/compile_package.mjs +43 -2
  2. package/compile_proof.d.mts +47 -0
  3. package/compile_proof.mjs +343 -0
  4. package/dart/CHANGELOG.md +7 -0
  5. package/dart/lib/src/dev_cloud.dart +8 -8
  6. package/dart/lib/src/nms_generated_runtime.dart +2 -0
  7. package/dart/lib/src/runner/node-entry.mjs +5504 -1236
  8. package/dart/lib/src/runner/offline-install.mjs +4578 -528
  9. package/dart/lib/src/test_harness.dart +206 -152
  10. package/dart/lib/src/webview_bridge.dart +196 -89
  11. package/dart/pubspec.lock +1 -1
  12. package/dart/pubspec.yaml +2 -2
  13. package/dart/test/acquire_session_credentials_test.dart +4 -3
  14. package/dart/test/adv_realm_interference_test.dart +7 -6
  15. package/dart/test/cloud_bound_topology_test.dart +3 -3
  16. package/dart/test/cluster_births_test.dart +4 -3
  17. package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
  18. package/dart/test/cluster_local_autosync_test.dart +3 -2
  19. package/dart/test/cluster_scale_test.dart +3 -2
  20. package/dart/test/dlq_session_surface_test.dart +7 -6
  21. package/dart/test/first_app_gauntlet_test.dart +14 -21
  22. package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
  23. package/dart/test/four_layer_nested_topology_test.dart +4 -4
  24. package/dart/test/harness_phase_timeout_test.dart +2 -2
  25. package/dart/test/local_sync_inert_test.dart +1 -1
  26. package/dart/test/parent_signer_recovery_test.dart +2 -2
  27. package/dart/test/sealing_client_test.dart +4 -3
  28. package/dart/test/session_bound_test.dart +8 -7
  29. package/dart/test/test_harness_test.dart +13 -12
  30. package/dart/test/vo_read_typing_test.dart +4 -3
  31. package/dart/test/watch_after_offer_stall_test.dart +4 -3
  32. package/dart/test/webview_bridge_test.dart +102 -5
  33. package/dart_types/CHANGELOG.md +5 -0
  34. package/dart_types/lib/nomos_types.dart +9 -0
  35. package/dart_types/lib/src/nomos_ref.dart +20 -0
  36. package/dart_types/lib/src/optional_ref.dart +85 -0
  37. package/dart_types/lib/src/sha256_digest.dart +41 -0
  38. package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
  39. package/dart_types/pubspec.yaml +1 -1
  40. package/dart_types/test/optional_ref_test.dart +44 -0
  41. package/dart_types/test/sha256_digest_test.dart +18 -0
  42. package/package.json +9 -5
  43. package/runtime/generated-application-host.js +5365 -1105
  44. package/src/aggregate.ts +18 -0
  45. package/src/application_composition.ts +6 -32
  46. package/src/attested_core.ts +6 -3
  47. package/src/birth_meter.ts +1 -0
  48. package/src/birth_meter_ensure.ts +28 -2
  49. package/src/births.ts +1 -1
  50. package/src/build_package.ts +162 -538
  51. package/src/business_birth.ts +19 -3
  52. package/src/canonical_json.ts +21 -0
  53. package/src/check_package_main.ts +76 -35
  54. package/src/clear_directive.ts +19 -8
  55. package/src/codegen_dart.ts +305 -22
  56. package/src/codegen_dart_application.ts +844 -292
  57. package/src/codegen_dart_client.ts +1084 -407
  58. package/src/codegen_dart_scenario.ts +12 -12
  59. package/src/codegen_proof.ts +169 -39
  60. package/src/codegen_provider_dart.ts +17 -1
  61. package/src/codegen_ts.ts +298 -106
  62. package/src/compat_manifest.ts +2 -2
  63. package/src/compat_package_main.ts +8 -12
  64. package/src/compile_package_main.ts +1866 -597
  65. package/src/compose.ts +1 -1
  66. package/src/directive.ts +208 -15
  67. package/src/engine_entry.ts +165 -111
  68. package/src/evolve_preview.ts +43 -81
  69. package/src/extremum.ts +1 -1
  70. package/src/fields.ts +183 -4
  71. package/src/flow_gate.ts +75 -2
  72. package/src/framework/application_home.ts +2 -0
  73. package/src/framework/auth_provider_law.ts +2 -2
  74. package/src/framework/birth_meter.ts +7 -19
  75. package/src/framework/bootstrap.ts +0 -2
  76. package/src/framework/business_corrections.ts +9 -5
  77. package/src/framework/domain_lifecycle.ts +15 -29
  78. package/src/framework/encryption_scope.ts +2 -1
  79. package/src/framework/framework_aggregates.ts +2 -0
  80. package/src/framework/genesis.ts +16 -14
  81. package/src/framework/home.ts +20 -14
  82. package/src/framework/owned_custody.ts +9 -3
  83. package/src/framework/platform_admin.ts +3 -3
  84. package/src/framework/relations.ts +21 -13
  85. package/src/framework/signing_identity.ts +45 -40
  86. package/src/framework/workspace_invariant.ts +245 -0
  87. package/src/framework/workspaces.ts +23 -29
  88. package/src/index.ts +36 -16
  89. package/src/intent.ts +178 -6
  90. package/src/law_check.ts +32 -11
  91. package/src/law_graph.ts +2902 -0
  92. package/src/law_graph_usda.ts +1799 -0
  93. package/src/law_identity.ts +435 -0
  94. package/src/law_value_type.ts +230 -0
  95. package/src/lsp_server.ts +61 -31
  96. package/src/ops.ts +7 -0
  97. package/src/package_config.ts +141 -0
  98. package/src/package_dependencies.ts +108 -0
  99. package/src/package_dependency_usage.ts +150 -0
  100. package/src/package_source_imports.ts +71 -0
  101. package/src/plan_source_slice.ts +335 -0
  102. package/src/point_guard.ts +192 -0
  103. package/src/precondition.ts +44 -9
  104. package/src/read_projection_namespace.ts +1 -1
  105. package/src/runtime_zod.ts +44 -0
  106. package/src/secondary_ensure.ts +67 -14
  107. package/src/shape_compat.ts +1 -1
  108. package/src/stable_id_identity_bridge.ts +149 -0
  109. package/src/stable_id_lockfile.ts +278 -0
  110. package/src/stable_ids.ts +7 -2
  111. package/src/stable_ids_for_module.ts +348 -0
  112. package/src/stable_ids_types.ts +1 -1
  113. package/src/sum.ts +1 -1
  114. package/src/usd_state.ts +1 -1
  115. package/src/usda_relationship_integrity.ts +66 -0
  116. package/src/usda_text.ts +18 -0
  117. package/src/usdz.ts +78 -0
  118. package/src/value_object.ts +95 -0
  119. package/src/wire.ts +19 -0
  120. package/src/wire_encode.ts +22 -0
  121. package/src/workspace_sharding.ts +52 -36
  122. package/src/workspace_sharing.ts +28 -4
  123. package/src/zod_introspection.ts +1 -1
  124. package/dart/test/cluster_attested_test.dart +0 -201
  125. package/src/compile_engine.ts +0 -595
  126. package/src/manifest.ts +0 -1476
  127. package/src/read_projection_composition.ts +0 -117
  128. package/src/read_projection_manifest.ts +0 -115
  129. package/src/relation_composition.ts +0 -286
  130. package/src/usd.ts +0 -1967
  131. package/src/usd_layers.ts +0 -1776
package/src/codegen_ts.ts CHANGED
@@ -43,7 +43,7 @@ import {
43
43
  OWNED_PREMINTED_RECORDS_PAYLOAD_FIELD,
44
44
  type AggregateHandle,
45
45
  } from "./aggregate.js";
46
- import type { Field, ValueObjectDescriptor, ValueObjectFieldDescriptor } from "./fields.js";
46
+ import { isSha256DigestSchema, type Field, type ValueObjectDescriptor, type ValueObjectFieldDescriptor } from "./fields.js";
47
47
  import type { Directive } from "./directive.js";
48
48
  import type { DomainModule } from "./codegen_dart.js";
49
49
  import { OWNED_DEVICE_APPROVAL_DIRECTIVE, ownedRecordActionDirectiveId } from "./framework/owned_custody.js";
@@ -51,7 +51,7 @@ import { BUSINESS_CORRECTION_DIRECTIVE } from "./framework/business_corrections.
51
51
  import { collectActivityDescriptors, type PackageActivityEntry } from "./codegen_dart.js";
52
52
  import type { NomosCompat } from "./compat_manifest.js";
53
53
  import type { QueryDecl } from "./query.js";
54
- import { voMetaOf, collectPayloadValueObjectDescriptors, idOfMetaOf, idOfTargetKindOf } from "./value_object.js";
54
+ import { voMetaOf, collectPayloadValueObjectDescriptors, idOfMetaOf, idOfTargetKindOf, versionedRefOfMetaOf } from "./value_object.js";
55
55
  import { recursiveTypeBaseName, zodUnwrapLazy } from "./zod_lazy.js";
56
56
  import { finishCount } from "./count.js";
57
57
  import { finishSum } from "./sum.js";
@@ -119,7 +119,7 @@ const tsOptAccess = (name: string): string =>
119
119
  /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? `.${name}` : `.[${JSON.stringify(name)}]`;
120
120
 
121
121
  // ── NAME NORMALIZATION (no naming convention to learn) ────────────────────────────
122
- // `nomos.package.mjs` names and domain keys may be ANY string — kebab-case,
122
+ // `nomos.project.ts` names and domain keys may be ANY string — kebab-case,
123
123
  // snake_case, space-separated, PascalCase. Artifact FILENAMES keep the raw name
124
124
  // verbatim; generated SYMBOLS normalize it:
125
125
 
@@ -308,6 +308,17 @@ function tsTypeOfZod(
308
308
  reg?: LazyTypeRegistry,
309
309
  path = "",
310
310
  ): string {
311
+ if (isSha256DigestSchema(zt)) return "Sha256Digest";
312
+ const versioned = versionedRefOfMetaOf(zt);
313
+ if (versioned !== undefined) {
314
+ const target = versioned.targetKind === "contract"
315
+ ? contractTypeName(versioned.targetAggregateType)
316
+ : versioned.targetAggregateType;
317
+ const series = versioned.seriesKind === "contract"
318
+ ? contractTypeName(versioned.seriesAggregateType)
319
+ : versioned.seriesAggregateType;
320
+ return `VersionedNomosRef<${target}, ${series}>`;
321
+ }
311
322
  if (recipientMetaOf(zt) !== undefined) return "Recipient<User>";
312
323
  const aggregateType = idOfMetaOf(zt);
313
324
  if (aggregateType !== undefined) {
@@ -339,6 +350,8 @@ function tsTypeOfZod(
339
350
  return "number"; // `.int()` is a runtime constraint; TS has one number type
340
351
  case "boolean":
341
352
  return "boolean";
353
+ case "null":
354
+ return "null";
342
355
  case "enum":
343
356
  return union(zodEnumValues(zt).map((v) => JSON.stringify(v)));
344
357
  case "literal": {
@@ -471,7 +484,7 @@ function tsTypeOfPayload(
471
484
  reg?: LazyTypeRegistry,
472
485
  pathPrefix = "",
473
486
  ): string {
474
- if (zodKind(schema) !== "object" || (autoStamped.length === 0 && minted.length === 0 && autoPlaced.length === 0 && privateFields.length === 0)) {
487
+ if (zodKind(schema) !== "object") {
475
488
  return tsTypeOfZod(schema, "", reg, pathPrefix);
476
489
  }
477
490
  const stamped = new Set(autoStamped);
@@ -485,8 +498,10 @@ function tsTypeOfPayload(
485
498
  .map(([name, raw]) => {
486
499
  let f = raw as z.ZodTypeAny;
487
500
  let optional = stamped.has(name) || mintedSet.has(name) || placedSet.has(name);
501
+ let schemaOptional = false;
488
502
  while (zodKind(f) === "optional" || zodKind(f) === "default") {
489
503
  optional = true;
504
+ schemaOptional = true;
490
505
  f = zodDef(f).innerType!;
491
506
  }
492
507
  const note = stamped.has(name)
@@ -495,6 +510,13 @@ function tsTypeOfPayload(
495
510
  ? " // picked LEAST-LOADED over the coordinator's law-state at offer when omitted (sharding §3)"
496
511
  : "";
497
512
  const fieldPath = pathPrefix === "" ? name : `${pathPrefix}.${name}`;
513
+ const optionalReferenceTarget = schemaOptional ? idOfMetaOf(raw as z.ZodTypeAny) : undefined;
514
+ if (optionalReferenceTarget !== undefined) {
515
+ const target = idOfTargetKindOf(raw as z.ZodTypeAny) === "contract"
516
+ ? contractTypeName(optionalReferenceTarget)
517
+ : optionalReferenceTarget;
518
+ return ` ${name}: NomosOptionalRef<${target}>; // required choice: absent or present`;
519
+ }
498
520
  return ` ${name}${optional ? "?" : ""}: ${tsTypeOfZod(f, " ", reg, fieldPath)};${note}`;
499
521
  });
500
522
  return fields.length ? `{\n${fields.join("\n")}\n}` : "Record<string, never>";
@@ -522,7 +544,7 @@ function tsTypeOfField(field: Field): string {
522
544
  function tsBaseTypeOfField(field: Field): string {
523
545
  switch (field.kind) {
524
546
  case "string":
525
- return "string";
547
+ return isSha256DigestSchema(field.zod as z.ZodTypeAny) ? "Sha256Digest" : "string";
526
548
  case "int":
527
549
  return "number";
528
550
  // STRUCTURAL CRDT scalars (read-collapsed): a counter reads as its net number, a position as its
@@ -543,6 +565,16 @@ function tsBaseTypeOfField(field: Field): string {
543
565
  // Both reference shapes keep aggregate identity typed. A same-workspace edge is the canonical
544
566
  // NomosRef<TAggregate>; a foreign edge adds its decoder/read helpers.
545
567
  const fr = (field as { foreignRef?: { targetAggregateId: string; targetIsContract?: boolean } }).foreignRef;
568
+ const vr = field.versionedForeignRef;
569
+ if (vr !== undefined) {
570
+ const target = vr.targetIsContract === true
571
+ ? contractTypeName(vr.targetAggregateId)
572
+ : vr.targetAggregateId;
573
+ const series = vr.seriesIsContract === true
574
+ ? contractTypeName(vr.seriesAggregateId)
575
+ : vr.seriesAggregateId;
576
+ return `VersionedNomosRef<${target}, ${series}>`;
577
+ }
546
578
  // A contract target types against the shared `<ContractId>Contract` interface (the public DTO),
547
579
  // not the concrete `<Agg>Data` — that is how the reference stays acyclic.
548
580
  return fr !== undefined
@@ -599,7 +631,7 @@ function tsTypeOfVoField(f: ValueObjectFieldDescriptor): string {
599
631
  case "string":
600
632
  return f.idOf !== undefined
601
633
  ? `NomosRef<${f.idOfContract === true ? contractTypeName(f.idOf) : f.idOf}>`
602
- : f.brand ?? "string"; // a brandedId(...) → the emitted branded alias
634
+ : f.digest === "sha256" ? "Sha256Digest" : f.brand ?? "string";
603
635
  case "number":
604
636
  return "number"; // `.int()`/units are Dart-side nominal products; TS keeps one number
605
637
  case "boolean":
@@ -699,9 +731,9 @@ function aggregatesOf(mod: Record<string, unknown>): AggregateHandle[] {
699
731
  // ── the generator ─────────────────────────────────────────────────────────────────
700
732
 
701
733
  export interface TsClientOptions {
702
- /** The package name (`nomos.package.mjs` `name`) — used for the hash constant. */
734
+ /** The package name (`nomos.project.ts` `name`) — used for the hash constant. */
703
735
  readonly packageName: string;
704
- /** sha256 of the built `.package.usda` — the law this client offers under. */
736
+ /** Content identity of the built typed USDA closure (`.law.usdz`). */
705
737
  readonly domainHash: string;
706
738
  /** The package's STRUCTURAL INTERFACE (#72) — directives (payload shapes) + aggregates
707
739
  * (read shapes, sid-keyed), keyed by domain. Baked in so `connect()` can negotiate
@@ -1024,6 +1056,36 @@ function anyPackedTaxonomy(modules: readonly DomainModule[]): boolean {
1024
1056
  return false;
1025
1057
  }
1026
1058
 
1059
+ /** Whether a schema contains the framework SHA-256 semantic string, including nested
1060
+ * value objects/arrays. Used only to keep its declaration omit-when-unused. */
1061
+ function zodUsesSha256Digest(schema: z.ZodTypeAny, seen = new Set<z.ZodTypeAny>()): boolean {
1062
+ if (seen.has(schema)) return false;
1063
+ seen.add(schema);
1064
+ if (isSha256DigestSchema(schema)) return true;
1065
+ switch (zodKind(schema)) {
1066
+ case "optional":
1067
+ case "default":
1068
+ case "nullable": {
1069
+ const inner = zodDef(schema).innerType;
1070
+ return inner !== undefined && zodUsesSha256Digest(inner, seen);
1071
+ }
1072
+ case "array":
1073
+ return zodUsesSha256Digest(zodArrayElement(schema), seen);
1074
+ case "object":
1075
+ return Object.values(zodObjectShape(schema)).some((field) => zodUsesSha256Digest(field, seen));
1076
+ case "record": {
1077
+ const value = zodDef(schema).valueType;
1078
+ return value !== undefined && zodUsesSha256Digest(value, seen);
1079
+ }
1080
+ case "union":
1081
+ return (zodDef(schema).options ?? []).some((option) => zodUsesSha256Digest(option, seen));
1082
+ case "lazy":
1083
+ return zodUsesSha256Digest(zodUnwrapLazy(schema), seen);
1084
+ default:
1085
+ return false;
1086
+ }
1087
+ }
1088
+
1027
1089
  export function generateTsClient(modules: readonly DomainModule[], opts: TsClientOptions): string {
1028
1090
  const topologyModules = modules;
1029
1091
  // A public business birth can target a deliberately hidden root aggregate. Its private binder still
@@ -1136,6 +1198,27 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1136
1198
  ),
1137
1199
  ),
1138
1200
  );
1201
+ const hasVersionedRefs = modules.some((m) =>
1202
+ m.aggregates.some((a) =>
1203
+ Object.values(a.fields as Record<string, Field>).some((field) => field.versionedForeignRef !== undefined),
1204
+ ) || (m.directives as Directive<unknown>[]).some((directive) => {
1205
+ const birth = (directive as { businessBirth?: { publicPayloadSchema?: z.ZodTypeAny } }).businessBirth;
1206
+ const schema = birth?.publicPayloadSchema ?? (directive as { payloadSchema?: z.ZodTypeAny }).payloadSchema;
1207
+ return schema !== undefined && zodKind(schema) === "object" &&
1208
+ Object.values(zodObjectShape(schema)).some((field) => versionedRefOfMetaOf(field) !== undefined);
1209
+ }),
1210
+ );
1211
+ const hasSha256Digests = modules.some((module) =>
1212
+ module.aggregates.some((aggregate) =>
1213
+ Object.values(aggregate.fields as Record<string, Field>).some((field) =>
1214
+ isSha256DigestSchema(field.zod as z.ZodTypeAny),
1215
+ ),
1216
+ ) || (module.directives as Directive<unknown>[]).some((directive) => {
1217
+ const birth = (directive as { businessBirth?: { publicPayloadSchema?: z.ZodTypeAny } }).businessBirth;
1218
+ const schema = birth?.publicPayloadSchema ?? (directive as { payloadSchema?: z.ZodTypeAny }).payloadSchema;
1219
+ return schema !== undefined && zodUsesSha256Digest(schema);
1220
+ }),
1221
+ );
1139
1222
  const hasConflictResolvers = modules.some(
1140
1223
  (m) => ((m as { surfacedResolvers?: readonly unknown[] }).surfacedResolvers?.length ?? 0) > 0,
1141
1224
  );
@@ -1182,6 +1265,20 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1182
1265
  ``,
1183
1266
  `import { nomosInternalOpenGeneratedApplication, nomosInternalOpenGeneratedChildApplication${hasInvitations ? ", nomosInternalCreateInvitation, nomosInternalRedeemInvitation" : ""} } from "@githolon/client/_generated_application_host_7c1f5e";`,
1184
1267
  ``,
1268
+ ...(hasSha256Digests
1269
+ ? [
1270
+ `/** A SHA-256 digest in canonical Nomos wire form: 64 lowercase hexadecimal characters. */`,
1271
+ `export type Sha256Digest = string & { readonly __nomosSha256Digest: unique symbol };`,
1272
+ `/** Validate and construct a Sha256Digest before an offer is assembled. */`,
1273
+ `export const sha256Digest = (value: string): Sha256Digest => {`,
1274
+ ` if (!/^[0-9a-f]{64}$/.test(value)) {`,
1275
+ ` throw new TypeError("expected a 64-character lowercase hexadecimal SHA-256 digest");`,
1276
+ ` }`,
1277
+ ` return value as Sha256Digest;`,
1278
+ `};`,
1279
+ ``,
1280
+ ]
1281
+ : []),
1185
1282
  `interface _NomosApplicationStatus {`,
1186
1283
  ` readonly phase: "opening" | "current" | "saving" | "receiving" | "catching-up" | "offline" | "attention";`,
1187
1284
  ` readonly localChanges: number;`,
@@ -1210,7 +1307,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1210
1307
  ]
1211
1308
  : []),
1212
1309
  ``,
1213
- `/** sha256 of the deployed \`.package.usda\` — the installed law this client offers under. */`,
1310
+ `/** Content identity of the installed typed USDA closure (transported as \`.law.usdz\`). */`,
1214
1311
  `const ${hashConst} = ${JSON.stringify(opts.domainHash)};`,
1215
1312
  ``,
1216
1313
  ...(clientInterface !== undefined
@@ -1248,6 +1345,15 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1248
1345
  ` /** Stable, portable nomos:// address. An address identifies a record; it never grants authority. */`,
1249
1346
  ` readonly address: string;`,
1250
1347
  `}`,
1348
+ `/** An optional first-class relationship expressed as a REQUIRED business choice.`,
1349
+ ` * Use NomosOptionalRef.absent() to deliberately omit it or present(ref) to supply it. */`,
1350
+ `export type NomosOptionalRef<TAggregate> =`,
1351
+ ` | { readonly kind: "absent" }`,
1352
+ ` | { readonly kind: "present"; readonly reference: NomosRef<TAggregate> };`,
1353
+ `export const NomosOptionalRef = {`,
1354
+ ` absent: (): { readonly kind: "absent" } => ({ kind: "absent" }),`,
1355
+ ` present: <TAggregate>(reference: NomosRef<TAggregate>): NomosOptionalRef<TAggregate> => ({ kind: "present", reference }),`,
1356
+ `} as const;`,
1251
1357
  `interface _NomosReferenceData<TAggregate> extends NomosRef<TAggregate> { workspace: string; type: string; id: string; address: string }`,
1252
1358
  `const _nmsReferenceData = <TAggregate>(ref: NomosRef<TAggregate>): _NomosReferenceData<TAggregate> => ref as _NomosReferenceData<TAggregate>;`,
1253
1359
  `const _nmsRef = <TAggregate>(workspace: string, type: string, id: string): NomosRef<TAggregate> => ({`,
@@ -1360,18 +1466,23 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1360
1466
  `/** Finish a business action without leaking ledger heads or framework write envelopes. */`,
1361
1467
  `async function _commit(r: Promise<_NomosRawWriteResult>): Promise<void> { void await r; }`,
1362
1468
  `/** Turn the sealed intent's complete affected set into its generated, business-named result. */`,
1363
- `async function _businessResult<TResult>(ws: string, outputs: readonly { member: string; type: string; many?: boolean }[], r: Promise<_NomosRawWriteResult>): Promise<TResult> {`,
1469
+ `async function _businessResult<TResult>(ws: string, outputs: readonly { member: string; type: string; many?: boolean; optional?: boolean }[], r: Promise<_NomosRawWriteResult>): Promise<TResult> {`,
1364
1470
  ` const result = await r;`,
1365
1471
  ` if (typeof result === "string" || !Array.isArray(result.affected)) {`,
1366
1472
  ` throw new Error("Nomos committed the business action without returning its affected records");`,
1367
1473
  ` }`,
1368
- ` const answer: Record<string, NomosRef<_NomosAnyAggregate> | readonly NomosRef<_NomosAnyAggregate>[]> = {};`,
1474
+ ` const answer: Record<string, NomosRef<_NomosAnyAggregate> | readonly NomosRef<_NomosAnyAggregate>[] | undefined> = {};`,
1369
1475
  ` for (const output of outputs) {`,
1370
1476
  ` const matches = result.affected.filter((affected) => affected.type === output.type || affected.type.endsWith("." + output.type));`,
1371
1477
  ` if (output.many) {`,
1372
1478
  ` answer[output.member] = matches.map((match) => _nmsRef<_NomosAnyAggregate>(ws, match.type, match.id));`,
1373
1479
  ` continue;`,
1374
1480
  ` }`,
1481
+ ` if (output.optional) {`,
1482
+ ` if (matches.length > 1) throw new Error(\`Nomos expected at most one affected \${output.type} record, received \${matches.length}\`);`,
1483
+ ` answer[output.member] = matches.length === 0 ? undefined : _nmsRef<_NomosAnyAggregate>(ws, matches[0]!.type, matches[0]!.id);`,
1484
+ ` continue;`,
1485
+ ` }`,
1375
1486
  ` if (matches.length !== 1) throw new Error(\`Nomos expected one affected ${"${output.type}"} record, received ${"${matches.length}"}\`);`,
1376
1487
  ` answer[output.member] = _nmsRef<_NomosAnyAggregate>(ws, matches[0]!.type, matches[0]!.id);`,
1377
1488
  ` }`,
@@ -1447,6 +1558,34 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1447
1558
  ` async exists() { return (await this.resolveResult()).status === "found"; },`,
1448
1559
  ` };`,
1449
1560
  `}`,
1561
+ ...(hasVersionedRefs
1562
+ ? [
1563
+ `export type ReferenceFreshness<TVersion> =`,
1564
+ ` | { readonly status: "current"; readonly latest: NomosRef<TVersion> }`,
1565
+ ` | { readonly status: "stale"; readonly latest: NomosRef<TVersion> }`,
1566
+ ` | { readonly status: "unavailable"; readonly reason: string };`,
1567
+ `/** An immutable publication pin plus the stable series which declares its current publication.`,
1568
+ ` * freshness() is O(current state): it never reconstructs the pinned record or walks history. */`,
1569
+ `export interface VersionedNomosRef<TVersion, TSeries> {`,
1570
+ ` readonly pinned: NomosRef<TVersion>;`,
1571
+ ` readonly series: NomosRef<TSeries>;`,
1572
+ ` freshness(): Promise<ReferenceFreshness<TVersion>>;`,
1573
+ `}`,
1574
+ `type _NomosRawAddress = { workspace: string; type: string; id: string };`,
1575
+ `function _versionedNomosRef<TVersion, TSeries>(nomos: NomosHolon, raw: { pinned: _NomosRawAddress; series: _NomosRawAddress }, currentField: string): VersionedNomosRef<TVersion, TSeries> {`,
1576
+ ` const pinned = _nmsRef<TVersion>(raw.pinned.workspace, raw.pinned.type, raw.pinned.id);`,
1577
+ ` const series = _nmsRef<TSeries>(raw.series.workspace, raw.series.type, raw.series.id);`,
1578
+ ` return { pinned, series, async freshness() {`,
1579
+ ` if (nomos.checkRefFreshness === undefined) return { status: "unavailable", reason: "installed Nomos runtime does not support reference freshness" };`,
1580
+ ` const result = await nomos.checkRefFreshness([{ pinned: raw.pinned, series: raw.series, currentField }]);`,
1581
+ ` const outcome = result.unique[0]?.outcome;`,
1582
+ ` if (outcome?.status === "current") return { status: "current", latest: pinned };`,
1583
+ ` if (outcome?.status === "stale" && outcome.latest !== undefined) return { status: "stale", latest: _nmsRef<TVersion>(outcome.latest.workspace, outcome.latest.type, outcome.latest.id) };`,
1584
+ ` return { status: "unavailable", reason: String(outcome?.status ?? "unknown") };`,
1585
+ ` } };`,
1586
+ `}`,
1587
+ ]
1588
+ : []),
1450
1589
  ``,
1451
1590
  `/** Arbitrary JSON — the type a RECURSIVE \`z.lazy\` payload subtree degrades to (a self-`,
1452
1591
  ` * referencing tree/JSON schema can't be a finite named literal, so the engine validates it`,
@@ -1542,12 +1681,12 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1542
1681
  ` /** THE ONE ENCODER offer (the single v2-envelope codec lives in @githolon/client \`encodeIntentOfferBytes\`):`,
1543
1682
  ` * hand the holon a PLANNED directive + the verified \`actor\`; the holon encodes + offers. The generated`,
1544
1683
  ` * client calls THIS, never building offer bytes itself — so there is exactly ONE copy of the wire format. */`,
1545
- ` offerDirective(domain: string, directiveId: string, payload: unknown, opts?: { domainHash?: string; actor?: string; returnCreated?: boolean; attestations?: readonly { queryId: string; result: unknown; attestation: unknown }[] }): Promise<string | { head: string; created?: { type: string; id: string }[]; affected?: { type: string; id: string }[]; workspace?: string; born?: unknown; birthOutcomes?: unknown }>;`,
1684
+ ` offerDirective(domain: string, directiveId: string, payload: unknown, opts?: { domainHash?: string; actor?: string; returnCreated?: boolean }): Promise<string | { head: string; created?: { type: string; id: string }[]; affected?: { type: string; id: string }[]; workspace?: string; born?: unknown; birthOutcomes?: unknown }>;`,
1546
1685
  ...(sourceTrustPresent
1547
1686
  ? [
1548
- ` /** FRAMEWORK-INTERNAL: obtain the source workspace's signed answer. Generated business`,
1549
- ` * actions privately establish trust and carry it; application code never receives this seam. */`,
1550
- ` fetchAttestedRead(from: string, queryId: string, args?: Record<string, unknown>): Promise<{ queryId: string; result: unknown; attestation: unknown }>;`,
1687
+ ` /** FRAMEWORK-INTERNAL: inspect a sibling's immutable custody identity before authoring`,
1688
+ ` * the ordinary source-enrolment offer. This identity is never accepted as read proof. */`,
1689
+ ` __nomosWorkspaceGenesis(from: string): Promise<string>;`,
1551
1690
  ]
1552
1691
  : []),
1553
1692
  ...(hasCorrections
@@ -1578,6 +1717,12 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1578
1717
  ` __nomosChildWorkspaces?(): Promise<readonly { workspace: string; status: "active" | "retired" | "reclaimed" | "unknown"; bornAt: string | null }[]>;`,
1579
1718
  ` /** FRAMEWORK-INTERNAL child navigation captured by generated description links. */`,
1580
1719
  ` __nomosOpenChild?(workspace: string): Promise<NomosHolon>;`,
1720
+ ...(hasVersionedRefs
1721
+ ? [
1722
+ ` /** Framework-private O(current-state) version check used by generated VersionedNomosRef. */`,
1723
+ ` checkRefFreshness?(refs: readonly { pinned: _NomosRawAddress; series: _NomosRawAddress; currentField: string }[]): Promise<{ unique: { outcome: { status: string; latest?: _NomosRawAddress } }[]; order: number[] }>;`,
1724
+ ]
1725
+ : []),
1581
1726
  ` /** FRAMEWORK-INTERNAL exact-history adoption seam. Generated clients expose only`,
1582
1727
  ` * \`model.adopt()\` and a chained \`decision.accept(reason)\`. */`,
1583
1728
  ` __nomosAdoptModel?(domain: string, candidateHash: string, opts?: { because?: string }): Promise<__NomosModelAdoptionWire>;`,
@@ -1617,7 +1762,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1617
1762
  ` /** FRAMEWORK-INTERNAL: prepare the atomic law payload behind share/unshare/transfer. */`,
1618
1763
  ` __nomosPrepareOwnedRecordAction?(args: { action: "share" | "unshare" | "transfer"; scope: string; record: NomosRef<_NomosAnyAggregate>; peer: string }): Promise<Record<string, unknown>>;`,
1619
1764
  ` /** FRAMEWORK-INTERNAL: derive the deterministic unborn child's owned key before atomic genesis. */`,
1620
- ` __nomosOwnedBirthScopeKey?(args: { scope: string; namespace: string; key: unknown }): Promise<NomosScopeKey & { workspace: string }>;`,
1765
+ ` __nomosOwnedBirthScopeKey?(args: { scope: string; namespace: string; key: unknown; record: { type: string; id: string } }): Promise<NomosScopeKey & { workspace: string }>;`,
1621
1766
  ...(ownedPresent
1622
1767
  ? [
1623
1768
  ` /** FRAMEWORK-INTERNAL: make the opaque request for this session's not-yet-approved device. */`,
@@ -1655,12 +1800,8 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1655
1800
  ` * OPTIONAL member: structural binding tolerates a pre-spatial holon (the accessor then throws at call). */`,
1656
1801
  ` spatialWithin?(spatialId: string, bbox: { minX: number; minY: number; maxX: number; maxY: number } | { minLng: number; minLat: number; maxLng: number; maxLat: number }): Promise<unknown>;`,
1657
1802
  ` sum(sumId: string, groupKey?: string): Promise<number>;`,
1658
- ...(anyPackedTaxonomy(modules)
1659
- ? [
1660
- ` /** THE LOGICAL PAGED ROW SURFACE (sharding §5.3, ruling 2) — present on a ROUTED holon. */`,
1661
- ` pages?(queryId: string, params?: Record<string, unknown>, page?: { pageSize?: number; cursor?: string | null }): Promise<{ rows: unknown[]; cursor: string | null; done: boolean }>;`,
1662
- ]
1663
- : []),
1803
+ ` /** Bounded authenticated query page. Optional only so generated clients diagnose an old runtime cleanly. */`,
1804
+ ` queryPage?(queryId: string, params?: Record<string, unknown>, page?: { pageSize?: number; cursor?: string | null }, keys?: readonly unknown[], principal?: string): Promise<{ rows: unknown[]; cursor: string | null; done: boolean }>;`,
1664
1805
  ` /** The DEAD-LETTER QUEUE — refused-but-legitimate intents the app must handle`,
1665
1806
  ` * explicitly (inspect → retry after a law fix → or discard). Work is never lost. */`,
1666
1807
  ` deadLetters(): Promise<{ id?: string; oid: string; source: string; error: string; domain?: string | null; directiveId?: string | null; at: string }[]>;`,
@@ -1681,6 +1822,8 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1681
1822
  ` * and the holon computes the LIVE per-member verdict on \`holon.compat\`. A KNOWN-incompatible directive`,
1682
1823
  ` * then refuses CLIENT-SIDE at write time (LawMismatchError), never as a server 422. */`,
1683
1824
  ` checkCompat?(compat: { lawHash: string; interfaceHash: string; packageName: string }, requirement?: unknown): NomosCompatVerdict | null;`,
1825
+ ` /** Framework-private readiness hint; generated domain bindings keep first-use plan work off the offer path. */`,
1826
+ ` prepareDomain?(domain: string, packageHash: string): void;`,
1684
1827
  ` /** RELEASE COHERENCE: throws a typed KernelCoherenceError at bind if the cloud's kernel lacks a`,
1685
1828
  ` * capability this law requires (e.g. \`compoundKeys\`) — instead of an opaque deep fold refusal. */`,
1686
1829
  ` assertKernelCoherence?(requiresKernel: readonly string[], opts?: { lawHash?: string }): unknown;`,
@@ -2136,7 +2279,13 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2136
2279
  `const _nmsWireReferences = <T extends object>(payload: T, fields: Readonly<Record<string, string>>, workspace: string): T => {`,
2137
2280
  ` const out = { ...payload } as Record<string, unknown>;`,
2138
2281
  ` for (const [field, expectedType] of Object.entries(fields)) {`,
2139
- ` const value = out[field];`,
2282
+ ` let value = out[field];`,
2283
+ ` if (typeof value === "object" && value !== null && "kind" in value) {`,
2284
+ ` const choice = value as { kind?: unknown; reference?: unknown };`,
2285
+ ` if (choice.kind === "absent") { delete out[field]; continue; }`,
2286
+ ` if (choice.kind !== "present") throw new TypeError(field + " must choose NomosOptionalRef.absent() or .present(ref)");`,
2287
+ ` value = choice.reference;`,
2288
+ ` }`,
2140
2289
  ` if (value !== undefined) out[field] = _nmsReferenceId(value, expectedType, workspace, field);`,
2141
2290
  ` }`,
2142
2291
  ` return out as T;`,
@@ -2144,52 +2293,36 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2144
2293
  ``,
2145
2294
  );
2146
2295
  }
2147
- if (anyRecipientPayload) {
2296
+ if (hasVersionedRefs) {
2148
2297
  out.push(
2149
- `const _nmsWireRecipients = <T extends object>(payload: T, fields: readonly string[]): T => {`,
2298
+ `const _nmsWireVersionedReferences = <T extends object>(payload: T, fields: readonly string[]): T => {`,
2150
2299
  ` const out = { ...payload } as Record<string, unknown>;`,
2151
2300
  ` for (const field of fields) {`,
2152
- ` const value = out[field];`,
2153
- ` if (value !== undefined) out[field] = _nmsRecipientAddress(value, field);`,
2301
+ ` const value = out[field] as { pinned?: unknown; series?: unknown } | undefined;`,
2302
+ ` if (value === undefined) continue;`,
2303
+ ` if (typeof value !== "object" || value === null) throw new TypeError(field + " must be a VersionedNomosRef returned by the generated application");`,
2304
+ ` const address = (part: unknown, label: string): _NomosRawAddress => {`,
2305
+ ` if (typeof part !== "object" || part === null) throw new TypeError(field + "." + label + " must carry a NomosRef");`,
2306
+ ` const ref = _nmsReferenceData(part as NomosRef<_NomosAnyAggregate>);`,
2307
+ ` if (!ref.workspace || !ref.type || !ref.id) throw new TypeError(field + "." + label + " must carry a complete NomosRef");`,
2308
+ ` return { workspace: ref.workspace, type: ref.type, id: ref.id };`,
2309
+ ` };`,
2310
+ ` out[field] = { pinned: address(value.pinned, "pinned"), series: address(value.series, "series") };`,
2154
2311
  ` }`,
2155
2312
  ` return out as T;`,
2156
2313
  `};`,
2157
2314
  ``,
2158
2315
  );
2159
2316
  }
2160
-
2161
- if (sourceTrustPresent) {
2317
+ if (anyRecipientPayload) {
2162
2318
  out.push(
2163
- `/** Private dynamic-source composition. A business action names a source record; Nomos fetches its`,
2164
- ` * signed answer, derives the portable source identity, seats the law-declared trust policy and`,
2165
- ` * carries the envelope on the offer. None of these values are application arguments. */`,
2166
- `type _NomosAttestedEntry = { queryId: string; result: unknown; attestation: Record<string, unknown> };`,
2167
- `const _nmsAttestedEntry = (value: unknown): _NomosAttestedEntry => {`,
2168
- ` const raw = typeof value === "object" && value !== null && "entry" in value`,
2169
- ` ? (value as { entry?: unknown }).entry`,
2170
- ` : value;`,
2171
- ` if (typeof raw !== "object" || raw === null) throw new Error("Nomos source did not return an attested business record");`,
2172
- ` const entry = raw as { queryId?: unknown; result?: unknown; attestation?: unknown };`,
2173
- ` if (typeof entry.queryId !== "string" || typeof entry.attestation !== "object" || entry.attestation === null) {`,
2174
- ` throw new Error("Nomos source omitted its signed identity");`,
2319
+ `const _nmsWireRecipients = <T extends object>(payload: T, fields: readonly string[]): T => {`,
2320
+ ` const out = { ...payload } as Record<string, unknown>;`,
2321
+ ` for (const field of fields) {`,
2322
+ ` const value = out[field];`,
2323
+ ` if (value !== undefined) out[field] = _nmsRecipientAddress(value, field);`,
2175
2324
  ` }`,
2176
- ` return entry as _NomosAttestedEntry;`,
2177
- `};`,
2178
- `const _nmsSourceGenesis = (entry: _NomosAttestedEntry): string => {`,
2179
- ` const value = entry.attestation.source_genesis;`,
2180
- ` if (typeof value !== "string" || value.length === 0) throw new Error("Nomos source omitted its genesis identity");`,
2181
- ` return value;`,
2182
- `};`,
2183
- `const _nmsSourceKeyHash = async (entry: _NomosAttestedEntry): Promise<string> => {`,
2184
- ` const publicKey = entry.attestation.public_key;`,
2185
- ` if (typeof publicKey !== "string" || publicKey.length === 0) throw new Error("Nomos source omitted its attestation key");`,
2186
- ` let bytes: Uint8Array;`,
2187
- ` try {`,
2188
- ` if (typeof atob === "function") bytes = Uint8Array.from(atob(publicKey), (character) => character.charCodeAt(0));`,
2189
- ` else bytes = Uint8Array.from((globalThis as unknown as { Buffer: { from(value: string, encoding: string): Uint8Array } }).Buffer.from(publicKey, "base64"));`,
2190
- ` } catch { throw new Error("Nomos source returned an invalid attestation key"); }`,
2191
- ` const digest = await crypto.subtle.digest("SHA-256", bytes as unknown as BufferSource);`,
2192
- ` return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");`,
2325
+ ` return out as T;`,
2193
2326
  `};`,
2194
2327
  ``,
2195
2328
  );
@@ -2261,14 +2394,16 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2261
2394
 
2262
2395
  out.push(`// ── read models (projection \`data\` shapes; partial-fold optional, EXCEPT .fromBirth()`, `// fields — their presence is gate law, so the type is non-optional) ──`, ``);
2263
2396
  const foreignRefFields: { agg: string; field: string; workspaceField: string; target: string }[] = [];
2397
+ const atomicRefFields: { agg: string; field: string }[] = [];
2398
+ const versionedRefFields: { agg: string; field: string; currentField: string }[] = [];
2264
2399
  // wrap a read accessor return expression through the foreignRef row-hydrator (identity + byte-identical
2265
2400
  // when the package declares no foreignRef, so non-foreignRef packages are unchanged).
2266
2401
  // Every read accessor's rows get their canonical `.ref` stamped from `holon.workspace` (additive); a
2267
2402
  // foreignRef-declaring package ALSO hydrates its foreignRef fields into typed locators first.
2268
2403
  const wrapP = (expr: string): string =>
2269
- `_businessRecordsP(_stampRefsP(holon.workspace, ${foreignRefFields.length > 0 ? `_hydrateP(holon, ${expr})` : expr}))`;
2404
+ `_businessRecordsP(_stampRefsP(holon.workspace, ${foreignRefFields.length + atomicRefFields.length + versionedRefFields.length > 0 ? `_hydrateP(holon, ${expr})` : expr}))`;
2270
2405
  const wrapRows = (expr: string): string =>
2271
- `_businessRecords(_stampRefs(holon.workspace, ${foreignRefFields.length > 0 ? `_hydrateRows(holon, ${expr})` : expr}))`;
2406
+ `_businessRecords(_stampRefs(holon.workspace, ${foreignRefFields.length + atomicRefFields.length + versionedRefFields.length > 0 ? `_hydrateRows(holon, ${expr})` : expr}))`;
2272
2407
  // ATTRIBUTED READS (architecture/field_provenance.md): the shared provenance shape +
2273
2408
  // per-aggregate accessors — emitted ONLY when the package declares ≥1 `.attributed()`
2274
2409
  // field, so a provenance-free package's client is byte-identical (the era rule).
@@ -2297,6 +2432,9 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2297
2432
  out.push(` ${tsKey(name)}${required ? "" : "?"}: ${tsTypeOfField(field as Field)};`);
2298
2433
  const fr = (field as { foreignRef?: { workspaceField: string; targetAggregateId: string } }).foreignRef;
2299
2434
  if (fr !== undefined) foreignRefFields.push({ agg: typeId, field: name, workspaceField: fr.workspaceField, target: fr.targetAggregateId });
2435
+ if ((field as Field).nomosRef !== undefined) atomicRefFields.push({ agg: typeId, field: name });
2436
+ const vr = (field as Field).versionedForeignRef;
2437
+ if (vr !== undefined) versionedRefFields.push({ agg: typeId, field: name, currentField: vr.currentField });
2300
2438
  }
2301
2439
  if (attributed.length > 0) {
2302
2440
  out.push(
@@ -2337,7 +2475,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2337
2475
  // row into a `ForeignRef<TargetAggregate>` — reading the foreign id (the field) + the foreign workspace id (the
2338
2476
  // declared sibling field). The read model stays honest (the field is the raw stored id string); this gives
2339
2477
  // the TYPED locator with resolve/watch on top, without cloning the other holon.
2340
- if (foreignRefFields.length > 0) {
2478
+ if (foreignRefFields.length + atomicRefFields.length + versionedRefFields.length > 0) {
2341
2479
  const byAgg = new Map<string, typeof foreignRefFields>();
2342
2480
  for (const f of foreignRefFields) { const l = byAgg.get(f.agg) ?? []; l.push(f); byAgg.set(f.agg, l); }
2343
2481
  out.push(`// ── foreign-ref locators (t.foreignRef): typed cross-workspace edges (resolve/watch on demand) ──`);
@@ -2360,15 +2498,27 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2360
2498
  // `row.data.<field>.id` is still the raw id. Identity for any aggregate without foreignRefs.
2361
2499
  const hydrateSpecs: Record<string, { field: string; wsField: string; target: string }[]> = {};
2362
2500
  for (const f of foreignRefFields) { (hydrateSpecs[f.agg] ??= []).push({ field: f.field, wsField: f.workspaceField, target: f.target }); }
2501
+ const atomicSpecs: Record<string, { field: string }[]> = {};
2502
+ for (const f of atomicRefFields) { (atomicSpecs[f.agg] ??= []).push({ field: f.field }); }
2503
+ const versionedSpecs: Record<string, { field: string; currentField: string }[]> = {};
2504
+ for (const f of versionedRefFields) { (versionedSpecs[f.agg] ??= []).push({ field: f.field, currentField: f.currentField }); }
2363
2505
  out.push(
2364
2506
  `const _FOREIGN_REFS: Record<string, { field: string; wsField: string; target: string }[]> = ${JSON.stringify(hydrateSpecs)};`,
2507
+ `const _ATOMIC_REFS: Record<string, { field: string }[]> = ${JSON.stringify(atomicSpecs)};`,
2508
+ `const _VERSIONED_REFS: Record<string, { field: string; currentField: string }[]> = ${JSON.stringify(versionedSpecs)};`,
2365
2509
  `function _hydrateRows<T>(nomos: NomosForeignReader, rows: Row<T>[]): Row<T>[] {`,
2366
2510
  ` return rows.map((row) => {`,
2367
2511
  ` const raw = row as Row<T> & { type: string };`,
2368
- ` const specs = _FOREIGN_REFS[raw.type];`,
2369
- ` if (specs === undefined || row.data == null) return row;`,
2512
+ ` const specs = _FOREIGN_REFS[raw.type] ?? [];`,
2513
+ ` const atomic = _ATOMIC_REFS[raw.type] ?? [];`,
2514
+ ` const versioned = _VERSIONED_REFS[raw.type] ?? [];`,
2515
+ ` if ((specs.length + atomic.length + versioned.length) === 0 || row.data == null) return row;`,
2370
2516
  ` const data: Record<string, unknown> = { ...(row.data as Record<string, unknown>) };`,
2371
2517
  ` for (const s of specs) { const v = data[s.field]; if (typeof v === "string") data[s.field] = foreignRef(nomos, s.target, v, String(data[s.wsField] ?? "")); }`,
2518
+ ` for (const s of atomic) { const v = data[s.field] as { workspace: string; type: string; id: string } | undefined; if (v && typeof v === "object") data[s.field] = _nmsRef(v.workspace, v.type, v.id); }`,
2519
+ ...(hasVersionedRefs
2520
+ ? [` for (const s of versioned) { const v = data[s.field] as { pinned: _NomosRawAddress; series: _NomosRawAddress } | undefined; if (v && typeof v === "object") data[s.field] = _versionedNomosRef(nomos as NomosHolon, v, s.currentField); }`]
2521
+ : []),
2372
2522
  ` return { ...row, data: data as T };`,
2373
2523
  ` });`,
2374
2524
  `}`,
@@ -2621,6 +2771,44 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2621
2771
  const runtimeTypeOf = (aggregate: AggregateHandle): string => isNamespacedAggregate(aggregate)
2622
2772
  ? qualifyReadProjectionType(domain, aggregate.id)
2623
2773
  : aggregate.id;
2774
+ const versionedFactories = new Map<string, ReturnType<typeof versionedRefOfMetaOf>>();
2775
+ for (const directive of dirs) {
2776
+ if (!isBusinessIntent(directive)) continue;
2777
+ const schema = (directive as { payloadSchema: z.ZodTypeAny }).payloadSchema;
2778
+ if (zodKind(schema) !== "object") continue;
2779
+ for (const [field, fieldSchema] of Object.entries(zodObjectShape(schema))) {
2780
+ const meta = versionedRefOfMetaOf(fieldSchema as z.ZodTypeAny);
2781
+ if (meta === undefined) continue;
2782
+ const method = `pin${cap(camel(field))}`;
2783
+ const prior = versionedFactories.get(method);
2784
+ if (prior !== undefined && JSON.stringify(prior) !== JSON.stringify(meta)) {
2785
+ throw new Error(`generated version-pin factory '${method}' names incompatible reference contracts`);
2786
+ }
2787
+ versionedFactories.set(method, meta);
2788
+ }
2789
+ }
2790
+ for (const [method, meta] of [...versionedFactories].sort(([a], [b]) => a.localeCompare(b))) {
2791
+ if (meta === undefined) continue;
2792
+ const target = meta.targetKind === "contract"
2793
+ ? contractTypeName(meta.targetAggregateType)
2794
+ : meta.targetAggregateType;
2795
+ const series = meta.seriesKind === "contract"
2796
+ ? contractTypeName(meta.seriesAggregateType)
2797
+ : meta.seriesAggregateType;
2798
+ const seriesInput = meta.seriesKind === "contract"
2799
+ ? meta.seriesAggregateType
2800
+ : series;
2801
+ methods.push(
2802
+ ` /** Pin one immutable publication to its stable series. The current pointer is compiled law. */`,
2803
+ ` ${method}: (pinned: NomosRef<${target}>, series: NomosRef<${seriesInput}>): VersionedNomosRef<${target}, ${series}> => {`,
2804
+ ` const p = _nmsReferenceData(pinned); const s = _nmsReferenceData(series);`,
2805
+ ` _nmsReferenceId(pinned, ${JSON.stringify(meta.targetAggregateType)}, "", "pinned");`,
2806
+ ` _nmsReferenceId(series, ${JSON.stringify(meta.seriesAggregateType)}, "", "series");`,
2807
+ ` if (p.workspace !== s.workspace) throw new TypeError("A version and its stable series must share one workspace");`,
2808
+ ` return _versionedNomosRef<${target}, ${series}>(holon, { pinned: { workspace: p.workspace, type: p.type, id: p.id }, series: { workspace: s.workspace, type: s.type, id: s.id } }, ${JSON.stringify(meta.currentField)});`,
2809
+ ` },`,
2810
+ );
2811
+ }
2624
2812
  // E2E READ INJECTION: the distinct encryption scopes a read of `typeId` must inject keys for (empty
2625
2813
  // ⇒ nothing encrypted on that type ⇒ the accessor is emitted plain, unchanged). The `_readKeys` helper
2626
2814
  // returns undefined when no keyStore is bound, so an injected accessor still behaves as today offline.
@@ -2739,13 +2927,21 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2739
2927
  .filter(([name, field]) => name !== mint?.field && recipientMetaOf(field) !== undefined)
2740
2928
  .map(([name]) => name)
2741
2929
  : [];
2930
+ const versionedReferencePayloadFields = zodKind(schema) === "object"
2931
+ ? Object.entries(zodObjectShape(schema))
2932
+ .filter(([name, field]) => name !== mint?.field && versionedRefOfMetaOf(field) !== undefined)
2933
+ .map(([name]) => name)
2934
+ : [];
2742
2935
  const wireReferences = (expr: string): string => {
2743
2936
  const references = referencePayloadFields.length === 0
2744
2937
  ? expr
2745
2938
  : `_nmsWireReferences(${expr}, ${JSON.stringify(referencePayloadTypes)}, holon.workspace)`;
2746
- return recipientPayloadFields.length === 0
2939
+ const versioned = versionedReferencePayloadFields.length === 0
2747
2940
  ? references
2748
- : `_nmsWireRecipients(${references}, ${JSON.stringify(recipientPayloadFields)})`;
2941
+ : `_nmsWireVersionedReferences(${references}, ${JSON.stringify(versionedReferencePayloadFields)})`;
2942
+ return recipientPayloadFields.length === 0
2943
+ ? versioned
2944
+ : `_nmsWireRecipients(${versioned}, ${JSON.stringify(recipientPayloadFields)})`;
2749
2945
  };
2750
2946
  const stamps = autoStampFields(schema);
2751
2947
  const placed = placementDirs.has(d.id) ? ["shard"] : [];
@@ -2811,26 +3007,16 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2811
3007
  const ownedBirthPrelude = ownedBirthDescriptor === undefined
2812
3008
  ? ""
2813
3009
  : ` if (!holon.__nomosOwnedBirthScopeKey) throw new Error("this holon predates automatic .owned() birth custody — upgrade @githolon/client");\n` +
2814
- ` const _ownedBirthKey = await holon.__nomosOwnedBirthScopeKey({ scope: ${JSON.stringify(ownedBirthDescriptor.scope)}, namespace: ${JSON.stringify(businessBirth!.namespace)}, key: (${inputPayload} as Record<string, unknown>)[${JSON.stringify(businessBirth!.keyField)}] });`;
3010
+ ` const _ownedBirthKey = await holon.__nomosOwnedBirthScopeKey({ scope: ${JSON.stringify(ownedBirthDescriptor.scope)}, namespace: ${JSON.stringify(businessBirth!.namespace)}, key: (${inputPayload} as Record<string, unknown>)[${JSON.stringify(businessBirth!.keyField)}], record: { type: ${JSON.stringify(ownedRuntimeType)}, id: ${JSON.stringify(businessBirth!.rootId)} } });`;
2815
3011
  const paramSig = `${argumentName}: ${payloadName}`;
2816
- const sourceQueryArgs = sourceTrust === undefined
2817
- ? ""
2818
- : Object.entries(sourceTrust.queryArgs)
2819
- .sort(([a], [b]) => a.localeCompare(b))
2820
- .map(([argument, field]) => `${JSON.stringify(argument)}: (${argumentName} as Record<string, unknown>)[${JSON.stringify(field)}]`)
2821
- .join(", ");
2822
- const sourceQueryReferenceTypes = sourceTrust === undefined
2823
- ? {}
2824
- : Object.fromEntries(Object.entries(sourceTrust.queryArgs).flatMap(([argument, field]) => {
2825
- const type = referencePayloadTypes[field];
2826
- return type === undefined ? [] : [[argument, type]];
2827
- }));
3012
+ const payloadPath = (root: string, path: string): string =>
3013
+ path.split(".").reduce((expr, part) => `${expr}[${JSON.stringify(part)}]`, `(${root} as { [key: string]: any })`);
2828
3014
  const copiedTrustFields = sourceTrust === undefined
2829
3015
  ? []
2830
3016
  : Object.entries(sourceTrust.copy)
2831
3017
  .sort(([a], [b]) => a.localeCompare(b))
2832
3018
  .map(([target, field]) =>
2833
- ` ${JSON.stringify(target)}: (${inputPayload} as Record<string, unknown>)[${JSON.stringify(field)}],`);
3019
+ ` ${JSON.stringify(target)}: ${payloadPath(inputPayload, field)},`);
2834
3020
  const copiedTrustReferenceTypes = sourceTrust === undefined
2835
3021
  ? {}
2836
3022
  : Object.fromEntries(Object.entries(sourceTrust.copy).flatMap(([target, field]) => {
@@ -2840,25 +3026,23 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2840
3026
  const sourceTrustPrelude = sourceTrust === undefined
2841
3027
  ? []
2842
3028
  : [
2843
- ` const _sourceWorkspace = String((${inputPayload} as Record<string, unknown>)[${JSON.stringify(sourceTrust.fromPayload)}] ?? "");`,
3029
+ ` const _sourceWorkspace = String(${payloadPath(inputPayload, sourceTrust.fromPayload)} ?? "");`,
2844
3030
  ` if (!_sourceWorkspace.trim()) throw new Error(${JSON.stringify(`${d.id} needs a source workspace`)});`,
2845
- ` const _sourceQueryArgs = _nmsWireReferences({ ${sourceQueryArgs} }, ${JSON.stringify(sourceQueryReferenceTypes)}, _sourceWorkspace);`,
2846
- ` const _sourceEntry = _nmsAttestedEntry(await holon.fetchAttestedRead(_sourceWorkspace, ${JSON.stringify(sourceTrust.queryId)}, _sourceQueryArgs));`,
3031
+ ` const _sourceGenesis = await holon.__nomosWorkspaceGenesis(_sourceWorkspace);`,
2847
3032
  ` await _commit(holon.offerDirective(${JSON.stringify(domain)}, ${JSON.stringify(sourceTrust.viaDirectiveId)}, _nmsWireReferences({`,
2848
3033
  ...copiedTrustFields,
2849
- ` ${JSON.stringify(sourceTrust.genesisField)}: _nmsSourceGenesis(_sourceEntry),`,
2850
- ` ${JSON.stringify(sourceTrust.keyHashesField)}: [await _nmsSourceKeyHash(_sourceEntry)],`,
3034
+ ` ${JSON.stringify(sourceTrust.genesisField)}: _sourceGenesis,`,
3035
+ ` ${JSON.stringify(sourceTrust.keyHashesField)}: [${JSON.stringify("0".repeat(64))}],`,
2851
3036
  ` }, ${JSON.stringify(copiedTrustReferenceTypes)}, _sourceWorkspace), { domainHash, actor, returnCreated: true }) as Promise<_NomosRawWriteResult>);`,
2852
3037
  ];
2853
- const offerExtra = sourceTrust === undefined ? "" : ", attestations: [_sourceEntry]";
2854
3038
  const sealPrelude = wantSeal
2855
3039
  ? ` const _sealed = await _sealFields(holon, keyStore, ${encConst}[${JSON.stringify(domain)}].directives[${JSON.stringify(d.id)}] as Record<string, { scope: string; aggSid: string; fieldSid: string; owned?: true }>, ${inputPayload} as Record<string, unknown>, true${ownedBirthDescriptor !== undefined ? ", _ownedBirthKey" : ", undefined"}${ownedRecordPrelude ? ", _ownedRecord" : ""}) as ${payloadName};`
2856
3040
  : "";
2857
3041
  const pv = wantSeal ? "_sealed" : inputPayload;
2858
3042
  // MULTI-OUTPUT: name every aggregate this directive writes (primary marker target + declared
2859
3043
  // secondaryOutputs), so the frontend dev sees "writes A + B" at the call site (not one by convention).
2860
- const dd = d as unknown as { aggregateId: string; marker: string; secondaryOutputs?: readonly { aggregateId: string; marker: string; many?: true }[] };
2861
- const allOutputs = [{ aggregateId: dd.aggregateId, marker: dd.marker, many: false }, ...(dd.secondaryOutputs ?? []).map((output) => ({ ...output, many: output.many === true }))]
3044
+ const dd = d as unknown as { aggregateId: string; marker: string; outputCardinality?: "many" | "optional"; secondaryOutputs?: readonly { aggregateId: string; marker: string; many?: true; optional?: true }[] };
3045
+ const allOutputs = [{ aggregateId: dd.aggregateId, marker: dd.marker, many: dd.outputCardinality === "many", optional: dd.outputCardinality === "optional" }, ...(dd.secondaryOutputs ?? []).map((output) => ({ ...output, many: output.many === true, optional: output.optional === true }))]
2862
3046
  .filter((output) => publicAggregateIds.has(output.aggregateId))
2863
3047
  .filter((output, index, outputs) =>
2864
3048
  outputs.findIndex((candidate) => candidate.aggregateId === output.aggregateId) === index);
@@ -2886,7 +3070,9 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2886
3070
  resultMembers.add(field);
2887
3071
  return output.many
2888
3072
  ? ` readonly ${tsKey(field)}: readonly NomosRef<${output.aggregateId}>[];`
2889
- : ` readonly ${tsKey(field)}: NomosRef<${output.aggregateId}>;`;
3073
+ : output.optional
3074
+ ? ` readonly ${tsKey(field)}: NomosRef<${output.aggregateId}> | undefined;`
3075
+ : ` readonly ${tsKey(field)}: NomosRef<${output.aggregateId}>;`;
2890
3076
  });
2891
3077
  out.push(
2892
3078
  `/** Every business record affected by \`${domain}/${d.id}\`. No ledger or transport receipt leaks through this type. */`,
@@ -2976,10 +3162,11 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2976
3162
  member: tsResultMemberName(output.aggregateId),
2977
3163
  type: output.aggregateId,
2978
3164
  ...(output.many ? { many: true } : {}),
3165
+ ...(output.optional ? { optional: true } : {}),
2979
3166
  })));
2980
3167
  const mintedCall = hasBusinessResult
2981
- ? `_businessResult<${resultName}>(holon.workspace, ${resultSpec}, holon.offerDirective(${JSON.stringify(domain)}, ${JSON.stringify(d.id)}, ${body}, { domainHash, actor, returnCreated: true${offerExtra} }) as Promise<_NomosRawWriteResult>)`
2982
- : `_commit(holon.offerDirective(${JSON.stringify(domain)}, ${JSON.stringify(d.id)}, ${body}, { domainHash, actor, returnCreated: true${offerExtra} }) as Promise<_NomosRawWriteResult>)`;
3168
+ ? `_businessResult<${resultName}>(holon.workspace, ${resultSpec}, holon.offerDirective(${JSON.stringify(domain)}, ${JSON.stringify(d.id)}, ${body}, { domainHash, actor, returnCreated: true }) as Promise<_NomosRawWriteResult>)`
3169
+ : `_commit(holon.offerDirective(${JSON.stringify(domain)}, ${JSON.stringify(d.id)}, ${body}, { domainHash, actor, returnCreated: true }) as Promise<_NomosRawWriteResult>)`;
2983
3170
  methods.push(
2984
3171
  ` /** Author \`${domain}/${d.id}\`: the generated client MINTS \`${mint.field}\` internally`,
2985
3172
  ` * — ids are not application input — ${hasBusinessResult ? `and returns its typed ${resultName}` : "and keeps framework write details private"}.${gate ? ` GATED: needs \`{ actor }\` (\`${gate.relation}\`).` : ""} */`,
@@ -3004,10 +3191,11 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
3004
3191
  member: tsResultMemberName(output.aggregateId),
3005
3192
  type: output.aggregateId,
3006
3193
  ...(output.many ? { many: true } : {}),
3194
+ ...(output.optional ? { optional: true } : {}),
3007
3195
  })));
3008
3196
  const commitCall = hasBusinessResult
3009
- ? `_businessResult<${resultName}>(holon.workspace, ${resultSpec}, holon.offerDirective(${JSON.stringify(domain)}, ${JSON.stringify(d.id)}, ${offerPayload}, { domainHash, actor, returnCreated: true${offerExtra} }) as Promise<_NomosRawWriteResult>)`
3010
- : `_commit(holon.offerDirective(${JSON.stringify(domain)}, ${JSON.stringify(d.id)}, ${offerPayload}, { domainHash, actor, returnCreated: true${offerExtra} }) as Promise<_NomosRawWriteResult>)`;
3197
+ ? `_businessResult<${resultName}>(holon.workspace, ${resultSpec}, holon.offerDirective(${JSON.stringify(domain)}, ${JSON.stringify(d.id)}, ${offerPayload}, { domainHash, actor, returnCreated: true }) as Promise<_NomosRawWriteResult>)`
3198
+ : `_commit(holon.offerDirective(${JSON.stringify(domain)}, ${JSON.stringify(d.id)}, ${offerPayload}, { domainHash, actor, returnCreated: true }) as Promise<_NomosRawWriteResult>)`;
3011
3199
  const outcomeDoc =
3012
3200
  d.marker === "creates"
3013
3201
  ? `Author \`${domain}/${d.id}\`: the LAW mints the ${d.aggregateId ?? "aggregate"} identity and returns every affected business reference — no id input and no query-after.`
@@ -3066,7 +3254,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
3066
3254
  const qOwned = qScopes.some((scope) => scope.owned === true);
3067
3255
  const qScopeJson = JSON.stringify(qScopes);
3068
3256
  methods.push(
3069
- ` /** Declared query \`${q.id}\` (indexed probe — routed by the read manifest).${qScopes.length ? " Encrypted fields decrypt automatically when this signed-in application holds their keys." : ""} */`,
3257
+ ` /** @deprecated Whole-result reads scale with result size. Use ${lcFirst(camel(q.id))}Pages() for bounded UX.${qScopes.length ? " Encrypted fields decrypt automatically when this signed-in application holds their keys." : ""} */`,
3070
3258
  qScopes.length
3071
3259
  ? qOwned
3072
3260
  ? ` ${lcFirst(camel(q.id))}: async (params: { ${params} }) => ${wrapP(`_readWithOwnedKeys(holon, keyStore, ${qScopeJson}, (keys) => holon.query(${JSON.stringify(q.id)}, ${wireParams}, keys) as Promise<Row<${q.returns}>[]>)`)},`
@@ -3085,18 +3273,21 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
3085
3273
  : ` ${lcFirst(camel(q.id))}: async (params: { ${params} }) => ${wrapP(`historic.query(${JSON.stringify(q.id)}, ${wireParams}, await _readKeys(holon, keyStore, ${qScopeJson})) as Promise<Row<${q.returns}>[]>`)},`
3086
3274
  : ` ${lcFirst(camel(q.id))}: (params: { ${params} }) => ${wrapP(`historic.query(${JSON.stringify(q.id)}, ${wireParams}) as Promise<Row<${q.returns}>[]>`)},`,
3087
3275
  );
3088
- // THE LOGICAL PAGED ROW SURFACE (sharding §5.3, ruling 2 — slice 4): emitted
3089
- // ONLY for packed-taxonomy domains (taxonomy-free clients stay byte-identical).
3090
- // The dev sees ordinary pagination; the shard walk is the implementation.
3091
- if (anyPackedTaxonomy(modules)) {
3092
- methods.push(
3093
- ` /** \`${q.id}\`, paged at estate width (ruling 2): ordinary pagination, stable cursor. */`,
3094
- ` ${lcFirst(camel(q.id))}Pages: (params: { ${params} }, page?: { pageSize?: number; cursor?: string | null }) => {`,
3095
- ` if (!holon.pages) throw new Error("pages() needs the ROUTED holon - connect() to the logical (coordinator) workspace, not a shard");`,
3096
- ` return (holon.pages(${JSON.stringify(q.id)}, ${wireParams}, page) as Promise<{ rows: Row<${q.returns}>[]; cursor: string | null; done: boolean }>).then((result) => ({ ...result, rows: ${wrapRows("result.rows")} }));`,
3097
- ` },`,
3098
- );
3099
- }
3276
+ const queryPageCall = qScopes.length
3277
+ ? qOwned
3278
+ ? `_readWithOwnedKeys(holon, keyStore, ${qScopeJson}, (keys) => (holon.queryPage ?? (() => { throw new Error("bounded reads need a current @githolon/client"); }))(${JSON.stringify(q.id)}, ${wireParams}, page, keys) as Promise<{ rows: Row<${q.returns}>[]; cursor: string | null; done: boolean }>)`
3279
+ : `(holon.queryPage ?? (() => { throw new Error("bounded reads need a current @githolon/client"); }))(${JSON.stringify(q.id)}, ${wireParams}, page, await _readKeys(holon, keyStore, ${qScopeJson})) as Promise<{ rows: Row<${q.returns}>[]; cursor: string | null; done: boolean }>`
3280
+ : `(holon.queryPage ?? (() => { throw new Error("bounded reads need a current @githolon/client"); }))(${JSON.stringify(q.id)}, ${wireParams}, page) as Promise<{ rows: Row<${q.returns}>[]; cursor: string | null; done: boolean }>`;
3281
+ methods.push(
3282
+ ` /** Bounded \`${q.id}\` read. This is the default estate-scale surface: stable opaque cursor, no whole-result hydration. */`,
3283
+ ` ${lcFirst(camel(q.id))}Pages: async (params: { ${params} }, page: { pageSize?: number; cursor?: string | null } = {}) => {`,
3284
+ // Parenthesize the asserted Promise before awaiting it. Without these parens TypeScript
3285
+ // parses `await call as Promise<Page>` as `(await call) as Promise<Page>`, leaving `result`
3286
+ // incorrectly typed as a Promise and making every generated `.rows` access invalid.
3287
+ ` const result = await (${queryPageCall});`,
3288
+ ` return { ...result, rows: ${wrapRows("result.rows")} };`,
3289
+ ` },`,
3290
+ );
3100
3291
  }
3101
3292
 
3102
3293
  for (const c of (mod.counts ?? []).map((raw) => finishCount(raw))) {
@@ -3482,6 +3673,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
3482
3673
  `function ${tsClientFactoryName(domain)}(holon: NomosHolon) {`,
3483
3674
  ` const domainHash = ${hashConst};`,
3484
3675
  ` const actor = holon.principal;`,
3676
+ ` holon.prepareDomain?.(${JSON.stringify(domain)}, domainHash);`,
3485
3677
  ...(encPresent ? [` const keyStore: NomosKeyStore | undefined = ${domScopes.length > 0 ? `{ scopeKey: async (scope) => { if (!holon.__nomosScopeKey) throw new Error("encrypted business data needs the current @githolon/client"); return holon.__nomosScopeKey({ scope }); }, readKeys: async (scopes) => { if (!holon.__nomosScopeKeys) throw new Error("encrypted business data needs the current @githolon/client"); return holon.__nomosScopeKeys({ scopes }); } }` : "undefined"};`] : []),
3486
3678
  ...(ownedScope !== undefined && Object.keys(ownedActions).length > 0
3487
3679
  ? [
@@ -3767,7 +3959,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
3767
3959
  ` * or parallel cache around it. Nomos owns offline custody and synchronization. */`,
3768
3960
  `export function ${applicationOpener}(options: ${applicationName}OpenOptions): Promise<${applicationName}> {`,
3769
3961
  ` return nomosInternalOpenGeneratedApplication(`,
3770
- ` { cloud: options.cloud, application: options.application, signIn: options.signIn },`,
3962
+ ` { cloud: options.cloud, application: options.application, signIn: options.signIn, lawHash: ${hashConst} },`,
3771
3963
  ` (holon, user) => _nomosBind${applicationName}(holon as NomosHolon, user),`,
3772
3964
  ` );`,
3773
3965
  `}`,