@githolon/dsl 0.101.5 → 0.102.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/dart/CHANGELOG.md +13 -0
  2. package/dart/lib/nomos_client.dart +3 -52
  3. package/dart/lib/src/dev_cloud.dart +5 -0
  4. package/dart/lib/src/home.dart +10 -15
  5. package/dart/lib/src/nms_conformance_runtime.dart +12 -0
  6. package/dart/lib/src/nms_generated_runtime.dart +29 -0
  7. package/dart/lib/src/nms_test_environment.dart +8 -0
  8. package/dart/lib/src/node_runner_transport.dart +56 -18
  9. package/dart/lib/src/provider.dart +5 -1
  10. package/dart/lib/src/record_origin.dart +55 -0
  11. package/dart/lib/src/runner/node-entry.mjs +9105 -0
  12. package/dart/lib/src/runner/offline-install.mjs +4729 -0
  13. package/dart/lib/src/session_bound.dart +23 -10
  14. package/dart/lib/src/source_trust.dart +51 -0
  15. package/dart/lib/src/test_harness.dart +490 -58
  16. package/dart/lib/src/webview_bridge.dart +167 -58
  17. package/dart/lib/src/workspace_name.dart +7 -1
  18. package/dart/pubspec.lock +3 -3
  19. package/dart/pubspec.yaml +4 -2
  20. package/dart/test/acquire_session_credentials_test.dart +34 -17
  21. package/dart/test/acquire_session_test.dart +109 -24
  22. package/dart/test/adv_realm_interference_test.dart +227 -99
  23. package/dart/test/bundle_version_pairing_test.dart +1 -1
  24. package/dart/test/cloud_bound_topology_test.dart +1 -1
  25. package/dart/test/cluster_attested_test.dart +52 -35
  26. package/dart/test/cluster_births_test.dart +5 -2
  27. package/dart/test/cluster_device_keyed_acceptance_test.dart +5 -2
  28. package/dart/test/cluster_local_autosync_test.dart +5 -2
  29. package/dart/test/cluster_scale_test.dart +5 -2
  30. package/dart/test/compat_test.dart +1 -1
  31. package/dart/test/dlq_session_surface_test.dart +2 -1
  32. package/dart/test/errors_test.dart +1 -1
  33. package/dart/test/first_app_gauntlet_test.dart +78 -51
  34. package/dart/test/four_layer_nested_topology_test.dart +1 -1
  35. package/dart/test/harness_phase_timeout_test.dart +1 -1
  36. package/dart/test/home_parity_test.dart +9 -5
  37. package/dart/test/local_sync_inert_test.dart +22 -14
  38. package/dart/test/node_runner_transport_test.dart +9 -5
  39. package/dart/test/null_slot_test.dart +1 -1
  40. package/dart/test/parent_signer_recovery_test.dart +5 -2
  41. package/dart/test/schema_validation_test.dart +1 -1
  42. package/dart/test/sealing_client_test.dart +2 -1
  43. package/dart/test/session_bound_test.dart +92 -119
  44. package/dart/test/stale_bundle_test.dart +1 -1
  45. package/dart/test/test_harness_test.dart +77 -22
  46. package/dart/test/vo_read_typing_test.dart +32 -14
  47. package/dart/test/watch_after_offer_stall_test.dart +2 -1
  48. package/dart/test/webview_bridge_test.dart +17 -4
  49. package/dart/test/wire_test.dart +1 -1
  50. package/dart_types/CHANGELOG.md +8 -0
  51. package/dart_types/lib/nomos_types.dart +7 -6
  52. package/dart_types/lib/src/activity.dart +23 -13
  53. package/dart_types/lib/src/foreign_ref.dart +61 -112
  54. package/dart_types/lib/src/invitation.dart +96 -0
  55. package/dart_types/lib/src/nomos_ref.dart +97 -59
  56. package/dart_types/pubspec.yaml +1 -1
  57. package/dart_types/test/surfaced_test.dart +125 -19
  58. package/package.json +7 -3
  59. package/runtime/generated-application-host.js +8851 -0
  60. package/src/activity.ts +2 -2
  61. package/src/aggregate.ts +24 -1
  62. package/src/application_composition.ts +282 -0
  63. package/src/application_topology.ts +151 -0
  64. package/src/attested_core.ts +2 -2
  65. package/src/authoring_context.ts +9 -0
  66. package/src/birth_meter_ensure.ts +1 -1
  67. package/src/births.ts +53 -5
  68. package/src/build_package.ts +79 -71
  69. package/src/business_birth.ts +93 -10
  70. package/src/clear_directive.ts +3 -1
  71. package/src/codegen_dart.ts +369 -420
  72. package/src/codegen_dart_application.ts +1508 -0
  73. package/src/codegen_dart_client.ts +156 -95
  74. package/src/codegen_dart_scenario.ts +299 -0
  75. package/src/codegen_openapi.ts +8 -3
  76. package/src/codegen_proof.ts +1113 -1137
  77. package/src/codegen_provider_dart.ts +12 -1
  78. package/src/codegen_ts.ts +1176 -496
  79. package/src/compat_package_main.ts +22 -10
  80. package/src/compile_package_main.ts +1536 -337
  81. package/src/contract.ts +10 -4
  82. package/src/dart_barrel_guard.ts +5 -1
  83. package/src/describe_model.ts +13 -5
  84. package/src/directive.ts +244 -5
  85. package/src/engine_entry.ts +56 -13
  86. package/src/engine_javascript.ts +31 -0
  87. package/src/evolve_preview.ts +3 -12
  88. package/src/framework/application_home.ts +24 -10
  89. package/src/framework/capability.ts +15 -1
  90. package/src/framework/domain_lifecycle.ts +8 -0
  91. package/src/framework/framework_aggregates.ts +2 -2
  92. package/src/framework/genesis.ts +11 -1
  93. package/src/framework/home.ts +178 -75
  94. package/src/framework/impure_capability.ts +24 -7
  95. package/src/framework/relations.ts +13 -5
  96. package/src/framework/workspaces.ts +13 -12
  97. package/src/generated_client_names.ts +42 -3
  98. package/src/idof_lint.ts +65 -13
  99. package/src/index.ts +46 -12
  100. package/src/intent.ts +587 -0
  101. package/src/law_check.ts +64 -16
  102. package/src/precondition.ts +16 -4
  103. package/src/read_projection_namespace.ts +2 -7
  104. package/src/recipient.ts +33 -0
  105. package/src/relation_schema.ts +93 -0
  106. package/src/secondary_ensure.ts +11 -2
  107. package/src/shape_compat.ts +11 -1
  108. package/src/usd.ts +30 -8
  109. package/src/usd_layers.ts +447 -246
  110. package/src/usd_state.ts +1 -1
  111. package/src/value_object.ts +49 -13
  112. package/src/wire_encode.ts +124 -26
  113. package/src/workspace_sharing.ts +269 -0
  114. package/src/zod_introspection.ts +84 -0
  115. package/dart/example/nomos_secure_key_store.dart +0 -82
  116. package/dart/example/nomos_webview_transport.dart +0 -63
  117. package/dart/lib/nomos_headless.dart +0 -50
  118. package/dart/lib/testing.dart +0 -14
  119. package/dart/test/co2_product_gauntlet_test.dart +0 -244
  120. package/src/framework/identity.ts +0 -537
package/src/codegen_ts.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * Dart apps get generated payload classes / read models / typed query accessors;
12
12
  * until this file, a web app on `@githolon/client` hand-typed directive ids and
13
- * payload shapes against the raw `offer()` surface. `generateTsClient(modules,
13
+ * payload shapes against framework transport primitives. `generateTsClient(modules,
14
14
  * opts)` closes that: ONE generated `.ts` file per package, emitted by
15
15
  * `nomos-compile` AFTER the package is hashed (the deployed law's content hash is
16
16
  * baked in as a constant), carrying:
@@ -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 } from "./value_object.js";
54
+ import { voMetaOf, collectPayloadValueObjectDescriptors, idOfMetaOf, idOfTargetKindOf } 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";
@@ -61,18 +61,32 @@ import { resolveWorkspaceTypes, type ResolvedWorkspaceType, type WorkspaceTypeDe
61
61
  import {
62
62
  deriveClientMintPlan,
63
63
  deriveDirectiveRecordTarget,
64
+ mintedCreateField,
64
65
  type ClientMintPlanEntry,
65
66
  } from "./workspace_routing.js";
66
67
  import { admitsNull, nullDocLine } from "./null_meta.js";
67
68
  import { isNamespacedAggregate, qualifyReadProjectionType } from "./read_projection_namespace.js";
68
69
  import { publicClientInterface, type DomainInterface } from "./shape_compat.js";
69
70
  import { buildNomosDescribeModel, type NomosDescribeModel } from "./describe_model.js";
70
- import { assertModelClientMemberAvailable } from "./generated_client_names.js";
71
+ import { assertModelClientMemberAvailable, generatedAggregateCollectionName } from "./generated_client_names.js";
72
+ import { isBusinessIntent } from "./intent.js";
73
+ import { isFrameworkAggregate, isFrameworkDirective, isFrameworkQuery } from "./framework/framework_aggregates.js";
74
+ import { contractTypeName } from "./contract.js";
75
+ import { resolveApplicationTopology } from "./application_topology.js";
76
+ import { recipientMetaOf } from "./recipient.js";
77
+ import {
78
+ zodArrayElement,
79
+ zodDef,
80
+ zodEnumValues,
81
+ zodKind,
82
+ zodObjectShape,
83
+ } from "./zod_introspection.js";
71
84
 
72
85
  const cap = (s: string) => (s.length ? s[0]!.toUpperCase() + s.slice(1) : s);
73
86
  const camel = (s: string) => s.replace(/[_\s-]+(\w)/g, (_m, c: string) => c.toUpperCase());
74
87
  const lcFirst = (s: string) => (s.length ? s[0]!.toLowerCase() + s.slice(1) : s);
75
88
  const upperSnake = (s: string) => s.replace(/[^A-Za-z0-9]+/g, "_").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase();
89
+ const tsDomainApplicationType = (domain: string) => `_${cap(camel(domain))}DomainApplication`;
76
90
 
77
91
  /** A count/sum group-by field → a valid TS param identifier. A DOTTED group-by (`location.source`,
78
92
  * the #30 follow-up) camel-joins into `locationSource` (a bare `location.source` is not a legal
@@ -89,6 +103,15 @@ const tsGroupParam = (by: string) => {
89
103
  const tsKey = (name: string): string =>
90
104
  /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
91
105
 
106
+ /** One stable business name for an aggregate consequence in an intent result. */
107
+ const tsResultMemberName = (aggregateId: string): string => {
108
+ const bare = aggregateId.split(".").filter(Boolean).at(-1) ?? aggregateId;
109
+ const normalized = lcFirst(camel(bare.replace(/[^A-Za-z0-9_$]+/g, " ")));
110
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(normalized) && normalized.length > 0
111
+ ? normalized
112
+ : "record";
113
+ };
114
+
92
115
  /** OPTIONAL-CHAIN member access onto a value keyed by a possibly-dotted field label,
93
116
  * meant to follow a `?` (e.g. `x?${tsOptAccess(f)}`): `.foo` for an identifier,
94
117
  * `.["foo.bar"]` otherwise (bracket access preserves the single property). */
@@ -103,6 +126,10 @@ const tsOptAccess = (name: string): string =>
103
126
  /** `tool-library` / `tool_library` / `Tool Library` → `toolLibraryClient` — the factory symbol. */
104
127
  export const tsClientFactoryName = (domain: string): string => `${lcFirst(camel(domain))}Client`;
105
128
 
129
+ /** Package display names may also contain dots; generated application symbols never do. */
130
+ export const tsApplicationName = (packageName: string): string =>
131
+ `${cap(camel(packageName.replace(/[^A-Za-z0-9]+/g, " ")))}Application`;
132
+
106
133
  /** `tool-library` / `tool_library` / `Tool Library` → `TOOL_LIBRARY_DOMAIN_HASH` — the hash const. */
107
134
  export const tsHashConstName = (packageName: string): string => `${upperSnake(packageName)}_DOMAIN_HASH`;
108
135
 
@@ -235,56 +262,10 @@ function buildEncryptionMap(
235
262
  // The walkers are exported: codegen_proof.ts synthesizes SAMPLE payloads from the
236
263
  // same zod shapes this file derives TYPES from — one introspection, two emitters.
237
264
 
238
- type ZodInternalDef = {
239
- type?: string | z.ZodTypeAny;
240
- innerType?: z.ZodTypeAny;
241
- element?: z.ZodTypeAny;
242
- shape?: Record<string, z.ZodTypeAny> | (() => Record<string, z.ZodTypeAny>);
243
- entries?: Record<string, string | number>;
244
- options?: z.ZodTypeAny[];
245
- value?: unknown;
246
- values?: readonly unknown[];
247
- valueType?: z.ZodTypeAny;
248
- getter?: () => z.ZodTypeAny; // `z.lazy(() => …)` — the deferred inner schema (zod v4)
249
- };
250
-
251
265
  // The lazy-unwrap + recursive-name helpers live in the dependency-free `zod_lazy.ts` (shared with
252
266
  // codegen_dart WITHOUT an import cycle). Re-exported so codegen_proof's existing import holds.
253
267
  export { zodUnwrapLazy };
254
-
255
- export function zodDef(zt: z.ZodTypeAny): ZodInternalDef {
256
- const raw = zt as unknown as { _def?: ZodInternalDef; def?: ZodInternalDef };
257
- return raw._def ?? raw.def ?? {};
258
- }
259
-
260
- export function zodKind(zt: z.ZodTypeAny): string {
261
- const t = zodDef(zt).type;
262
- return typeof t === "string" ? t : "unknown";
263
- }
264
-
265
- export function zodEnumValues(zt: z.ZodTypeAny): string[] {
266
- const raw = zt as unknown as { options?: readonly unknown[] };
267
- if (raw.options !== undefined) return raw.options.map(String);
268
- const entries = zodDef(zt).entries;
269
- if (entries !== undefined) return Object.values(entries).map(String);
270
- throw new Error("codegen-ts: ZodEnum has no values");
271
- }
272
-
273
- export function zodObjectShape(zt: z.ZodTypeAny): Record<string, z.ZodTypeAny> {
274
- const shape = zodDef(zt).shape;
275
- if (typeof shape === "function") return shape();
276
- if (shape !== undefined) return shape;
277
- const raw = zt as unknown as { shape?: Record<string, z.ZodTypeAny> };
278
- if (raw.shape !== undefined) return raw.shape;
279
- throw new Error("codegen-ts: ZodObject has no shape");
280
- }
281
-
282
- export function zodArrayElement(zt: z.ZodTypeAny): z.ZodTypeAny {
283
- const def = zodDef(zt);
284
- if (def.element !== undefined) return def.element;
285
- if (typeof def.type !== "string" && def.type !== undefined) return def.type;
286
- throw new Error("codegen-ts: ZodArray has no element type");
287
- }
268
+ export { zodArrayElement, zodDef, zodEnumValues, zodKind, zodObjectShape };
288
269
 
289
270
  const union = (parts: string[]) => [...new Set(parts)].join(" | ");
290
271
  const paren = (t: string) => (t.includes("|") || t.includes("&") ? `(${t})` : t);
@@ -327,6 +308,12 @@ function tsTypeOfZod(
327
308
  reg?: LazyTypeRegistry,
328
309
  path = "",
329
310
  ): string {
311
+ if (recipientMetaOf(zt) !== undefined) return "Recipient<User>";
312
+ const aggregateType = idOfMetaOf(zt);
313
+ if (aggregateType !== undefined) {
314
+ const target = idOfTargetKindOf(zt) === "contract" ? contractTypeName(aggregateType) : aggregateType;
315
+ return `NomosRef<${target}>`;
316
+ }
330
317
  // A DECLARED VALUE OBJECT keeps its NAME (vo_authoring_end_to_end.md slice B): the payload
331
318
  // references the same exported interface the read model uses — one name, both directions —
332
319
  // instead of an anonymous inline literal type. Checked on the BARE object only: optional/
@@ -377,7 +364,12 @@ function tsTypeOfZod(
377
364
  }
378
365
  case "record": {
379
366
  const vt = zodDef(zt).valueType;
380
- return `Record<string, ${vt ? tsTypeOfZod(vt, indent, reg, `${path}.*`) : "unknown"}>`;
367
+ const value = vt ? tsTypeOfZod(vt, indent, reg, `${path}.*`) : "unknown";
368
+ // TypeScript permits direct structural recursion but rejects the same knot hidden behind
369
+ // its `Record` utility alias. A generated lazy type is necessarily already registered here.
370
+ return reg?.used.has(value) === true
371
+ ? `{ [key: string]: ${value} }`
372
+ : `Record<string, ${value}>`;
381
373
  }
382
374
  case "optional":
383
375
  case "default":
@@ -466,7 +458,8 @@ export function autoStampFields(schema: z.ZodTypeAny): string[] {
466
458
  return out;
467
459
  }
468
460
 
469
- /** The payload type text, with auto-stamped / front-door-minted / auto-placed fields optional (`?`).
461
+ /** The payload type text, with auto-stamped / auto-placed fields optional (`?`) and front-door-minted
462
+ * fields omitted entirely (they are framework implementation, returned as typed refs).
470
463
  * `reg` collects any RECURSIVE `z.lazy` named types minted while walking (emitted once by the
471
464
  * caller); `pathPrefix` (the directive id) scopes their names so two directives can't collide. */
472
465
  function tsTypeOfPayload(
@@ -474,16 +467,22 @@ function tsTypeOfPayload(
474
467
  autoStamped: readonly string[],
475
468
  minted: readonly string[] = [],
476
469
  autoPlaced: readonly string[] = [],
470
+ privateFields: readonly string[] = [],
477
471
  reg?: LazyTypeRegistry,
478
472
  pathPrefix = "",
479
473
  ): string {
480
- if (zodKind(schema) !== "object" || (autoStamped.length === 0 && minted.length === 0 && autoPlaced.length === 0)) {
474
+ if (zodKind(schema) !== "object" || (autoStamped.length === 0 && minted.length === 0 && autoPlaced.length === 0 && privateFields.length === 0)) {
481
475
  return tsTypeOfZod(schema, "", reg, pathPrefix);
482
476
  }
483
477
  const stamped = new Set(autoStamped);
484
478
  const mintedSet = new Set(minted);
485
479
  const placedSet = new Set(autoPlaced);
486
- const fields = Object.entries(zodObjectShape(schema)).map(([name, raw]) => {
480
+ const privateSet = new Set(privateFields);
481
+ const fields = Object.entries(zodObjectShape(schema))
482
+ // A kernel-minted create id is not optional application input; it is not application input at all.
483
+ // The generated method mints it internally and returns the resulting NomosRef<T> on the outcome.
484
+ .filter(([name]) => !mintedSet.has(name) && !privateSet.has(name))
485
+ .map(([name, raw]) => {
487
486
  let f = raw as z.ZodTypeAny;
488
487
  let optional = stamped.has(name) || mintedSet.has(name) || placedSet.has(name);
489
488
  while (zodKind(f) === "optional" || zodKind(f) === "default") {
@@ -492,9 +491,7 @@ function tsTypeOfPayload(
492
491
  }
493
492
  const note = stamped.has(name)
494
493
  ? " // auto-stamped with ISO now() at offer when omitted"
495
- : mintedSet.has(name)
496
- ? " // kernel-minted at offer when omitted (the front-door mint; returned on the result)"
497
- : placedSet.has(name)
494
+ : placedSet.has(name)
498
495
  ? " // picked LEAST-LOADED over the coordinator's law-state at offer when omitted (sharding §3)"
499
496
  : "";
500
497
  const fieldPath = pathPrefix === "" ? name : `${pathPrefix}.${name}`;
@@ -543,16 +540,18 @@ function tsBaseTypeOfField(field: Field): string {
543
540
  return "string";
544
541
  }
545
542
  case "ref": {
546
- // A `t.foreignRef` field surfaces as a TYPED cross-workspace locator (hydrated by the accessors from
547
- // the raw id + the declared workspace field); a plain same-workspace `t.ref` stays the id string.
543
+ // Both reference shapes keep aggregate identity typed. A same-workspace edge is the canonical
544
+ // NomosRef<TAggregate>; a foreign edge adds its decoder/read helpers.
548
545
  const fr = (field as { foreignRef?: { targetAggregateId: string; targetIsContract?: boolean } }).foreignRef;
549
546
  // A contract target types against the shared `<ContractId>Contract` interface (the public DTO),
550
547
  // not the concrete `<Agg>Data` — that is how the reference stays acyclic.
551
548
  return fr !== undefined
552
549
  ? fr.targetIsContract === true
553
- ? `ForeignRef<${fr.targetAggregateId}Contract>`
554
- : `ForeignRef<${fr.targetAggregateId}Data>`
555
- : "string";
550
+ ? `ForeignRef<${contractTypeName(fr.targetAggregateId)}>`
551
+ : `ForeignRef<${fr.targetAggregateId}>`
552
+ : field.refAggregateId !== undefined
553
+ ? `NomosRef<${field.refAggregateId}>`
554
+ : "string";
556
555
  }
557
556
  case "json":
558
557
  // A NAMED VALUE OBJECT field reads as its generated interface (`value_object.ts`);
@@ -598,7 +597,9 @@ const geometryFactoryName: Record<string, string> = {
598
597
  function tsTypeOfVoField(f: ValueObjectFieldDescriptor): string {
599
598
  switch (f.type) {
600
599
  case "string":
601
- return f.brand ?? "string"; // a brandedId(...) → the emitted branded alias
600
+ return f.idOf !== undefined
601
+ ? `NomosRef<${f.idOf}>`
602
+ : f.brand ?? "string"; // a brandedId(...) → the emitted branded alias
602
603
  case "number":
603
604
  return "number"; // `.int()`/units are Dart-side nominal products; TS keeps one number
604
605
  case "boolean":
@@ -709,6 +710,9 @@ export interface TsClientOptions {
709
710
  /** The CLIENT/LAW COMPAT MANIFEST (§5) — baked in so the factory can hand it to the
710
711
  * holon's connect-time classifier (DIAGNOSIS only, never a gate). */
711
712
  readonly compat?: NomosCompat;
713
+ /** Explicit application home for direct/synthetic generation. The package compiler derives this from
714
+ * business birth topology and passes it here; ambiguous multi-domain packages fail rather than flatten. */
715
+ readonly homeDomain?: string;
712
716
  }
713
717
 
714
718
  /** TS string literal (JSON.stringify covers escaping). */
@@ -728,13 +732,13 @@ function emitTsActivityBlock(entries: readonly PackageActivityEntry[], describeM
728
732
  : `{ ${desc.refs
729
733
  .map((r) => {
730
734
  // A `from: "@created"` ref reads the id the LAW MINTED (the row's `created` list, first type match);
731
- // any other `from` is a payload path. Every ref carries the session workspace (the row's `workspace`).
735
+ // any other `from` is a payload path. Every ref carries the record address (the row's `workspace`).
732
736
  const id = r.from === "@created"
733
737
  ? `_activityAggregateId(row.created, ${_actLit(r.type)})`
734
738
  : r.from === "@affected"
735
739
  ? `_activityAggregateId(row.affected, ${_actLit(r.type)})`
736
740
  : `_activityPayloadPath(row.payload, ${_actLit(r.from)})`;
737
- return `${_actLit(r.name)}: { workspace: row.workspace ?? "", type: ${_actLit(r.type)}, id: ${id} }`;
741
+ return `${_actLit(r.name)}: _nmsRef(row.workspace ?? "", ${_actLit(r.type)}, ${id})`;
738
742
  })
739
743
  .join(", ")} }`;
740
744
  return [
@@ -742,7 +746,7 @@ function emitTsActivityBlock(entries: readonly PackageActivityEntry[], describeM
742
746
  ` const params: Record<string, string> = { ${params} };`,
743
747
  ` const vars = { actor: row.actorSubject, ...params };`,
744
748
  ` return {`,
745
- ` eventId: ${e.correction === true ? "_activityOpaqueCorrectionId(row.intentId)" : "row.intentId"}, kind: ${_actLit(desc.kind)}, actorSubject: row.actorSubject, occurredAt: row.occurredAt,`,
749
+ ` eventId: ${e.correction === true ? "_activityOpaqueCorrectionId(row.intentId)" : "row.intentId"}, kind: ${_actLit(desc.kind)}, performedBy: row.actorSubject, occurredAt: row.occurredAt,`,
746
750
  ` title: _renderActivityTemplate(${_actLit(desc.title)}, vars), message: _renderActivityTemplate(${_actLit(desc.message)}, vars),`,
747
751
  ` titleKey: ${_actLit(desc.titleKey)}, messageKey: ${_actLit(desc.messageKey)},`,
748
752
  ` params, refs: ${refs},`,
@@ -755,18 +759,18 @@ function emitTsActivityBlock(entries: readonly PackageActivityEntry[], describeM
755
759
  };
756
760
  return [
757
761
  `// ── model evolution — generated business surface over exact-history proof ──`,
758
- `export interface NomosModelDifference {`,
762
+ `interface NomosModelDifference {`,
759
763
  ` kind: "rules" | "outcome" | "evidence"; area?: string; action?: string; summary: string;`,
760
764
  `}`,
761
- `export interface NomosModelReview {`,
765
+ `interface NomosModelReview {`,
762
766
  ` checkedActions: number; candidateActions: number; sameOutcome: boolean | null;`,
763
767
  ` differences: NomosModelDifference[];`,
764
768
  `}`,
765
- `export type NomosModelAdoption =`,
769
+ `type NomosModelAdoption =`,
766
770
  ` | { status: "adopted"; summary: string; review: NomosModelReview }`,
767
771
  ` | { status: "needs-decision"; summary: string; review: NomosModelReview; accept(because: string): Promise<NomosModelAdoption> }`,
768
772
  ` | { status: "blocked"; summary: string; review: NomosModelReview };`,
769
- `export type NomosModelRestoration =`,
773
+ `type NomosModelRestoration =`,
770
774
  ` | { status: "restored"; summary: string; review: NomosModelReview }`,
771
775
  ` | { status: "needs-decision"; summary: string; review: NomosModelReview; accept(because: string): Promise<NomosModelRestoration> }`,
772
776
  ` | { status: "blocked"; summary: string; review: NomosModelReview };`,
@@ -886,13 +890,13 @@ function emitTsActivityBlock(entries: readonly PackageActivityEntry[], describeM
886
890
  `}`,
887
891
  ``,
888
892
  `// ── activity feed (architecture/activity_descriptors.md) — typed rows folded from the intent log ──`,
889
- `/** A declared navigation target on an activity row: the canonical [NomosRef] triple (workspace + type +`,
890
- ` * resolved id). A \`from: "@created"\` ref resolves the id the LAW minted; others a payload path. */`,
891
- `export interface ActivityRef { workspace: string; type: string; id: string }`,
893
+ `/** A declared navigation target on an activity row: the same inspectable aggregate reference returned by`,
894
+ ` * generated reads and writes. A \`from: "@created"\` ref resolves the identity the law minted. */`,
895
+ `export type ActivityRef = NomosRef<_NomosAnyAggregate>;`,
892
896
  `/** A rendered activity row — every public business directive has a framework-owned baseline;`,
893
897
  ` * an optional \`.activity(...)\` declaration enriches its language and references. */`,
894
898
  `export interface ActivityEvent {`,
895
- ` eventId: string; kind: string; actorSubject: string; occurredAt: string;`,
899
+ ` eventId: string; kind: string; performedBy: string; occurredAt: string;`,
896
900
  ` title: string; message: string; titleKey: string; messageKey: string;`,
897
901
  ` params: Record<string, string>; refs: Record<string, ActivityRef>;`,
898
902
  ` category?: string; icon?: string;`,
@@ -900,7 +904,7 @@ function emitTsActivityBlock(entries: readonly PackageActivityEntry[], describeM
900
904
  ` domain: string; directiveId: string;`,
901
905
  `}`,
902
906
  `/** The runtime's per-committed-intent RAW ROW (the shape \`holon.watchActivity\`/\`activityLog\` yield). */`,
903
- `export interface ActivityRawRow { intentId: string | null; domain: string | null; directiveId: string | null; actorSubject: string | null; occurredAt: string | null; payload: Record<string, unknown>; created?: { type: string; id: string }[]; affected?: { type: string; id: string }[]; workspace?: string }`,
907
+ `interface ActivityRawRow { intentId: string | null; domain: string | null; directiveId: string | null; actorSubject: string | null; occurredAt: string | null; payload: Record<string, unknown>; created?: { type: string; id: string }[]; affected?: { type: string; id: string }[]; workspace?: string }`,
904
908
  `interface _CompleteActivityRawRow extends ActivityRawRow { intentId: string; domain: string; directiveId: string; actorSubject: string; occurredAt: string }`,
905
909
  `/** A verified narrative over the activity log. \`describeHistory\` returns this narrow form;`,
906
910
  ` * \`describe\` returns the richer linked [NomosDescription] while preserving these two fields. */`,
@@ -912,13 +916,14 @@ function emitTsActivityBlock(entries: readonly PackageActivityEntry[], describeM
912
916
  `}`,
913
917
  `export interface NomosDescribeItem {`,
914
918
  ` kind: string; id: string; label: string; summary: string;`,
915
- ` details?: Record<string, unknown>; refs?: NomosRef[];`,
919
+ ` details?: Record<string, unknown>; refs?: NomosRef<_NomosAnyAggregate>[];`,
916
920
  `}`,
917
- `/** A parent-ledger link to another part of the organisation. Open that workspace's generated client`,
918
- ` * and call describe() again; each child explains itself in its own installed business language. */`,
921
+ `/** A verified link to another part of the organisation. Following it returns that part's`,
922
+ ` * generated explanation without exposing navigation machinery. */`,
919
923
  `export interface NomosDescribeChild {`,
920
- ` workspace: string; status: "active" | "retired" | "reclaimed" | "unknown"; bornAt: string | null;`,
924
+ ` status: "active" | "retired" | "reclaimed" | "unknown"; bornAt: string | null;`,
921
925
  ` section: "root"; label: string; question: string; summary: string;`,
926
+ ` open(): Promise<NomosDescription>;`,
922
927
  `}`,
923
928
  `export interface NomosOrganisationDescription { complete: boolean; children: NomosDescribeChild[] }`,
924
929
  `/** One grounded page in the hierarchical self-description. Root links to exactly six detail pages. */`,
@@ -974,7 +979,7 @@ function emitTsActivityBlock(entries: readonly PackageActivityEntry[], describeM
974
979
  `/** Correction refs are derived by the runtime from the corrected action's sealed events. */`,
975
980
  `function _activityCorrectionRefs(row: ActivityRawRow): Record<string, ActivityRef> {`,
976
981
  ` return Object.fromEntries((row.affected ?? []).map((ref, index) => [`,
977
- ` \`subject\${index + 1}\`, { workspace: row.workspace ?? "", type: ref.type, id: ref.id },`,
982
+ ` \`subject\${index + 1}\`, _nmsRef(row.workspace ?? "", ref.type, ref.id),`,
978
983
  ` ]));`,
979
984
  `}`,
980
985
  `/** Keep a framework-private correction identity stable and traceable without exposing its internal name. */`,
@@ -995,7 +1000,7 @@ function emitTsActivityBlock(entries: readonly PackageActivityEntry[], describeM
995
1000
  `};`,
996
1001
  `/** Fold ONE committed-intent raw row into a typed [ActivityEvent], or null for framework/control work`,
997
1002
  ` * outside this generated business package. Pure — the kernel/ledger stays the audit truth. */`,
998
- `export function renderActivity(row: ActivityRawRow): ActivityEvent | null {`,
1003
+ `function renderActivity(row: ActivityRawRow): ActivityEvent | null {`,
999
1004
  ` if (typeof row.intentId !== "string" || typeof row.domain !== "string" || typeof row.directiveId !== "string" || typeof row.actorSubject !== "string" || typeof row.occurredAt !== "string") return null;`,
1000
1005
  ` const f = _ACTIVITY_REGISTRY[\`${"${row.domain.length}:${row.domain}${row.directiveId}"}\`];`,
1001
1006
  ` return f === undefined ? null : f(row as _CompleteActivityRawRow);`,
@@ -1020,6 +1025,25 @@ function anyPackedTaxonomy(modules: readonly DomainModule[]): boolean {
1020
1025
  }
1021
1026
 
1022
1027
  export function generateTsClient(modules: readonly DomainModule[], opts: TsClientOptions): string {
1028
+ const topologyModules = modules;
1029
+ // A public business birth can target a deliberately hidden root aggregate. Its private binder still
1030
+ // needs a nominal type, but unrelated framework records must remain absent even from generated source.
1031
+ const publicBusinessAggregateIds = new Set(modules.flatMap((mod) => mod.aggregates
1032
+ .filter((aggregate) =>
1033
+ (aggregate as { businessBirthInternal?: unknown }).businessBirthInternal !== true
1034
+ && !isFrameworkAggregate(aggregate))
1035
+ .map((aggregate) => aggregate.id)));
1036
+ const internalBirthAggregateIds = [...new Set(modules.flatMap((mod) =>
1037
+ (mod.applicationIntents ?? mod.directives).flatMap((directive) => {
1038
+ if ((directive as { businessBirthInternal?: unknown }).businessBirthInternal === true) return [];
1039
+ const aggregateId = (directive as { businessBirth?: { aggregateId?: unknown } }).businessBirth?.aggregateId;
1040
+ return typeof aggregateId === "string" && !publicBusinessAggregateIds.has(aggregateId) ? [aggregateId] : [];
1041
+ })))].sort();
1042
+ const topology = resolveApplicationTopology(topologyModules);
1043
+ const homeDomain = opts.homeDomain ?? topology.homeDomain;
1044
+ if (!topology.rootDomains.includes(homeDomain)) {
1045
+ throw new Error(`generated TypeScript application cannot use child domain '${homeDomain}' as its primary facet`);
1046
+ }
1023
1047
  // Collect the one framework-authored correction testimony before presentation filtering removes its
1024
1048
  // private directive. Ordinary internal machinery stays absent from the registry.
1025
1049
  const packageActivityEntries = collectActivityDescriptors(modules);
@@ -1027,23 +1051,43 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1027
1051
  // vocabulary. Strip only the presentation marker here; manifests, hashes and the engine see the full law.
1028
1052
  modules = modules.map((mod) => {
1029
1053
  const publicAggregates = mod.aggregates.filter(
1030
- (a) => (a as { businessBirthInternal?: unknown }).businessBirthInternal !== true,
1054
+ (a) => (a as { businessBirthInternal?: unknown }).businessBirthInternal !== true && !isFrameworkAggregate(a),
1031
1055
  );
1032
1056
  const publicAggregateIds = new Set(publicAggregates.map((a) => a.id));
1033
1057
  return {
1034
1058
  ...mod,
1035
1059
  aggregates: publicAggregates,
1036
- directives: mod.directives.filter(
1037
- (d) => (d as { businessBirthInternal?: unknown }).businessBirthInternal !== true,
1038
- ),
1060
+ // An explicit application language wins. Otherwise the tenant's exported,
1061
+ // non-framework directives are its business verbs. Framework directives are
1062
+ // branded by object identity and can never leak merely because they compose
1063
+ // into the same law.
1064
+ directives: (mod.applicationIntents ?? mod.directives).filter((d) =>
1065
+ (d as { businessBirthInternal?: unknown }).businessBirthInternal !== true
1066
+ && !isFrameworkDirective(d)),
1039
1067
  ...(mod.queries === undefined
1040
1068
  ? {}
1041
- : { queries: mod.queries.filter((q) => (q as { businessBirthInternal?: unknown }).businessBirthInternal !== true) }),
1069
+ : { queries: mod.queries.filter((q) => (q as { businessBirthInternal?: unknown }).businessBirthInternal !== true && !isFrameworkQuery(q)) }),
1042
1070
  ...(mod.readModelAggregates === undefined
1043
1071
  ? {}
1044
1072
  : { readModelAggregates: mod.readModelAggregates.filter((id) => publicAggregateIds.has(id)) }),
1045
1073
  };
1046
1074
  });
1075
+ const aggregateOwners = new Map<string, string[]>();
1076
+ for (const module of modules) {
1077
+ const domain = module.domain ?? module.name;
1078
+ for (const aggregate of module.aggregates) {
1079
+ aggregateOwners.set(aggregate.id, [...(aggregateOwners.get(aggregate.id) ?? []), domain]);
1080
+ }
1081
+ }
1082
+ const aggregateIdentityTypes = [
1083
+ ...new Set(modules.flatMap((mod) => mod.aggregates.map((aggregate) => aggregate.id))),
1084
+ ].sort();
1085
+ const publicAggregateIds = new Set(aggregateIdentityTypes);
1086
+ // A law-only package can legitimately expose no business aggregate while still receiving the
1087
+ // universal description/application implementation. `never` makes internal generic helpers
1088
+ // uncallable and produces invalid declarations; `unknown` preserves the sealed brand without
1089
+ // inventing a public aggregate identity.
1090
+ const anyAggregateType = aggregateIdentityTypes.length === 0 ? "unknown" : aggregateIdentityTypes.join(" | ");
1047
1091
  for (const mod of modules) assertModelClientMemberAvailable(mod);
1048
1092
  const clientInterface = opts.interface === undefined
1049
1093
  ? undefined
@@ -1061,11 +1105,14 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1061
1105
  Object.values(fields).some((field) => field.owned === true),
1062
1106
  ),
1063
1107
  );
1064
- const manualEncryptionPresent = encMap !== null && Object.values(encMap).some((domain) =>
1065
- [...Object.values(domain.directives), ...Object.values(domain.reads)].some((fields) =>
1066
- Object.values(fields).some((field) => field.owned !== true),
1067
- ),
1108
+ const sourceTrustPresent = modules.some((mod) =>
1109
+ (mod.directives as Directive<unknown>[]).some((directive) => directive.businessSourceTrust !== undefined),
1068
1110
  );
1111
+ // The old generic owner ceremony accepted raw scopes, directive ids, payloads and key material. It was a
1112
+ // complete second framework API hiding inside every encrypted generated client. Never emit it: ordinary
1113
+ // encryption remains statefully owned by the connected application, while record sharing is exposed only
1114
+ // through generated share/unshare/transfer business methods for aggregates that declare that policy.
1115
+ const manualEncryptionPresent = false;
1069
1116
  const encConst = encryptionConstName(opts.packageName);
1070
1117
  const internalFieldsByType = Object.fromEntries(
1071
1118
  [...modules.flatMap((mod) => mod.aggregates)]
@@ -1076,14 +1123,10 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1076
1123
  .filter(([, fields]) => fields.length > 0),
1077
1124
  );
1078
1125
  const hasInternalFields = Object.keys(internalFieldsByType).length > 0;
1079
- // A package declaring ≥1 `.births()` directive emits the branded identity wrappers (below) its typed
1080
- // birth methods take for `actor`/`authorSecret`. Births-free packages emit nothing (byte-identical).
1081
- const hasBirths = modules.some((m) =>
1082
- (m.directives as Directive<unknown>[]).some((d) =>
1083
- (d as { birthsChild?: boolean })?.birthsChild === true
1084
- && (d as { businessBirth?: unknown }).businessBirth === undefined,
1085
- ),
1086
- );
1126
+ // Raw `.births()` directives are framework/operator law, not an alternative tenant API. Only an explicit
1127
+ // businessBirth declaration receives a generated application method; therefore no principal/secret/parent
1128
+ // wrappers are emitted here.
1129
+ const hasBirths = false;
1087
1130
  // SURFACED MERGE CONFLICTS: a package declaring ≥1 `.merge(Surfaced(...))` field emits the
1088
1131
  // `SurfacedValue<T>` union + guard (below). Surfaced-free packages emit nothing (byte-identical).
1089
1132
  const hasSurfaced = modules.some((m) =>
@@ -1110,22 +1153,65 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1110
1153
  ),
1111
1154
  );
1112
1155
  const out: string[] = [];
1156
+ const applicationName = tsApplicationName(opts.packageName);
1157
+ const applicationOpener = `open${applicationName}`;
1158
+ // A signed-in person gets one application home. Other generated domain factories exist only so a
1159
+ // business birth or typed navigation can bind them to their own child workspace inside the private plane.
1160
+ const generatedDomains = [...new Set(modules.map((mod) => mod.domain ?? mod.name))];
1161
+ const invitationPolicies = modules.flatMap((module) =>
1162
+ (module.applicationWorkspaceSharing ?? []).map((policy) => ({
1163
+ domain: module.domain ?? module.name,
1164
+ domainHash: opts.domainHash,
1165
+ query: policy.invitationLaw.pendingByRecipient.id,
1166
+ redeem: policy.invitationLaw.redeem.id,
1167
+ personField: policy.person,
1168
+ roleField: policy.role,
1169
+ scopeField: policy.scope,
1170
+ }))
1171
+ );
1172
+ const hasInvitations = invitationPolicies.length > 0;
1173
+ const clientDomains = topology.rootDomains;
1113
1174
  // The PACKAGE-LEVEL recursive-type registry: every directive payload's `z.lazy` knots mint
1114
1175
  // named recursive type aliases into it (deduped by lazy identity across directives); the decls
1115
1176
  // are emitted once, after the payload types (TS hoists type aliases, so order is free).
1116
1177
  const lazyReg = newLazyRegistry();
1117
1178
  out.push(
1118
- `// AUTO-GENERATED by nomos-compile — typed TS client for package "${opts.packageName}".`,
1119
- `// Regenerated on every compile; do not edit. Zero imports: bind it to any holon`,
1120
- `// exposing the @githolon/client surface (the structural NomosHolon below), e.g.`,
1121
- `//`,
1122
- `// import { connect } from "@githolon/client";`,
1123
- `// import { ${tsClientFactoryName(modules[0]?.domain ?? "domain")} } from "./build/${opts.packageName}.client.js";`,
1124
- `// const holon = await connect({ cloud, workspace, clientId });`,
1125
- `// const app = ${tsClientFactoryName(modules[0]?.domain ?? "domain")}(holon);`,
1179
+ `// AUTO-GENERATED by nomos-compile — typed TS application implementation for package "${opts.packageName}".`,
1180
+ `// Regenerated on every compile; do not edit. Framework binding types below are deliberately private:`,
1181
+ `// tenant application code receives the generated business API and cannot import transport machinery.`,
1182
+ ``,
1183
+ `import { nomosInternalOpenGeneratedApplication, nomosInternalOpenGeneratedChildApplication${hasInvitations ? ", nomosInternalCreateInvitation, nomosInternalRedeemInvitation" : ""} } from "@githolon/client/_generated_application_host_7c1f5e";`,
1184
+ ``,
1185
+ `interface _NomosApplicationStatus {`,
1186
+ ` readonly phase: "opening" | "current" | "saving" | "receiving" | "catching-up" | "offline" | "attention";`,
1187
+ ` readonly localChanges: number;`,
1188
+ ` readonly acceptedChanges: number;`,
1189
+ ` readonly decisionsNeeded: number;`,
1190
+ ` readonly lastUpdatedAt: Date | null;`,
1191
+ ` readonly message: string | null;`,
1192
+ `}`,
1193
+ `interface _NomosApplicationParticipant {`,
1194
+ ` readonly currentUser: User;`,
1195
+ `}`,
1196
+ `interface _NomosApplicationLifecycle {`,
1197
+ ` currentStatus(): Promise<_NomosApplicationStatus>;`,
1198
+ ` watchStatus(listener: (status: _NomosApplicationStatus) => void): () => void;`,
1199
+ `}`,
1200
+ ...(hasInvitations
1201
+ ? [
1202
+ `interface _NomosApplicationInvitations {`,
1203
+ ` /** Cross an identity-provider uid into Nomos once; business actions accept only this typed recipient. */`,
1204
+ ` recipient(uid: string): Recipient<User>;`,
1205
+ ` /** Create an invitation; transport its link however your product chooses. */`,
1206
+ ` createInvitation(): Promise<Invitation<User>>;`,
1207
+ ` /** Redeem a transported invitation as currentUser. */`,
1208
+ ` redeemInvitation(link: string): Promise<void>;`,
1209
+ `}`,
1210
+ ]
1211
+ : []),
1126
1212
  ``,
1127
1213
  `/** sha256 of the deployed \`.package.usda\` — the installed law this client offers under. */`,
1128
- `export const ${hashConst} = ${JSON.stringify(opts.domainHash)};`,
1214
+ `const ${hashConst} = ${JSON.stringify(opts.domainHash)};`,
1129
1215
  ``,
1130
1216
  ...(clientInterface !== undefined
1131
1217
  ? [
@@ -1133,7 +1219,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1133
1219
  ` * and aggregates (read shapes, sid-keyed) it depends on, keyed by domain. \`connect()\``,
1134
1220
  ` * negotiates this against the workspace's installed offer by SHAPE (stable-id subtyping),`,
1135
1221
  ` * not by law hash — so the domain can evolve without republishing this client. */`,
1136
- `export const ${interfaceConstName(opts.packageName)} = ${JSON.stringify(clientInterface)} as const;`,
1222
+ `const ${interfaceConstName(opts.packageName)} = ${JSON.stringify(clientInterface)} as const;`,
1137
1223
  ``,
1138
1224
  ]
1139
1225
  : []),
@@ -1143,139 +1229,222 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1143
1229
  ` * provenance this client was generated against. A DIAGNOSIS surface — the factory hands it to`,
1144
1230
  ` * the holon's connect-time classifier, which logs a warning on a law-hash mismatch. It NEVER`,
1145
1231
  ` * gates: a hash mismatch with a compatible SHAPE is normal during law evolution (#72). */`,
1146
- `export const ${compatConstName(opts.packageName)} = ${JSON.stringify(opts.compat)} as const;`,
1232
+ `const ${compatConstName(opts.packageName)} = ${JSON.stringify(opts.compat)} as const;`,
1147
1233
  ``,
1148
1234
  ]
1149
1235
  : []),
1150
- `/** THE ONE REFERENCE (architecture/nomos_ref.md) — the canonical, workspace-qualified identity of an`,
1151
- ` * aggregate instance: \`{ workspace, type, id }\`, with the stable interop string`,
1152
- ` * \`nomos://<workspace>/<type>/<id>\`. Carried on every read \`Row.ref\` + write outcome + activity ref. */`,
1153
- `export interface NomosRef { workspace: string; type: string; id: string }`,
1154
- `/** A newly born business root. The phantom type makes the returned reference aggregate-specific while`,
1155
- ` * its runtime identity stays the one canonical {workspace,type,id} NomosRef. */`,
1156
- `export type NomosBorn<TData> = NomosRef & { readonly __nomosData?: TData };`,
1236
+ `/** THE ONE REFERENCE (architecture/nomos_ref.md) — a framework-created, aggregate-typed identity`,
1237
+ ` * returned by generated reads and writes. Its identity is inspectable and serialisable; its brand`,
1238
+ ` * prevents tenant code from constructing an untrusted replacement. */`,
1239
+ `declare const _nomosAggregateType: unique symbol;`,
1240
+ `type _NomosAnyAggregate = ${anyAggregateType};`,
1241
+ ...internalBirthAggregateIds.map((id) => `type ${id} = { readonly [_nomosAggregateType]?: ${JSON.stringify(id)} };`),
1242
+ `export interface NomosRef<TAggregate> {`,
1243
+ ` /** Compile-time aggregate identity only. Invariant so wrong aggregate refs fail. */`,
1244
+ ` readonly [_nomosAggregateType]: (aggregate: TAggregate) => TAggregate;`,
1245
+ ` readonly workspace: string;`,
1246
+ ` readonly type: string;`,
1247
+ ` readonly id: string;`,
1248
+ ` /** Stable, portable nomos:// address. An address identifies a record; it never grants authority. */`,
1249
+ ` readonly address: string;`,
1250
+ `}`,
1251
+ `interface _NomosReferenceData<TAggregate> extends NomosRef<TAggregate> { workspace: string; type: string; id: string; address: string }`,
1252
+ `const _nmsReferenceData = <TAggregate>(ref: NomosRef<TAggregate>): _NomosReferenceData<TAggregate> => ref as _NomosReferenceData<TAggregate>;`,
1253
+ `const _nmsRef = <TAggregate>(workspace: string, type: string, id: string): NomosRef<TAggregate> => ({`,
1254
+ ` workspace, type, id,`,
1255
+ ` address: "nomos://" + encodeURIComponent(workspace) + "/" + encodeURIComponent(type) + "/" + encodeURIComponent(id),`,
1256
+ `} as unknown as NomosRef<TAggregate>);`,
1257
+ `/** A framework-owned authority coordinate. Identity is not an aggregate or application row; it is`,
1258
+ ` * established by Nomos from the verified signer, representation and custody graph. */`,
1259
+ `declare const _nomosIdentityType: unique symbol;`,
1260
+ `export interface Identity<TSubject> {`,
1261
+ ` readonly [_nomosIdentityType]: (subject: TSubject) => TSubject;`,
1262
+ ` readonly id: string;`,
1263
+ ` readonly address: string;`,
1264
+ `}`,
1265
+ `/** The durable, independently attributable participant currently using this application.`,
1266
+ ` * A User is a capability over a Nomos Identity, not a tenant Person, record or login token. */`,
1267
+ `declare const _nomosUserType: unique symbol;`,
1268
+ `export interface User {`,
1269
+ ` readonly [_nomosUserType]: "Nomos.User";`,
1270
+ ` readonly identity: Identity<User>;`,
1271
+ `}`,
1272
+ `/** A framework-created address for a User. It carries no authority and tenant code cannot construct it. */`,
1273
+ `declare const _nomosRecipientType: unique symbol;`,
1274
+ `export interface Recipient<TSubject> {`,
1275
+ ` readonly [_nomosRecipientType]: (subject: TSubject) => TSubject;`,
1276
+ `}`,
1277
+ `/** A single-use invitation. Transport its link however your product chooses; transport is not identity. */`,
1278
+ `export interface Invitation<TSubject> {`,
1279
+ ` readonly recipient: Recipient<TSubject>;`,
1280
+ ` link(baseUrl: string): string;`,
1281
+ `}`,
1282
+ `const _nmsRecipientAddresses = new WeakMap<object, string>();`,
1283
+ `const _nmsRecipient = <TSubject>(address: string): Recipient<TSubject> => {`,
1284
+ ` const value = Object.freeze({}) as Recipient<TSubject>;`,
1285
+ ` _nmsRecipientAddresses.set(value as object, address);`,
1286
+ ` return value;`,
1287
+ `};`,
1288
+ `const _nmsRecipientAddress = (value: unknown, field = "recipient"): string => {`,
1289
+ ` if (typeof value !== "object" || value === null) throw new TypeError(field + " must be a Recipient<User> returned by this application");`,
1290
+ ` const address = _nmsRecipientAddresses.get(value);`,
1291
+ ` if (!address) throw new TypeError(field + " must be a Recipient<User> returned by this application");`,
1292
+ ` return address;`,
1293
+ `};`,
1294
+ `const _nmsInvitation = <TSubject>(material: { address: string; token: string }): Invitation<TSubject> => {`,
1295
+ ` const recipient = _nmsRecipient<TSubject>(material.address);`,
1296
+ ` return Object.freeze({`,
1297
+ ` recipient,`,
1298
+ ` link(baseUrl: string): string {`,
1299
+ ` const url = new URL(baseUrl);`,
1300
+ ` url.hash = "nomos-invitation=" + material.token;`,
1301
+ ` return url.toString();`,
1302
+ ` },`,
1303
+ ` });`,
1304
+ `};`,
1305
+ `/** Compare two public record addresses. Neither reference carries or grants authority. */`,
1306
+ `export function sameRecord<TAggregate>(a: NomosRef<TAggregate>, b: NomosRef<TAggregate>): boolean {`,
1307
+ ` const left = _nmsReferenceData(a); const right = _nmsReferenceData(b);`,
1308
+ ` return left.workspace === right.workspace && left.type === right.type && left.id === right.id;`,
1309
+ `}`,
1157
1310
  `/** The stable \`nomos://<workspace>/<type>/<id>\` string form (each segment URI-encoded — a workspace/`,
1158
1311
  ` * type/id containing \`/\` or spaces round-trips). Survives URLs, nav params, JSON, logs. */`,
1159
- `export function nomosRefUri(ref: NomosRef): string {`,
1160
- ` return "nomos://" + encodeURIComponent(ref.workspace) + "/" + encodeURIComponent(ref.type) + "/" + encodeURIComponent(ref.id);`,
1312
+ `function nomosRefUri<TAggregate>(ref: NomosRef<TAggregate>): string {`,
1313
+ ` return _nmsReferenceData(ref).address;`,
1161
1314
  `}`,
1162
1315
  `/** Parse a \`nomos://<workspace>/<type>/<id>\` string back into a [NomosRef] (the inverse of [nomosRefUri]);`,
1163
1316
  ` * throws on anything else. */`,
1164
- `export function parseNomosRef(uri: string): NomosRef {`,
1317
+ `function parseNomosRef<TAggregate>(uri: string): NomosRef<TAggregate> {`,
1165
1318
  " const m = /^nomos:\\/\\/([^/]+)\\/([^/]+)\\/([^/]+)$/.exec(uri);",
1166
1319
  ` if (m === null) throw new Error("parseNomosRef: expected nomos://<workspace>/<type>/<id>: " + uri);`,
1167
- ` return { workspace: decodeURIComponent(m[1]!), type: decodeURIComponent(m[2]!), id: decodeURIComponent(m[3]!) };`,
1320
+ ` return _nmsRef(decodeURIComponent(m[1]!), decodeURIComponent(m[2]!), decodeURIComponent(m[3]!));`,
1168
1321
  `}`,
1169
- `/** One projection row: the aggregate's wire type, its id, the folded fields, and the canonical [NomosRef]`,
1170
- ` * (composed at decode from the session workspace + the row's type/id — additive, the \`data\` is untouched). */`,
1171
- `export interface Row<T> { type: string; id: string; data: T; ref: NomosRef }`,
1172
- `/** Stamp each row's canonical [NomosRef] from the session \`ws\` + the row's type/id (additive — already-`,
1322
+ `/** One business record with the canonical reference Nomos returned for it. */`,
1323
+ `export type NomosRecord<TAggregate> = TAggregate & { readonly ref: NomosRef<TAggregate> };`,
1324
+ `/** Framework-private projection envelope. It is flattened before any application read resolves. */`,
1325
+ `interface Row<TAggregate> { data: TAggregate; ref: NomosRef<TAggregate> }`,
1326
+ `/** Stamp each row's canonical [NomosRef] from its verified address (additive — already-`,
1173
1327
  ` * stamped rows pass through).${hasInternalFields ? " Framework-owned fields are removed at this decode boundary." : ""} */`,
1174
1328
  ...(hasInternalFields
1175
1329
  ? [
1176
1330
  `const _INTERNAL_FIELDS: Record<string, readonly string[]> = ${JSON.stringify(internalFieldsByType)};`,
1177
- `function _stampRefs<T>(ws: string, rows: Row<T>[]): Row<T>[] {`,
1331
+ `function _stampRefs<TAggregate>(ws: string, rows: Row<TAggregate>[]): Row<TAggregate>[] {`,
1178
1332
  ` return rows.map((r) => {`,
1179
- ` const fields = _INTERNAL_FIELDS[r.type] ?? _INTERNAL_FIELDS[r.type.slice(r.type.lastIndexOf(".") + 1)];`,
1333
+ ` const raw = r as Row<TAggregate> & { type: string; id: string };`,
1334
+ ` const fields = _INTERNAL_FIELDS[raw.type] ?? _INTERNAL_FIELDS[raw.type.slice(raw.type.lastIndexOf(".") + 1)];`,
1180
1335
  ` let row = r;`,
1181
1336
  ` if (fields !== undefined && fields.length > 0 && r.data !== null && typeof r.data === "object") {`,
1182
1337
  ` const data = { ...(r.data as Record<string, unknown>) };`,
1183
1338
  ` for (const field of fields) delete data[field];`,
1184
- ` row = { ...r, data: data as T };`,
1339
+ ` row = { ...r, data: data as TAggregate };`,
1185
1340
  ` }`,
1186
- ` return row.ref !== undefined ? row : { ...row, ref: { workspace: ws, type: row.type, id: row.id } };`,
1341
+ ` const decoded = row as Row<TAggregate> & { type: string; id: string };`,
1342
+ ` return row.ref !== undefined ? row : { ...row, ref: _nmsRef(ws, decoded.type, decoded.id) };`,
1187
1343
  ` });`,
1188
1344
  `}`,
1189
1345
  ]
1190
1346
  : [
1191
- `function _stampRefs<T>(ws: string, rows: Row<T>[]): Row<T>[] {`,
1192
- ` return rows.map((r) => (r.ref !== undefined ? r : { ...r, ref: { workspace: ws, type: r.type, id: r.id } }));`,
1347
+ `function _stampRefs<TAggregate>(ws: string, rows: Row<TAggregate>[]): Row<TAggregate>[] {`,
1348
+ ` return rows.map((r) => { const raw = r as Row<TAggregate> & { type: string; id: string }; return r.ref !== undefined ? r : { ...r, ref: _nmsRef(ws, raw.type, raw.id) }; });`,
1193
1349
  `}`,
1194
1350
  ]),
1195
- `function _stampRefsP<T>(ws: string, p: Promise<Row<T>[]>): Promise<Row<T>[]> { return p.then((rows) => _stampRefs(ws, rows)); }`,
1196
- `/** THE TYPED WRITE OUTCOME (architecture/nomos_ref.md) — every generated directive method returns this.`,
1197
- ` * \`created\` is EVERY aggregate the plan minted (typed [NomosRef]s, composed with the session workspace;`,
1198
- ` * \`[]\` on a holon too old to return them — never a fake id). \`target\` is the addressed instance for a`,
1199
- ` * front-door-mint lane (statically known); omitted otherwise. \`head\` is the new local head. */`,
1200
- `export interface NomosOutcome { head: string; created: NomosRef[]; target?: NomosRef }`,
1201
- `/** Fold a holon offer result (a bare head, or \`{ head, created }\` under returnCreated) into a [NomosOutcome]`,
1202
- ` * — composing each created \`{type,id}\` with the session \`ws\`. \`target\` rides through when the caller`,
1203
- ` * statically knows the addressed instance (the front-door mint). */`,
1204
- `async function _outcome(ws: string, r: Promise<string | { head: string; created?: { type: string; id: string }[] }>, target?: NomosRef): Promise<NomosOutcome> {`,
1205
- ` const v = await r;`,
1206
- ` const obj = typeof v === "object" && v !== null;`,
1207
- ` const head = obj ? v.head : String(v);`,
1208
- ` const created = (obj ? v.created ?? [] : []).map((c) => ({ workspace: ws, type: c.type, id: c.id }));`,
1209
- ` return target !== undefined ? { head, created, target } : { head, created };`,
1351
+ `function _stampRefsP<TAggregate>(ws: string, p: Promise<Row<TAggregate>[]>): Promise<Row<TAggregate>[]> { return p.then((rows) => _stampRefs(ws, rows)); }`,
1352
+ `function _businessRecords<TAggregate>(rows: Row<TAggregate>[]): NomosRecord<TAggregate>[] {`,
1353
+ ` return rows.map((row) => ({ ...(row.data as object), ref: row.ref } as NomosRecord<TAggregate>));`,
1354
+ `}`,
1355
+ `function _businessRecordsP<TAggregate>(p: Promise<Row<TAggregate>[]>): Promise<NomosRecord<TAggregate>[]> {`,
1356
+ ` return p.then(_businessRecords);`,
1357
+ `}`,
1358
+ `type _NomosRawReference = { type: string; id: string };`,
1359
+ `type _NomosRawWriteResult = string | { head: string; created?: _NomosRawReference[]; affected?: _NomosRawReference[] };`,
1360
+ `/** Finish a business action without leaking ledger heads or framework write envelopes. */`,
1361
+ `async function _commit(r: Promise<_NomosRawWriteResult>): Promise<void> { void await r; }`,
1362
+ `/** 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> {`,
1364
+ ` const result = await r;`,
1365
+ ` if (typeof result === "string" || !Array.isArray(result.affected)) {`,
1366
+ ` throw new Error("Nomos committed the business action without returning its affected records");`,
1367
+ ` }`,
1368
+ ` const answer: Record<string, NomosRef<_NomosAnyAggregate> | readonly NomosRef<_NomosAnyAggregate>[]> = {};`,
1369
+ ` for (const output of outputs) {`,
1370
+ ` const matches = result.affected.filter((affected) => affected.type === output.type || affected.type.endsWith("." + output.type));`,
1371
+ ` if (output.many) {`,
1372
+ ` answer[output.member] = matches.map((match) => _nmsRef<_NomosAnyAggregate>(ws, match.type, match.id));`,
1373
+ ` continue;`,
1374
+ ` }`,
1375
+ ` if (matches.length !== 1) throw new Error(\`Nomos expected one affected ${"${output.type}"} record, received ${"${matches.length}"}\`);`,
1376
+ ` answer[output.member] = _nmsRef<_NomosAnyAggregate>(ws, matches[0]!.type, matches[0]!.id);`,
1377
+ ` }`,
1378
+ ` return answer as TResult;`,
1210
1379
  `}`,
1211
1380
  // MULTI-WORKSPACE LOCATORS (t.foreignRef): a TYPED cross-workspace edge — the foreign aggregate's id +
1212
1381
  // the foreign workspace id (from a sibling field). NOT an atomic FK: it does not clone/open the other
1213
- // holon; `resolve`/`watch` open it ON DEMAND over a realm (openRealm's `.workspace(ws)` surface).
1214
- // The reader `resolve`/`watch`/`attest` need: the multi-workspace `.workspace(ws)` surface `openRealm()`
1215
- // returns (a plain `connect()` holon is single-workspace — pass a Realm). `attestForeign` is the attested-
1216
- // read extension (present only when your law declares `trustSource` + `.readsFrom`). ANY object with this
1217
- // shape works — the generated client is import-free and binds structurally to your @githolon/client.
1218
- `export interface NomosForeignReader { workspace(ws: string): { queryById(id: string): Promise<{ type: string; id: string; data: unknown }[]>; watchById?(id: string, cb: (rows: { type: string; id: string; data: unknown }[]) => void, opts?: { intervalMs?: number }): () => void }; attestForeign?(workspace: string, id: string, type: string): Promise<{ id: string; workspace: string; exists: boolean; attestation: string }>; }`,
1382
+ // holon; `resolve`/`watch` use the application's captured shared-plane navigator.
1383
+ // The reader is captured by the generated application. Tenant code receives navigation, never a reader.
1384
+ `interface _NomosForeignWorkspace { queryById(id: string): Promise<unknown>; watchById?(id: string, cb: (rows: unknown) => void, opts?: { intervalMs?: number }): () => void }`,
1385
+ `interface NomosForeignReader { __nomosOpenChild?(workspace: string): Promise<_NomosForeignWorkspace> }`,
1386
+ `async function _openForeign(nomos: NomosForeignReader, workspace: string): Promise<_NomosForeignWorkspace> {`,
1387
+ ` if (nomos.__nomosOpenChild === undefined) throw new Error("Nomos cannot follow this business reference on the installed runtime");`,
1388
+ ` return nomos.__nomosOpenChild(workspace);`,
1389
+ `}`,
1390
+ `function _foreignRow<TAggregate>(workspace: string, row: { type: string; id: string; data: unknown }): NomosRecord<TAggregate> {`,
1391
+ ` return { ...(row.data as object), ref: _nmsRef<TAggregate>(workspace, row.type, row.id) } as NomosRecord<TAggregate>;`,
1392
+ `}`,
1219
1393
  // resolve() collapses "not there" to undefined for the 99% lookup; resolveResult() names WHY — a foreign
1220
1394
  // read can come back empty because the target does not exist, you lack read access, or the holon is
1221
1395
  // unreachable, and those are very different. `denied`/`unreachable` are best-effort from the reader's
1222
1396
  // error (an error whose message mentions auth/permission/forbidden ⇒ denied; else unreachable).
1223
- `export type ForeignResolveResult<TData> = { status: "found"; row: Row<TData> } | { status: "not-found" } | { status: "denied"; error: unknown } | { status: "unreachable"; error: unknown };`,
1397
+ `export type ForeignResolveResult<TAggregate> = { status: "found"; row: NomosRecord<TAggregate> } | { status: "not-found" } | { status: "denied"; error: unknown } | { status: "unreachable"; error: unknown };`,
1224
1398
  // A ForeignRef is a TYPED CROSS-WORKSPACE LOCATOR, not an atomic FK. In ~99% of cases you use it as a
1225
1399
  // lookup — `resolve`/`watch` open the foreign holon on demand so you can do more reads. `exists` is the
1226
- // cheap "is the target still there?" check. `attest` is the LAW-GRADE one — signed evidence that the
1227
- // reference resolves — and only works when your law DECLARES the dependency (`trustSource` + a
1228
- // `.readsFrom(...)` on the directive); it is deliberately NOT a free method, because attesting has cost
1229
- // and law meaning. Resolving/existence are informational; attestation is what you reach for when the
1230
- // current workspace's LAW depends on the foreign fact.
1231
- `export interface ForeignRef<TData> {`,
1232
- ` /** The foreign aggregate's id (this field's stored value). */`,
1233
- ` readonly id: string;`,
1234
- ` /** The foreign workspace id (from the declared sibling workspace field). */`,
1400
+ // cheap "is the target still there?" check. Law-grade evidence is obtained privately when a declared
1401
+ // business action needs it; it is not a callable tenant capability.
1402
+ `export interface ForeignRef<TAggregate> {`,
1235
1403
  ` readonly workspace: string;`,
1236
- ` /** The foreign aggregate type this locator points at. */`,
1237
1404
  ` readonly type: string;`,
1405
+ ` readonly id: string;`,
1406
+ ` /** The same locator as the one canonical aggregate-typed reference. */`,
1407
+ ` readonly ref: NomosRef<TAggregate>;`,
1238
1408
  ` /** THE LOOKUP (the 99% case): open the foreign workspace + read the target by id — on demand, no clone.`,
1239
1409
  ` * Returns undefined for ANY empty outcome; use resolveResult() when you need to know WHY. */`,
1240
- ` resolve(nomos: NomosForeignReader): Promise<Row<TData> | undefined>;`,
1410
+ ` resolve(): Promise<NomosRecord<TAggregate> | undefined>;`,
1241
1411
  ` /** The lookup with the OUTCOME named — found / not-found / denied / unreachable (a foreign read can be`,
1242
1412
  ` * empty for very different reasons; do not collapse them when it matters). */`,
1243
- ` resolveResult(nomos: NomosForeignReader): Promise<ForeignResolveResult<TData>>;`,
1413
+ ` resolveResult(): Promise<ForeignResolveResult<TAggregate>>;`,
1244
1414
  ` /** Subscribe to the target's read model across the workspace boundary (returns an unsubscribe fn). */`,
1245
- ` watch(nomos: NomosForeignReader, cb: (row: Row<TData> | undefined) => void, opts?: { intervalMs?: number }): () => void;`,
1415
+ ` watch(cb: (row: NomosRecord<TAggregate> | undefined) => void, opts?: { intervalMs?: number }): () => void;`,
1246
1416
  ` /** Cheap existence check — does the referenced target still exist? (found ⇒ true; anything else ⇒ false). */`,
1247
- ` exists(nomos: NomosForeignReader): Promise<boolean>;`,
1248
- ` /** LAW-GRADE: signed evidence that the reference resolves, for when your law DEPENDS on the foreign fact.`,
1249
- ` * Requires the attested-read lane — declare \`trustSource(workspace, ...)\` + \`.readsFrom(...)\` on the`,
1250
- ` * directive; without it (no \`attestForeign\` on the reader) this throws, by design (a locator is not an FK). */`,
1251
- ` attest(nomos: NomosForeignReader): Promise<{ id: string; workspace: string; exists: boolean; attestation: string }>;`,
1417
+ ` exists(): Promise<boolean>;`,
1252
1418
  `}`,
1253
- `/** Build a typed foreign-ref locator from a row's id + workspace-id values. The generated \`<domain>ForeignRefs\` factory calls this with the declared fields. */`,
1254
- `export function foreignRef<TData>(type: string, id: string, workspace: string): ForeignRef<TData> {`,
1419
+ `/** Build a typed foreign-ref locator behind the generated application. Navigation and workspace`,
1420
+ ` * selection are captured here and never become application arguments. */`,
1421
+ `function foreignRef<TAggregate>(nomos: NomosForeignReader, type: string, id: string, workspace: string): ForeignRef<TAggregate> {`,
1255
1422
  ` return {`,
1256
- ` id, workspace, type,`,
1257
- ` async resolveResult(nomos) {`,
1423
+ ` workspace, type, id,`,
1424
+ ` ref: _nmsRef<TAggregate>(workspace, type, id),`,
1425
+ ` async resolveResult() {`,
1258
1426
  ` if (!id || !workspace) return { status: "not-found" };`,
1259
1427
  ` try {`,
1260
- ` const rows = await nomos.workspace(workspace).queryById(id);`,
1261
- ` const row = (rows.find((r) => r.id === id) ?? rows[0]) as Row<TData> | undefined;`,
1262
- ` return row !== undefined ? { status: "found", row } : { status: "not-found" };`,
1428
+ ` const rows = await (await _openForeign(nomos, workspace)).queryById(id) as { type: string; id: string; data: unknown }[];`,
1429
+ ` const raw = rows.find((r) => r.id === id) ?? rows[0];`,
1430
+ ` return raw !== undefined ? { status: "found", row: _foreignRow<TAggregate>(workspace, raw) } : { status: "not-found" };`,
1263
1431
  ` } catch (error) {`,
1264
1432
  ` const m = String((error as { message?: unknown })?.message ?? error).toLowerCase();`,
1265
1433
  ` return { status: /forbid|denied|permission|unauthor|not permitted|403|401/.test(m) ? "denied" : "unreachable", error };`,
1266
1434
  ` }`,
1267
1435
  ` },`,
1268
- ` async resolve(nomos) { const r = await this.resolveResult(nomos); return r.status === "found" ? r.row : undefined; },`,
1269
- ` watch(nomos, cb, opts) {`,
1270
- ` const w = nomos.workspace(workspace).watchById;`,
1271
- ` if (!w || !id || !workspace) { cb(undefined); return () => {}; }`,
1272
- ` return w(id, (rows) => cb((rows.find((r) => r.id === id) ?? rows[0]) as Row<TData> | undefined), opts);`,
1273
- ` },`,
1274
- ` async exists(nomos) { return (await this.resolveResult(nomos)).status === "found"; },`,
1275
- ` async attest(nomos) {`,
1276
- ` if (!nomos.attestForeign) throw new Error("attest() needs the attested-read lane — declare trustSource(" + JSON.stringify(workspace) + ", …) + .readsFrom(…) on the directive whose law depends on this reference (a foreignRef is a locator, not an atomic foreign key)");`,
1277
- ` return nomos.attestForeign(workspace, id, type);`,
1436
+ ` async resolve() { const r = await this.resolveResult(); return r.status === "found" ? r.row : undefined; },`,
1437
+ ` watch(cb, opts) {`,
1438
+ ` if (!id || !workspace) { cb(undefined); return () => {}; }`,
1439
+ ` let stopped = false; let stop = () => {};`,
1440
+ ` void _openForeign(nomos, workspace).then((foreign) => {`,
1441
+ ` if (stopped) return; const watch = foreign.watchById;`,
1442
+ ` if (watch === undefined) { cb(undefined); return; }`,
1443
+ ` stop = watch(id, (value) => { const rows = value as { type: string; id: string; data: unknown }[]; const raw = rows.find((r) => r.id === id) ?? rows[0]; cb(raw === undefined ? undefined : _foreignRow<TAggregate>(workspace, raw)); }, opts);`,
1444
+ ` }).catch(() => { if (!stopped) cb(undefined); });`,
1445
+ ` return () => { stopped = true; stop(); };`,
1278
1446
  ` },`,
1447
+ ` async exists() { return (await this.resolveResult()).status === "found"; },`,
1279
1448
  ` };`,
1280
1449
  `}`,
1281
1450
  ``,
@@ -1305,14 +1474,14 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1305
1474
  `export interface SurfacedPending<T> { __surfaced: { status: "pending"; pending: SurfacedObservation<T>[] } }`,
1306
1475
  `/** A retained value authored under a different field contract. Nomos withholds the value`,
1307
1476
  ` * until a declared deterministic evolution can produce the current generated type. */`,
1308
- `export interface SurfacedEvolutionRequired { __surfaced: { status: "evolutionRequired"; targetSchema: string; evolution: { sourceSchemas: string[]; authoredUnder: string[] } } }`,
1477
+ `export interface SurfacedEvolutionRequired { __surfaced: { status: "evolutionRequired"; reason?: string } }`,
1309
1478
  `/** A Surfaced field's read value: resolved, pending, or awaiting schema evolution. */`,
1310
1479
  `export type SurfacedValue<T> = T | SurfacedPending<T> | SurfacedEvolutionRequired;`,
1311
1480
  `/** True iff \`v\` is the pending-conflict envelope (narrows to [SurfacedPending]). */`,
1312
- `export function isSurfacedPending<T>(v: SurfacedValue<T> | null | undefined): v is SurfacedPending<T> {`,
1481
+ `function isSurfacedPending<T>(v: SurfacedValue<T> | null | undefined): v is SurfacedPending<T> {`,
1313
1482
  ` return typeof v === "object" && v !== null && (v as { __surfaced?: { status?: unknown } }).__surfaced?.status === "pending";`,
1314
1483
  `}`,
1315
- `export function isSurfacedEvolutionRequired<T>(v: SurfacedValue<T> | null | undefined): v is SurfacedEvolutionRequired {`,
1484
+ `function isSurfacedEvolutionRequired<T>(v: SurfacedValue<T> | null | undefined): v is SurfacedEvolutionRequired {`,
1316
1485
  ` return typeof v === "object" && v !== null && (v as { __surfaced?: { status?: unknown } }).__surfaced?.status === "evolutionRequired";`,
1317
1486
  `}`,
1318
1487
  `/** ONE row of Nomos's maintained conflict index (reserved id \`nomosPendingConflicts\`):`,
@@ -1320,7 +1489,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1320
1489
  ` * (fold-time, O(pending) to read) and DERIVED, NOT DRAINED — a resolution write folding in removes`,
1321
1490
  ` * the row by derivation. The workspace-wide surface is HETEROGENEOUS, so each observation's`,
1322
1491
  ` * \`value\` is \`unknown\` here — the per-field typed seam stays on the read models (\`SurfacedValue<T>\`). */`,
1323
- `export interface PendingConflictRow {`,
1492
+ `interface PendingConflictRow {`,
1324
1493
  ` aggregateType: string;`,
1325
1494
  ` aggregateId: string;`,
1326
1495
  ` field: string;`,
@@ -1336,9 +1505,10 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1336
1505
  `export interface ConflictSummary {`,
1337
1506
  ` total: number;`,
1338
1507
  ` ready: number;`,
1339
- ` evolutionRequired: number;`,
1508
+ ` modelUpdatesRequired: number;`,
1340
1509
  ` byAggregate: Readonly<Record<string, number>>;`,
1341
1510
  `}`,
1511
+ `interface _NomosRawConflictSummary { total: number; ready: number; evolutionRequired: number; byAggregate: Readonly<Record<string, number>> }`,
1342
1512
  ``,
1343
1513
  ]
1344
1514
  : []),
@@ -1347,7 +1517,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1347
1517
  `export interface NomosMoment {`,
1348
1518
  ` readonly id: string;`,
1349
1519
  ` readonly occurredAt: string | null;`,
1350
- ` readonly actorSubject: string | null;`,
1520
+ ` readonly performedBy: string | null;`,
1351
1521
  ` readonly domain: string | null;`,
1352
1522
  ` readonly action: string | null;`,
1353
1523
  `}`,
@@ -1356,31 +1526,36 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1356
1526
  `/** Result of a reasoned business correction. Framework target and testimony ids remain private. */`,
1357
1527
  `export interface NomosCorrectionOutcome {`,
1358
1528
  ` readonly ok: boolean; readonly kind: "undo" | "redo";`,
1359
- ` readonly reason?: string; readonly message?: string; readonly head?: string;`,
1529
+ ` readonly reason?: string; readonly message?: string;`,
1360
1530
  `}`,
1361
1531
  ]
1362
1532
  : []),
1363
1533
  `/** Read-only primitive used behind the generated, business-shaped historic view. */`,
1364
- `export interface NomosHistoricHolon {`,
1534
+ `interface NomosHistoricHolon {`,
1365
1535
  ` query(queryId: string, params?: Record<string, unknown>, keys?: unknown): Promise<unknown>;`,
1366
1536
  ` queryById(aggregateId: string, keys?: unknown): Promise<unknown>;`,
1367
1537
  ` count(countId: string, groupKey?: string): Promise<number>;`,
1368
1538
  ` sum(sumId: string, groupKey?: string): Promise<number>;`,
1369
1539
  `}`,
1370
1540
  `/** The holon surface this client binds to (structurally satisfied by @githolon/client). */`,
1371
- `export interface NomosHolon {`,
1541
+ `interface NomosHolon {`,
1372
1542
  ` /** THE ONE ENCODER offer (the single v2-envelope codec lives in @githolon/client \`encodeIntentOfferBytes\`):`,
1373
1543
  ` * hand the holon a PLANNED directive + the verified \`actor\`; the holon encodes + offers. The generated`,
1374
1544
  ` * client calls THIS, never building offer bytes itself — so there is exactly ONE copy of the wire format. */`,
1375
- ` 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 }[]; workspace?: string; born?: unknown; birthOutcomes?: unknown }>;`,
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 }>;`,
1546
+ ...(sourceTrustPresent
1547
+ ? [
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 }>;`,
1551
+ ]
1552
+ : []),
1376
1553
  ...(hasCorrections
1377
1554
  ? [
1378
1555
  ` /** Framework-private seam used by generated \`corrections\`; apps never supply an intent id. */`,
1379
1556
  ` correctMostRecent?(opts: { kind: "undo" | "redo"; domain: string; actions: string[]; reason: string; domainHash?: string; actor?: string }): Promise<NomosCorrectionOutcome>;`,
1380
1557
  ]
1381
1558
  : []),
1382
- ` /** Opaque-bytes offer — retained for carriers that already hold an encoded envelope (back-compat). */`,
1383
- ` offer(intentBytes: string | Uint8Array): Promise<string>;`,
1384
1559
  ...(hasBirths
1385
1560
  ? [
1386
1561
  ` /** BIRTH A CHILD WORKSPACE (a signed CROSS-WORKSPACE governance offer to \`parent\`) — OPTIONAL:`,
@@ -1401,6 +1576,8 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1401
1576
  : []),
1402
1577
  ` /** FRAMEWORK-INTERNAL verified child graph. Generated describe() exposes only business-safe links. */`,
1403
1578
  ` __nomosChildWorkspaces?(): Promise<readonly { workspace: string; status: "active" | "retired" | "reclaimed" | "unknown"; bornAt: string | null }[]>;`,
1579
+ ` /** FRAMEWORK-INTERNAL child navigation captured by generated description links. */`,
1580
+ ` __nomosOpenChild?(workspace: string): Promise<NomosHolon>;`,
1404
1581
  ` /** FRAMEWORK-INTERNAL exact-history adoption seam. Generated clients expose only`,
1405
1582
  ` * \`model.adopt()\` and a chained \`decision.accept(reason)\`. */`,
1406
1583
  ` __nomosAdoptModel?(domain: string, candidateHash: string, opts?: { because?: string }): Promise<__NomosModelAdoptionWire>;`,
@@ -1425,6 +1602,10 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1425
1602
  ` * (bound to the scope + stable sids). OPTIONAL: structural binding tolerates a pre-sealing holon`,
1426
1603
  ` * (the write path then throws with an upgrade hint before any plaintext dispatch). */`,
1427
1604
  ` cryptoSealField?(args: { scopeKey: string; scope: string; epoch: number; aggSid: string; fieldSid: string; plaintext: string }): Promise<string>;`,
1605
+ ` /** FRAMEWORK-INTERNAL: current held key for an ordinary law-declared encryption scope. */`,
1606
+ ` __nomosScopeKey?(args: { scope: string }): Promise<NomosScopeKey | undefined>;`,
1607
+ ` /** FRAMEWORK-INTERNAL: every held epoch for ordinary law-declared encryption scopes. */`,
1608
+ ` __nomosScopeKeys?(args: { scopes: readonly string[] }): Promise<NomosScopeKey[]>;`,
1428
1609
  ` /** FRAMEWORK-INTERNAL: derive an owned aggregate's current record key from this session's private custody. */`,
1429
1610
  ` __nomosOwnedScopeKey?(args: { scope: string; record?: { type: string; id: string } }): Promise<NomosScopeKey>;`,
1430
1611
  ` /** FRAMEWORK-INTERNAL: every held key epoch for one record, including shared and rotated history. */`,
@@ -1434,7 +1615,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1434
1615
  ` /** FRAMEWORK-INTERNAL: atomically enable the opt-in recovery policy. */`,
1435
1616
  ` __nomosProtectOwnedIdentity?(args: { scope: string; domain: string; domainHash?: string; recoveryPrincipal: string }): Promise<{ status: "protected"; recoveryPrincipal: string; unchanged?: boolean }>;`,
1436
1617
  ` /** FRAMEWORK-INTERNAL: prepare the atomic law payload behind share/unshare/transfer. */`,
1437
- ` __nomosPrepareOwnedRecordAction?(args: { action: "share" | "unshare" | "transfer"; scope: string; record: NomosRef; peer: string }): Promise<Record<string, unknown>>;`,
1618
+ ` __nomosPrepareOwnedRecordAction?(args: { action: "share" | "unshare" | "transfer"; scope: string; record: NomosRef<_NomosAnyAggregate>; peer: string }): Promise<Record<string, unknown>>;`,
1438
1619
  ` /** FRAMEWORK-INTERNAL: derive the deterministic unborn child's owned key before atomic genesis. */`,
1439
1620
  ` __nomosOwnedBirthScopeKey?(args: { scope: string; namespace: string; key: unknown }): Promise<NomosScopeKey & { workspace: string }>;`,
1440
1621
  ...(ownedPresent
@@ -1465,7 +1646,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1465
1646
  ` * OPTIONAL member: structural binding tolerates a pre-Surfaced holon (the accessor throws at call). */`,
1466
1647
  ` pendingConflicts?(aggregateType?: string): Promise<PendingConflictRow[]>;`,
1467
1648
  ` /** Counts-only workspace view; competing business values do not cross this call. */`,
1468
- ` pendingConflictSummary?(): Promise<ConflictSummary>;`,
1649
+ ` pendingConflictSummary?(): Promise<_NomosRawConflictSummary>;`,
1469
1650
  ]
1470
1651
  : []),
1471
1652
  ` /** Declared-count / declared-sum O(1) reads (maintained by the read engine). */`,
@@ -1522,7 +1703,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1522
1703
  `/** THE LIVE COMPAT VERDICT (#72b) stashed on \`holon.compat\`. \`verdict\` is the three-state roll-up;`,
1523
1704
  ` * \`members\` names each directive/aggregate's own \`{ ok, reason? }\`; \`install\` is the deployed law's`,
1524
1705
  ` * lifecycle phase. \`has(id)\` = the feature flag; \`refresh()\` re-evaluates against the projection. */`,
1525
- `export interface NomosCompatVerdict {`,
1706
+ `interface NomosCompatVerdict {`,
1526
1707
  ` generatedFor: string;`,
1527
1708
  ` workspaceRuns: string[];`,
1528
1709
  ` matches: boolean;`,
@@ -1537,11 +1718,11 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1537
1718
  ``,
1538
1719
  `/** AUTHORITY IS UNFORGEABLE-BY-CONSTRUCTION: a GATED directive (one carrying a \`.requires(relation)\` write`,
1539
1720
  ` * gate) authored with NO \`actor\` cannot produce an offer — it throws THIS before any submit, so an`,
1540
- ` * actorless gated offer is impossible to GENERATE, never merely refused at the kernel gate. Bind the`,
1541
- ` * client with \`{ actor }\` (the verified principal) to author a gated directive. */`,
1542
- `export class MissingOfferAuthority extends Error {`,
1721
+ ` * actorless gated offer is impossible to GENERATE, never merely refused at the kernel gate. The`,
1722
+ ` * verified principal comes only from the signed-in application session. */`,
1723
+ `class MissingOfferAuthority extends Error {`,
1543
1724
  ` constructor(readonly domain: string, readonly directiveId: string, readonly requires: string) {`,
1544
- ` super(\`\${domain}/\${directiveId} requires the "\${requires}" relation — bind the client with { actor } (the verified principal) to author it; a gated offer cannot be made without an authority\`);`,
1725
+ ` super(\`\${domain}/\${directiveId} requires the "\${requires}" relation, but the signed-in application session has no verified principal; reopen the generated application after sign-in\`);`,
1545
1726
  ` this.name = "MissingOfferAuthority";`,
1546
1727
  ` }`,
1547
1728
  `}`,
@@ -1555,14 +1736,14 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1555
1736
  out.push(
1556
1737
  `/** A verified principal identity (\`user:<uid>\` on the wire) — construct with [nomosPrincipal]. A distinct`,
1557
1738
  ` * branded type from [NomosDeviceSecret] so the two birth-method params can never be transposed. */`,
1558
- `export type NomosPrincipal = string & { readonly __nomosPrincipal: unique symbol };`,
1739
+ `type NomosPrincipal = string & { readonly __nomosPrincipal: unique symbol };`,
1559
1740
  `/** A device signing secret (base64) — construct with [nomosDeviceSecret]. Distinct from [NomosPrincipal]. */`,
1560
- `export type NomosDeviceSecret = string & { readonly __nomosDeviceSecret: unique symbol };`,
1741
+ `type NomosDeviceSecret = string & { readonly __nomosDeviceSecret: unique symbol };`,
1561
1742
  `/** Brand a principal: a bare uid (\`ada\`) or a wire string (\`user:ada\`) → \`user:ada\`. */`,
1562
- `export const nomosPrincipal = (uidOrWire: string): NomosPrincipal =>`,
1743
+ `const nomosPrincipal = (uidOrWire: string): NomosPrincipal =>`,
1563
1744
  ` (uidOrWire.startsWith("user:") ? uidOrWire : \`user:\${uidOrWire}\`) as NomosPrincipal;`,
1564
1745
  `/** Brand a device signing secret (base64). */`,
1565
- `export const nomosDeviceSecret = (base64: string): NomosDeviceSecret => base64 as NomosDeviceSecret;`,
1746
+ `const nomosDeviceSecret = (base64: string): NomosDeviceSecret => base64 as NomosDeviceSecret;`,
1566
1747
  ``,
1567
1748
  );
1568
1749
  }
@@ -1575,7 +1756,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1575
1756
  ``,
1576
1757
  `/** Per directive: which payload fields seal → their { scope, aggSid, fieldSid }. Per aggregate: which`,
1577
1758
  ` * read-model fields are encrypted (+ their scope). Keyed by domain. */`,
1578
- `export const ${encConst} = ${JSON.stringify(encMap)} as const;`,
1759
+ `const ${encConst} = ${JSON.stringify(encMap)} as const;`,
1579
1760
  ``,
1580
1761
  `/** A held scope key: the base64 symmetric key + the epoch it belongs to (forward-only rotation). */`,
1581
1762
  `${manualEncryptionPresent ? "export " : ""}interface NomosScopeKey { scope: string; epoch: number; key: string; ownedKeySubject?: string; ownedCustody?: string }`,
@@ -1589,10 +1770,9 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1589
1770
  `const _OWNED_CUSTODY_PAYLOAD_FIELD = ${JSON.stringify(OWNED_CUSTODY_PAYLOAD_FIELD)};`,
1590
1771
  `const _OWNED_PREMINTED_RECORDS_PAYLOAD_FIELD = ${JSON.stringify(OWNED_PREMINTED_RECORDS_PAYLOAD_FIELD)};`,
1591
1772
  ``,
1592
- `/** The app-supplied KEY CUSTODY surface. Supply one to \`<domain>Client(holon, { keyStore })\` and the`,
1593
- ` * generated methods seal writes + decrypt reads automatically. \`wrappedKeyStore\` is the default over`,
1594
- ` * the on-chain WrappedKeys; an app may implement this to source keys from a secure enclave. */`,
1595
- `${manualEncryptionPresent ? "export " : ""}interface NomosKeyStore {`,
1773
+ `/** Framework-private key custody used by generated methods. The connected client owns device`,
1774
+ ` * credentials, wrapped-key discovery and all epochs; applications never supply this object. */`,
1775
+ `interface NomosKeyStore {`,
1596
1776
  ` /** The CURRENT (highest-epoch) scope key, for SEALING a write. undefined ⇒ EncryptionKeyUnavailable. */`,
1597
1777
  ` scopeKey(scope: string): Promise<NomosScopeKey | undefined>;`,
1598
1778
  ` /** EVERY held key for these scopes (all epochs), injected on reads so old ciphertext also decrypts. */`,
@@ -1610,8 +1790,8 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1610
1790
  ` }`,
1611
1791
  `}`,
1612
1792
  ``,
1613
- `/** Seal the mapped encrypted fields of a payload before dispatch. No keyStore / \`seal:false\` ⇒ passthrough`,
1614
- ` * (today's behavior). A held key seals; a missing key throws EncryptionKeyUnavailable (never plaintext). */`,
1793
+ `/** Seal mapped encrypted fields before dispatch. A held key seals; a missing key throws`,
1794
+ ` * EncryptionKeyUnavailable and never falls through to plaintext. */`,
1615
1795
  `const _sealFields = async (`,
1616
1796
  ` holon: NomosHolon,`,
1617
1797
  ` keyStore: NomosKeyStore | undefined,`,
@@ -1672,7 +1852,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1672
1852
  ``,
1673
1853
  `/** Owned multi-row reads stay business-shaped: first fetch the authorized ciphertext rows locally,`,
1674
1854
  ` * derive only those record keys, then repeat the same local indexed read with decryption injected. */`,
1675
- `const _readWithOwnedKeys = async <T extends { type: string; id: string }>(`,
1855
+ `const _readWithOwnedKeys = async <T>(`,
1676
1856
  ` holon: NomosHolon,`,
1677
1857
  ` keyStore: NomosKeyStore | undefined,`,
1678
1858
  ` scopes: readonly { scope: string; owned?: true }[],`,
@@ -1681,13 +1861,17 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1681
1861
  ` const firstKeys = await _readKeys(holon, keyStore, scopes);`,
1682
1862
  ` const first = await read(firstKeys);`,
1683
1863
  ` if (!scopes.some((scope) => scope.owned === true) || first.length === 0) return first;`,
1684
- ` const keys = await _readKeys(holon, keyStore, scopes, first.map(({ type, id }) => ({ type, id })));`,
1864
+ ` const records = first.map((row) => {`,
1865
+ ` const raw = row as T & { type: string; id: string };`,
1866
+ ` return { type: raw.type, id: raw.id };`,
1867
+ ` });`,
1868
+ ` const keys = await _readKeys(holon, keyStore, scopes, records);`,
1685
1869
  ` return read(keys);`,
1686
1870
  `};`,
1687
1871
  ``,
1688
1872
  `/** Reactive owned reads use the exact same two-pass, record-key-aware read as one-shot reads.`,
1689
1873
  ` * The raw watch is only a local change signal; ciphertext rows never reach application callbacks. */`,
1690
- `const _watchWithOwnedKeys = <T extends { type: string; id: string }>(`,
1874
+ `const _watchWithOwnedKeys = <T>(`,
1691
1875
  ` subscribe: (changed: () => void) => () => void,`,
1692
1876
  ` read: () => Promise<T[]>,`,
1693
1877
  ` cb: (rows: T[]) => void,`,
@@ -1712,37 +1896,6 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1712
1896
  `};`,
1713
1897
  ``,
1714
1898
  ...(manualEncryptionPresent ? [
1715
- `/** THE DEFAULT KeyStore — unwraps this principal's on-chain WrappedKeys with its device secret, caching`,
1716
- ` * per scope. Mirror of the documented reader handshake (e2e_encryption.md): the secret stays client-side,`,
1717
- ` * the operator stays blind. Key custody (where \`deviceSecret\` lives) remains the app's concern. */`,
1718
- `export function wrappedKeyStore(opts: { holon: NomosHolon; principal: string; deviceSecret: string }): NomosKeyStore {`,
1719
- ` const { holon, principal, deviceSecret } = opts;`,
1720
- ` const cache = new Map<string, Promise<NomosScopeKey[]>>();`,
1721
- ` const loadScope = (scope: string): Promise<NomosScopeKey[]> => {`,
1722
- ` let p = cache.get(scope);`,
1723
- ` if (p !== undefined) return p;`,
1724
- ` p = (async () => {`,
1725
- ` const rows = (await holon.query("wrappedKeysByRecipient", { recipientPrincipal: principal })) as { data?: Record<string, unknown> }[];`,
1726
- ` const held: NomosScopeKey[] = [];`,
1727
- ` for (const row of rows ?? []) {`,
1728
- ` const d = (row.data ?? row) as Record<string, unknown>;`,
1729
- ` if (String(d.scope) !== scope) continue;`,
1730
- ` if (!holon.cryptoUnwrapKey) throw new Error("this holon predates E2E unwrap — upgrade @githolon/client");`,
1731
- ` const key = await holon.cryptoUnwrapKey({ secret: deviceSecret, hpkeEpk: String(d.hpkeEpk), ct: String(d.ct) });`,
1732
- ` held.push({ scope, epoch: Number(d.epoch) || 0, key });`,
1733
- ` }`,
1734
- ` held.sort((a, b) => b.epoch - a.epoch);`,
1735
- ` return held;`,
1736
- ` })();`,
1737
- ` cache.set(scope, p);`,
1738
- ` return p;`,
1739
- ` };`,
1740
- ` return {`,
1741
- ` async scopeKey(scope) { return (await loadScope(scope))[0]; },`,
1742
- ` async readKeys(scopes) { const all: NomosScopeKey[] = []; for (const s of scopes) all.push(...(await loadScope(s))); return all; },`,
1743
- ` };`,
1744
- `}`,
1745
- ``,
1746
1899
  `// ── E2E OWNER CEREMONY (the sealing client, slice 2) — grantAndWrap / revokeAndRotate / rotate ──`,
1747
1900
  `// The owner-side twin of the KeyStore. It composes the framework key-epoch fan-out (\`advanceKeyEpoch\`,`,
1748
1901
  `// dispatched by its REAL id — never an \`export const alias\`) with the domain's own grant/revoke`,
@@ -1775,7 +1928,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1775
1928
  `/** Thrown when the resolved roster != the caller's declared \`expectRoster\` BEFORE dispatch — pre-firing`,
1776
1929
  ` * the kernel fan-out gate (wrap set == roster) so a rotation against a stale roster view never spends`,
1777
1930
  ` * an epoch on the chain. */`,
1778
- `export class EncryptionRosterMismatch extends Error {`,
1931
+ `class EncryptionRosterMismatch extends Error {`,
1779
1932
  ` readonly code = "nomos.crypto.roster_mismatch";`,
1780
1933
  ` readonly recommendedAction = "reconcileRoster";`,
1781
1934
  ` constructor(readonly scope: string, readonly missing: readonly string[], readonly extra: readonly string[]) {`,
@@ -1787,7 +1940,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1787
1940
  `/** The owner-ceremony surface — attached as \`<domain>Client(holon, { actor }).encryption\`. All three`,
1788
1941
  ` * verbs open a FRESH epoch (a fresh 32-byte secret) and fan the wraps to the resolved roster; the`,
1789
1942
  ` * membership change (if any) is the domain grant/revoke dispatched first, in the same call. */`,
1790
- `export interface NomosEncryption {`,
1943
+ `interface NomosEncryption {`,
1791
1944
  ` /** The encryption scopes this domain defines (each a \`requires(cap)\`). */`,
1792
1945
  ` readonly scopes: readonly string[];`,
1793
1946
  ` /** The CURRENT roster of a scope (active direct-grant members, principal-form). Defaults to the sole scope. */`,
@@ -1906,6 +2059,11 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1906
2059
  );
1907
2060
  }
1908
2061
 
2062
+ const aggByType = new Map<string, AggregateHandle>();
2063
+ for (const mod of modules) {
2064
+ for (const agg of mod.aggregates as AggregateHandle[]) aggByType.set(agg.id, agg);
2065
+ }
2066
+
1909
2067
  const anyAutoStamp = modules.some((mod) =>
1910
2068
  (mod.directives as Directive<unknown>[]).some(
1911
2069
  (d) =>
@@ -1918,21 +2076,128 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1918
2076
  out.push(
1919
2077
  `/** Caller-side clock sugar: fill omitted \`…At\` timestamp payload fields with ISO now().`,
1920
2078
  ` * The stamp rides IN the payload, so plans stay pure and replay byte-identical. */`,
1921
- `const stampNow = <T extends object>(payload: T, fields: string[]): T => {`,
2079
+ `const stampNow = <T extends object, const K extends string>(payload: T, fields: readonly K[]): T & Record<K, string> => {`,
1922
2080
  ` const now = new Date().toISOString();`,
1923
2081
  ` const out = { ...payload } as Record<string, unknown>;`,
1924
2082
  ` for (const f of fields) if (out[f] === undefined) out[f] = now;`,
2083
+ ` return out as T & Record<K, string>;`,
2084
+ `};`,
2085
+ ``,
2086
+ );
2087
+ }
2088
+
2089
+ const anyPrincipalBinding = modules.some((mod) =>
2090
+ (mod.directives as Directive<unknown>[]).some((directive) => directive.businessPrincipalField !== undefined),
2091
+ );
2092
+ if (anyPrincipalBinding) {
2093
+ out.push(
2094
+ `/** Bind identity from the verified application session. Tenant facts can never impersonate another principal. */`,
2095
+ `const _nmsBindCurrentPrincipal = <T extends object>(payload: T, field: string, principal: string | undefined): T & Record<string, string> => {`,
2096
+ ` if (!principal || !principal.trim()) throw new Error("this action needs a signed-in Nomos application");`,
2097
+ ` return { ...payload, [field]: principal };`,
2098
+ `};`,
2099
+ ``,
2100
+ );
2101
+ }
2102
+
2103
+ const anyRecipientPayload = modules.some((mod) =>
2104
+ (mod.directives as Directive<unknown>[]).some((directive) => {
2105
+ const birth = (directive as { businessBirth?: { publicPayloadSchema?: z.ZodTypeAny } }).businessBirth;
2106
+ const schema = birth?.publicPayloadSchema ?? (directive as { payloadSchema?: z.ZodTypeAny }).payloadSchema;
2107
+ return schema !== undefined && zodKind(schema) === "object" &&
2108
+ Object.values(zodObjectShape(schema)).some((field) => recipientMetaOf(field) !== undefined);
2109
+ }),
2110
+ );
2111
+ // Every generated record accessor accepts NomosRef<T>, even when the domain has no ref-valued
2112
+ // payload/query fields of its own. Keep the lowering/checker available for those accessors too.
2113
+ {
2114
+ out.push(
2115
+ `/** Internal wire lowering: tenant inputs must carry genuine, workspace-bound business references;`,
2116
+ ` * law/query wires receive only their stable ids. Runtime checks preserve this boundary even from JS`,
2117
+ ` * or from TypeScript code that deliberately defeats the generated types. */`,
2118
+ `const _nmsReferenceTypeMatches = (actual: string, expected: string): boolean =>`,
2119
+ ` expected === "" || actual === expected || actual.endsWith("." + expected);`,
2120
+ `const _nmsReferenceId = (value: unknown, expectedType: string, workspace: string, field: string): string => {`,
2121
+ ` if (typeof value !== "object" || value === null) {`,
2122
+ ` throw new TypeError(field + " must be a NomosRef returned by the generated application; plain record ids are not accepted");`,
2123
+ ` }`,
2124
+ ` const ref = value as { workspace?: unknown; type?: unknown; id?: unknown };`,
2125
+ ` if (typeof ref.id !== "string" || ref.id.length === 0 || typeof ref.type !== "string" || ref.type.length === 0 || typeof ref.workspace !== "string" || ref.workspace.length === 0) {`,
2126
+ ` throw new TypeError(field + " must carry a complete NomosRef { workspace, type, id }");`,
2127
+ ` }`,
2128
+ ` if (!_nmsReferenceTypeMatches(ref.type, expectedType)) {`,
2129
+ ` throw new TypeError(field + " refers to " + ref.type + "; expected " + expectedType);`,
2130
+ ` }`,
2131
+ ` if (workspace !== "" && ref.workspace !== workspace) {`,
2132
+ ` throw new TypeError(field + " belongs to workspace " + ref.workspace + "; this application is bound to " + workspace);`,
2133
+ ` }`,
2134
+ ` return ref.id;`,
2135
+ `};`,
2136
+ `const _nmsWireReferences = <T extends object>(payload: T, fields: Readonly<Record<string, string>>, workspace: string): T => {`,
2137
+ ` const out = { ...payload } as Record<string, unknown>;`,
2138
+ ` for (const [field, expectedType] of Object.entries(fields)) {`,
2139
+ ` const value = out[field];`,
2140
+ ` if (value !== undefined) out[field] = _nmsReferenceId(value, expectedType, workspace, field);`,
2141
+ ` }`,
2142
+ ` return out as T;`,
2143
+ `};`,
2144
+ ``,
2145
+ );
2146
+ }
2147
+ if (anyRecipientPayload) {
2148
+ out.push(
2149
+ `const _nmsWireRecipients = <T extends object>(payload: T, fields: readonly string[]): T => {`,
2150
+ ` const out = { ...payload } as Record<string, unknown>;`,
2151
+ ` for (const field of fields) {`,
2152
+ ` const value = out[field];`,
2153
+ ` if (value !== undefined) out[field] = _nmsRecipientAddress(value, field);`,
2154
+ ` }`,
1925
2155
  ` return out as T;`,
1926
2156
  `};`,
1927
2157
  ``,
1928
2158
  );
1929
2159
  }
1930
2160
 
2161
+ if (sourceTrustPresent) {
2162
+ 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");`,
2175
+ ` }`,
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("");`,
2193
+ `};`,
2194
+ ``,
2195
+ );
2196
+ }
2197
+
1931
2198
  // ── read models: one interface per aggregate wire type (deduped), deriveds/combineds merged ──
1932
- const aggByType = new Map<string, AggregateHandle>();
1933
2199
  const extrasByType = new Map<string, string[]>();
1934
2200
  for (const mod of modules) {
1935
- for (const agg of mod.aggregates as AggregateHandle[]) aggByType.set(agg.id, agg);
1936
2201
  for (const d of (mod.deriveds ?? []) as DerivedDecl[]) {
1937
2202
  (extrasByType.get(d.of) ?? extrasByType.set(d.of, []).get(d.of)!).push(
1938
2203
  ` /** derived read field (engine-projected) */\n ${d.id}?: ${tsTypeOfReturns(d.returns)};`,
@@ -1976,7 +2241,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1976
2241
  }
1977
2242
  }
1978
2243
 
1979
- // ── domain contracts: one `<Id>Contract` interface per public DTO shape, deduped across domains ──
2244
+ // ── domain contracts: one stable public DTO interface per contract, deduped across domains ──
1980
2245
  const contractByIdTs = new Map<string, import("./contract.js").ContractHandle>();
1981
2246
  for (const mod of modules) {
1982
2247
  for (const c of ((mod as { contracts?: readonly import("./contract.js").ContractHandle[] }).contracts ?? [])) {
@@ -1986,7 +2251,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1986
2251
  if (contractByIdTs.size > 0) {
1987
2252
  out.push(`// ── domain contracts (public DTO shapes — architecture/domain_contracts.md) ──`, ``);
1988
2253
  for (const [, c] of [...contractByIdTs].sort(([a], [b]) => a.localeCompare(b))) {
1989
- out.push(`export interface ${c.id}Contract {`);
2254
+ out.push(`export interface ${contractTypeName(c.id)} {`);
1990
2255
  for (const [name, field] of Object.entries(c.fields)) {
1991
2256
  out.push(` ${tsKey(name)}?: ${tsTypeOfField(field as Field)};`);
1992
2257
  }
@@ -2001,9 +2266,9 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2001
2266
  // Every read accessor's rows get their canonical `.ref` stamped from `holon.workspace` (additive); a
2002
2267
  // foreignRef-declaring package ALSO hydrates its foreignRef fields into typed locators first.
2003
2268
  const wrapP = (expr: string): string =>
2004
- `_stampRefsP(holon.workspace, ${foreignRefFields.length > 0 ? `_hydrateP(${expr})` : expr})`;
2269
+ `_businessRecordsP(_stampRefsP(holon.workspace, ${foreignRefFields.length > 0 ? `_hydrateP(holon, ${expr})` : expr}))`;
2005
2270
  const wrapRows = (expr: string): string =>
2006
- `_stampRefs(holon.workspace, ${foreignRefFields.length > 0 ? `_hydrateRows(${expr})` : expr})`;
2271
+ `_businessRecords(_stampRefs(holon.workspace, ${foreignRefFields.length > 0 ? `_hydrateRows(holon, ${expr})` : expr}))`;
2007
2272
  // ATTRIBUTED READS (architecture/field_provenance.md): the shared provenance shape +
2008
2273
  // per-aggregate accessors — emitted ONLY when the package declares ≥1 `.attributed()`
2009
2274
  // field, so a provenance-free package's client is byte-identical (the era rule).
@@ -2040,7 +2305,12 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2040
2305
  );
2041
2306
  }
2042
2307
  for (const extra of extrasByType.get(typeId) ?? []) out.push(extra);
2043
- out.push(`}`, ``);
2308
+ out.push(
2309
+ `}`,
2310
+ `/** Aggregate identity type for NomosRef<${typeId}>. The symbol brand never appears on the wire. */`,
2311
+ `export type ${typeId} = ${typeId}Data & { readonly [_nomosAggregateType]?: ${JSON.stringify(typeId)} };`,
2312
+ ``,
2313
+ );
2044
2314
  if (attributed.length > 0) {
2045
2315
  const ns = camel(typeId);
2046
2316
  out.push(
@@ -2064,20 +2334,20 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2064
2334
  }
2065
2335
  }
2066
2336
  // MULTI-WORKSPACE LOCATOR FACTORY (t.foreignRef): per aggregate, a typed builder that turns a projection
2067
- // row into a `ForeignRef<TargetData>` — reading the foreign id (the field) + the foreign workspace id (the
2337
+ // row into a `ForeignRef<TargetAggregate>` — reading the foreign id (the field) + the foreign workspace id (the
2068
2338
  // declared sibling field). The read model stays honest (the field is the raw stored id string); this gives
2069
2339
  // the TYPED locator with resolve/watch on top, without cloning the other holon.
2070
2340
  if (foreignRefFields.length > 0) {
2071
2341
  const byAgg = new Map<string, typeof foreignRefFields>();
2072
2342
  for (const f of foreignRefFields) { const l = byAgg.get(f.agg) ?? []; l.push(f); byAgg.set(f.agg, l); }
2073
2343
  out.push(`// ── foreign-ref locators (t.foreignRef): typed cross-workspace edges (resolve/watch on demand) ──`);
2074
- out.push(`export const ${camel(opts.packageName)}ForeignRefs = {`);
2344
+ out.push(`const ${camel(opts.packageName)}ForeignRefs = {`);
2075
2345
  for (const [agg, fields] of [...byAgg].sort(([a], [b]) => a.localeCompare(b))) {
2076
2346
  out.push(` ${agg}: {`);
2077
2347
  for (const f of fields) {
2078
2348
  out.push(
2079
2349
  ` /** \`${agg}.${f.field}\` → \`${f.target}\` in the workspace named by \`${f.workspaceField}\`. */`,
2080
- ` ${f.field}: (row: ${agg}Data): ForeignRef<${f.target}Data> => foreignRef(${JSON.stringify(f.target)}, String(row.${f.field} ?? ""), String((row as Record<string, unknown>)[${JSON.stringify(f.workspaceField)}] ?? "")),`,
2350
+ ` ${f.field}: (nomos: NomosForeignReader, row: ${agg}Data): ForeignRef<${f.target}> => foreignRef(nomos, ${JSON.stringify(f.target)}, String(row.${f.field} ?? ""), String((row as Record<string, unknown>)[${JSON.stringify(f.workspaceField)}] ?? "")),`,
2081
2351
  );
2082
2352
  }
2083
2353
  out.push(` },`);
@@ -2092,16 +2362,17 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2092
2362
  for (const f of foreignRefFields) { (hydrateSpecs[f.agg] ??= []).push({ field: f.field, wsField: f.workspaceField, target: f.target }); }
2093
2363
  out.push(
2094
2364
  `const _FOREIGN_REFS: Record<string, { field: string; wsField: string; target: string }[]> = ${JSON.stringify(hydrateSpecs)};`,
2095
- `function _hydrateRows<T>(rows: Row<T>[]): Row<T>[] {`,
2365
+ `function _hydrateRows<T>(nomos: NomosForeignReader, rows: Row<T>[]): Row<T>[] {`,
2096
2366
  ` return rows.map((row) => {`,
2097
- ` const specs = _FOREIGN_REFS[row.type];`,
2367
+ ` const raw = row as Row<T> & { type: string };`,
2368
+ ` const specs = _FOREIGN_REFS[raw.type];`,
2098
2369
  ` if (specs === undefined || row.data == null) return row;`,
2099
2370
  ` const data: Record<string, unknown> = { ...(row.data as Record<string, unknown>) };`,
2100
- ` for (const s of specs) { const v = data[s.field]; if (typeof v === "string") data[s.field] = foreignRef(s.target, v, String(data[s.wsField] ?? "")); }`,
2371
+ ` 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] ?? "")); }`,
2101
2372
  ` return { ...row, data: data as T };`,
2102
2373
  ` });`,
2103
2374
  `}`,
2104
- `const _hydrateP = <T>(p: Promise<Row<T>[]>): Promise<Row<T>[]> => p.then(_hydrateRows);`,
2375
+ `const _hydrateP = <T>(nomos: NomosForeignReader, p: Promise<Row<T>[]>): Promise<Row<T>[]> => p.then((rows) => _hydrateRows(nomos, rows));`,
2105
2376
  ``,
2106
2377
  );
2107
2378
  }
@@ -2115,17 +2386,22 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2115
2386
  // plumbing — the composed workspace name comes back on the result for `connect()`.
2116
2387
  // PACKED child types derive NO holon-birth helper: a packed birth is a shard-map
2117
2388
  // PLACEMENT (slice 2's lane).
2118
- const birthModules = modules.filter(
2119
- (m) => m.workspaceTypes !== undefined && m.workspaceTypes.length > 0,
2120
- );
2121
- // THE FRONT-DOOR MINT PLAN (sharding slice 2; taxonomy packages ONLY — a
2122
- // taxonomy-free package's generated client is byte-identical to before this
2123
- // existed): routed `.creates` directives whose target id rides the payload mint
2124
- // it through the holon's kernel mint with the home's ROUTE TAG folded in
2125
- // (self-routing ids, §4); the derived placement directives mint the axis-root id.
2389
+ // Taxonomy birth helpers used to be exported as free functions taking cloud/workspace/auth machinery.
2390
+ // Keep that lane closed until each birth is surfaced through a declared business intent.
2391
+ const birthModules: DomainModule[] = [];
2392
+ // THE FRONT-DOOR MINT PLAN: any `.creates` directive whose target id rides the sealed payload gets that
2393
+ // id minted INSIDE the generated client and omitted from application input. Taxonomy routes enrich the
2394
+ // same plan with home/tag placement; there is still one mechanism and one returned NomosRef<T>.
2126
2395
  const mintPlans = new Map<DomainModule, Map<string, ClientMintPlanEntry>>();
2127
- for (const mod of birthModules) {
2128
- const plan = deriveClientMintPlan(
2396
+ for (const mod of modules) {
2397
+ const aggregates = new Map(mod.aggregates.map((aggregate) => [aggregate.id, aggregate]));
2398
+ const generic = (mod.directives as Directive<unknown>[]).flatMap((directive): ClientMintPlanEntry[] => {
2399
+ const aggregate = aggregates.get(directive.aggregateId);
2400
+ if (aggregate === undefined) return [];
2401
+ const field = mintedCreateField(directive, aggregate);
2402
+ return field === undefined ? [] : [{ directive: directive.id, field, mintType: aggregate.id }];
2403
+ });
2404
+ const routed = deriveClientMintPlan(
2129
2405
  mod as {
2130
2406
  name: string;
2131
2407
  aggregates: AggregateHandle[];
@@ -2135,34 +2411,42 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2135
2411
  workspaceTypes?: any[];
2136
2412
  },
2137
2413
  );
2138
- if (plan.length > 0) mintPlans.set(mod, new Map(plan.map((e) => [e.directive, e])));
2414
+ const plan = new Map(generic.map((entry) => [entry.directive, entry]));
2415
+ for (const entry of routed) plan.set(entry.directive, entry);
2416
+ if (plan.size > 0) mintPlans.set(mod, plan);
2139
2417
  }
2140
2418
  if (mintPlans.size > 0) {
2141
2419
  out.push(
2142
- `// ── the front-door kernel mint (routed creates — ids are internal plumbing) ──`,
2420
+ `// ── the front-door kernel mint (create ids are internal plumbing) ──`,
2143
2421
  ``,
2144
- `/** The ROUTED holon surface (@githolon/client with a taxonomy law): the kernel mint. */`,
2145
- `export interface NomosRoutedHolon extends NomosHolon {`,
2422
+ `/** Framework minting surface used only inside generated create methods. */`,
2423
+ `interface NomosMintingHolon extends NomosHolon {`,
2146
2424
  ` /** Reserve a kernel-minted typed id; \`homeKey\`/\`tagHex\` fold the home's route tag in. */`,
2147
2425
  ` mint(typeTag: string, opts?: { homeKey?: string; tagHex?: string }): Promise<string>;`,
2148
2426
  `}`,
2149
2427
  ``,
2150
- `/** The 48-bit route-tag slot of a kernel-minted id (the UUIDv7 leading 12 hex chars). */`,
2151
- `const routeTagOfId = (id: string): string | undefined => {`,
2152
- ` const i = id.indexOf("_");`,
2153
- ` if (i <= 0) return undefined;`,
2154
- ` const body = id.slice(i + 1).replace(/-/g, "").toLowerCase();`,
2155
- ` return /^[0-9a-f]{32}$/.test(body) ? body.slice(0, 12) : undefined;`,
2156
- `};`,
2157
- ``,
2158
2428
  );
2429
+ const needsRouteTag = [...mintPlans.values()].some((plan) =>
2430
+ [...plan.values()].some((entry) => entry.tagFromIdField !== undefined));
2431
+ if (needsRouteTag) {
2432
+ out.push(
2433
+ `/** The 48-bit route-tag slot of a kernel-minted id (the UUIDv7 leading 12 hex chars). */`,
2434
+ `const routeTagOfId = (id: string): string | undefined => {`,
2435
+ ` const i = id.indexOf("_");`,
2436
+ ` if (i <= 0) return undefined;`,
2437
+ ` const body = id.slice(i + 1).replace(/-/g, "").toLowerCase();`,
2438
+ ` return /^[0-9a-f]{32}$/.test(body) ? body.slice(0, 12) : undefined;`,
2439
+ `};`,
2440
+ ``,
2441
+ );
2442
+ }
2159
2443
  }
2160
2444
  if (birthModules.length > 0) {
2161
2445
  out.push(
2162
2446
  `// ── derived birth lanes (workspace taxonomy → the EXISTING platform lanes) ──`,
2163
2447
  ``,
2164
2448
  `/** Auth a birth presents — every birth needs a principal (the cloud's law). */`,
2165
- `export interface NomosBirthAuth {`,
2449
+ `interface NomosBirthAuth {`,
2166
2450
  ` /** Auth-provider access token, sent as \`x-nomos-auth\` (verified by the cloud). */`,
2167
2451
  ` authToken?: string;`,
2168
2452
  ` /** Transitional bare principal uid, sent as \`x-nomos-principal\` (no token). */`,
@@ -2175,7 +2459,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2175
2459
  `}`,
2176
2460
  ``,
2177
2461
  `/** A birth lane's response — \`workspace\` is the born holon's addressable name. */`,
2178
- `export interface NomosBirthResult {`,
2462
+ `interface NomosBirthResult {`,
2179
2463
  ` ok: boolean;`,
2180
2464
  ` /** The addressable workspace name (pass it to \`connect({ workspace })\`). */`,
2181
2465
  ` workspace?: string;`,
@@ -2298,11 +2582,21 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2298
2582
 
2299
2583
  // ── payload types + per-domain client factories ──
2300
2584
  const usedPayloadNames = new Set<string>();
2585
+ const usedResultNames = new Set<string>();
2586
+ const applicationBirths: Array<{
2587
+ sourceDomain: string;
2588
+ method: string;
2589
+ payloadName: string;
2590
+ aggregateId: string;
2591
+ targetDomain: string;
2592
+ applicationType: string;
2593
+ }> = [];
2301
2594
  for (const mod of modules) {
2302
2595
  const domain = mod.domain ?? mod.name;
2303
2596
  const dirs = (mod.directives as Directive<unknown>[]).filter(
2304
2597
  (d) => d && typeof d.id === "string" && typeof (d as { payloadSchema?: unknown }).payloadSchema === "object"
2305
- && (d as { businessBirthInternal?: unknown }).businessBirthInternal !== true,
2598
+ && (d as { businessBirthInternal?: unknown }).businessBirthInternal !== true
2599
+ && ((d as { birthsChild?: unknown }).birthsChild !== true || (d as { businessBirth?: unknown }).businessBirth !== undefined),
2306
2600
  );
2307
2601
  const correctionActions = dirs.filter((d) =>
2308
2602
  (d as { birthsChild?: unknown }).birthsChild !== true
@@ -2371,11 +2665,11 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2371
2665
  if (ownedScope !== undefined && Object.keys(ownedActions).length > 0) {
2372
2666
  methods.push(
2373
2667
  ` /** Share one record. The peer can read its history and future changes; Nomos owns the keys and policy. */`,
2374
- ` share: (record: NomosRef, peer: string): Promise<NomosOutcome> => _ownedRecordAction("share", record, peer),`,
2668
+ ` share: (record: NomosRef<_NomosAnyAggregate>, peer: string): Promise<void> => _ownedRecordAction("share", record, peer),`,
2375
2669
  ` /** Stop sharing one record. Earlier knowledge cannot be erased; future changes use a fresh private key. */`,
2376
- ` unshare: (record: NomosRef, peer: string): Promise<NomosOutcome> => _ownedRecordAction("unshare", record, peer),`,
2670
+ ` unshare: (record: NomosRef<_NomosAnyAggregate>, peer: string): Promise<void> => _ownedRecordAction("unshare", record, peer),`,
2377
2671
  ` /** Hand ownership, readable history and future authority for one record to another person. */`,
2378
- ` transfer: (record: NomosRef, peer: string): Promise<NomosOutcome> => _ownedRecordAction("transfer", record, peer),`,
2672
+ ` transfer: (record: NomosRef<_NomosAnyAggregate>, peer: string): Promise<void> => _ownedRecordAction("transfer", record, peer),`,
2379
2673
  );
2380
2674
  }
2381
2675
  if (correctionActions.length > 0) {
@@ -2397,19 +2691,74 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2397
2691
  }
2398
2692
  // directiveId → its emitted payload TYPE name (the generated conflict references use it).
2399
2693
  const dirPayloadNames = new Map<string, string>();
2694
+ const dirById = new Map(dirs.map((directive) => [directive.id, directive] as const));
2400
2695
  for (const d of dirs) {
2401
- let payloadName = `${cap(d.id)}Payload`;
2402
- if (usedPayloadNames.has(payloadName)) payloadName = `${cap(camel(domain))}${cap(d.id)}Payload`;
2403
- usedPayloadNames.add(payloadName);
2404
- dirPayloadNames.set(d.id, payloadName);
2405
2696
  const businessBirth = (d as unknown as {
2406
2697
  businessBirth?: { aggregateId: string; publicPayloadSchema: z.ZodTypeAny; rootId: string; namespace: string; keyField: string };
2407
2698
  }).businessBirth;
2699
+ const inputSuffix = isBusinessIntent(d) || businessBirth !== undefined ? "Input" : "Payload";
2700
+ const publicBusinessName = businessBirth !== undefined && !isBusinessIntent(d)
2701
+ ? `Create${cap(camel(businessBirth.aggregateId))}Input`
2702
+ : `${cap(d.id)}${inputSuffix}`;
2703
+ let payloadName = publicBusinessName;
2704
+ if (usedPayloadNames.has(payloadName)) payloadName = `${cap(camel(domain))}${publicBusinessName}`;
2705
+ usedPayloadNames.add(payloadName);
2706
+ dirPayloadNames.set(d.id, payloadName);
2707
+ const businessBirthTargetDomain = businessBirth === undefined
2708
+ ? undefined
2709
+ : (() => {
2710
+ const owners = [...new Set(aggregateOwners.get(businessBirth.aggregateId) ?? [])];
2711
+ if (owners.length !== 1) {
2712
+ throw new Error(
2713
+ `codegen: business birth '${domain}/${d.id}' must target one generated domain owning ` +
2714
+ `'${businessBirth.aggregateId}', found ${owners.length === 0 ? "none" : owners.join(", ")}`,
2715
+ );
2716
+ }
2717
+ return owners[0]!;
2718
+ })();
2408
2719
  const schema = businessBirth?.publicPayloadSchema
2409
2720
  ?? (d as unknown as { payloadSchema: z.ZodTypeAny }).payloadSchema;
2410
- const stamps = autoStampFields(schema);
2411
2721
  const mint = mintPlan?.get(d.id);
2722
+ const referencePayloadFields = zodKind(schema) === "object"
2723
+ ? Object.entries(zodObjectShape(schema))
2724
+ .filter(([name, field]) => name !== mint?.field && idOfMetaOf(field) !== undefined)
2725
+ .map(([name]) => name)
2726
+ : [];
2727
+ const referencePayloadTypes = zodKind(schema) === "object"
2728
+ ? Object.fromEntries(
2729
+ Object.entries(zodObjectShape(schema))
2730
+ .filter(([name, field]) => name !== mint?.field && idOfMetaOf(field) !== undefined)
2731
+ .map(([name, field]) => [
2732
+ name,
2733
+ idOfTargetKindOf(field) === "contract" ? "" : idOfMetaOf(field)!,
2734
+ ]),
2735
+ )
2736
+ : {};
2737
+ const recipientPayloadFields = zodKind(schema) === "object"
2738
+ ? Object.entries(zodObjectShape(schema))
2739
+ .filter(([name, field]) => name !== mint?.field && recipientMetaOf(field) !== undefined)
2740
+ .map(([name]) => name)
2741
+ : [];
2742
+ const wireReferences = (expr: string): string => {
2743
+ const references = referencePayloadFields.length === 0
2744
+ ? expr
2745
+ : `_nmsWireReferences(${expr}, ${JSON.stringify(referencePayloadTypes)}, holon.workspace)`;
2746
+ return recipientPayloadFields.length === 0
2747
+ ? references
2748
+ : `_nmsWireRecipients(${references}, ${JSON.stringify(recipientPayloadFields)})`;
2749
+ };
2750
+ const stamps = autoStampFields(schema);
2412
2751
  const placed = placementDirs.has(d.id) ? ["shard"] : [];
2752
+ const principalField = d.businessPrincipalField;
2753
+ // Business births are public application actions even when they are synthesized from
2754
+ // `birth(Aggregate, spec)` rather than explicitly wrapped in `intent(...)`. Their generated
2755
+ // method is therefore `birth(input)`, and every hidden custody/sealing prelude must bind the
2756
+ // same name. Calling it `payload` here emitted an undeclared identifier for owned births.
2757
+ const argumentName = isBusinessIntent(d) || businessBirth !== undefined ? "input" : "payload";
2758
+ const principalPrelude = principalField === undefined
2759
+ ? []
2760
+ : [` const _businessPayload = _nmsBindCurrentPrincipal(${argumentName}, ${JSON.stringify(principalField)}, actor ?? holon.principal);`];
2761
+ const inputPayload = principalField === undefined ? argumentName : "_businessPayload";
2413
2762
  // THE AUTHORITY GATE (codegen-time): a directive carrying `.requires(relation)` is GATED — its
2414
2763
  // generated method refuses to encode an offer without an `actor` (throws MissingOfferAuthority
2415
2764
  // BEFORE any submit), so an actorless gated offer is impossible to GENERATE, not merely refused.
@@ -2423,14 +2772,13 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2423
2772
  // are byte-identical (wantSeal === false ⇒ no prelude, no extra param, plain `payload`).
2424
2773
  const encFields = encMap?.[domain]?.directives?.[d.id];
2425
2774
  const wantSeal = encFields !== undefined;
2426
- // FALLBACK ATTESTATION INJECTION (co2): a directive whose law declares a foreign read
2427
- // (`.readsFrom(...)`) accepts pre-fetched signed envelopes as `callOpts.attestations` — the manual
2428
- // escape hatch when the colocation resolver can't serve the read (the source isn't on the plane). The
2429
- // in-engine colocation path (the offer resolves the read from the mounted source, zero HTTP) remains
2430
- // the default; this is the explicit override a caller reaches for. Emitted ONLY for foreign-read
2431
- // directives (an ordinary directive's signature is byte-identical).
2432
- const wantAttest = (((d as { declaredForeignReads?: unknown[] }).declaredForeignReads?.length ?? 0) > 0);
2433
- const attTy = `readonly { queryId: string; result: unknown; attestation: unknown }[]`;
2775
+ // A dynamic foreign source is a BUSINESS input, but attestation transport and trust seating are not.
2776
+ // `.trustsSource(...)` gives codegen the whole private recipe: fetch the signed declared read, derive
2777
+ // the source identity/key, author the hidden trust directive, then carry the envelope. Directives with
2778
+ // an ordinary declared foreign read need no public override either: the bound client follows the
2779
+ // kernel's typed missing-evidence refusal automatically. There is therefore no `callOpts.attestations`
2780
+ // lane on any generated tenant method.
2781
+ const sourceTrust = d.businessSourceTrust;
2434
2782
  const ownedSeal = encFields !== undefined && Object.values(encFields).every((field) => field.owned === true);
2435
2783
  const ownedTargetAggregate = ownedSeal
2436
2784
  ? (mod.aggregates as AggregateHandle[]).find((aggregate) => aggregate.id === (d as { aggregateId?: string }).aggregateId)
@@ -2451,7 +2799,11 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2451
2799
  ? ` if (!holon.mint) throw new Error("this holon predates record-bound .owned() identity — upgrade @githolon/client");\n` +
2452
2800
  ` const _ownedRecord = { type: ${JSON.stringify(ownedRuntimeType)}, id: await holon.mint(${JSON.stringify(ownedRuntimeType)}), preMinted: true as const };`
2453
2801
  : ownedRecordTarget.kind === "payload"
2454
- ? ` const _ownedRecord = { type: ${JSON.stringify(ownedRuntimeType)}, id: String((payload as Record<string, unknown>)[${JSON.stringify(ownedRecordTarget.field)}] ?? "") };`
2802
+ ? ` const _ownedRecord = { type: ${JSON.stringify(ownedRuntimeType)}, id: ${
2803
+ referencePayloadFields.includes(ownedRecordTarget.field)
2804
+ ? `_nmsReferenceId((${inputPayload} as Record<string, unknown>)[${JSON.stringify(ownedRecordTarget.field)}], ${JSON.stringify(referencePayloadTypes[ownedRecordTarget.field] ?? "")}, holon.workspace, ${JSON.stringify(ownedRecordTarget.field)})`
2805
+ : `String((${inputPayload} as Record<string, unknown>)[${JSON.stringify(ownedRecordTarget.field)}] ?? "")`
2806
+ } };`
2455
2807
  : ` const _ownedRecord = { type: ${JSON.stringify(ownedRuntimeType)}, id: ${JSON.stringify(ownedRecordTarget.id)} };`;
2456
2808
  const ownedBirthDescriptor = businessBirth === undefined || encFields === undefined
2457
2809
  ? undefined
@@ -2459,43 +2811,115 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2459
2811
  const ownedBirthPrelude = ownedBirthDescriptor === undefined
2460
2812
  ? ""
2461
2813
  : ` if (!holon.__nomosOwnedBirthScopeKey) throw new Error("this holon predates automatic .owned() birth custody — upgrade @githolon/client");\n` +
2462
- ` const _ownedBirthKey = await holon.__nomosOwnedBirthScopeKey({ scope: ${JSON.stringify(ownedBirthDescriptor.scope)}, namespace: ${JSON.stringify(businessBirth!.namespace)}, key: (payload as Record<string, unknown>)[${JSON.stringify(businessBirth!.keyField)}] });`;
2463
- const callOptsFields = [
2464
- ...(wantSeal && !ownedSeal ? ["seal?: boolean"] : []),
2465
- ...(wantAttest ? [`attestations?: ${attTy}`] : []),
2466
- ];
2467
- const paramSig = callOptsFields.length
2468
- ? `payload: ${payloadName}, callOpts?: { ${callOptsFields.join("; ")} }`
2469
- : `payload: ${payloadName}`;
2470
- const offerExtra = wantAttest ? ", attestations: callOpts?.attestations" : "";
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)}] });`;
2815
+ 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
+ }));
2828
+ const copiedTrustFields = sourceTrust === undefined
2829
+ ? []
2830
+ : Object.entries(sourceTrust.copy)
2831
+ .sort(([a], [b]) => a.localeCompare(b))
2832
+ .map(([target, field]) =>
2833
+ ` ${JSON.stringify(target)}: (${inputPayload} as Record<string, unknown>)[${JSON.stringify(field)}],`);
2834
+ const copiedTrustReferenceTypes = sourceTrust === undefined
2835
+ ? {}
2836
+ : Object.fromEntries(Object.entries(sourceTrust.copy).flatMap(([target, field]) => {
2837
+ const type = referencePayloadTypes[field];
2838
+ return type === undefined ? [] : [[target, type]];
2839
+ }));
2840
+ const sourceTrustPrelude = sourceTrust === undefined
2841
+ ? []
2842
+ : [
2843
+ ` const _sourceWorkspace = String((${inputPayload} as Record<string, unknown>)[${JSON.stringify(sourceTrust.fromPayload)}] ?? "");`,
2844
+ ` 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));`,
2847
+ ` await _commit(holon.offerDirective(${JSON.stringify(domain)}, ${JSON.stringify(sourceTrust.viaDirectiveId)}, _nmsWireReferences({`,
2848
+ ...copiedTrustFields,
2849
+ ` ${JSON.stringify(sourceTrust.genesisField)}: _nmsSourceGenesis(_sourceEntry),`,
2850
+ ` ${JSON.stringify(sourceTrust.keyHashesField)}: [await _nmsSourceKeyHash(_sourceEntry)],`,
2851
+ ` }, ${JSON.stringify(copiedTrustReferenceTypes)}, _sourceWorkspace), { domainHash, actor, returnCreated: true }) as Promise<_NomosRawWriteResult>);`,
2852
+ ];
2853
+ const offerExtra = sourceTrust === undefined ? "" : ", attestations: [_sourceEntry]";
2471
2854
  const sealPrelude = wantSeal
2472
- ? ` 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 }>, payload as Record<string, unknown>, ${businessBirth !== undefined || ownedSeal ? "true" : "callOpts?.seal !== false"}${ownedBirthDescriptor !== undefined ? ", _ownedBirthKey" : ", undefined"}${ownedRecordPrelude ? ", _ownedRecord" : ""}) as ${payloadName};`
2855
+ ? ` 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};`
2473
2856
  : "";
2474
- const pv = wantSeal ? "_sealed" : "payload";
2857
+ const pv = wantSeal ? "_sealed" : inputPayload;
2475
2858
  // MULTI-OUTPUT: name every aggregate this directive writes (primary marker target + declared
2476
2859
  // secondaryOutputs), so the frontend dev sees "writes A + B" at the call site (not one by convention).
2477
- const dd = d as unknown as { aggregateId: string; marker: string; secondaryOutputs?: readonly { aggregateId: string; marker: string }[] };
2478
- const allOutputs = [{ aggregateId: dd.aggregateId, marker: dd.marker }, ...(dd.secondaryOutputs ?? [])];
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 }))]
2862
+ .filter((output) => publicAggregateIds.has(output.aggregateId))
2863
+ .filter((output, index, outputs) =>
2864
+ outputs.findIndex((candidate) => candidate.aggregateId === output.aggregateId) === index);
2479
2865
  const writesClause = allOutputs.length > 1
2480
2866
  ? ` Writes ${allOutputs.map((o) => `\`${o.aggregateId}\` (${o.marker})`).join(" + ")}.`
2481
2867
  : "";
2482
2868
  out.push(
2483
- `/** \`${domain}/${d.id}\` payload (mirrors the engine's zod schema).${writesClause}${stamps.length ? ` Omitted ${stamps.map((s) => `\`${s}\``).join("/")} auto-stamp(s) ISO now() at offer.` : ""}${mint ? ` Omitted \`${mint.field}\` is KERNEL-MINTED at offer (returned on the result).` : ""}${placed.length ? " Omitted `shard` is picked LEAST-LOADED at offer (the routed holon)." : ""} */`,
2484
- `export type ${payloadName} = ${tsTypeOfPayload(schema, stamps, mint ? [mint.field] : [], placed, lazyReg, d.id)};`,
2869
+ `/** \`${domain}/${d.id}\` ${isBusinessIntent(d) ? "business input contract" : "private directive payload"}.${writesClause}${stamps.length ? ` Omitted ${stamps.map((s) => `\`${s}\``).join("/")} auto-stamp(s) ISO now() at offer.` : ""}${mint ? ` Omitted \`${mint.field}\` is KERNEL-MINTED at offer (returned on the result).` : ""}${placed.length ? " Omitted `shard` is picked LEAST-LOADED at offer (the routed holon)." : ""} */`,
2870
+ `export type ${payloadName} = ${tsTypeOfPayload(schema, stamps, mint ? [mint.field] : [], placed, principalField === undefined ? [] : [principalField], lazyReg, d.id)};`,
2485
2871
  ``,
2486
2872
  );
2873
+ let resultName = `${cap(camel(d.id))}Result`;
2874
+ if (usedResultNames.has(resultName)) resultName = `${cap(camel(domain))}${resultName}`;
2875
+ usedResultNames.add(resultName);
2876
+ if (businessBirth === undefined && allOutputs.length > 0) {
2877
+ const resultMembers = new Set<string>();
2878
+ const fields = allOutputs.map((output) => {
2879
+ const field = tsResultMemberName(output.aggregateId);
2880
+ if (resultMembers.has(field)) {
2881
+ throw new Error(
2882
+ `codegen: action '${domain}/${d.id}' has two aggregate consequences named '${field}'; ` +
2883
+ `rename one aggregate so its generated business result is unambiguous`,
2884
+ );
2885
+ }
2886
+ resultMembers.add(field);
2887
+ return output.many
2888
+ ? ` readonly ${tsKey(field)}: readonly NomosRef<${output.aggregateId}>[];`
2889
+ : ` readonly ${tsKey(field)}: NomosRef<${output.aggregateId}>;`;
2890
+ });
2891
+ out.push(
2892
+ `/** Every business record affected by \`${domain}/${d.id}\`. No ledger or transport receipt leaks through this type. */`,
2893
+ `export interface ${resultName} {`,
2894
+ ...fields,
2895
+ `}`,
2896
+ ``,
2897
+ );
2898
+ }
2487
2899
  // ONE BUSINESS BIRTH: the session is the parent; identity, timestamp, child addressing, law selection,
2488
2900
  // provenance and device seating are internal. The app supplies only the declared business payload and
2489
- // receives the born root's typed NomosRef.
2901
+ // receives the born root's typed NomosRef inside the private domain binder. The one public application
2902
+ // wrapper below immediately follows that reference and returns the opened child business application.
2490
2903
  if (businessBirth !== undefined) {
2904
+ const birthMethod = isBusinessIntent(d) ? JSON.stringify(d.id) : "birth";
2905
+ const applicationMethod = isBusinessIntent(d) ? d.id : "birth";
2906
+ applicationBirths.push({
2907
+ sourceDomain: domain,
2908
+ method: applicationMethod,
2909
+ payloadName,
2910
+ aggregateId: businessBirth.aggregateId,
2911
+ targetDomain: businessBirthTargetDomain!,
2912
+ applicationType: `${cap(camel(businessBirth.aggregateId))}Application`,
2913
+ });
2491
2914
  methods.push(
2492
2915
  ` /** Give one ${businessBirth.aggregateId} its own durable life. Nomos owns identity, provenance, persistence and recovery. */`,
2493
- ` birth: async (payload: ${payloadName}): Promise<NomosBorn<${businessBirth.aggregateId}Data>> => {`,
2916
+ ` ${birthMethod}: async (input: ${payloadName}): Promise<NomosRef<${businessBirth.aggregateId}>> => {`,
2494
2917
  ` const principal = actor ?? holon.principal;`,
2495
2918
  ` if (!principal || !principal.trim()) throw new Error("birth() needs a signed-in Nomos session");`,
2919
+ ...principalPrelude,
2496
2920
  ...(ownedBirthPrelude ? [ownedBirthPrelude] : []),
2497
2921
  ...(wantSeal ? [sealPrelude] : []),
2498
- ` const result = await holon.offerDirective(${JSON.stringify(domain)}, ${JSON.stringify(d.id)}, { ...payload, principal, bornAt: new Date().toISOString()${wantSeal ? `, ${JSON.stringify(BUSINESS_BIRTH_SEED_PAYLOAD_FIELD)}: JSON.stringify(_sealed)` : ""}${ownedBirthDescriptor !== undefined ? `, [_OWNED_KEY_PAYLOAD_FIELD]: _ownedBirthKey.ownedKeySubject` : ""} }, { domainHash, actor: principal, returnCreated: true });`,
2922
+ ` const result = await holon.offerDirective(${JSON.stringify(domain)}, ${JSON.stringify(d.id)}, { ...${wireReferences("input")}, principal, bornAt: new Date().toISOString()${wantSeal ? `, ${JSON.stringify(BUSINESS_BIRTH_SEED_PAYLOAD_FIELD)}: JSON.stringify(${wireReferences("_sealed")})` : ""}${ownedBirthDescriptor !== undefined ? `, [_OWNED_KEY_PAYLOAD_FIELD]: _ownedBirthKey.ownedKeySubject` : ""} }, { domainHash, actor: principal, returnCreated: true });`,
2499
2923
  ` const born = typeof result === "object" && result !== null && Array.isArray(result.born) ? result.born[0] : undefined;`,
2500
2924
  ` if (typeof born !== "object" || born === null || typeof born.workspace !== "string" || !born.workspace) throw new Error("birth() committed without returning the born workspace");`,
2501
2925
  ...(ownedBirthDescriptor !== undefined
@@ -2504,7 +2928,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2504
2928
  ` await holon.sync?.();`,
2505
2929
  ` if (!holon.settleBirth) throw new Error("birth() requires the current @githolon/client to complete durable recovery");`,
2506
2930
  ` await holon.settleBirth(born.workspace);`,
2507
- ` return { workspace: born.workspace, type: ${JSON.stringify(businessBirth.aggregateId)}, id: ${JSON.stringify(businessBirth.rootId)} };`,
2931
+ ` return _nmsRef<${businessBirth.aggregateId}>(born.workspace, ${JSON.stringify(businessBirth.aggregateId)}, ${JSON.stringify(businessBirth.rootId)});`,
2508
2932
  ` },`,
2509
2933
  );
2510
2934
  continue;
@@ -2514,7 +2938,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2514
2938
  // directiveId + baked domainHash bound at codegen time), NOT an `offerDirective`, so a frontend dev
2515
2939
  // never hand-writes the four raw strings this replaces (the co2 production bug).
2516
2940
  if ((d as { birthsChild?: boolean }).birthsChild === true) {
2517
- const birthPayload = stamps.length ? `stampNow(payload, ${JSON.stringify(stamps)})` : "payload";
2941
+ const birthPayload = wireReferences(stamps.length ? `stampNow(payload, ${JSON.stringify(stamps)})` : "payload");
2518
2942
  const gateBirthDoc = gate ? ` GATED: needs \`actor\` (\`${gate.relation}\`).` : "";
2519
2943
  methods.push(
2520
2944
  ` /** Births a CHILD WORKSPACE (named by \`${payloadName}\`) under \`parent\`, via a SIGNED cross-workspace`,
@@ -2530,54 +2954,77 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2530
2954
  );
2531
2955
  continue;
2532
2956
  }
2533
- // THE ONE WRITE OUTCOME (architecture/nomos_ref.md): EVERY directive method returns Promise<NomosOutcome>
2534
- // — `created` (typed refs of all minted aggregates, from returnCreated) + `head`, plus `target` for the
2535
- // front-door-mint lane (the one statically-known addressed instance). One async block covers every case.
2957
+ // BUSINESS RESULTS: every action returns the generated set of aggregate references its law says it
2958
+ // affected. The framework receipt, ledger head and transport mechanics remain private.
2536
2959
  if (mint !== undefined) {
2537
2960
  const mintOpts =
2538
2961
  mint.homeKeyField !== undefined
2539
- ? `, { homeKey: payload.${mint.homeKeyField} }`
2962
+ ? `, { homeKey: ${argumentName}.${mint.homeKeyField} }`
2540
2963
  : mint.tagFromIdField !== undefined
2541
- ? `, { tagHex: routeTagOfId(payload.${mint.tagFromIdField}) }`
2964
+ ? `, { tagHex: routeTagOfId(${argumentName}.${mint.tagFromIdField}) }`
2542
2965
  : "";
2543
- const body = stamps.length
2966
+ const body = wireReferences(stamps.length
2544
2967
  ? `stampNow({ ...${pv}, ${mint.field} }, ${JSON.stringify(stamps)})`
2545
- : `{ ...${pv}, ${mint.field} }`;
2968
+ : `{ ...${pv}, ${mint.field} }`);
2546
2969
  const ownedMintTarget = ownedSeal
2547
2970
  && ownedRecordTarget?.kind === "payload"
2548
2971
  && ownedRecordTarget.field === mint.field
2549
2972
  && ownedRuntimeType !== undefined;
2973
+ const hasBusinessResult = allOutputs.length > 0;
2974
+ const mintedReturnType = hasBusinessResult ? resultName : "void";
2975
+ const resultSpec = JSON.stringify(allOutputs.map((output) => ({
2976
+ member: tsResultMemberName(output.aggregateId),
2977
+ type: output.aggregateId,
2978
+ ...(output.many ? { many: true } : {}),
2979
+ })));
2980
+ 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>)`;
2550
2983
  methods.push(
2551
- ` /** Author \`${domain}/${d.id}\`: the front-door MINTS \`${mint.field}\` (route-tagged) when omitted`,
2552
- ` * — ids stay internal plumbing — and returns the outcome (\`target\` = the minted ${dd.aggregateId ?? "aggregate"}).${gate ? ` GATED: needs \`{ actor }\` (\`${gate.relation}\`).` : ""} */`,
2553
- ` ${d.id}: async (${paramSig}): Promise<NomosOutcome> => {`,
2984
+ ` /** Author \`${domain}/${d.id}\`: the generated client MINTS \`${mint.field}\` internally`,
2985
+ ` * — ids are not application input — ${hasBusinessResult ? `and returns its typed ${resultName}` : "and keeps framework write details private"}.${gate ? ` GATED: needs \`{ actor }\` (\`${gate.relation}\`).` : ""} */`,
2986
+ ` ${d.id}: async (${paramSig}): Promise<${mintedReturnType}> => {`,
2554
2987
  ...(gateGuard ? [gateGuard.replace(/\n$/, "")] : []),
2555
- ` const ${mint.field} = payload.${mint.field} ?? (await (holon as NomosRoutedHolon).mint(${JSON.stringify(mint.mintType)}${mintOpts}));`,
2988
+ ...principalPrelude,
2989
+ ...sourceTrustPrelude,
2990
+ ` const ${mint.field} = await (holon as NomosMintingHolon).mint(${JSON.stringify(mint.mintType)}${mintOpts});`,
2556
2991
  ...(ownedMintTarget
2557
2992
  ? [` const _ownedRecord = { type: ${JSON.stringify(ownedRuntimeType)}, id: String(${mint.field}) };`]
2558
2993
  : ownedRecordPrelude ? [ownedRecordPrelude] : []),
2559
2994
  ...(sealPrelude ? [sealPrelude] : []),
2560
- ` return _outcome(holon.workspace, holon.offerDirective(${JSON.stringify(domain)}, ${JSON.stringify(d.id)}, ${body}, { domainHash, actor, returnCreated: true${offerExtra} }) as Promise<string | { head: string; created?: { type: string; id: string }[] }>, { workspace: holon.workspace, type: ${JSON.stringify(dd.aggregateId ?? mint.mintType)}, id: ${mint.field} });`,
2995
+ ` return ${mintedCall};`,
2561
2996
  ` },`,
2562
2997
  );
2563
2998
  continue;
2564
2999
  }
2565
- const offerPayload = stamps.length ? `stampNow(${pv}, ${JSON.stringify(stamps)})` : pv;
3000
+ const offerPayload = wireReferences(stamps.length ? `stampNow(${pv}, ${JSON.stringify(stamps)})` : pv);
3001
+ const hasBusinessResult = allOutputs.length > 0;
3002
+ const returnType = hasBusinessResult ? resultName : "void";
3003
+ const resultSpec = JSON.stringify(allOutputs.map((output) => ({
3004
+ member: tsResultMemberName(output.aggregateId),
3005
+ type: output.aggregateId,
3006
+ ...(output.many ? { many: true } : {}),
3007
+ })));
3008
+ 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>)`;
2566
3011
  const outcomeDoc =
2567
3012
  d.marker === "creates"
2568
- ? `Author \`${domain}/${d.id}\`: the LAW mints the ${d.aggregateId ?? "aggregate"} id inside the sealed intent; it rides back on \`outcome.created\` — no id in the payload, no query-after.`
2569
- : `Author \`${domain}/${d.id}\` locally under the installed law; returns the typed [NomosOutcome] (\`head\` + any \`created\` refs).`;
3013
+ ? `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.`
3014
+ : `Author \`${domain}/${d.id}\` locally under the installed law and return every affected business reference. Framework write details remain private.`;
2570
3015
  const sealDoc = ownedSeal
2571
3016
  ? " Owned content is encrypted automatically by Nomos."
2572
- : wantSeal ? " Encrypted field(s) are SEALED via the bound { keyStore } before dispatch (pass { seal: false } to skip)." : "";
2573
- const gateDoc = gate ? ` GATED: needs \`{ actor }\` (\`${gate.relation}\`) — throws MissingOfferAuthority otherwise.` : "";
3017
+ : wantSeal ? " Encrypted fields are sealed automatically from framework-owned custody before dispatch." : "";
3018
+ const gateDoc = gate ? ` GATED: the signed-in person must hold \`${gate.relation}\`; an unauthenticated application refuses locally.` : "";
2574
3019
  methods.push(
2575
3020
  ` /** ${outcomeDoc}${sealDoc}${gateDoc} */`,
2576
- ` ${d.id}: async (${paramSig}): Promise<NomosOutcome> => {`,
3021
+ ` ${d.id}: async (${paramSig}): Promise<${returnType}> => {`,
2577
3022
  ...(gateGuard ? [gateGuard.replace(/\n$/, "")] : []),
3023
+ ...principalPrelude,
3024
+ ...sourceTrustPrelude,
2578
3025
  ...(ownedRecordPrelude ? [ownedRecordPrelude] : []),
2579
3026
  ...(sealPrelude ? [sealPrelude] : []),
2580
- ` return _outcome(holon.workspace, holon.offerDirective(${JSON.stringify(domain)}, ${JSON.stringify(d.id)}, ${offerPayload}, { domainHash, actor, returnCreated: true${offerExtra} }) as Promise<string | { head: string; created?: { type: string; id: string }[] }>);`,
3027
+ ` return ${commitCall};`,
2581
3028
  ` },`,
2582
3029
  );
2583
3030
  }
@@ -2585,7 +3032,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2585
3032
  for (const sp of (mod.spatials ?? []) as { id: string; of: string; on: string }[]) {
2586
3033
  methods.push(
2587
3034
  ` /** Declared spatial index \`${sp.id}\` — rows of \`${sp.of}\` whose \`${sp.on}\` geometry intersects the bbox (R*Tree probe). */`,
2588
- ` ${lcFirst(camel(sp.id))}: (bbox: { minX: number; minY: number; maxX: number; maxY: number } | { minLng: number; minLat: number; maxLng: number; maxLat: number }) => ${wrapP(`(holon.spatialWithin ?? (() => { throw new Error("this holon predates spatial reads — upgrade @githolon/client"); }))(${JSON.stringify(sp.id)}, bbox) as Promise<Row<${sp.of}Data>[]>`)},`,
3035
+ ` ${lcFirst(camel(sp.id))}: (bbox: { minX: number; minY: number; maxX: number; maxY: number } | { minLng: number; minLat: number; maxLng: number; maxLat: number }) => ${wrapP(`(holon.spatialWithin ?? (() => { throw new Error("this holon predates spatial reads — upgrade @githolon/client"); }))(${JSON.stringify(sp.id)}, bbox) as Promise<Row<${sp.of}>[]>`)},`,
2589
3036
  );
2590
3037
  }
2591
3038
  for (const q of (mod.queries ?? []) as QueryDecl[]) {
@@ -2594,32 +3041,49 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2594
3041
  .map((k) => {
2595
3042
  const lead = k.includes(".") ? k.split(".")[0]! : k;
2596
3043
  const f = agg?.fields[lead] as Field | undefined;
2597
- const t = f ? (f.kind === "int" ? "number" : "string") : "string";
3044
+ const t = f
3045
+ ? f.kind === "int" || f.kind === "counter"
3046
+ ? "number"
3047
+ : f.kind === "ref"
3048
+ ? tsBaseTypeOfField(f)
3049
+ : "string"
3050
+ : "string";
2598
3051
  return `${JSON.stringify(k)}: ${t}`;
2599
3052
  })
2600
3053
  .join("; ");
3054
+ const referenceKeys = q.key.filter((key) => {
3055
+ const lead = key.includes(".") ? key.split(".")[0]! : key;
3056
+ return (agg?.fields[lead] as Field | undefined)?.kind === "ref";
3057
+ });
3058
+ const referenceKeyTypes = Object.fromEntries(referenceKeys.map((key) => {
3059
+ const lead = key.includes(".") ? key.split(".")[0]! : key;
3060
+ return [key, (agg?.fields[lead] as Field | undefined)?.refAggregateId ?? ""];
3061
+ }));
3062
+ const wireParams = referenceKeys.length > 0
3063
+ ? `_nmsWireReferences(params, ${JSON.stringify(referenceKeyTypes)}, holon.workspace)`
3064
+ : "params";
2601
3065
  const qScopes = readScopesFor(q.returns);
2602
3066
  const qOwned = qScopes.some((scope) => scope.owned === true);
2603
3067
  const qScopeJson = JSON.stringify(qScopes);
2604
3068
  methods.push(
2605
- ` /** Declared query \`${q.id}\` (indexed probe — routed by the read manifest).${qScopes.length ? " Encrypted field(s) decrypt when the bound { keyStore } holds the scope key; else they stay opaque envelopes." : ""} */`,
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." : ""} */`,
2606
3070
  qScopes.length
2607
3071
  ? qOwned
2608
- ? ` ${lcFirst(camel(q.id))}: async (params: { ${params} }) => ${wrapP(`_readWithOwnedKeys(holon, keyStore, ${qScopeJson}, (keys) => holon.query(${JSON.stringify(q.id)}, params, keys) as Promise<Row<${q.returns}Data>[]>)`)},`
2609
- : ` ${lcFirst(camel(q.id))}: async (params: { ${params} }) => ${wrapP(`holon.query(${JSON.stringify(q.id)}, params, await _readKeys(holon, keyStore, ${qScopeJson})) as Promise<Row<${q.returns}Data>[]>`)},`
2610
- : ` ${lcFirst(camel(q.id))}: (params: { ${params} }) => ${wrapP(`holon.query(${JSON.stringify(q.id)}, params) as Promise<Row<${q.returns}Data>[]>`)},`,
3072
+ ? ` ${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}>[]>)`)},`
3073
+ : ` ${lcFirst(camel(q.id))}: async (params: { ${params} }) => ${wrapP(`holon.query(${JSON.stringify(q.id)}, ${wireParams}, await _readKeys(holon, keyStore, ${qScopeJson})) as Promise<Row<${q.returns}>[]>`)},`
3074
+ : ` ${lcFirst(camel(q.id))}: (params: { ${params} }) => ${wrapP(`holon.query(${JSON.stringify(q.id)}, ${wireParams}) as Promise<Row<${q.returns}>[]>`)},`,
2611
3075
  ` /** Reactive \`${q.id}\` — re-emits typed rows on every local change; returns stop(). */`,
2612
3076
  qOwned
2613
- ? ` watch${cap(camel(q.id))}: (params: { ${params} }, cb: (rows: Row<${q.returns}Data>[]) => void, opts?: { intervalMs?: number }) => _watchWithOwnedKeys((changed) => holon.watch(${JSON.stringify(q.id)}, params, changed as (rows: unknown) => void, opts), () => ${wrapP(`_readWithOwnedKeys(holon, keyStore, ${qScopeJson}, (keys) => holon.query(${JSON.stringify(q.id)}, params, keys) as Promise<Row<${q.returns}Data>[]>)`)}, cb),`
2614
- : ` watch${cap(camel(q.id))}: (params: { ${params} }, cb: (rows: Row<${q.returns}Data>[]) => void, opts?: { intervalMs?: number }) => holon.watch(${JSON.stringify(q.id)}, params, ((rows: Row<${q.returns}Data>[]) => cb(${wrapRows("rows")})) as (rows: unknown) => void, opts),`,
3077
+ ? ` watch${cap(camel(q.id))}: (params: { ${params} }, cb: (records: NomosRecord<${q.returns}>[]) => void, opts?: { intervalMs?: number }) => _watchWithOwnedKeys((changed) => holon.watch(${JSON.stringify(q.id)}, ${wireParams}, changed as (rows: unknown) => void, opts), () => ${wrapP(`_readWithOwnedKeys(holon, keyStore, ${qScopeJson}, (keys) => holon.query(${JSON.stringify(q.id)}, ${wireParams}, keys) as Promise<Row<${q.returns}>[]>)`)}, cb),`
3078
+ : ` watch${cap(camel(q.id))}: (params: { ${params} }, cb: (records: NomosRecord<${q.returns}>[]) => void, opts?: { intervalMs?: number }) => holon.watch(${JSON.stringify(q.id)}, ${wireParams}, ((rows: Row<${q.returns}>[]) => cb(${wrapRows("rows")})) as (rows: unknown) => void, opts),`,
2615
3079
  );
2616
3080
  pastMethods.push(
2617
3081
  ` /** Declared query \`${q.id}\` in the verified world at this moment. */`,
2618
3082
  qScopes.length
2619
3083
  ? qOwned
2620
- ? ` ${lcFirst(camel(q.id))}: async (params: { ${params} }) => ${wrapP(`_readWithOwnedKeys(holon, keyStore, ${qScopeJson}, (keys) => historic.query(${JSON.stringify(q.id)}, params, keys) as Promise<Row<${q.returns}Data>[]>)`)},`
2621
- : ` ${lcFirst(camel(q.id))}: async (params: { ${params} }) => ${wrapP(`historic.query(${JSON.stringify(q.id)}, params, await _readKeys(holon, keyStore, ${qScopeJson})) as Promise<Row<${q.returns}Data>[]>`)},`
2622
- : ` ${lcFirst(camel(q.id))}: (params: { ${params} }) => ${wrapP(`historic.query(${JSON.stringify(q.id)}, params) as Promise<Row<${q.returns}Data>[]>`)},`,
3084
+ ? ` ${lcFirst(camel(q.id))}: async (params: { ${params} }) => ${wrapP(`_readWithOwnedKeys(holon, keyStore, ${qScopeJson}, (keys) => historic.query(${JSON.stringify(q.id)}, ${wireParams}, keys) as Promise<Row<${q.returns}>[]>)`)},`
3085
+ : ` ${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
+ : ` ${lcFirst(camel(q.id))}: (params: { ${params} }) => ${wrapP(`historic.query(${JSON.stringify(q.id)}, ${wireParams}) as Promise<Row<${q.returns}>[]>`)},`,
2623
3087
  );
2624
3088
  // THE LOGICAL PAGED ROW SURFACE (sharding §5.3, ruling 2 — slice 4): emitted
2625
3089
  // ONLY for packed-taxonomy domains (taxonomy-free clients stay byte-identical).
@@ -2629,7 +3093,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2629
3093
  ` /** \`${q.id}\`, paged at estate width (ruling 2): ordinary pagination, stable cursor. */`,
2630
3094
  ` ${lcFirst(camel(q.id))}Pages: (params: { ${params} }, page?: { pageSize?: number; cursor?: string | null }) => {`,
2631
3095
  ` if (!holon.pages) throw new Error("pages() needs the ROUTED holon - connect() to the logical (coordinator) workspace, not a shard");`,
2632
- ` return holon.pages(${JSON.stringify(q.id)}, params, page) as Promise<{ rows: Row<${q.returns}Data>[]; cursor: string | null; done: boolean }>;`,
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")} }));`,
2633
3097
  ` },`,
2634
3098
  );
2635
3099
  }
@@ -2638,32 +3102,44 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2638
3102
  for (const c of (mod.counts ?? []).map((raw) => finishCount(raw))) {
2639
3103
  const grouped = c.by != null;
2640
3104
  const p = grouped ? tsGroupParam(c.by!) : "";
3105
+ const lead = grouped ? (c.by!.includes(".") ? c.by!.split(".")[0]! : c.by!) : "";
3106
+ const groupField = grouped ? aggByType.get(c.of)?.fields[lead] as Field | undefined : undefined;
3107
+ const groupType = groupField?.kind === "ref" ? tsBaseTypeOfField(groupField) : "string";
3108
+ const groupWire = groupField?.kind === "ref"
3109
+ ? `_nmsReferenceId(${p}, ${JSON.stringify(groupField.refAggregateId ?? "")}, holon.workspace, ${JSON.stringify(c.by)})`
3110
+ : p;
2641
3111
  methods.push(
2642
3112
  ` /** Declared count \`${c.id}\` — the maintained O(1) ${grouped ? `tally per \`${c.by}\`` : "grand total"}. */`,
2643
3113
  grouped
2644
- ? ` ${lcFirst(camel(c.id))}: (${p}: string) => holon.count(${JSON.stringify(c.id)}, ${p}),`
3114
+ ? ` ${lcFirst(camel(c.id))}: (${p}: ${groupType}) => holon.count(${JSON.stringify(c.id)}, ${groupWire}),`
2645
3115
  : ` ${lcFirst(camel(c.id))}: () => holon.count(${JSON.stringify(c.id)}),`,
2646
3116
  );
2647
3117
  pastMethods.push(
2648
3118
  ` /** Declared count \`${c.id}\` at this moment. */`,
2649
3119
  grouped
2650
- ? ` ${lcFirst(camel(c.id))}: (${p}: string) => historic.count(${JSON.stringify(c.id)}, ${p}),`
3120
+ ? ` ${lcFirst(camel(c.id))}: (${p}: ${groupType}) => historic.count(${JSON.stringify(c.id)}, ${groupWire}),`
2651
3121
  : ` ${lcFirst(camel(c.id))}: () => historic.count(${JSON.stringify(c.id)}),`,
2652
3122
  );
2653
3123
  }
2654
3124
  for (const s of (mod.sums ?? []).map((raw) => finishSum(raw))) {
2655
3125
  const grouped = s.by != null;
2656
3126
  const p = grouped ? tsGroupParam(s.by!) : "";
3127
+ const lead = grouped ? (s.by!.includes(".") ? s.by!.split(".")[0]! : s.by!) : "";
3128
+ const groupField = grouped ? aggByType.get(s.of)?.fields[lead] as Field | undefined : undefined;
3129
+ const groupType = groupField?.kind === "ref" ? tsBaseTypeOfField(groupField) : "string";
3130
+ const groupWire = groupField?.kind === "ref"
3131
+ ? `_nmsReferenceId(${p}, ${JSON.stringify(groupField.refAggregateId ?? "")}, holon.workspace, ${JSON.stringify(s.by)})`
3132
+ : p;
2657
3133
  methods.push(
2658
3134
  ` /** Declared sum \`${s.id}\` — the maintained O(1) ${grouped ? `total per \`${s.by}\`` : "grand total"}. */`,
2659
3135
  grouped
2660
- ? ` ${lcFirst(camel(s.id))}: (${p}: string) => holon.sum(${JSON.stringify(s.id)}, ${p}),`
3136
+ ? ` ${lcFirst(camel(s.id))}: (${p}: ${groupType}) => holon.sum(${JSON.stringify(s.id)}, ${groupWire}),`
2661
3137
  : ` ${lcFirst(camel(s.id))}: () => holon.sum(${JSON.stringify(s.id)}),`,
2662
3138
  );
2663
3139
  pastMethods.push(
2664
3140
  ` /** Declared sum \`${s.id}\` at this moment. */`,
2665
3141
  grouped
2666
- ? ` ${lcFirst(camel(s.id))}: (${p}: string) => historic.sum(${JSON.stringify(s.id)}, ${p}),`
3142
+ ? ` ${lcFirst(camel(s.id))}: (${p}: ${groupType}) => historic.sum(${JSON.stringify(s.id)}, ${groupWire}),`
2667
3143
  : ` ${lcFirst(camel(s.id))}: () => historic.sum(${JSON.stringify(s.id)}),`,
2668
3144
  );
2669
3145
  }
@@ -2694,50 +3170,83 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2694
3170
  ` conflicts: {`,
2695
3171
  ` summary: async (): Promise<ConflictSummary> => {`,
2696
3172
  ` if (!holon.pendingConflictSummary) throw new Error("conflicts need the current @githolon/client");`,
2697
- ` return holon.pendingConflictSummary();`,
3173
+ ` const summary = await holon.pendingConflictSummary();`,
3174
+ ` return { total: summary.total, ready: summary.ready, modelUpdatesRequired: summary.evolutionRequired, byAggregate: summary.byAggregate };`,
2698
3175
  ` },`,
2699
3176
  );
2700
3177
  for (const [aggregateId, bindings] of byAggregate) {
2701
3178
  const runtimeAggregateType = conflictAggregateType(aggregateId);
3179
+ const typedBindings = bindings.flatMap((binding) => {
3180
+ if (binding.valuePayloadField === undefined || binding.idPayloadField === undefined) return [];
3181
+ const payloadName = dirPayloadNames.get(binding.directiveId);
3182
+ return payloadName === undefined ? [] : [{ binding, payloadName }];
3183
+ });
3184
+ const pendingType = typedBindings.flatMap(({ binding: b, payloadName }) => {
3185
+ const valueType = `${payloadName}[${JSON.stringify(b.valuePayloadField!)}]`;
3186
+ const restType = `Omit<${payloadName}, ${[b.valuePayloadField!, b.idPayloadField!].map((field) => JSON.stringify(field)).join(" | ")}>`;
3187
+ const common = `record: NomosRef<${aggregateId}>; field: ${JSON.stringify(b.field)}`;
3188
+ return [
3189
+ `{ ${common}; status: "modelUpdateRequired"; reason: string }`,
3190
+ `{ ${common}; status: "ready"; versions: SurfacedObservation<${valueType}>[]; merge: (reconcile: (versions: readonly SurfacedObservation<${valueType}>[]) => ${valueType}, rest?: ${restType}) => Promise<void> }`,
3191
+ ];
3192
+ }).join(" | ") || "never";
2702
3193
  methods.push(
2703
3194
  ` ${lcFirst(camel(aggregateId))}: {`,
2704
3195
  ` /** Pending merges for ${aggregateId}. Querying creates typed merge references;`,
2705
3196
  ` * no aggregate id, field name, or directive id is supplied by the app. */`,
2706
- ` pending: async () => {`,
3197
+ ` pending: async (): Promise<Array<${pendingType}>> => {`,
2707
3198
  ` if (!holon.pendingConflicts) throw new Error("conflicts need the current @githolon/client");`,
2708
3199
  ` const rows = await holon.pendingConflicts(${JSON.stringify(runtimeAggregateType)});`,
2709
- ` return rows.flatMap((conflict) => {`,
3200
+ ` return rows.flatMap((conflict): Array<${pendingType}> => {`,
2710
3201
  ` if (conflict.aggregateType !== ${JSON.stringify(runtimeAggregateType)}) return [];`,
2711
3202
  ` if (typeof conflict.snapshot !== "string" || conflict.snapshot.length === 0 || typeof conflict.targetSchema !== "string" || conflict.targetSchema.length === 0) {`,
2712
3203
  ` throw new Error("conflicts need a runtime with immutable conflict snapshots; upgrade Nomos before merging");`,
2713
3204
  ` }`,
3205
+ ` const record: NomosRef<${aggregateId}> = _nmsRef(holon.workspace, conflict.aggregateType, conflict.aggregateId);`,
2714
3206
  ` switch (conflict.field) {`,
2715
3207
  );
2716
- for (const b of bindings) {
2717
- if (b.valuePayloadField === undefined || b.idPayloadField === undefined) continue;
2718
- const payloadName = dirPayloadNames.get(b.directiveId);
2719
- if (payloadName === undefined) continue;
2720
- const valueType = `${payloadName}[${JSON.stringify(b.valuePayloadField)}]`;
2721
- const bound = [b.valuePayloadField, b.idPayloadField];
3208
+ for (const { binding: b, payloadName } of typedBindings) {
3209
+ const valuePayloadField = b.valuePayloadField!;
3210
+ const idPayloadField = b.idPayloadField!;
3211
+ const valueType = `${payloadName}[${JSON.stringify(valuePayloadField)}]`;
3212
+ const bound = [valuePayloadField, idPayloadField];
2722
3213
  const restType = `Omit<${payloadName}, ${bound.map((f) => JSON.stringify(f)).join(" | ")}>`;
2723
- const payloadExpr = `{ ...(rest ?? {}), ${JSON.stringify(b.idPayloadField)}: conflict.aggregateId, ${JSON.stringify(b.valuePayloadField)}: merged } as ${payloadName}`;
3214
+ const referenceMap = JSON.stringify({ [idPayloadField]: aggregateId });
3215
+ const resolverDirective = dirById.get(b.directiveId);
3216
+ if (resolverDirective === undefined) {
3217
+ throw new Error(`generated conflict policy refers to unknown directive '${b.directiveId}' in domain '${domain}'`);
3218
+ }
3219
+ const resolverSchema = (resolverDirective as unknown as { payloadSchema: z.ZodTypeAny }).payloadSchema;
3220
+ const conflictTimeDefaults = zodKind(resolverSchema) === "object"
3221
+ ? Object.entries(zodObjectShape(resolverSchema)).flatMap(([name, field]) => {
3222
+ if (bound.includes(name) || !name.endsWith("At")) return [];
3223
+ const kind = zodKind(field as z.ZodTypeAny);
3224
+ return kind === "string"
3225
+ ? [`${JSON.stringify(name)}: rest?.[${JSON.stringify(name)}] ?? new Date().toISOString()`]
3226
+ : kind === "number"
3227
+ ? [`${JSON.stringify(name)}: rest?.[${JSON.stringify(name)}] ?? Date.now()`]
3228
+ : [];
3229
+ })
3230
+ : [];
3231
+ const resolutionLiteral = `{ ...(rest ?? {}), ${conflictTimeDefaults.length > 0 ? `${conflictTimeDefaults.join(", ")}, ` : ""}${JSON.stringify(idPayloadField)}: record, ${JSON.stringify(valuePayloadField)}: merged }`;
2724
3232
  methods.push(
2725
3233
  ` case ${JSON.stringify(b.field)}: {`,
2726
3234
  ` if (conflict.status === "evolutionRequired") {`,
2727
- ` return [{ aggregateId: conflict.aggregateId, field: ${JSON.stringify(b.field)} as const, status: "evolutionRequired" as const, evolution: conflict.evolution! }];`,
3235
+ ` return [{ record, field: ${JSON.stringify(b.field)} as const, status: "modelUpdateRequired" as const, reason: conflict.evolution?.reason ?? "The business model cannot yet compare every retained version. Update its evolution policy; unrelated work can continue." }];`,
2728
3236
  ` }`,
2729
3237
  ` if (!Array.isArray(conflict.observations)) throw new Error("Nomos returned a ready conflict without projected versions");`,
2730
3238
  ` const versions = conflict.observations as SurfacedObservation<${valueType}>[];`,
2731
3239
  ` return [{`,
2732
- ` aggregateId: conflict.aggregateId,`,
3240
+ ` record,`,
2733
3241
  ` field: ${JSON.stringify(b.field)} as const,`,
2734
3242
  ` status: "ready" as const,`,
2735
3243
  ` versions,`,
2736
3244
  ` /** Produce and commit a merge result from the competing versions. The result`,
2737
3245
  ` * may equal either side or be a new business value, exactly like a Git merge. */`,
2738
- ` merge: (reconcile: (versions: readonly SurfacedObservation<${valueType}>[]) => ${valueType}, rest?: ${restType}): Promise<NomosOutcome> => {`,
3246
+ ` merge: (reconcile: (versions: readonly SurfacedObservation<${valueType}>[]) => ${valueType}, rest?: ${restType}): Promise<void> => {`,
2739
3247
  ` const merged = reconcile(versions);`,
2740
- ` return _outcome(holon.workspace, (holon.offerDirective as __NomosConflictOffer)(${JSON.stringify(domain)}, ${JSON.stringify(b.directiveId)}, ${payloadExpr}, { domainHash, actor, returnCreated: true, __nomosConflictSnapshots: [{ aggregateType: conflict.aggregateType, aggregateId: conflict.aggregateId, field: conflict.field, targetSchema: conflict.targetSchema, token: conflict.snapshot }] }) as Promise<string | { head: string; created?: { type: string; id: string }[] }>);`,
3248
+ ` const resolution = ${resolutionLiteral} satisfies ${payloadName};`,
3249
+ ` return _commit((holon.offerDirective as __NomosConflictOffer)(${JSON.stringify(domain)}, ${JSON.stringify(b.directiveId)}, _nmsWireReferences(resolution, ${referenceMap}, holon.workspace), { domainHash, actor, returnCreated: true, __nomosConflictSnapshots: [{ aggregateType: conflict.aggregateType, aggregateId: conflict.aggregateId, field: conflict.field, targetSchema: conflict.targetSchema, token: conflict.snapshot }] }) as Promise<_NomosRawWriteResult>);`,
2741
3250
  ` },`,
2742
3251
  ` }];`,
2743
3252
  ` }`,
@@ -2758,33 +3267,52 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2758
3267
  const aOwned = aScopes.some((scope) => scope.owned === true);
2759
3268
  const aScopeJson = JSON.stringify(aScopes);
2760
3269
  const runtimeType = runtimeTypeOf(agg);
3270
+ const collection = generatedAggregateCollectionName(agg.id);
3271
+ const findRows = aScopes.length
3272
+ ? `holon.queryById(id, await _readKeys(holon, keyStore, ${aScopeJson}${aOwned ? `, [{ type: ${JSON.stringify(runtimeType)}, id }]` : ""})) as Promise<Row<${agg.id}>[]>`
3273
+ : `holon.queryById(id) as Promise<Row<${agg.id}>[]>`;
3274
+ const allRows = aScopes.length
3275
+ ? aOwned
3276
+ ? `_readWithOwnedKeys(holon, keyStore, ${aScopeJson}, (keys) => holon.query("aggregatesByType", { __type: ${JSON.stringify(runtimeType)} }, keys) as Promise<Row<${agg.id}>[]>)`
3277
+ : `holon.query("aggregatesByType", { __type: ${JSON.stringify(runtimeType)} }, await _readKeys(holon, keyStore, ${aScopeJson})) as Promise<Row<${agg.id}>[]>`
3278
+ : `holon.query("aggregatesByType", { __type: ${JSON.stringify(runtimeType)} }) as Promise<Row<${agg.id}>[]>`;
2761
3279
  methods.push(
2762
- ` /** Read one ${agg.id} by aggregate id.${aScopes.length ? " Encrypted field(s) decrypt when the bound { keyStore } holds the scope key." : ""} */`,
2763
- aScopes.length
2764
- ? ` ${lcFirst(camel(agg.id))}ById: async (id: string) => ${wrapP(`holon.queryById(id, await _readKeys(holon, keyStore, ${aScopeJson}${aOwned ? `, [{ type: ${JSON.stringify(runtimeType)}, id }]` : ""})) as Promise<Row<${agg.id}Data>[]>`)},`
2765
- : ` ${lcFirst(camel(agg.id))}ById: (id: string) => ${wrapP(`holon.queryById(id) as Promise<Row<${agg.id}Data>[]>`)},`,
2766
- ` /** Every ${agg.id} (the type-index probe — O(result), not a scan). */`,
2767
- aScopes.length
2768
- ? aOwned
2769
- ? ` list${agg.id}s: async () => ${wrapP(`_readWithOwnedKeys(holon, keyStore, ${aScopeJson}, (keys) => holon.query("aggregatesByType", { __type: ${JSON.stringify(agg.id)} }, keys) as Promise<Row<${agg.id}Data>[]>)`)},`
2770
- : ` list${agg.id}s: async () => ${wrapP(`holon.query("aggregatesByType", { __type: ${JSON.stringify(agg.id)} }, await _readKeys(holon, keyStore, ${aScopeJson})) as Promise<Row<${agg.id}Data>[]>`)},`
2771
- : ` list${agg.id}s: () => ${wrapP(`holon.query("aggregatesByType", { __type: ${JSON.stringify(agg.id)} }) as Promise<Row<${agg.id}Data>[]>`)},`,
2772
- ` /** Watch one ${agg.id} by aggregate id (local reactive read; returns stop()). */`,
2773
- aOwned
2774
- ? ` watch${agg.id}ById: (id: string, cb: (rows: Row<${agg.id}Data>[]) => void, opts?: { intervalMs?: number }) => _watchWithOwnedKeys((changed) => holon.watchById(id, changed as (rows: unknown) => void, opts), async () => ${wrapP(`holon.queryById(id, await _readKeys(holon, keyStore, ${aScopeJson}, [{ type: ${JSON.stringify(runtimeType)}, id }])) as Promise<Row<${agg.id}Data>[]>`)}, cb),`
2775
- : ` watch${agg.id}ById: (id: string, cb: (rows: Row<${agg.id}Data>[]) => void, opts?: { intervalMs?: number }) => holon.watchById(id, ((rows: Row<${agg.id}Data>[]) => cb(${wrapRows("rows")})) as (rows: unknown) => void, opts),`,
3280
+ ` /** ${agg.id} identity and reads. Business actions remain application-level because one action may change many aggregate kinds. */`,
3281
+ ` ${collection}: {`,
3282
+ ` /** Recover a typed reference from its stable public address. This identifies a record; it never grants authority. */`,
3283
+ ` fromAddress: (address: string): NomosRef<${agg.id}> => { const record = parseNomosRef<${agg.id}>(address); _nmsReferenceId(record, ${JSON.stringify(agg.id)}, "", "address"); return record; },`,
3284
+ ` /** Read one record. Nomos follows the reference's workspace privately when needed. */`,
3285
+ ` find: async (record: NomosRef<${agg.id}>): Promise<NomosRecord<${agg.id}> | undefined> => {`,
3286
+ ` const ref = _nmsReferenceData(record); _nmsReferenceId(record, ${JSON.stringify(agg.id)}, "", "record");`,
3287
+ ` if (ref.workspace !== holon.workspace) {`,
3288
+ ` if (!holon.__nomosOpenChild) throw new Error("opening this record needs the current @githolon/client");`,
3289
+ ` return (await ${tsClientFactoryName(domain)}(await holon.__nomosOpenChild(ref.workspace)).${collection}.find(record));`,
3290
+ ` }`,
3291
+ ` const id = ref.id; const records = await ${wrapP(findRows)}; return records[0];`,
3292
+ ` },`,
3293
+ ` /** Every ${agg.id} visible to the signed-in application. */`,
3294
+ ` all: async (): Promise<NomosRecord<${agg.id}>[]> => ${wrapP(allRows)},`,
3295
+ ` /** Watch one record; returns stop(). */`,
3296
+ ` watchOne: (record: NomosRef<${agg.id}>, cb: (record: NomosRecord<${agg.id}> | undefined) => void, opts?: { intervalMs?: number }): (() => void) => {`,
3297
+ ` const ref = _nmsReferenceData(record); _nmsReferenceId(record, ${JSON.stringify(agg.id)}, "", "record");`,
3298
+ ` if (ref.workspace !== holon.workspace) {`,
3299
+ ` let active = true; let stop = () => {};`,
3300
+ ` if (!holon.__nomosOpenChild) throw new Error("opening this record needs the current @githolon/client");`,
3301
+ ` void holon.__nomosOpenChild(ref.workspace).then((child) => { if (active) stop = ${tsClientFactoryName(domain)}(child).${collection}.watchOne(record, cb, opts); }).catch(() => { if (active) cb(undefined); });`,
3302
+ ` return () => { active = false; stop(); };`,
3303
+ ` }`,
3304
+ ` const id = ref.id;`,
3305
+ ...(aOwned
3306
+ ? [` return _watchWithOwnedKeys((changed) => holon.watchById(id, changed as (rows: unknown) => void, opts), async () => ${wrapP(`holon.queryById(id, await _readKeys(holon, keyStore, ${aScopeJson}, [{ type: ${JSON.stringify(runtimeType)}, id }])) as Promise<Row<${agg.id}>[]>`)}, (records) => cb(records[0]));`]
3307
+ : [` return holon.watchById(id, ((rows: Row<${agg.id}>[]) => { const records = ${wrapRows("rows")}; cb(records[0]); }) as (rows: unknown) => void, opts);`]),
3308
+ ` },`,
3309
+ ` },`,
2776
3310
  );
2777
3311
  pastMethods.push(
2778
- ` /** Read one ${agg.id} by aggregate id at this moment. */`,
2779
- aScopes.length
2780
- ? ` ${lcFirst(camel(agg.id))}ById: async (id: string) => ${wrapP(`historic.queryById(id, await _readKeys(holon, keyStore, ${aScopeJson}${aOwned ? `, [{ type: ${JSON.stringify(runtimeType)}, id }]` : ""})) as Promise<Row<${agg.id}Data>[]>`)},`
2781
- : ` ${lcFirst(camel(agg.id))}ById: (id: string) => ${wrapP(`historic.queryById(id) as Promise<Row<${agg.id}Data>[]>`)},`,
2782
- ` /** Every ${agg.id} at this moment. */`,
2783
- aScopes.length
2784
- ? aOwned
2785
- ? ` list${agg.id}s: async () => ${wrapP(`_readWithOwnedKeys(holon, keyStore, ${aScopeJson}, (keys) => historic.query("aggregatesByType", { __type: ${JSON.stringify(agg.id)} }, keys) as Promise<Row<${agg.id}Data>[]>)`)},`
2786
- : ` list${agg.id}s: async () => ${wrapP(`historic.query("aggregatesByType", { __type: ${JSON.stringify(agg.id)} }, await _readKeys(holon, keyStore, ${aScopeJson})) as Promise<Row<${agg.id}Data>[]>`)},`
2787
- : ` list${agg.id}s: () => ${wrapP(`historic.query("aggregatesByType", { __type: ${JSON.stringify(agg.id)} }) as Promise<Row<${agg.id}Data>[]>`)},`,
3312
+ ` ${collection}: {`,
3313
+ ` find: async (record: NomosRef<${agg.id}>): Promise<NomosRecord<${agg.id}> | undefined> => { const id = _nmsReferenceId(record, ${JSON.stringify(agg.id)}, holon.workspace, "record"); const records = await ${wrapP(aScopes.length ? `historic.queryById(id, await _readKeys(holon, keyStore, ${aScopeJson}${aOwned ? `, [{ type: ${JSON.stringify(runtimeType)}, id }]` : ""})) as Promise<Row<${agg.id}>[]>` : `historic.queryById(id) as Promise<Row<${agg.id}>[]>`)}; return records[0]; },`,
3314
+ ` all: async (): Promise<NomosRecord<${agg.id}>[]> => ${wrapP(aScopes.length ? (aOwned ? `_readWithOwnedKeys(holon, keyStore, ${aScopeJson}, (keys) => historic.query("aggregatesByType", { __type: ${JSON.stringify(runtimeType)} }, keys) as Promise<Row<${agg.id}>[]>)` : `historic.query("aggregatesByType", { __type: ${JSON.stringify(runtimeType)} }, await _readKeys(holon, keyStore, ${aScopeJson})) as Promise<Row<${agg.id}>[]>`) : `historic.query("aggregatesByType", { __type: ${JSON.stringify(runtimeType)} }) as Promise<Row<${agg.id}>[]>`)},`,
3315
+ ` },`,
2788
3316
  );
2789
3317
  }
2790
3318
 
@@ -2793,13 +3321,13 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2793
3321
  ` past: {`,
2794
3322
  ` moments: async (opts: { limit?: number } = {}): Promise<NomosMoment[]> => {`,
2795
3323
  ` if (!holon.commits) throw new Error("past.moments() needs the current @githolon/client");`,
2796
- ` const actions = new Set(${JSON.stringify([...dirs.map((directive) => directive.id), ...(correctionActions.length > 0 ? [BUSINESS_CORRECTION_DIRECTIVE] : [])])});`,
3324
+ ` const actions = new Set<string>(${JSON.stringify([...dirs.map((directive) => directive.id), ...(correctionActions.length > 0 ? [BUSINESS_CORRECTION_DIRECTIVE] : [])])});`,
2797
3325
  ` return (await holon.commits(opts))`,
2798
3326
  ` .filter((commit) => commit.domain === ${JSON.stringify(domain)} && commit.directiveId !== null && actions.has(commit.directiveId))`,
2799
3327
  ` .map((commit) => ({`,
2800
3328
  ` id: commit.oid,`,
2801
3329
  ` occurredAt: commit.at == null ? null : new Date(commit.at).toISOString(),`,
2802
- ` actorSubject: commit.actorSubject,`,
3330
+ ` performedBy: commit.actorSubject,`,
2803
3331
  ` domain: commit.domain,`,
2804
3332
  ` action: commit.directiveId === ${JSON.stringify(BUSINESS_CORRECTION_DIRECTIVE)} ? "correction" : commit.directiveId,`,
2805
3333
  ` }));`,
@@ -2827,27 +3355,31 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2827
3355
  ` /** THE SELF-DESCRIPTION: root links to exactly six grounded pages — model, current state, rules,`,
2828
3356
  ` * history, decisions and evidence. The old \`events\` / \`transcript\` fields remain, so this is a`,
2829
3357
  ` * strict enrichment of the activity narrative. \`section\` selects one page. */`,
2830
- ` describe: async (opts: { section?: NomosDescribeSection; since?: string; actor?: string; category?: string; limit?: number } = {}): Promise<NomosDescription> => {`,
3358
+ ` describe: async (opts: { section?: NomosDescribeSection; since?: string; performedBy?: string; category?: string; limit?: number } = {}): Promise<NomosDescription> => {`,
2831
3359
  ` const raw = await holon.activityLog(opts.limit !== undefined ? { limit: opts.limit } : {});`,
2832
3360
  ` let events = raw.map(renderActivity).filter((e): e is ActivityEvent => e !== null);`,
2833
- ` if (opts.actor !== undefined) events = events.filter((e) => e.actorSubject === opts.actor);`,
3361
+ ` if (opts.performedBy !== undefined) events = events.filter((e) => e.performedBy === opts.performedBy);`,
2834
3362
  ` if (opts.category !== undefined) events = events.filter((e) => e.category === opts.category);`,
2835
3363
  ` if (opts.since !== undefined) events = events.filter((e) => e.occurredAt >= opts.since!);`,
2836
3364
  ` let organisation: NomosOrganisationDescription = { complete: false, children: [] };`,
2837
3365
  ` if (holon.__nomosChildWorkspaces !== undefined) {`,
2838
3366
  ` try {`,
2839
3367
  ` const children = await holon.__nomosChildWorkspaces();`,
2840
- ` organisation = { complete: children.every((child) => child.status !== "unknown"), children: children.map((child) => ({`,
2841
- ` ...child, section: "root" as const, label: child.workspace,`,
2842
- ` question: "What does this part of the organisation do, and what has happened there?",`,
3368
+ ` organisation = { complete: children.every((child) => child.status !== "unknown"), children: children.map((child) => ({`,
3369
+ ` status: child.status, bornAt: child.bornAt, section: "root" as const, label: "Part of the organisation",`,
3370
+ ` question: "What does this part of the organisation do, and what has happened there?",`,
2843
3371
  ` summary: child.status === "active"`,
2844
3372
  ` ? "This part of the organisation is active. Follow its generated description to continue."`,
2845
3373
  ` : child.status === "retired"`,
2846
3374
  ` ? "This part of the organisation is retired. Its recorded history remains explainable."`,
2847
3375
  ` : child.status === "reclaimed"`,
2848
3376
  ` ? "This part of the organisation was reclaimed. Its birth remains part of this workspace's history."`,
2849
- ` : "This part of the organisation has a newer lifecycle this client does not understand. Its link remains visible.",`,
2850
- ` })) };`,
3377
+ ` : "This part of the organisation has a newer lifecycle this client does not understand. Its link remains visible.",`,
3378
+ ` open: async (): Promise<NomosDescription> => {`,
3379
+ ` if (!holon.__nomosOpenChild) throw new Error("opening an organisation link needs the current @githolon/client");`,
3380
+ ` return ${tsClientFactoryName(domain)}(await holon.__nomosOpenChild(child.workspace)).describe();`,
3381
+ ` },`,
3382
+ ` })) };`,
2851
3383
  ` } catch {`,
2852
3384
  ` // An old/unavailable runtime cannot prove a leaf. Keep incompleteness explicit.`,
2853
3385
  ` }`,
@@ -2863,10 +3395,10 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2863
3395
  ` } else if (section === "current") {`,
2864
3396
  ` title = "Current state";`,
2865
3397
  ` summary = "The business records this session is allowed to read now. An unavailable type stays named without leaking its contents.";`,
2866
- ` items = await Promise.all(_NOMOS_DESCRIBE_MODEL.aggregateTypes.map(async (aggregate): Promise<NomosDescribeItem> => {`,
3398
+ ` items = await Promise.all((_NOMOS_DESCRIBE_MODEL.aggregateTypes as readonly { queryType: string; audience: string; domain: string; type: string; label: string }[]).map(async (aggregate): Promise<NomosDescribeItem> => {`,
2867
3399
  ` try {`,
2868
3400
  ` const rows = await holon.query("aggregatesByType", { __type: aggregate.queryType }) as { type: string; id: string }[];`,
2869
- ` const refs = rows.map((row) => ({ workspace: holon.workspace, type: row.type, id: row.id }));`,
3401
+ ` const refs = rows.map((row) => _nmsRef<_NomosAnyAggregate>(holon.workspace, row.type, row.id));`,
2870
3402
  ` const exact = rows.length > 0 || (aggregate.audience as string) === "public";`,
2871
3403
  ` return { kind: "current", id: \`${"${aggregate.domain}.${aggregate.type}"}\`, label: aggregate.label,`,
2872
3404
  ` summary: exact ? \`${"${rows.length} ${aggregate.label} record${rows.length === 1 ? \"\" : \"s\"} visible now."}\` : \`${"No ${aggregate.label} records are visible to this session."}\`,`,
@@ -2880,17 +3412,17 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2880
3412
  ` } else if (section === "history") {`,
2881
3413
  ` title = "Verified business history"; summary = "What changed, who changed it and when, read from verified committed testimony.";`,
2882
3414
  ` items = events.map((event) => ({ kind: "change", id: event.eventId, label: event.title, summary: event.message,`,
2883
- ` details: { actor: event.actorSubject, occurredAt: event.occurredAt, domain: event.domain, kind: event.kind },`,
3415
+ ` details: { performedBy: event.performedBy, occurredAt: event.occurredAt, domain: event.domain, kind: event.kind },`,
2884
3416
  ` refs: Object.values(event.refs) }));`,
2885
3417
  ` } else if (section === "decisions") {`,
2886
3418
  ` title = "Decisions and work needing attention"; summary = "Recorded business reasons, pending merges and retained rejected work, plus the model's declared conflict policy.";`,
2887
3419
  ` const decisions: NomosDescribeItem[] = events.filter((event) => event.kind.endsWith(".correction"))`,
2888
3420
  ` .map((event) => ({ kind: "decision", id: event.eventId, label: event.title, summary: event.message,`,
2889
- ` details: { actor: event.actorSubject, occurredAt: event.occurredAt, domain: event.domain }, refs: Object.values(event.refs) }));`,
3421
+ ` details: { performedBy: event.performedBy, occurredAt: event.occurredAt, domain: event.domain }, refs: Object.values(event.refs) }));`,
2890
3422
  ...(hasSurfaced
2891
3423
  ? [
2892
3424
  ` const pending: NomosDescribeItem[] = (holon.pendingConflicts ? await holon.pendingConflicts() : []).flatMap((conflict) => {`,
2893
- ` const aggregate = _NOMOS_DESCRIBE_MODEL.aggregateTypes.find((candidate) => candidate.queryType === conflict.aggregateType);`,
3425
+ ` const aggregate = (_NOMOS_DESCRIBE_MODEL.aggregateTypes as readonly { queryType: string; audience: string; domain: string; type: string; label: string }[]).find((candidate) => candidate.queryType === conflict.aggregateType);`,
2894
3426
  ` if (aggregate === undefined) return [];`,
2895
3427
  ` const rule = (_NOMOS_DESCRIBE_MODEL.pages.rules.items as readonly NomosDescribeItem[])`,
2896
3428
  ` .find((item) => item.id === \`${"${aggregate.domain}.${aggregate.type}.${conflict.field}.merge"}\`);`,
@@ -2902,7 +3434,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2902
3434
  ` return [{ kind: conflict.status === "ready" ? "pending-merge" : "evolution-required",`,
2903
3435
  ` id: \`${"decision:${aggregate.domain}.${aggregate.type}:${conflict.aggregateId}:${conflict.field}"}\`, label: "Review " + subject, summary,`,
2904
3436
  ` details: { domain: aggregate.domain, type: aggregate.type, field: conflict.field, status: conflict.status, versions, next: conflict.status === "ready" ? "merge" : "update-model" },`,
2905
- ` refs: [{ workspace: holon.workspace, type: aggregate.type, id: conflict.aggregateId }] }];`,
3437
+ ` refs: [_nmsRef<_NomosAnyAggregate>(holon.workspace, aggregate.type, conflict.aggregateId)] }];`,
2906
3438
  ` });`,
2907
3439
  ]
2908
3440
  : []),
@@ -2923,35 +3455,34 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2923
3455
  ` title = "Evidence"; summary = "Opaque identities tying this answer to one workspace, one generated law and its verified business history.";`,
2924
3456
  ` const latestMoment = events[0];`,
2925
3457
  ` items = [`,
2926
- ` { kind: "evidence", id: "workspace", label: "Workspace", summary: holon.workspace, details: { workspace: holon.workspace } },`,
2927
- ` { kind: "evidence", id: "law", label: "Generated law", summary: domainHash, details: { domainHash } },`,
2928
- ` { kind: "evidence", id: "reader", label: "Verified reader", summary: actor ?? "anonymous", details: { actor: actor ?? null } },`,
3458
+ ` { kind: "evidence", id: "application", label: "Application", summary: "This answer is bound to one verified local application.", details: { verified: true } },`,
3459
+ ` { kind: "evidence", id: "model", label: "Business model", summary: "The installed model was checked before this application opened.", details: { compatible: true } },`,
3460
+ ` { kind: "evidence", id: "reader", label: "Verified reader", summary: holon.principal ?? "anonymous", details: { reader: holon.principal ?? null } },`,
2929
3461
  ` ...(latestMoment === undefined ? [] : [{ kind: "evidence", id: latestMoment.eventId, label: "Latest verified moment",`,
2930
- ` summary: latestMoment.eventId, details: { occurredAt: latestMoment.occurredAt, actor: latestMoment.actorSubject, domain: latestMoment.domain, kind: latestMoment.kind } }]),`,
3462
+ ` summary: latestMoment.eventId, details: { occurredAt: latestMoment.occurredAt, performedBy: latestMoment.performedBy, domain: latestMoment.domain, kind: latestMoment.kind } }]),`,
2931
3463
  ` ];`,
2932
3464
  ` }`,
2933
3465
  ` return { section, title, summary, items, links: _NOMOS_DESCRIBE_MODEL.links.map((link) => ({ ...link })), organisation, events, transcript: _activityTranscript(events) };`,
2934
3466
  ` },`,
2935
- ` /** NARRATIVE for ONE thing: the story of the aggregate \`aggregateId\` — events whose declared refs`,
3467
+ ` /** NARRATIVE for ONE thing: the story of a generated record reference — events whose declared refs`,
2936
3468
  ` * point at it (only directives declaring a ref to the aggregate appear in its story) + a transcript. */`,
2937
- ` describeHistory: async (aggregateId: string, opts: { limit?: number } = {}): Promise<ActivityNarrative> => {`,
3469
+ ` describeHistory: async <TAggregate>(record: NomosRef<TAggregate>, opts: { limit?: number } = {}): Promise<ActivityNarrative> => {`,
3470
+ ` const aggregateId = _nmsReferenceId(record, "", holon.workspace, "record");`,
2938
3471
  ` const raw = await holon.activityLog(opts.limit !== undefined ? { limit: opts.limit } : {});`,
2939
3472
  ` const events = raw.map(renderActivity).filter((e): e is ActivityEvent => e !== null)`,
2940
- ` .filter((e) => Object.values(e.refs).some((r) => r.id === aggregateId));`,
3473
+ ` .filter((e) => Object.values(e.refs).some((r) => _nmsReferenceData(r).id === aggregateId));`,
2941
3474
  ` return { events, transcript: _activityTranscript(events) };`,
2942
3475
  ` },`,
2943
3476
  );
2944
3477
  }
2945
3478
 
2946
3479
  out.push(
2947
- `/** Typed client for the \`${domain}\` domain, bound to a connected holon.`,
2948
- ` * Pass \`{ actor }\` (the verified principal uid) to author AS that user — every write seals it as`,
2949
- ` * the captured principal, so the ReBAC \`.requires(...)\` gate + the integrity-dual endorse model`,
2950
- ` * judge the real author. Omit it for open (un-gated) domains. */`,
2951
- `export function ${tsClientFactoryName(domain)}(holon: NomosHolon, opts: { domainHash?: string; actor?: string${domScopes.length > 0 ? "; keyStore?: NomosKeyStore" : ""} } = {}) {`,
2952
- ` const domainHash = opts.domainHash ?? ${hashConst};`,
2953
- ` const actor = opts.actor ?? holon.principal;`,
2954
- ...(encPresent ? [` const keyStore: NomosKeyStore | undefined = ${domScopes.length > 0 ? "opts.keyStore" : "undefined"};`] : []),
3480
+ `/** Framework binding for the generated application. Law identity, author identity and key custody`,
3481
+ ` * are captured from the connected stateful client; callers cannot override them. */`,
3482
+ `function ${tsClientFactoryName(domain)}(holon: NomosHolon) {`,
3483
+ ` const domainHash = ${hashConst};`,
3484
+ ` const actor = holon.principal;`,
3485
+ ...(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"};`] : []),
2955
3486
  ...(ownedScope !== undefined && Object.keys(ownedActions).length > 0
2956
3487
  ? [
2957
3488
  ` const _ownedActions: Record<string, Record<"share" | "unshare" | "transfer", string>> = ${JSON.stringify(ownedActions)};`,
@@ -2959,16 +3490,17 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2959
3490
  ` const _ownedReady = holon.__nomosEnsureOwnedIdentity`,
2960
3491
  ` ? holon.__nomosEnsureOwnedIdentity({ scope: ${JSON.stringify(ownedScope)}, domain: ${JSON.stringify(domain)}, domainHash }).catch((error) => { _ownedReadyError = error; })`,
2961
3492
  ` : Promise.resolve().then(() => { _ownedReadyError = new Error("owned records need the current @githolon/client"); });`,
2962
- ` const _ownedRecordAction = async (action: "share" | "unshare" | "transfer", record: NomosRef, peer: string): Promise<NomosOutcome> => {`,
3493
+ ` const _ownedRecordAction = async (action: "share" | "unshare" | "transfer", record: NomosRef<_NomosAnyAggregate>, peer: string): Promise<void> => {`,
2963
3494
  ` await _ownedReady;`,
2964
3495
  ` if (_ownedReadyError !== undefined) throw _ownedReadyError;`,
2965
- ` if (record.workspace !== holon.workspace) throw new Error(\`${"${action}()"} cannot use a record from another workspace\`);`,
2966
- ` const directiveId = _ownedActions[record.type]?.[action];`,
3496
+ ` const recordData = _nmsReferenceData(record);`,
3497
+ ` if (recordData.workspace !== holon.workspace) throw new Error(\`${"${action}()"} cannot use a record from another workspace\`);`,
3498
+ ` const directiveId = _ownedActions[recordData.type]?.[action];`,
2967
3499
  ` if (directiveId === undefined) throw new Error(\`${"${action}()"} needs a generated owned record reference\`);`,
2968
3500
  ` if (!peer.trim()) throw new Error(\`${"${action}()"} needs the person to act with\`);`,
2969
3501
  ` if (!holon.__nomosPrepareOwnedRecordAction) throw new Error("record sharing needs the current @githolon/client");`,
2970
3502
  ` const payload = await holon.__nomosPrepareOwnedRecordAction({ action, scope: ${JSON.stringify(ownedScope)}, record, peer });`,
2971
- ` return _outcome(holon.workspace, holon.offerDirective(${JSON.stringify(domain)}, directiveId, payload, { domainHash, actor, returnCreated: true }) as Promise<string | { head: string; created?: { type: string; id: string }[] }>, record);`,
3503
+ ` return _commit(holon.offerDirective(${JSON.stringify(domain)}, directiveId, payload, { domainHash, actor, returnCreated: true }) as Promise<_NomosRawWriteResult>);`,
2972
3504
  ` };`,
2973
3505
  ]
2974
3506
  : []),
@@ -3010,21 +3542,20 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
3010
3542
  ` if (!holon.__nomosOwnedDeviceRequest) throw new Error("devices.request() needs the current @githolon/client");`,
3011
3543
  ` return await holon.__nomosOwnedDeviceRequest({ scope: ${JSON.stringify(ownedScope)} }) as NomosDeviceRequest;`,
3012
3544
  ` },`,
3013
- ` approve: async (request: NomosDeviceRequest): Promise<NomosOutcome> => {`,
3545
+ ` approve: async (request: NomosDeviceRequest): Promise<void> => {`,
3014
3546
  ` const principal = actor ?? holon.principal;`,
3015
3547
  ` if (!principal || !principal.trim()) throw new Error("devices.approve() needs a signed-in Nomos session");`,
3016
3548
  ` if (!holon.__nomosPrepareOwnedDeviceApproval) throw new Error("devices.approve() needs the current @githolon/client");`,
3017
3549
  ` const payload = await holon.__nomosPrepareOwnedDeviceApproval({ request, scope: ${JSON.stringify(ownedScope)} });`,
3018
- ` return _outcome(holon.workspace, holon.offerDirective(${JSON.stringify(domain)}, ${JSON.stringify(OWNED_DEVICE_APPROVAL_DIRECTIVE)}, payload, { domainHash, actor: principal, returnCreated: true }) as Promise<string | { head: string; created?: { type: string; id: string }[] }>);`,
3550
+ ` return _commit(holon.offerDirective(${JSON.stringify(domain)}, ${JSON.stringify(OWNED_DEVICE_APPROVAL_DIRECTIVE)}, payload, { domainHash, actor: principal, returnCreated: true }) as Promise<_NomosRawWriteResult>);`,
3019
3551
  ` },`,
3020
3552
  ` },`,
3021
3553
  ]
3022
3554
  : []),
3023
3555
  // THE OWNER CEREMONY (slice 2): only for a domain that actually defines an encryption scope.
3024
- ...(domScopes.length > 0
3556
+ ...(manualEncryptionPresent && domScopes.length > 0
3025
3557
  ? [
3026
- ` /** OWNER CEREMONY (slice 2): grantAndWrap / revokeAndRotate / rotate — the operator-blind`,
3027
- ` * key-epoch handshake as one call. Bind \`{ actor }\` (the owner) to author the fan-out. */`,
3558
+ ` /** Owner policy: grant, revoke and rotate under the signed-in identity. */`,
3028
3559
  ` encryption: _ownerEncryption(holon, { domain: ${JSON.stringify(domain)}, domainHash, actor, scopes: ${JSON.stringify(domScopes)} }) as NomosEncryption,`,
3029
3560
  ]
3030
3561
  : []),
@@ -3035,64 +3566,213 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
3035
3566
  );
3036
3567
  }
3037
3568
 
3038
- // ROLE SUGAR — the per-(role × aggregate) `canEdit<Agg>` / `canView<Agg>` UI-HINT helpers. They
3039
- // evaluate LOCALLY against the folded RelationTuple projection (the client-side MIRROR of
3040
- // `engine.check`) — INFORMATIONAL only; THE KERNEL GATE REMAINS THE AUTHORITY and refuses regardless.
3569
+ // THE ONE APPLICATION ENTRY. Domain factories remain implementation detail; this package-level opener
3570
+ // owns identity, home binding, durable custody, continuous sync, compatibility and recovery. Multi-domain
3571
+ // applications are flattened into one business vocabulary and collision-check rather than silently choosing.
3041
3572
  {
3042
- const editRels = new Map<string, Set<string>>(); // aggregate id → the edit relations that cover it
3043
- const viewRels = new Map<string, Set<string>>(); // aggregate id → the view relations that cover it
3044
- for (const m of modules) {
3045
- for (const r of m.roles ?? []) {
3046
- if (r.editRelation) for (const agg of r.editAggregates) (editRels.get(agg) ?? editRels.set(agg, new Set()).get(agg)!).add(r.editRelation);
3047
- // A role-holder granted `.canView` holds BOTH its view relation AND (when it can edit) its edit
3048
- // relation — an editor can always read what it edits, so canView accepts either.
3049
- if (r.viewRelation) for (const agg of r.viewAggregates) {
3050
- const s = viewRels.get(agg) ?? viewRels.set(agg, new Set()).get(agg)!;
3051
- s.add(r.viewRelation);
3052
- if (r.editRelation && r.editAggregates.includes(agg)) s.add(r.editRelation);
3053
- }
3054
- }
3573
+ const rootBirths = applicationBirths.filter((birth) => clientDomains.includes(birth.sourceDomain));
3574
+ const childApplicationTypes = applicationBirths.filter((birth, index) =>
3575
+ applicationBirths.findIndex((candidate) => candidate.applicationType === birth.applicationType) === index);
3576
+ const ambiguousChildApplication = childApplicationTypes.find((birth) =>
3577
+ applicationBirths.some((candidate) =>
3578
+ candidate.applicationType === birth.applicationType && candidate.targetDomain !== birth.targetDomain));
3579
+ if (ambiguousChildApplication !== undefined) {
3580
+ throw new Error(
3581
+ `generated TypeScript child application '${ambiguousChildApplication.applicationType}' names more than one domain; rename the born aggregate`,
3582
+ );
3055
3583
  }
3056
- if (editRels.size > 0 || viewRels.size > 0) {
3057
- out.push(
3058
- `// ── ROLE POLICY (UI hints — the KERNEL GATE is the authority) ─────────────────────────────`,
3059
- `/** One folded RelationTuple row (as \`relationTuplesByObject\`/\`queryById("reltuple-…")\` yields). */`,
3060
- `export interface NomosRoleRelationRow { data?: { relation?: string; subjects?: Record<string, unknown> } }`,
3061
- `/** True iff \`principal\` holds ANY of \`relations\` as an ACTIVE tuple in \`rows\` (or a \`user:*\` wildcard). */`,
3062
- `function _holdsAnyRelation(rows: readonly NomosRoleRelationRow[] | undefined, relations: readonly string[], principal: string): boolean {`,
3063
- ` const want = new Set(relations);`,
3064
- ` for (const r of rows ?? []) {`,
3065
- ` if (r?.data?.relation === undefined || !want.has(r.data.relation)) continue;`,
3066
- ` for (const [subj, leafRaw] of Object.entries(r.data.subjects ?? {})) {`,
3067
- ` let leaf: { status?: string };`,
3068
- ` try { leaf = typeof leafRaw === "string" ? JSON.parse(leafRaw) : (leafRaw as { status?: string }); } catch { leaf = {}; }`,
3069
- ` if (leaf?.status !== undefined && leaf.status !== "active") continue;`,
3070
- ` if (subj === \`user:\${principal}\` || subj === "user:*") return true;`,
3071
- ` }`,
3072
- ` }`,
3073
- ` return false;`,
3074
- `}`,
3584
+ if (childApplicationTypes.some((birth) => birth.applicationType === applicationName)) {
3585
+ throw new Error(
3586
+ `Sorry mate — a born aggregate claims generated TypeScript application type '${applicationName}', which is already owned by the package application. ` +
3587
+ `Nomos will not silently rename it; rename the aggregate and compile again.`,
3075
3588
  );
3076
- for (const [agg, rels] of [...editRels].sort((a, b) => (a[0] < b[0] ? -1 : 1))) {
3077
- out.push(
3078
- `/** UI HINT — may \`principal\` EDIT a ${agg}? Mirrors the kernel's ReBAC check over the folded`,
3079
- ` * tuples; INFORMATIONAL only (the one wasm gate is the authority and refuses regardless). */`,
3080
- `export function canEdit${cap(agg)}(relations: readonly NomosRoleRelationRow[] | undefined, principal: string): boolean {`,
3081
- ` return _holdsAnyRelation(relations, ${JSON.stringify([...rels].sort())}, principal);`,
3082
- `}`,
3083
- );
3084
- }
3085
- for (const [agg, rels] of [...viewRels].sort((a, b) => (a[0] < b[0] ? -1 : 1))) {
3086
- out.push(
3087
- `/** UI HINT — may \`principal\` VIEW a ${agg}? Mirrors the kernel's ReBAC read gate;`,
3088
- ` * INFORMATIONAL only (the one wasm read boundary is the authority). */`,
3089
- `export function canView${cap(agg)}(relations: readonly NomosRoleRelationRow[] | undefined, principal: string): boolean {`,
3090
- ` return _holdsAnyRelation(relations, ${JSON.stringify([...rels].sort())}, principal);`,
3091
- `}`,
3092
- );
3093
- }
3094
- out.push(``);
3095
3589
  }
3590
+ const duplicateRootBirth = rootBirths.find((birth, index) =>
3591
+ rootBirths.findIndex((candidate) => candidate.method === birth.method) !== index);
3592
+ if (duplicateRootBirth !== undefined) {
3593
+ throw new Error(
3594
+ `generated TypeScript application birth '${duplicateRootBirth.method}' is declared by more than one co-resident root domain`,
3595
+ );
3596
+ }
3597
+ const internalApplicationDirectiveMembers = modules.flatMap((module) =>
3598
+ (module.directives as Directive<unknown>[])
3599
+ .filter((directive) =>
3600
+ (directive as { birthsChild?: boolean }).birthsChild === true &&
3601
+ (directive as { businessBirth?: unknown }).businessBirth === undefined
3602
+ )
3603
+ .map((directive) => directive.id)
3604
+ );
3605
+ const privateApplicationMembers = [
3606
+ "domain",
3607
+ "domainHash",
3608
+ "interface",
3609
+ "model",
3610
+ "encryption",
3611
+ "recovery",
3612
+ "devices",
3613
+ ...internalApplicationDirectiveMembers,
3614
+ ...applicationBirths.map((birth) => birth.method),
3615
+ ] as const;
3616
+ const baseApplicationType = clientDomains
3617
+ .map((domain) => tsDomainApplicationType(domain))
3618
+ .join(" & ") || "Record<never, never>";
3619
+ const birthApplicationType = rootBirths.length === 0
3620
+ ? "Record<never, never>"
3621
+ : `{ ${rootBirths.map((birth) =>
3622
+ `readonly ${JSON.stringify(birth.method)}: (input: ${birth.payloadName}) => Promise<${birth.applicationType}>`
3623
+ ).join("; ")} }`;
3624
+ const applicationType = `${baseApplicationType} & ${birthApplicationType}`;
3625
+ out.push(
3626
+ `type _NomosPrivateApplicationMember = ${privateApplicationMembers.map((name) => JSON.stringify(name)).join(" | ")};`,
3627
+ `type _NomosPublicDomain<T> = Omit<T, _NomosPrivateApplicationMember>;`,
3628
+ ...generatedDomains.map((domain) =>
3629
+ `type ${tsDomainApplicationType(domain)} = _NomosPublicDomain<ReturnType<typeof ${tsClientFactoryName(domain)}>>;`
3630
+ ),
3631
+ ...childApplicationTypes.map((birth) =>
3632
+ `export type ${birth.applicationType} = _NomosPublicDomain<ReturnType<typeof ${tsClientFactoryName(birth.targetDomain)}>>;`
3633
+ ),
3634
+ `type _${applicationName}Business = ${applicationType};`,
3635
+ `export type ${applicationName}Status = _NomosApplicationStatus;`,
3636
+ `export type ${applicationName} = _${applicationName}Business & _NomosApplicationParticipant & _NomosApplicationLifecycle${hasInvitations ? " & _NomosApplicationInvitations" : ""};`,
3637
+ `export interface ${applicationName}OpenOptions {`,
3638
+ ` /** Nomos Cloud serving this deployed application. Configure it once at the app boundary. */`,
3639
+ ` readonly cloud: string;`,
3640
+ ` /** The deployed business application anchor, configured once at the app boundary. */`,
3641
+ ` readonly application: string;`,
3642
+ ` /** Return the signed-in authority credential; when supplied, bind its opaque device challenge. */`,
3643
+ ` readonly signIn: (request?: { readonly deviceChallenge?: string }) => Promise<string>;`,
3644
+ `}`,
3645
+ `function _nomosPublicDomain<T extends Record<string, unknown>>(domain: T): _NomosPublicDomain<T> {`,
3646
+ ` const application: Record<string, unknown> = {};`,
3647
+ ` const privateMembers = new Set<string>(${JSON.stringify(privateApplicationMembers)});`,
3648
+ ` for (const [name, value] of Object.entries(domain)) if (!privateMembers.has(name)) application[name] = value;`,
3649
+ ` return application as _NomosPublicDomain<T>;`,
3650
+ `}`,
3651
+ `function _nomosBind${applicationName}(`,
3652
+ ` holon: NomosHolon,`,
3653
+ ` user: { readonly identityId: string; readonly identityAddress: string },`,
3654
+ `): _${applicationName}Business & _NomosApplicationParticipant${hasInvitations ? " & _NomosApplicationInvitations" : ""} {`,
3655
+ ...clientDomains.map((domain, index) =>
3656
+ ` const _nmsRoot${index} = ${tsClientFactoryName(domain)}(holon);`),
3657
+ ` const domains = [${clientDomains.map((_domain, index) => `_nmsRoot${index}`).join(", ")}] as const;`,
3658
+ ` const application: Record<string, unknown> = {};`,
3659
+ ` application.currentUser = { identity: { id: user.identityId, address: user.identityAddress } } as User;`,
3660
+ ...(hasInvitations
3661
+ ? [
3662
+ ` for (const reserved of ["recipient", "createInvitation", "redeemInvitation"] as const) {`,
3663
+ ` if (domains.some((domain) => reserved in domain)) throw new Error("Sorry mate — '" + reserved + "' is a Nomos application concept; rename the tenant action");`,
3664
+ ` }`,
3665
+ ` application.recipient = (uid: string): Recipient<User> => {`,
3666
+ ` const value = uid.trim();`,
3667
+ ` if (!value) throw new TypeError("recipient(uid) needs the identity provider user id");`,
3668
+ ` return _nmsRecipient<User>("user:" + value);`,
3669
+ ` };`,
3670
+ ` application.createInvitation = async (): Promise<Invitation<User>> =>`,
3671
+ ` _nmsInvitation<User>(await nomosInternalCreateInvitation(holon));`,
3672
+ ` application.redeemInvitation = (link: string): Promise<void> =>`,
3673
+ ` nomosInternalRedeemInvitation(holon, link, ${JSON.stringify(invitationPolicies)});`,
3674
+ ]
3675
+ : []),
3676
+ ` const privateMembers = new Set<string>(${JSON.stringify(privateApplicationMembers)});`,
3677
+ ` const singletonMembers = new Set<string>(["watchActivity", "activityLog", "describe", "describeHistory"]);`,
3678
+ ` const pasts: Array<{ moments: (opts?: { limit?: number }) => Promise<NomosMoment[]>; at: (moment: NomosMoment) => Record<string, unknown> }> = [];`,
3679
+ ` const conflictSets: Array<Record<string, unknown>> = [];`,
3680
+ ...(hasCorrections
3681
+ ? [` const correctionSets: Array<{ domain: string; value: { undo(reason: string): Promise<NomosCorrectionOutcome>; redo(reason: string): Promise<NomosCorrectionOutcome> } }> = [];`]
3682
+ : []),
3683
+ ` const routedOwnedActions = new Map<string, Array<(...args: unknown[]) => Promise<unknown>>>();`,
3684
+ ` for (const domain of domains) for (const [name, value] of Object.entries(domain)) {`,
3685
+ ` if (privateMembers.has(name)) continue;`,
3686
+ ` if (name === "past") { pasts.push(value as typeof pasts[number]); continue; }`,
3687
+ ` if (name === "conflicts") { conflictSets.push(value as Record<string, unknown>); continue; }`,
3688
+ ...(hasCorrections
3689
+ ? [` if (name === "corrections") { correctionSets.push({ domain: String(domain.domain), value: value as typeof correctionSets[number]["value"] }); continue; }`]
3690
+ : []),
3691
+ ` if (name === "share" || name === "unshare" || name === "transfer") {`,
3692
+ ` const candidates = routedOwnedActions.get(name) ?? [];`,
3693
+ ` candidates.push(value as (...args: unknown[]) => Promise<unknown>); routedOwnedActions.set(name, candidates); continue;`,
3694
+ ` }`,
3695
+ ` if (singletonMembers.has(name) && name in application) continue;`,
3696
+ ` if (name in application) throw new Error("Nomos application member collision: '" + name + "' is declared by more than one business domain; rename one of them");`,
3697
+ ` application[name] = value;`,
3698
+ ` }`,
3699
+ ...rootBirths.flatMap((birth) => {
3700
+ const rootIndex = clientDomains.indexOf(birth.sourceDomain);
3701
+ return [
3702
+ ` application[${JSON.stringify(birth.method)}] = async (input: ${birth.payloadName}): Promise<${birth.applicationType}> => {`,
3703
+ ` const root = await _nmsRoot${rootIndex}[${JSON.stringify(birth.method)}](input);`,
3704
+ ` const address = _nmsReferenceData(root);`,
3705
+ ` return nomosInternalOpenGeneratedChildApplication(`,
3706
+ ` holon,`,
3707
+ ` address.workspace,`,
3708
+ ` (child) => _nomosPublicDomain(${tsClientFactoryName(birth.targetDomain)}(child as NomosHolon)),`,
3709
+ ` );`,
3710
+ ` };`,
3711
+ ];
3712
+ }),
3713
+ ` if (pasts.length > 0) application.past = {`,
3714
+ ` moments: async (opts: { limit?: number } = {}): Promise<NomosMoment[]> => {`,
3715
+ ` const moments = (await Promise.all(pasts.map((past) => past.moments(opts)))).flat();`,
3716
+ ` moments.sort((a, b) => (b.occurredAt ?? "").localeCompare(a.occurredAt ?? ""));`,
3717
+ ` return opts.limit === undefined ? moments : moments.slice(0, opts.limit);`,
3718
+ ` },`,
3719
+ ` at: (moment: NomosMoment): Record<string, unknown> => {`,
3720
+ ` const historic: Record<string, unknown> = {};`,
3721
+ ` for (const past of pasts) for (const [name, value] of Object.entries(past.at(moment))) {`,
3722
+ ` if (name in historic) throw new Error("Nomos historic member collision: '" + name + "' is declared by more than one business domain; rename the aggregate");`,
3723
+ ` historic[name] = value;`,
3724
+ ` }`,
3725
+ ` return historic;`,
3726
+ ` },`,
3727
+ ` };`,
3728
+ ` if (conflictSets.length > 0) {`,
3729
+ ` const conflicts: Record<string, unknown> = {};`,
3730
+ ` for (const set of conflictSets) for (const [name, value] of Object.entries(set)) {`,
3731
+ ` if (name === "summary" && name in conflicts) continue;`,
3732
+ ` if (name in conflicts) throw new Error("Nomos conflict member collision: '" + name + "' is declared by more than one business domain; rename the aggregate");`,
3733
+ ` conflicts[name] = value;`,
3734
+ ` }`,
3735
+ ` application.conflicts = conflicts;`,
3736
+ ` }`,
3737
+ ...(hasCorrections
3738
+ ? [
3739
+ ` if (correctionSets.length > 0) {`,
3740
+ ` const currentCorrections = async () => {`,
3741
+ ` if (!holon.commits) throw new Error("corrections need verified application history");`,
3742
+ ` const supported = new Set(correctionSets.map((entry) => entry.domain));`,
3743
+ ` const latest = (await holon.commits({ limit: 1000 })).find((commit) => commit.domain !== null && supported.has(commit.domain));`,
3744
+ ` const selected = correctionSets.find((entry) => entry.domain === latest?.domain);`,
3745
+ ` if (selected === undefined) throw new Error("there is no business change to correct yet");`,
3746
+ ` return selected.value;`,
3747
+ ` };`,
3748
+ ` application.corrections = {`,
3749
+ ` undo: async (reason: string) => (await currentCorrections()).undo(reason),`,
3750
+ ` redo: async (reason: string) => (await currentCorrections()).redo(reason),`,
3751
+ ` };`,
3752
+ ` }`,
3753
+ ]
3754
+ : []),
3755
+ ` for (const [name, candidates] of routedOwnedActions) application[name] = async (...args: unknown[]): Promise<unknown> => {`,
3756
+ ` let unsupported: unknown;`,
3757
+ ` for (const candidate of candidates) try { return await candidate(...args); } catch (error) {`,
3758
+ ` if (!(error instanceof Error) || !error.message.includes("needs a generated owned record reference")) throw error;`,
3759
+ ` unsupported = error;`,
3760
+ ` }`,
3761
+ ` throw unsupported ?? new Error(name + " needs a generated owned record reference");`,
3762
+ ` };`,
3763
+ ` return application as unknown as _${applicationName}Business & _NomosApplicationParticipant${hasInvitations ? " & _NomosApplicationInvitations" : ""};`,
3764
+ `}`,
3765
+ `/** Open the one durable, signed-in ${opts.packageName} business application.`,
3766
+ ` * Keep and pass this object down as application state; do not create a repository, runtime host`,
3767
+ ` * or parallel cache around it. Nomos owns offline custody and synchronization. */`,
3768
+ `export function ${applicationOpener}(options: ${applicationName}OpenOptions): Promise<${applicationName}> {`,
3769
+ ` return nomosInternalOpenGeneratedApplication(`,
3770
+ ` { cloud: options.cloud, application: options.application, signIn: options.signIn },`,
3771
+ ` (holon, user) => _nomosBind${applicationName}(holon as NomosHolon, user),`,
3772
+ ` );`,
3773
+ `}`,
3774
+ ``,
3775
+ );
3096
3776
  }
3097
3777
 
3098
3778
  // RECURSIVE PAYLOAD TYPES (`z.lazy`): emit each minted named alias once. Sorted by name for