@githolon/dsl 0.102.5 → 0.103.1

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 (132) hide show
  1. package/compile_package.mjs +43 -2
  2. package/compile_proof.d.mts +47 -0
  3. package/compile_proof.mjs +343 -0
  4. package/dart/CHANGELOG.md +14 -0
  5. package/dart/lib/src/dev_cloud.dart +8 -8
  6. package/dart/lib/src/nms_generated_runtime.dart +2 -0
  7. package/dart/lib/src/runner/node-entry.mjs +5539 -1228
  8. package/dart/lib/src/runner/offline-install.mjs +4578 -528
  9. package/dart/lib/src/test_harness.dart +206 -152
  10. package/dart/lib/src/webview_bridge.dart +239 -102
  11. package/dart/pubspec.lock +1 -1
  12. package/dart/pubspec.yaml +2 -2
  13. package/dart/test/acquire_session_credentials_test.dart +4 -3
  14. package/dart/test/acquire_session_test.dart +9 -0
  15. package/dart/test/adv_realm_interference_test.dart +7 -6
  16. package/dart/test/cloud_bound_topology_test.dart +3 -3
  17. package/dart/test/cluster_births_test.dart +4 -3
  18. package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
  19. package/dart/test/cluster_local_autosync_test.dart +3 -2
  20. package/dart/test/cluster_scale_test.dart +3 -2
  21. package/dart/test/dlq_session_surface_test.dart +7 -6
  22. package/dart/test/first_app_gauntlet_test.dart +14 -21
  23. package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
  24. package/dart/test/four_layer_nested_topology_test.dart +4 -4
  25. package/dart/test/harness_phase_timeout_test.dart +2 -2
  26. package/dart/test/local_sync_inert_test.dart +1 -1
  27. package/dart/test/parent_signer_recovery_test.dart +2 -2
  28. package/dart/test/sealing_client_test.dart +4 -3
  29. package/dart/test/session_bound_test.dart +8 -7
  30. package/dart/test/test_harness_test.dart +13 -12
  31. package/dart/test/vo_read_typing_test.dart +4 -3
  32. package/dart/test/watch_after_offer_stall_test.dart +4 -3
  33. package/dart/test/webview_bridge_test.dart +102 -5
  34. package/dart_types/CHANGELOG.md +5 -0
  35. package/dart_types/lib/nomos_types.dart +9 -0
  36. package/dart_types/lib/src/nomos_ref.dart +20 -0
  37. package/dart_types/lib/src/optional_ref.dart +85 -0
  38. package/dart_types/lib/src/sha256_digest.dart +41 -0
  39. package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
  40. package/dart_types/pubspec.yaml +1 -1
  41. package/dart_types/test/optional_ref_test.dart +44 -0
  42. package/dart_types/test/sha256_digest_test.dart +18 -0
  43. package/package.json +9 -5
  44. package/runtime/generated-application-host.js +5365 -1105
  45. package/src/aggregate.ts +18 -0
  46. package/src/application_composition.ts +6 -32
  47. package/src/attested_core.ts +6 -3
  48. package/src/birth_meter.ts +1 -0
  49. package/src/birth_meter_ensure.ts +28 -2
  50. package/src/births.ts +1 -1
  51. package/src/build_package.ts +162 -538
  52. package/src/business_birth.ts +19 -3
  53. package/src/canonical_json.ts +21 -0
  54. package/src/check_package_main.ts +73 -33
  55. package/src/clear_directive.ts +19 -8
  56. package/src/codegen_dart.ts +305 -22
  57. package/src/codegen_dart_application.ts +862 -292
  58. package/src/codegen_dart_client.ts +1084 -407
  59. package/src/codegen_dart_scenario.ts +12 -12
  60. package/src/codegen_proof.ts +169 -39
  61. package/src/codegen_provider_dart.ts +17 -1
  62. package/src/codegen_ts.ts +298 -106
  63. package/src/compat_manifest.ts +2 -2
  64. package/src/compat_package_main.ts +8 -12
  65. package/src/compile_package_main.ts +1860 -592
  66. package/src/compose.ts +1 -1
  67. package/src/directive.ts +208 -15
  68. package/src/engine_entry.ts +165 -111
  69. package/src/evolve_preview.ts +29 -69
  70. package/src/extremum.ts +1 -1
  71. package/src/fields.ts +183 -4
  72. package/src/flow_gate.ts +75 -2
  73. package/src/framework/application_home.ts +2 -0
  74. package/src/framework/auth_provider_law.ts +2 -2
  75. package/src/framework/birth_meter.ts +7 -19
  76. package/src/framework/bootstrap.ts +0 -2
  77. package/src/framework/business_corrections.ts +9 -5
  78. package/src/framework/domain_lifecycle.ts +15 -29
  79. package/src/framework/encryption_scope.ts +2 -1
  80. package/src/framework/framework_aggregates.ts +2 -0
  81. package/src/framework/genesis.ts +16 -14
  82. package/src/framework/home.ts +20 -14
  83. package/src/framework/owned_custody.ts +9 -3
  84. package/src/framework/platform_admin.ts +3 -3
  85. package/src/framework/relations.ts +21 -13
  86. package/src/framework/signing_identity.ts +45 -40
  87. package/src/framework/workspace_invariant.ts +245 -0
  88. package/src/framework/workspaces.ts +23 -29
  89. package/src/index.ts +36 -16
  90. package/src/intent.ts +178 -6
  91. package/src/law_check.ts +32 -11
  92. package/src/law_graph.ts +2902 -0
  93. package/src/law_graph_usda.ts +1799 -0
  94. package/src/law_identity.ts +435 -0
  95. package/src/law_value_type.ts +230 -0
  96. package/src/lsp_server.ts +58 -29
  97. package/src/ops.ts +7 -0
  98. package/src/package_config.ts +141 -0
  99. package/src/package_dependencies.ts +108 -0
  100. package/src/package_dependency_usage.ts +150 -0
  101. package/src/package_source_imports.ts +71 -0
  102. package/src/plan_source_slice.ts +335 -0
  103. package/src/point_guard.ts +192 -0
  104. package/src/precondition.ts +44 -9
  105. package/src/read_projection_namespace.ts +1 -1
  106. package/src/runtime_zod.ts +44 -0
  107. package/src/secondary_ensure.ts +67 -14
  108. package/src/shape_compat.ts +1 -1
  109. package/src/stable_id_identity_bridge.ts +149 -0
  110. package/src/stable_id_lockfile.ts +140 -6
  111. package/src/stable_ids.ts +1 -1
  112. package/src/stable_ids_for_module.ts +348 -0
  113. package/src/stable_ids_types.ts +1 -1
  114. package/src/sum.ts +1 -1
  115. package/src/usd_state.ts +1 -1
  116. package/src/usda_relationship_integrity.ts +66 -0
  117. package/src/usda_text.ts +18 -0
  118. package/src/usdz.ts +78 -0
  119. package/src/value_object.ts +95 -0
  120. package/src/wire.ts +19 -0
  121. package/src/wire_encode.ts +22 -0
  122. package/src/workspace_sharding.ts +52 -36
  123. package/src/workspace_sharing.ts +28 -4
  124. package/src/zod_introspection.ts +1 -1
  125. package/dart/test/cluster_attested_test.dart +0 -201
  126. package/src/compile_engine.ts +0 -595
  127. package/src/manifest.ts +0 -1476
  128. package/src/read_projection_composition.ts +0 -117
  129. package/src/read_projection_manifest.ts +0 -115
  130. package/src/relation_composition.ts +0 -286
  131. package/src/usd.ts +0 -1967
  132. package/src/usd_layers.ts +0 -1776
@@ -7,13 +7,14 @@ import type { z } from "zod";
7
7
 
8
8
  import type { AggregateHandle } from "./aggregate.js";
9
9
  import type { Directive } from "./directive.js";
10
- import type { Field } from "./fields.js";
10
+ import { isSha256DigestSchema, type Field } from "./fields.js";
11
11
  import type { QueryDecl } from "./query.js";
12
12
  import type { DomainModule } from "./codegen_dart.js";
13
13
  import {
14
14
  cap,
15
15
  concreteClassName,
16
16
  dartBusinessRecordInputs,
17
+ dartForeignReferenceInputs,
17
18
  dartActionInputClassName,
18
19
  introspectPayload,
19
20
  readModelClassName,
@@ -22,7 +23,10 @@ import {
22
23
  import { dartClientClassName } from "./codegen_dart_client.js";
23
24
  import { finishCount } from "./count.js";
24
25
  import { finishSum } from "./sum.js";
25
- import { isNamespacedAggregate, qualifyReadProjectionType } from "./read_projection_namespace.js";
26
+ import {
27
+ isNamespacedAggregate,
28
+ qualifyReadProjectionType,
29
+ } from "./read_projection_namespace.js";
26
30
  import type { SurfacedResolverBinding } from "./surfaced_lint.js";
27
31
  import {
28
32
  isFrameworkAggregate,
@@ -39,14 +43,46 @@ import type {
39
43
 
40
44
  type AnyDirective = Directive<z.ZodTypeAny>;
41
45
 
42
- const camel = (s: string) => s.replace(/[_\s-]+(\w)/g, (_m, c: string) => c.toUpperCase());
43
- const lcFirst = (s: string) => (s.length ? s[0]!.toLowerCase() + s.slice(1) : s);
46
+ const camel = (s: string) =>
47
+ s.replace(/[_\s-]+(\w)/g, (_m, c: string) => c.toUpperCase());
48
+ const lcFirst = (s: string) =>
49
+ s.length ? s[0]!.toLowerCase() + s.slice(1) : s;
44
50
  const reserved = new Set([
45
- "assert", "break", "case", "catch", "class", "const", "continue", "default", "do", "else", "enum",
46
- "extends", "false", "final", "for", "if", "in", "is", "new", "null", "rethrow", "return", "super",
47
- "switch", "this", "throw", "true", "try", "var", "void", "while", "with", "dynamic",
51
+ "assert",
52
+ "break",
53
+ "case",
54
+ "catch",
55
+ "class",
56
+ "const",
57
+ "continue",
58
+ "default",
59
+ "do",
60
+ "else",
61
+ "enum",
62
+ "extends",
63
+ "false",
64
+ "final",
65
+ "for",
66
+ "if",
67
+ "in",
68
+ "is",
69
+ "new",
70
+ "null",
71
+ "rethrow",
72
+ "return",
73
+ "super",
74
+ "switch",
75
+ "this",
76
+ "throw",
77
+ "true",
78
+ "try",
79
+ "var",
80
+ "void",
81
+ "while",
82
+ "with",
83
+ "dynamic",
48
84
  ]);
49
- const safe = (s: string) => reserved.has(s) ? `${s}_` : s;
85
+ const safe = (s: string) => (reserved.has(s) ? `${s}_` : s);
50
86
  const member = (s: string) => safe(lcFirst(camel(s)));
51
87
  const TENANT_ROOT_MEMBERS = new Map<string, string>([
52
88
  ["nomosWidget", "the generated Nomos Flutter bootstrap"],
@@ -66,7 +102,8 @@ function plural(name: string): string {
66
102
 
67
103
  function keyParam(key: string): string {
68
104
  const parts = key.split(".").filter(Boolean);
69
- const raw = parts.length === 0 ? "key" : parts[0]! + parts.slice(1).map(cap).join("");
105
+ const raw =
106
+ parts.length === 0 ? "key" : parts[0]! + parts.slice(1).map(cap).join("");
70
107
  const cleaned = raw.replace(/[^A-Za-z0-9_]/g, "_");
71
108
  return safe(/^[0-9]/.test(cleaned) ? `v${cleaned}` : cleaned);
72
109
  }
@@ -77,12 +114,20 @@ function keyType(aggregate: AggregateHandle | undefined, key: string): string {
77
114
  if (field?.kind === "int" || field?.kind === "counter") return "int";
78
115
  if (field?.kind === "ref") {
79
116
  if (field.foreignRef !== undefined) {
80
- const target = field.foreignRef.targetIsContract === true
81
- ? contractTypeName(field.foreignRef.targetAggregateId)
82
- : concreteClassName(field.foreignRef.targetAggregateId);
117
+ const target =
118
+ field.foreignRef.targetIsContract === true
119
+ ? contractTypeName(field.foreignRef.targetAggregateId)
120
+ : concreteClassName(field.foreignRef.targetAggregateId);
83
121
  return `ForeignRef<${target}>`;
84
122
  }
85
- if (field.refAggregateId !== undefined) return `NomosRef<${concreteClassName(field.refAggregateId)}>`;
123
+ if (field.refAggregateId !== undefined)
124
+ return `NomosRef<${concreteClassName(field.refAggregateId)}>`;
125
+ }
126
+ if (
127
+ field?.kind === "string" &&
128
+ isSha256DigestSchema(field.zod as z.ZodTypeAny)
129
+ ) {
130
+ return "Sha256Digest";
86
131
  }
87
132
  return "String";
88
133
  }
@@ -90,13 +135,16 @@ function keyType(aggregate: AggregateHandle | undefined, key: string): string {
90
135
  function sharingOrderValue(
91
136
  policy: WorkspaceSharingPolicy,
92
137
  fieldName: string,
93
- target: "wire" | "value" = "value",
138
+ target: "wire" | "value" = "value"
94
139
  ): string {
95
140
  const field = policy.orders.fields[fieldName];
96
141
  if (field === undefined) {
97
- throw new Error(`workspaceSharing(${policy.orders.id}): unknown order field '${fieldName}'`);
142
+ throw new Error(
143
+ `workspaceSharing(${policy.orders.id}): unknown order field '${fieldName}'`
144
+ );
98
145
  }
99
- const value = `(order.${member(fieldName)} ?? (throw StateError(` +
146
+ const value =
147
+ `(order.${member(fieldName)} ?? (throw StateError(` +
100
148
  `'Incomplete ${policy.orders.id} ${member(fieldName)}')))`;
101
149
  if (target === "wire" && field.kind === "enum") return `${value}.wire`;
102
150
  if (field.kind === "ref") return `NmsInternalNomosRefs.id(${value})`;
@@ -105,40 +153,51 @@ function sharingOrderValue(
105
153
 
106
154
  function sharingReceiptCondition(
107
155
  policy: WorkspaceSharingPolicy,
108
- receipt: WorkspaceSharingReceipt,
156
+ receipt: WorkspaceSharingReceipt
109
157
  ): string {
110
158
  const condition = receipt.when;
111
159
  if (condition === undefined) return "true";
112
160
  const field = policy.orders.fields[condition.field];
113
161
  if (field === undefined) {
114
- throw new Error(`workspaceSharing(${policy.orders.id}): unknown receipt field '${condition.field}'`);
162
+ throw new Error(
163
+ `workspaceSharing(${policy.orders.id}): unknown receipt field '${condition.field}'`
164
+ );
115
165
  }
116
166
  if (!field.isOptional) {
117
167
  if (condition.is === "absent") {
118
168
  throw new Error(
119
- `workspaceSharing(${policy.orders.id}): '${condition.field}' cannot be absent because it is required`,
169
+ `workspaceSharing(${policy.orders.id}): '${condition.field}' cannot be absent because it is required`
120
170
  );
121
171
  }
122
172
  return "true";
123
173
  }
124
- return `order.${member(condition.field)} ${condition.is === "present" ? "!=" : "=="} null`;
174
+ return `order.${member(condition.field)} ${
175
+ condition.is === "present" ? "!=" : "=="
176
+ } null`;
125
177
  }
126
178
 
127
179
  function sharingReceiptPayload(
128
180
  policy: WorkspaceSharingPolicy,
129
181
  receipt: WorkspaceSharingReceipt,
130
- refused: boolean,
182
+ refused: boolean
131
183
  ): string[] {
132
- const fields = introspectPayload(receipt.records.payloadSchema as z.ZodTypeAny, new Map());
184
+ const fields = introspectPayload(
185
+ receipt.records.payloadSchema as z.ZodTypeAny,
186
+ new Map()
187
+ );
133
188
  return fields.flatMap((target) => {
134
189
  if (target.name === "failureReason") {
135
190
  return refused
136
- ? [` 'failureReason': share.message ?? 'shareDeliveryRefused',`]
191
+ ? [
192
+ ` 'failureReason': share.message ?? 'shareDeliveryRefused',`,
193
+ ]
137
194
  : target.optional
138
- ? []
139
- : (() => { throw new Error(
140
- `workspaceSharing(${policy.orders.id}): delivered receipt '${receipt.records.id}' requires failureReason`,
141
- ); })();
195
+ ? []
196
+ : (() => {
197
+ throw new Error(
198
+ `workspaceSharing(${policy.orders.id}): delivered receipt '${receipt.records.id}' requires failureReason`
199
+ );
200
+ })();
142
201
  }
143
202
  if (target.name === "deliveryHead" && target.optional) return [];
144
203
  const sourceName = receipt.payload?.[target.name] ?? target.name;
@@ -147,7 +206,7 @@ function sharingReceiptPayload(
147
206
  if (target.optional) return [];
148
207
  throw new Error(
149
208
  `workspaceSharing(${policy.orders.id}): receipt '${receipt.records.id}' requires '${target.name}', ` +
150
- `but the durable order does not record '${sourceName}'`,
209
+ `but the durable order does not record '${sourceName}'`
151
210
  );
152
211
  }
153
212
  const raw = sharingOrderValue(policy, sourceName);
@@ -156,34 +215,65 @@ function sharingReceiptPayload(
156
215
  });
157
216
  }
158
217
 
159
- export const dartApplicationClassName = (domain: string): string => `${cap(camel(domain))}Application`;
160
- export const dartApplicationActivityClassName = (domain: string): string => `${cap(camel(domain))}Activity`;
161
- export const dartAggregateCollectionClassName = (aggregate: AggregateHandle): string =>
162
- `${concreteClassName(aggregate.id)}Collection`;
218
+ export const dartApplicationClassName = (domain: string): string =>
219
+ `${cap(camel(domain))}Application`;
220
+ export const dartApplicationActivityClassName = (domain: string): string =>
221
+ `${cap(camel(domain))}Activity`;
222
+ export const dartAggregateCollectionClassName = (
223
+ aggregate: AggregateHandle
224
+ ): string => `${concreteClassName(aggregate.id)}Collection`;
163
225
 
164
- const aggregateConflictsClassName = (domain: string, aggregateId: string): string => {
226
+ const aggregateConflictsClassName = (
227
+ domain: string,
228
+ aggregateId: string
229
+ ): string => {
165
230
  const aggregateName = cap(camel(concreteClassName(aggregateId)));
166
231
  return aggregateName === cap(camel(domain))
167
232
  ? `${aggregateName}AggregateConflicts`
168
233
  : `${aggregateName}Conflicts`;
169
234
  };
170
235
 
171
- const dartActionResultClassName = (directiveId: string): string => `${cap(camel(directiveId))}Result`;
236
+ const dartActionResultClassName = (directiveId: string): string =>
237
+ `${cap(camel(directiveId))}Result`;
172
238
 
173
239
  function dartActionOutputs(
174
240
  directive: AnyDirective,
175
- aggregates: readonly AggregateHandle[],
176
- ): { aggregateId: string; field: string; dartType: string; many: boolean }[] {
177
- const known = new Set(aggregates.filter((aggregate) => !isFrameworkAggregate(aggregate)).map((aggregate) => aggregate.id));
241
+ aggregates: readonly AggregateHandle[]
242
+ ): {
243
+ aggregateId: string;
244
+ field: string;
245
+ dartType: string;
246
+ many: boolean;
247
+ optional: boolean;
248
+ }[] {
249
+ const known = new Set(
250
+ aggregates
251
+ .filter(
252
+ (aggregate) =>
253
+ !isFrameworkAggregate(aggregate) &&
254
+ (aggregate as { businessBirthInternal?: unknown })
255
+ .businessBirthInternal !== true
256
+ )
257
+ .map((aggregate) => aggregate.id)
258
+ );
178
259
  const raw = [
179
- { aggregateId: directive.aggregateId, many: false },
180
- ...((directive.secondaryOutputs ?? []).map((output) => ({
260
+ {
261
+ aggregateId: directive.aggregateId,
262
+ many: directive.outputCardinality === "many",
263
+ optional: directive.outputCardinality === "optional",
264
+ },
265
+ ...(directive.secondaryOutputs ?? []).map((output) => ({
181
266
  aggregateId: output.aggregateId,
182
267
  many: output.many === true,
183
- }))),
268
+ optional: output.optional === true,
269
+ })),
184
270
  ].filter((output) => known.has(output.aggregateId));
185
- const outputs = raw.filter((output, index) =>
186
- raw.findIndex((candidate) => candidate.aggregateId === output.aggregateId) === index);
271
+ const outputs = raw.filter(
272
+ (output, index) =>
273
+ raw.findIndex(
274
+ (candidate) => candidate.aggregateId === output.aggregateId
275
+ ) === index
276
+ );
187
277
  const fields = new Set<string>();
188
278
  return outputs.map((output) => {
189
279
  const dartType = concreteClassName(output.aggregateId);
@@ -191,7 +281,7 @@ function dartActionOutputs(
191
281
  if (fields.has(field)) {
192
282
  throw new Error(
193
283
  `Dart application result for '${directive.id}' has two aggregate consequences named '${field}'; ` +
194
- `rename one aggregate so the generated business result is unambiguous`,
284
+ `rename one aggregate so the generated business result is unambiguous`
195
285
  );
196
286
  }
197
287
  fields.add(field);
@@ -211,9 +301,16 @@ function dartConflictPublicClassNames(mod: DomainModule): string[] {
211
301
  const aggregateName = cap(camel(concreteClassName(binding.aggregateId)));
212
302
  names.add(aggregateConflictsClassName(domain, binding.aggregateId));
213
303
  names.add(`${aggregateName}PendingConflict`);
214
- if (binding.valuePayloadField !== undefined && binding.idPayloadField !== undefined) {
304
+ if (
305
+ binding.valuePayloadField !== undefined &&
306
+ binding.idPayloadField !== undefined
307
+ ) {
215
308
  names.add(`${aggregateName}${cap(camel(binding.field))}Conflict`);
216
- names.add(`${aggregateName}${cap(camel(binding.field))}ConflictModelUpdateRequired`);
309
+ names.add(
310
+ `${aggregateName}${cap(
311
+ camel(binding.field)
312
+ )}ConflictModelUpdateRequired`
313
+ );
217
314
  }
218
315
  }
219
316
  return [...names];
@@ -221,35 +318,50 @@ function dartConflictPublicClassNames(mod: DomainModule): string[] {
221
318
 
222
319
  export function dartApplicationPublicClassNames(
223
320
  mod: DomainModule,
224
- opts: { readonly tenantRoot?: boolean } = {},
321
+ opts: { readonly tenantRoot?: boolean } = {}
225
322
  ): string[] {
226
323
  registerDartNames(mod.aggregates);
227
- const actions = ((mod.applicationIntents ?? mod.directives) as AnyDirective[]).filter((directive) =>
228
- directive &&
229
- typeof directive.id === "string" &&
230
- (directive as { businessBirthInternal?: unknown }).businessBirthInternal !== true &&
231
- (directive as { businessBirth?: unknown }).businessBirth === undefined &&
232
- (directive as { birthsChild?: unknown }).birthsChild !== true &&
233
- !isFrameworkDirective(directive) &&
234
- dartActionOutputs(directive, mod.aggregates).length > 0);
324
+ const actions = (
325
+ (mod.applicationIntents ?? mod.directives) as AnyDirective[]
326
+ ).filter(
327
+ (directive) =>
328
+ directive &&
329
+ typeof directive.id === "string" &&
330
+ (directive as { businessBirthInternal?: unknown })
331
+ .businessBirthInternal !== true &&
332
+ (directive as { businessBirth?: unknown }).businessBirth === undefined &&
333
+ (directive as { birthsChild?: unknown }).birthsChild !== true &&
334
+ !isFrameworkDirective(directive) &&
335
+ dartActionOutputs(directive, mod.aggregates).length > 0
336
+ );
235
337
  return [
236
338
  ...(opts.tenantRoot === true
237
- ? ["Identity", "User", ...(mod.applicationWorkspaceSharing?.length ? ["Recipient", "Invitation"] : [])]
339
+ ? [
340
+ "Identity",
341
+ "User",
342
+ ...(mod.applicationWorkspaceSharing?.length
343
+ ? ["Recipient", "Invitation"]
344
+ : []),
345
+ ]
238
346
  : []),
239
347
  dartApplicationClassName(mod.domain ?? mod.name),
240
348
  dartApplicationActivityClassName(mod.domain ?? mod.name),
241
349
  ...actions.map((directive) => dartActionResultClassName(directive.id)),
242
350
  ...mod.aggregates
243
- .filter((aggregate) =>
244
- (aggregate as { businessBirthInternal?: unknown }).businessBirthInternal !== true &&
245
- !isFrameworkAggregate(aggregate))
351
+ .filter(
352
+ (aggregate) =>
353
+ (aggregate as { businessBirthInternal?: unknown })
354
+ .businessBirthInternal !== true && !isFrameworkAggregate(aggregate)
355
+ )
246
356
  .map(dartAggregateCollectionClassName),
247
357
  ...dartConflictPublicClassNames(mod),
248
358
  ];
249
359
  }
250
360
 
251
361
  export interface DartApplicationOptions {
252
- readonly packageAggregateDomains?: Readonly<Record<string, { domain: string; name: string }>>;
362
+ readonly packageAggregateDomains?: Readonly<
363
+ Record<string, { domain: string; name: string }>
364
+ >;
253
365
  readonly typesImportUri?: string;
254
366
  readonly explicitTypeImports?: readonly string[];
255
367
  readonly supportImport?: "vendored" | "package";
@@ -267,13 +379,18 @@ export interface DartApplicationOptions {
267
379
  * hosting and generated sibling facades import them directly from `src/generated`; tenant autocomplete sees
268
380
  * only `<Domain>Application`.
269
381
  */
270
- export function generateDartApplication(mod: DomainModule, opts: DartApplicationOptions = {}): string {
382
+ export function generateDartApplication(
383
+ mod: DomainModule,
384
+ opts: DartApplicationOptions = {}
385
+ ): string {
271
386
  registerDartNames(mod.aggregates);
272
387
  const domain = mod.domain ?? mod.name;
273
388
  const application = dartApplicationClassName(domain);
274
389
  const activity = dartApplicationActivityClassName(domain);
275
390
  const client = dartClientClassName(domain);
276
- const aggregates = new Map(mod.aggregates.map((aggregate) => [aggregate.id, aggregate]));
391
+ const aggregates = new Map(
392
+ mod.aggregates.map((aggregate) => [aggregate.id, aggregate])
393
+ );
277
394
  const packageDomains = opts.packageAggregateDomains ?? {};
278
395
  const appLines: string[] = [];
279
396
  const applicationMembers = new Map<string, string>();
@@ -282,7 +399,7 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
282
399
  if (prior !== undefined) {
283
400
  throw new Error(
284
401
  `Sorry mate — generated Dart application member '${name}' is already owned by ${prior}; ` +
285
- `${owner} cannot be silently renamed or rerouted. Rename the tenant concept and compile again.`,
402
+ `${owner} cannot be silently renamed or rerouted. Rename the tenant concept and compile again.`
286
403
  );
287
404
  }
288
405
  applicationMembers.set(name, owner);
@@ -295,13 +412,114 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
295
412
  let usesWorkspaceName = false;
296
413
  let usesAffectedReference = false;
297
414
  let usesAffectedReferences = false;
415
+ let usesAffectedReferenceOrNull = false;
298
416
  const actionResultDeclarations: string[] = [];
299
417
  const conflictDeclarations: string[] = [];
300
- const resolverBindings: readonly SurfacedResolverBinding[] = mod.surfacedResolvers ?? [];
301
- const sharingPolicies: readonly WorkspaceSharingPolicy[] = mod.applicationWorkspaceSharing ?? [];
418
+ const resolverBindings: readonly SurfacedResolverBinding[] =
419
+ mod.surfacedResolvers ?? [];
420
+ const sharingPolicies: readonly WorkspaceSharingPolicy[] =
421
+ mod.applicationWorkspaceSharing ?? [];
422
+ const hasVersionedRefs = mod.aggregates.some((aggregate) =>
423
+ Object.values(aggregate.fields as Record<string, Field>).some(
424
+ (field) => field.versionedForeignRef !== undefined
425
+ )
426
+ );
427
+ if (hasVersionedRefs) {
428
+ claimApplicationMember(
429
+ "freshness",
430
+ "the generated versioned-reference boundary"
431
+ );
432
+ appLines.push(
433
+ ` /// Check whether [reference]'s immutable publication is still current. This reads the`,
434
+ ` /// stable series' one compiled pointer; it never reconstructs publication history.`,
435
+ ` Future<ReferenceFreshness<TVersion>> freshness<TVersion, TSeries>(`,
436
+ ` VersionedNomosRef<TVersion, TSeries> reference) =>`,
437
+ ` _client.referenceFreshness(reference);`,
438
+ ``
439
+ );
440
+ }
441
+ const versionedPayloadFactories = new Map<
442
+ string,
443
+ {
444
+ target: string;
445
+ series: string;
446
+ targetInput: string;
447
+ seriesInput: string;
448
+ targetWireType: string;
449
+ seriesWireType: string;
450
+ currentField: string;
451
+ }
452
+ >();
453
+ for (const directive of mod.directives as AnyDirective[]) {
454
+ if (!isBusinessIntent(directive)) continue;
455
+ for (const spec of introspectPayload(
456
+ directive.payloadSchema as z.ZodTypeAny,
457
+ new Map()
458
+ )) {
459
+ if (
460
+ spec.toJson !== "versionedRef" ||
461
+ spec.versionedRefTarget === undefined ||
462
+ spec.versionedRefSeries === undefined ||
463
+ spec.versionedRefTargetInput === undefined ||
464
+ spec.versionedRefSeriesInput === undefined ||
465
+ spec.versionedRefTargetWireType === undefined ||
466
+ spec.versionedRefSeriesWireType === undefined ||
467
+ spec.versionedRefCurrentField === undefined
468
+ )
469
+ continue;
470
+ const method = `pin${cap(camel(spec.name))}`;
471
+ const next = {
472
+ target: spec.versionedRefTarget,
473
+ series: spec.versionedRefSeries,
474
+ targetInput: spec.versionedRefTargetInput,
475
+ seriesInput: spec.versionedRefSeriesInput,
476
+ targetWireType: spec.versionedRefTargetWireType,
477
+ seriesWireType: spec.versionedRefSeriesWireType,
478
+ currentField: spec.versionedRefCurrentField,
479
+ };
480
+ const prior = versionedPayloadFactories.get(method);
481
+ if (
482
+ prior !== undefined &&
483
+ JSON.stringify(prior) !== JSON.stringify(next)
484
+ ) {
485
+ throw new Error(
486
+ `generated version-pin factory '${method}' names incompatible reference contracts; rename one payload field`
487
+ );
488
+ }
489
+ versionedPayloadFactories.set(method, next);
490
+ }
491
+ }
492
+ for (const [method, spec] of [...versionedPayloadFactories].sort(([a], [b]) =>
493
+ a.localeCompare(b)
494
+ )) {
495
+ claimApplicationMember(method, "the generated version-pin factory");
496
+ appLines.push(
497
+ ` /// Pin one immutable publication to its stable series. The current-pointer field is`,
498
+ ` /// compiled from domain law; callers provide typed records, never workspace/id strings.`,
499
+ ` VersionedNomosRef<${spec.target}, ${spec.series}> ${method}({`,
500
+ ` required NomosRef<${spec.targetInput}> pinned,`,
501
+ ` required NomosRef<${spec.seriesInput}> series,`,
502
+ ` }) {`,
503
+ ` if (pinned.workspace != series.workspace) {`,
504
+ ` throw ArgumentError('A version and its stable series must share one workspace');`,
505
+ ` }`,
506
+ ` return NmsInternalVersionedNomosRefs.create<${spec.target}, ${spec.series}>(`,
507
+ ` pinned: NmsInternalNomosRefs.retype<${
508
+ spec.target
509
+ }>(pinned, expectedType: ${JSON.stringify(spec.targetWireType)}),`,
510
+ ` series: NmsInternalNomosRefs.retype<${
511
+ spec.series
512
+ }>(series, expectedType: ${JSON.stringify(spec.seriesWireType)}),`,
513
+ ` currentField: ${JSON.stringify(spec.currentField)},`,
514
+ ` );`,
515
+ ` }`,
516
+ ``
517
+ );
518
+ }
302
519
  const tenantRoot = opts.tenantRoot ?? opts.flutterHost === true;
303
520
  if (tenantRoot) {
304
- for (const [name, owner] of TENANT_ROOT_MEMBERS) claimApplicationMember(name, owner);
521
+ for (const [name, owner] of TENANT_ROOT_MEMBERS)
522
+ claimApplicationMember(name, owner);
305
523
  }
306
524
  if (!tenantRoot && sharingPolicies.length > 0) {
307
525
  for (const name of ["recipient", "createInvitation", "redeemInvitation"]) {
@@ -315,45 +533,76 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
315
533
  };
316
534
  const typeImportUriFor = (name: string): string => {
317
535
  for (const uri of explicitTypeImportUris) {
318
- if (uri.endsWith(`/${name}.dart`) || uri.endsWith(`${name}.dart`)) return uri;
536
+ if (uri.endsWith(`/${name}.dart`) || uri.endsWith(`${name}.dart`))
537
+ return uri;
319
538
  }
320
539
  return `${name}.dart`;
321
540
  };
322
541
 
323
- const actionDeclarations = (mod.applicationIntents ?? mod.directives) as AnyDirective[];
542
+ const actionDeclarations = (mod.applicationIntents ??
543
+ mod.directives) as AnyDirective[];
324
544
  const directives = actionDeclarations.filter(
325
- (directive) => directive && typeof directive.id === "string" &&
326
- typeof (directive as { payloadSchema?: unknown }).payloadSchema === "object" &&
327
- (directive as { businessBirthInternal?: unknown }).businessBirthInternal !== true &&
328
- !isFrameworkDirective(directive),
545
+ (directive) =>
546
+ directive &&
547
+ typeof directive.id === "string" &&
548
+ typeof (directive as { payloadSchema?: unknown }).payloadSchema ===
549
+ "object" &&
550
+ (directive as { businessBirthInternal?: unknown })
551
+ .businessBirthInternal !== true &&
552
+ !isFrameworkDirective(directive)
329
553
  );
330
554
  const actionReferenceChecks = (directive: AnyDirective): string[] => {
331
555
  let specs: ReturnType<typeof introspectPayload>;
332
556
  try {
333
- specs = introspectPayload(directive.payloadSchema as z.ZodTypeAny, new Map());
557
+ specs = introspectPayload(
558
+ directive.payloadSchema as z.ZodTypeAny,
559
+ new Map()
560
+ );
334
561
  } catch {
335
562
  return [];
336
563
  }
337
564
  const recordInputIds = new Set(
338
- dartBusinessRecordInputs(directive, mod.aggregates).map((input) => input.idField),
565
+ dartBusinessRecordInputs(directive, mod.aggregates).map(
566
+ (input) => input.idField
567
+ )
568
+ );
569
+ const foreignReferenceIds = new Set(
570
+ dartForeignReferenceInputs(directive, mod.aggregates).map(
571
+ (input) => input.idField
572
+ )
339
573
  );
340
574
  return specs
341
575
  .filter((spec) => spec.toJson === "ref" && !recordInputIds.has(spec.name))
342
576
  .map((spec) => {
343
- const targetClass = spec.dartType.match(/^NomosRef<(.*)>$/)?.[1] ?? "Object?";
344
- const targetWire = spec.referenceType ?? [...aggregates.values()].find(
345
- (aggregate) => concreteClassName(aggregate.id) === targetClass,
346
- )?.id ?? targetClass;
347
- const value = `facts.${member(spec.name)}${spec.optional ? "!" : ""}`;
348
- const check = `_nmsRequireReference(${value}, ${JSON.stringify(targetWire)}, _client.session?.workspace ?? '');`;
577
+ const targetClass =
578
+ spec.dartType.match(/^NomosRef<(.*)>$/)?.[1] ?? "Object?";
579
+ const targetWire =
580
+ spec.referenceType ??
581
+ [...aggregates.values()].find(
582
+ (aggregate) => concreteClassName(aggregate.id) === targetClass
583
+ )?.id ??
584
+ targetClass;
585
+ const expectedWorkspace = foreignReferenceIds.has(spec.name)
586
+ ? "''"
587
+ : "_client.session?.workspace ?? ''";
588
+ const value = `facts.${member(spec.name)}`;
589
+ const check = `_nmsRequireReference(${value}, ${JSON.stringify(
590
+ targetWire
591
+ )}, ${expectedWorkspace});`;
349
592
  return spec.optional
350
- ? ` if (facts.${member(spec.name)} != null) ${check}`
593
+ ? ` ${value}.when(absent: () {}, present: (reference) => _nmsRequireReference(reference, ${JSON.stringify(
594
+ targetWire
595
+ )}, ${expectedWorkspace}));`
351
596
  : ` ${check}`;
352
597
  });
353
598
  };
354
599
 
355
600
  for (const aggregate of mod.aggregates) {
356
- if ((aggregate as { businessBirthInternal?: unknown }).businessBirthInternal === true) continue;
601
+ if (
602
+ (aggregate as { businessBirthInternal?: unknown })
603
+ .businessBirthInternal === true
604
+ )
605
+ continue;
357
606
  if (isFrameworkAggregate(aggregate)) continue;
358
607
  collectionLines.set(aggregate.id, []);
359
608
  collectionMembers.set(aggregate.id, new Set());
@@ -367,7 +616,10 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
367
616
  const rowClass = `_${cap(camel(domain))}PendingConflictRow`;
368
617
  const byAggregate = new Map<string, SurfacedResolverBinding[]>();
369
618
  for (const binding of resolverBindings) {
370
- byAggregate.set(binding.aggregateId, [...(byAggregate.get(binding.aggregateId) ?? []), binding]);
619
+ byAggregate.set(binding.aggregateId, [
620
+ ...(byAggregate.get(binding.aggregateId) ?? []),
621
+ binding,
622
+ ]);
371
623
  }
372
624
  claimApplicationMember("conflicts", "the generated conflict policy");
373
625
  appLines.push(` final ${conflictsClass} conflicts;`);
@@ -398,17 +650,22 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
398
650
  ` byAggregate: Map.unmodifiable(byAggregate),`,
399
651
  ` );`,
400
652
  ` }`,
401
- ...[...byAggregate.keys()].map((aggregateId) =>
402
- ` ${aggregateConflictsClassName(domain, aggregateId)} get ${member(concreteClassName(aggregateId))} => ${aggregateConflictsClassName(domain, aggregateId)}._(_client);`,
653
+ ...[...byAggregate.keys()].map(
654
+ (aggregateId) =>
655
+ ` ${aggregateConflictsClassName(domain, aggregateId)} get ${member(
656
+ concreteClassName(aggregateId)
657
+ )} => ${aggregateConflictsClassName(domain, aggregateId)}._(_client);`
403
658
  ),
404
659
  `}`,
405
- ``,
660
+ ``
406
661
  );
407
662
 
408
663
  for (const [aggregateId, bindings] of byAggregate) {
409
664
  const aggregate = aggregates.get(aggregateId);
410
665
  if (aggregate === undefined) {
411
- throw new Error(`generated conflict policy refers to unknown aggregate '${aggregateId}' in domain '${domain}'`);
666
+ throw new Error(
667
+ `generated conflict policy refers to unknown aggregate '${aggregateId}' in domain '${domain}'`
668
+ );
412
669
  }
413
670
  const runtimeAggregateType = isNamespacedAggregate(aggregate)
414
671
  ? qualifyReadProjectionType(domain, aggregateId)
@@ -422,18 +679,23 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
422
679
  ` String get aggregateId;`,
423
680
  ` String get field;`,
424
681
  `}`,
425
- ``,
682
+ ``
426
683
  );
427
684
  for (const binding of bindings) {
428
- if (binding.valuePayloadField === undefined || binding.idPayloadField === undefined) continue;
685
+ if (
686
+ binding.valuePayloadField === undefined ||
687
+ binding.idPayloadField === undefined
688
+ )
689
+ continue;
429
690
  const fieldKind = aggregate.fields[binding.field]?.kind;
430
- const mergedType = fieldKind === "string" || fieldKind === "ref" || fieldKind === "enum"
431
- ? "String"
432
- : fieldKind === "int"
691
+ const mergedType =
692
+ fieldKind === "string" || fieldKind === "ref" || fieldKind === "enum"
693
+ ? "String"
694
+ : fieldKind === "int"
433
695
  ? "int"
434
696
  : fieldKind === "bool"
435
- ? "bool"
436
- : "Object?";
697
+ ? "bool"
698
+ : "Object?";
437
699
  const typeName = `${aggregateName}${cap(camel(binding.field))}Conflict`;
438
700
  const evolutionTypeName = `${typeName}ModelUpdateRequired`;
439
701
  const helper = `_merge${aggregateName}${cap(camel(binding.field))}`;
@@ -465,7 +727,7 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
465
727
  ` String get reason => _details.evolution?['reason']?.toString() ??`,
466
728
  ` 'The business model cannot yet compare every retained version. Update its evolution policy; unrelated work can continue.';`,
467
729
  `}`,
468
- ``,
730
+ ``
469
731
  );
470
732
  }
471
733
  conflictDeclarations.push(
@@ -473,13 +735,21 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
473
735
  ` final ${client} _client;`,
474
736
  ` const ${aggregateClass}._(this._client);`,
475
737
  ` Future<List<${pendingClass}>> pending() async {`,
476
- ` final rows = await _client._pendingConflictRows(${JSON.stringify(runtimeAggregateType)});`,
738
+ ` final rows = await _client._pendingConflictRows(${JSON.stringify(
739
+ runtimeAggregateType
740
+ )});`,
477
741
  ` final decisions = <${pendingClass}>[];`,
478
742
  ` for (final row in rows) {`,
479
743
  ` switch (row.field) {`,
480
744
  ...bindings.flatMap((binding) => {
481
- if (binding.valuePayloadField === undefined || binding.idPayloadField === undefined) return [];
482
- const typeName = `${aggregateName}${cap(camel(binding.field))}Conflict`;
745
+ if (
746
+ binding.valuePayloadField === undefined ||
747
+ binding.idPayloadField === undefined
748
+ )
749
+ return [];
750
+ const typeName = `${aggregateName}${cap(
751
+ camel(binding.field)
752
+ )}Conflict`;
483
753
  return [
484
754
  ` case ${JSON.stringify(binding.field)}:`,
485
755
  ` if (row.status == 'ready') {`,
@@ -496,94 +766,110 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
496
766
  ` return List.unmodifiable(decisions);`,
497
767
  ` }`,
498
768
  `}`,
499
- ``,
769
+ ``
500
770
  );
501
771
  }
502
772
  }
503
773
 
504
- const addCollectionMember = (aggregateId: string, name: string, memberLines: string[]) => {
774
+ const addCollectionMember = (
775
+ aggregateId: string,
776
+ name: string,
777
+ memberLines: string[]
778
+ ) => {
505
779
  const target = collectionLines.get(aggregateId);
506
780
  if (target === undefined) return false;
507
781
  const names = collectionMembers.get(aggregateId)!;
508
782
  if (names.has(name)) {
509
- throw new Error(`Dart application surface collision: '${aggregateId}.${name}' is declared more than once`);
783
+ throw new Error(
784
+ `Dart application surface collision: '${aggregateId}.${name}' is declared more than once`
785
+ );
510
786
  }
511
787
  names.add(name);
512
788
  target.push(...memberLines);
513
789
  return true;
514
790
  };
515
791
 
516
- const sourceTrustAction = (directive: AnyDirective): {
792
+ const sourceTrustAction = (
793
+ directive: AnyDirective
794
+ ): {
517
795
  prelude: string[];
518
796
  call: string;
519
797
  } => {
798
+ const factPath = (path: string): string =>
799
+ path
800
+ .split(".")
801
+ .reduce((expr, part) => `${expr}.${member(part)}`, "facts");
520
802
  const trust = directive.businessSourceTrust;
521
803
  const method = member(directive.id);
522
804
  if (trust === undefined) {
523
805
  return { prelude: [], call: `_client.${method}(facts)` };
524
806
  }
525
- const queryArgs = Object.entries(trust.queryArgs)
526
- .sort(([a], [b]) => a.localeCompare(b))
527
- .map(([argument, field]) => `${JSON.stringify(argument)}: facts.${member(field)}`);
528
807
  const copied = Object.entries(trust.copy)
529
808
  .sort(([a], [b]) => a.localeCompare(b))
530
- .map(([target, field]) => ` ${JSON.stringify(target)}: facts.${member(field)},`);
809
+ .map(
810
+ ([target, field]) =>
811
+ ` ${JSON.stringify(target)}: ${factPath(field)},`
812
+ );
531
813
  return {
532
814
  prelude: [
533
- ` final servedSource = await _client.bridge.attestedRead(`,
534
- ` from: facts.${member(trust.fromPayload)},`,
535
- ` queryId: ${JSON.stringify(trust.queryId)},`,
536
- ` args: {${queryArgs.join(", ")}},`,
537
- ` );`,
538
- ` final sourceEntry = NmsInternalSourceTrust.entry(servedSource);`,
539
- ` final sourceIdentity = NmsInternalSourceTrust.attestation(sourceEntry);`,
815
+ ` final sourceGenesis = await _client.bridge.nomosWorkspaceGenesis(${factPath(
816
+ trust.fromPayload
817
+ )});`,
540
818
  ` await _client._offer(`,
541
819
  ` ${JSON.stringify(trust.viaDirectiveId)},`,
542
820
  ` {`,
543
821
  ...copied,
544
- ` ${JSON.stringify(trust.genesisField)}: NmsInternalSourceTrust.genesis(sourceIdentity),`,
545
- ` ${JSON.stringify(trust.keyHashesField)}: [NmsInternalSourceTrust.keyHash(sourceIdentity)],`,
822
+ ` ${JSON.stringify(trust.genesisField)}: sourceGenesis,`,
823
+ ` ${JSON.stringify(trust.keyHashesField)}: [${JSON.stringify(
824
+ "0".repeat(64)
825
+ )}],`,
546
826
  ` },`,
547
827
  ` );`,
548
828
  ],
549
- call: `_client.${method}(facts, attestations: [sourceEntry])`,
829
+ call: `_client.${method}(facts)`,
550
830
  };
551
831
  };
552
832
 
553
833
  if (sharingPolicies.length > 0) {
554
834
  appLines.push(
555
- ` /// Turn an identity-provider uid into a typed business recipient.`,
556
- ` ///`,
557
- ` /// A uid crosses the string boundary once. Generated invitation actions then require`,
558
- ` /// [Recipient<User>], so an email address, principal or record id cannot be substituted.`,
559
- ` Recipient<User> recipient(String uid) =>`,
560
- ` NmsInternalApplicationHost.recipient<User>(uid);`,
561
- ``,
562
- ` /// Create a single-use invitation. Transport [Invitation.link] however your product chooses.`,
563
- ` Future<Invitation<User>> createInvitation() =>`,
564
- ` NmsInternalApplicationHost.createInvitation<User>(_client.bridge);`,
565
- ``,
566
- ` /// Redeem a transported invitation link as the verified [currentUser].`,
567
- ` Future<void> redeemInvitation(String link) =>`,
568
- ` NmsInternalApplicationHost.redeemInvitation<User>(`,
569
- ` _client.bridge,`,
570
- ` link: link,`,
571
- ` identity: _client.actor,`,
572
- ` policies: [`,
573
- ...sharingPolicies.map((policy) => [
835
+ ` /// Turn an identity-provider uid into a typed business recipient.`,
836
+ ` ///`,
837
+ ` /// A uid crosses the string boundary once. Generated invitation actions then require`,
838
+ ` /// [Recipient<User>], so an email address, principal or record id cannot be substituted.`,
839
+ ` Recipient<User> recipient(String uid) =>`,
840
+ ` NmsInternalApplicationHost.recipient<User>(uid);`,
841
+ ``,
842
+ ` /// Create a single-use invitation. Transport [Invitation.link] however your product chooses.`,
843
+ ` Future<Invitation<User>> createInvitation() =>`,
844
+ ` NmsInternalApplicationHost.createInvitation<User>(_client.bridge);`,
845
+ ``,
846
+ ` /// Redeem a transported invitation link as the verified [currentUser].`,
847
+ ` Future<void> redeemInvitation(String link) =>`,
848
+ ` NmsInternalApplicationHost.redeemInvitation<User>(`,
849
+ ` _client.bridge,`,
850
+ ` link: link,`,
851
+ ` identity: _client.actor,`,
852
+ ` policies: [`,
853
+ ...sharingPolicies
854
+ .map((policy) => [
574
855
  ` NmsInternalInvitationPolicy(`,
575
856
  ` domain: ${JSON.stringify(domain)},`,
576
857
  ` domainHash: _client.domainHash,`,
577
- ` query: ${JSON.stringify(policy.invitationLaw.pendingByRecipient.id)},`,
578
- ` redeem: ${JSON.stringify(policy.invitationLaw.redeem.id)},`,
858
+ ` query: ${JSON.stringify(
859
+ policy.invitationLaw.pendingByRecipient.id
860
+ )},`,
861
+ ` redeem: ${JSON.stringify(
862
+ policy.invitationLaw.redeem.id
863
+ )},`,
579
864
  ` personField: ${JSON.stringify(policy.person)},`,
580
865
  ` roleField: ${JSON.stringify(policy.role)},`,
581
866
  ` scopeField: ${JSON.stringify(policy.scope)},`,
582
867
  ` ),`,
583
- ]).flat(),
584
- ` ],`,
585
- ` );`,
586
- ``,
868
+ ])
869
+ .flat(),
870
+ ` ],`,
871
+ ` );`,
872
+ ``
587
873
  );
588
874
  for (const [index, policy] of sharingPolicies.entries()) {
589
875
  const pendingKey = policy.pending.key[0]!;
@@ -591,7 +877,10 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
591
877
  const person = sharingOrderValue(policy, policy.person, "wire");
592
878
  const role = sharingOrderValue(policy, policy.role, "wire");
593
879
  const scope = sharingOrderValue(policy, policy.scope, "wire");
594
- const receiptLines = (receipt: WorkspaceSharingReceipt, refused: boolean): string[] => {
880
+ const receiptLines = (
881
+ receipt: WorkspaceSharingReceipt,
882
+ refused: boolean
883
+ ): string[] => {
595
884
  const condition = sharingReceiptCondition(policy, receipt);
596
885
  const body = [
597
886
  ` await _client._offer(`,
@@ -609,10 +898,18 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
609
898
  ? body
610
899
  : [` if (${condition}) {`, ...body, ` }`];
611
900
  };
612
- const delivered = policy.delivered.flatMap((receipt) => receiptLines(receipt, false));
613
- const refused = policy.refused.flatMap((receipt) => receiptLines(receipt, true));
614
- const deliveredExhaustive = policy.delivered.some((receipt) => sharingReceiptCondition(policy, receipt) === "true");
615
- const refusedExhaustive = policy.refused.some((receipt) => sharingReceiptCondition(policy, receipt) === "true");
901
+ const delivered = policy.delivered.flatMap((receipt) =>
902
+ receiptLines(receipt, false)
903
+ );
904
+ const refused = policy.refused.flatMap((receipt) =>
905
+ receiptLines(receipt, true)
906
+ );
907
+ const deliveredExhaustive = policy.delivered.some(
908
+ (receipt) => sharingReceiptCondition(policy, receipt) === "true"
909
+ );
910
+ const refusedExhaustive = policy.refused.some(
911
+ (receipt) => sharingReceiptCondition(policy, receipt) === "true"
912
+ );
616
913
  appLines.push(
617
914
  ` Future<void>? _nmsWorkspaceSharingDrain${index};`,
618
915
  ` StreamSubscription<Object?>? _nmsWorkspaceSharingSync${index};`,
@@ -662,34 +959,46 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
662
959
  ...delivered,
663
960
  ...(deliveredExhaustive
664
961
  ? []
665
- : [` throw StateError('No delivered receipt matches ${policy.orders.id} \${NmsInternalNomosRefs.id(order.ref)}');`]),
962
+ : [
963
+ ` throw StateError('No delivered receipt matches ${policy.orders.id} \${NmsInternalNomosRefs.id(order.ref)}');`,
964
+ ]),
666
965
  ` }`,
667
966
  ...refused,
668
967
  ...(refusedExhaustive
669
968
  ? []
670
- : [` throw StateError('No refused receipt matches ${policy.orders.id} \${NmsInternalNomosRefs.id(order.ref)}');`]),
969
+ : [
970
+ ` throw StateError('No refused receipt matches ${policy.orders.id} \${NmsInternalNomosRefs.id(order.ref)}');`,
971
+ ]),
671
972
  ` }`,
672
973
  ` }`,
673
- ``,
974
+ ``
674
975
  );
675
976
  }
676
977
  appLines.push(
677
978
  ` Future<void> _nmsStartWorkspaceSharing() async {`,
678
- ...sharingPolicies.map((_policy, index) => ` await _nmsStartWorkspaceSharing${index}();`),
979
+ ...sharingPolicies.map(
980
+ (_policy, index) => ` await _nmsStartWorkspaceSharing${index}();`
981
+ ),
679
982
  ` }`,
680
983
  ``,
681
984
  ` Future<void> _nmsDrainWorkspaceSharing() async {`,
682
- ...sharingPolicies.map((_policy, index) => ` await _nmsDrainWorkspaceSharing${index}();`),
985
+ ...sharingPolicies.map(
986
+ (_policy, index) => ` await _nmsDrainWorkspaceSharing${index}();`
987
+ ),
683
988
  ` }`,
684
- ``,
989
+ ``
685
990
  );
686
991
  }
687
992
 
688
993
  const sharingInitiatorIds = new Set(
689
- sharingPolicies.flatMap((policy) => policy.initiatedBy.map((intent) => intent.id)),
994
+ sharingPolicies.flatMap((policy) =>
995
+ policy.initiatedBy.map((intent) => intent.id)
996
+ )
690
997
  );
691
998
  const afterBusinessWrite = (directive: AnyDirective): string[] =>
692
- sharingInitiatorIds.has(directive.id) ? [` await _nmsDrainWorkspaceSharing();`] : [];
999
+ sharingInitiatorIds.has(directive.id)
1000
+ ? [` await _nmsDrainWorkspaceSharing();`]
1001
+ : [];
693
1002
  const businessActionDoc = (directive: AnyDirective): string => {
694
1003
  return sharingInitiatorIds.has(directive.id)
695
1004
  ? "One business action. Nomos durably delivers its access change and retries after restart or reconnect; do not mirror delivery in application state."
@@ -698,15 +1007,17 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
698
1007
 
699
1008
  const birthCollectionsWithMine = new Set<string>();
700
1009
  for (const directive of directives) {
701
- const birth = (directive as unknown as {
702
- businessBirth?: {
703
- aggregateId: string;
704
- namespace: string;
705
- rootId: string;
706
- keyField: string;
707
- };
708
- birthsChild?: boolean;
709
- }).businessBirth;
1010
+ const birth = (
1011
+ directive as unknown as {
1012
+ businessBirth?: {
1013
+ aggregateId: string;
1014
+ namespace: string;
1015
+ rootId: string;
1016
+ keyField: string;
1017
+ };
1018
+ birthsChild?: boolean;
1019
+ }
1020
+ ).businessBirth;
710
1021
  if (birth !== undefined) {
711
1022
  // A business birth is declared on the parent domain but its aggregate
712
1023
  // belongs to the child domain. Keep a generated collection for that
@@ -723,9 +1034,13 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
723
1034
  const opener = `nomosInternalOpen${targetApplication}`;
724
1035
  const rootAggregate = aggregates.get(birth.aggregateId);
725
1036
  const rootType = concreteClassName(birth.aggregateId);
726
- const rootCollection = member(plural(rootAggregate === undefined
727
- ? birth.aggregateId
728
- : concreteClassName(rootAggregate.id)));
1037
+ const rootCollection = member(
1038
+ plural(
1039
+ rootAggregate === undefined
1040
+ ? birth.aggregateId
1041
+ : concreteClassName(rootAggregate.id)
1042
+ )
1043
+ );
729
1044
  if (targetFile !== mod.name) {
730
1045
  addSibling(`${targetFile}.application.dart`, targetApplication);
731
1046
  addSibling(`${targetFile}.application.dart`, opener);
@@ -735,21 +1050,27 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
735
1050
  const method = member(directive.id);
736
1051
  claimApplicationMember(method, `business intent '${directive.id}'`);
737
1052
  appLines.push(
738
- ` /// ${cap(camel(directive.id))}. One business action; the new ${birth.aggregateId} and every parent consequence commit atomically.`,
1053
+ ` /// ${cap(camel(directive.id))}. One business action; the new ${
1054
+ birth.aggregateId
1055
+ } and every parent consequence commit atomically.`,
739
1056
  ` /// Canonical write path: pass business facts here; never mirror this action in application state`,
740
1057
  ` /// or call a lower-level client.`,
741
- ` Future<${targetApplication}> ${method}(${dartActionInputClassName(directive)} facts) async {`,
1058
+ ` Future<${targetApplication}> ${method}(${dartActionInputClassName(
1059
+ directive
1060
+ )} facts) async {`,
742
1061
  ` final reference = await _client.${method}(facts);`,
743
1062
  opts.supportImport === "package"
744
1063
  ? ` return ${rootCollection}._nmsOpenReference(reference);`
745
1064
  : ` return ${opener}(_client.bridge, reference, _client.actor);`,
746
1065
  ` }`,
747
- ``,
1066
+ ``
748
1067
  );
749
1068
  } else {
750
1069
  addCollectionMember(birth.aggregateId, "birth", [
751
1070
  ` /// Give one ${birth.aggregateId} its own durable life.`,
752
- ` Future<${targetApplication}> birth(Create${cap(camel(birth.aggregateId))}Input facts) async {`,
1071
+ ` Future<${targetApplication}> birth(Create${cap(
1072
+ camel(birth.aggregateId)
1073
+ )}Input facts) async {`,
753
1074
  ` final reference = await _client.birth(facts);`,
754
1075
  opts.supportImport === "package"
755
1076
  ? ` return _nmsOpenReference(reference);`
@@ -760,9 +1081,10 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
760
1081
  }
761
1082
  if (opts.supportImport === "package") {
762
1083
  usesWorkspaceName = true;
763
- const rootModel = rootAggregate === undefined
764
- ? `${concreteClassName(birth.aggregateId)}ReadModel`
765
- : readModelClassName(rootAggregate.id);
1084
+ const rootModel =
1085
+ rootAggregate === undefined
1086
+ ? `${concreteClassName(birth.aggregateId)}ReadModel`
1087
+ : readModelClassName(rootAggregate.id);
766
1088
  addCollectionMember(birth.aggregateId, "open", [
767
1089
  ` final Map<String, Future<${targetApplication}>> _nmsApplications = {};`,
768
1090
  ``,
@@ -788,7 +1110,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
788
1110
  ` /// Open the ${birth.aggregateId} whose ${birth.keyField} is [key].`,
789
1111
  ` Future<${targetApplication}> open(String key) => _nmsOpenReference(`,
790
1112
  ` NmsInternalNomosRefs.create<${rootType}>(`,
791
- ` workspace: deriveWorkspaceName(${JSON.stringify(birth.namespace)}, key),`,
1113
+ ` workspace: deriveWorkspaceName(${JSON.stringify(
1114
+ birth.namespace
1115
+ )}, key),`,
792
1116
  ` type: ${JSON.stringify(birth.aggregateId)},`,
793
1117
  ` id: ${JSON.stringify(birth.rootId)},`,
794
1118
  ` ),`,
@@ -812,8 +1136,12 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
812
1136
  ` final raw = row['data'];`,
813
1137
  ` if (raw is! Map) continue;`,
814
1138
  ` final child = (raw['child'] ?? '').toString();`,
815
- ` if (!child.startsWith(${JSON.stringify(`${birth.namespace}-`)})) continue;`,
816
- ` final reference = NmsInternalNomosRefs.create<${rootType}>(workspace: child, type: ${JSON.stringify(birth.aggregateId)}, id: ${JSON.stringify(birth.rootId)});`,
1139
+ ` if (!child.startsWith(${JSON.stringify(
1140
+ `${birth.namespace}-`
1141
+ )})) continue;`,
1142
+ ` final reference = NmsInternalNomosRefs.create<${rootType}>(workspace: child, type: ${JSON.stringify(
1143
+ birth.aggregateId
1144
+ )}, id: ${JSON.stringify(birth.rootId)});`,
817
1145
  ` final application = await _nmsOpenReference(reference);`,
818
1146
  ` final record = await application.${rootCollection}.find(reference);`,
819
1147
  ` if (record != null) records.add(record);`,
@@ -840,18 +1168,34 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
840
1168
  ` for (final record in records) {`,
841
1169
  ` seen.add(NmsInternalRecordOrigins.require(record).workspace);`,
842
1170
  ` }`,
843
- ` final shares = await _client.bridge.query('aggregatesByType', {'__type': 'SharedWithMe'}, null, _client.actor);`,
1171
+ ` for (final record in await shared()) {`,
1172
+ ` final workspace = NmsInternalRecordOrigins.require(record).workspace;`,
1173
+ ` if (seen.add(workspace)) records.add(record);`,
1174
+ ` }`,
1175
+ ` return records;`,
1176
+ ` }`,
1177
+ ``,
1178
+ ` /// The ${birth.aggregateId} lives shared with the signed-in person.`,
1179
+ ` /// Unlike [available], this does not open lives founded by that person.`,
1180
+ ` Future<List<${rootModel}>> shared() async {`,
1181
+ ` final records = <${rootModel}>[];`,
1182
+ ` final seen = <String>{};`,
1183
+ ` final shares = await _client.bridge.query('sharedByObject', {'object': 'workspace:self'}, null, _client.actor);`,
844
1184
  ` for (final row in shares) {`,
845
1185
  ` final raw = row['data'];`,
846
1186
  ` if (raw is! Map) continue;`,
847
1187
  ` final workspace = (raw['asserterWorkspace'] ?? '').toString();`,
848
1188
  ` final object = (raw['object'] ?? '').toString();`,
849
1189
  ` final prunedAt = (raw['prunedAt'] ?? '').toString();`,
850
- ` if (!workspace.startsWith(${JSON.stringify(`${birth.namespace}-`)}) ||`,
1190
+ ` if (!workspace.startsWith(${JSON.stringify(
1191
+ `${birth.namespace}-`
1192
+ )}) ||`,
851
1193
  ` object != 'workspace:self' ||`,
852
1194
  ` prunedAt.isNotEmpty ||`,
853
1195
  ` !seen.add(workspace)) continue;`,
854
- ` final reference = NmsInternalNomosRefs.create<${rootType}>(workspace: workspace, type: ${JSON.stringify(birth.aggregateId)}, id: ${JSON.stringify(birth.rootId)});`,
1196
+ ` final reference = NmsInternalNomosRefs.create<${rootType}>(workspace: workspace, type: ${JSON.stringify(
1197
+ birth.aggregateId
1198
+ )}, id: ${JSON.stringify(birth.rootId)});`,
855
1199
  ` final application = await _nmsOpenReference(reference);`,
856
1200
  ` final record = await application.${rootCollection}.find(reference);`,
857
1201
  ` if (record != null) records.add(record);`,
@@ -859,17 +1203,56 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
859
1203
  ` return records;`,
860
1204
  ` }`,
861
1205
  ``,
1206
+ ` /// React when this person's shared ${birth.aggregateId} lives change.`,
1207
+ ` Stream<List<${rootModel}>> watchShared() =>`,
1208
+ ` _client.bridge`,
1209
+ ` .watch('sharedByObject', {'object': 'workspace:self'}, null, _client.actor, 60000)`,
1210
+ ` .asyncMap((_) => shared());`,
1211
+ ``,
862
1212
  ` /// React when this person's available ${birth.aggregateId} lives change.`,
863
1213
  ` Stream<List<${rootModel}>> watchAvailable() =>`,
864
- ` _client.bridge`,
865
- ` .watch('aggregatesByType', {'__type': 'SharedWithMe'}, null, _client.actor)`,
866
- ` .asyncMap((_) => available());`,
1214
+ ` Stream<List<${rootModel}>>.multi((events) {`,
1215
+ ` var reading = false;`,
1216
+ ` var reread = false;`,
1217
+ ` Future<void> refresh() async {`,
1218
+ ` if (reading) {`,
1219
+ ` reread = true;`,
1220
+ ` return;`,
1221
+ ` }`,
1222
+ ` reading = true;`,
1223
+ ` do {`,
1224
+ ` reread = false;`,
1225
+ ` try {`,
1226
+ ` final records = await available();`,
1227
+ ` if (!events.isClosed) events.add(records);`,
1228
+ ` } catch (error, stackTrace) {`,
1229
+ ` if (!events.isClosed) events.addError(error, stackTrace);`,
1230
+ ` }`,
1231
+ ` } while (reread && !events.isClosed);`,
1232
+ ` reading = false;`,
1233
+ ` }`,
1234
+ ` final birthChanges = _client.bridge`,
1235
+ ` .watch('birthRecordsByPrincipal', {'principal': _client.actor}, null, null, 60000)`,
1236
+ ` .skip(1)`,
1237
+ ` .listen((_) { refresh(); }, onError: events.addError);`,
1238
+ ` final shareChanges = _client.bridge`,
1239
+ ` .watch('sharedByObject', {'object': 'workspace:self'}, null, _client.actor, 60000)`,
1240
+ ` .skip(1)`,
1241
+ ` .listen((_) { refresh(); }, onError: events.addError);`,
1242
+ ` events.onCancel = () async {`,
1243
+ ` await birthChanges.cancel();`,
1244
+ ` await shareChanges.cancel();`,
1245
+ ` };`,
1246
+ ` refresh();`,
1247
+ ` });`,
867
1248
  ``,
868
1249
  ` /// Continue working in the business application that produced [record].`,
869
1250
  ` Future<${targetApplication}> applicationOf(${rootModel} record) {`,
870
1251
  ` final origin = NmsInternalRecordOrigins.require(record);`,
871
1252
  ` return _nmsOpenReference(`,
872
- ` NmsInternalNomosRefs.create<${rootType}>(workspace: origin.workspace, type: ${JSON.stringify(birth.aggregateId)}, id: origin.id),`,
1253
+ ` NmsInternalNomosRefs.create<${rootType}>(workspace: origin.workspace, type: ${JSON.stringify(
1254
+ birth.aggregateId
1255
+ )}, id: origin.id),`,
873
1256
  ` );`,
874
1257
  ` }`,
875
1258
  ``,
@@ -889,24 +1272,42 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
889
1272
  if (outputs.length > 0) {
890
1273
  usesAffectedReference = true;
891
1274
  usesAffectedReferences ||= outputs.some((output) => output.many);
1275
+ usesAffectedReferenceOrNull ||= outputs.some((output) => output.optional);
892
1276
  actionResultDeclarations.push(
893
1277
  `/// Every business record affected by ${cap(camel(directive.id))}.`,
894
1278
  `final class ${resultClass} {`,
895
1279
  ...outputs.map((output) =>
896
1280
  output.many
897
1281
  ? ` final List<NomosRef<${output.dartType}>> ${output.field};`
898
- : ` final NomosRef<${output.dartType}> ${output.field};`),
899
- ` const ${resultClass}({${outputs.map((output) => `required this.${output.field}`).join(", ")}});`,
1282
+ : output.optional
1283
+ ? ` final NomosRef<${output.dartType}>? ${output.field};`
1284
+ : ` final NomosRef<${output.dartType}> ${output.field};`
1285
+ ),
1286
+ ` const ${resultClass}({${outputs
1287
+ .map((output) => `required this.${output.field}`)
1288
+ .join(", ")}});`,
900
1289
  `}`,
901
- ``,
1290
+ ``
902
1291
  );
903
1292
  }
904
- const resultExpression = outputs.length === 0
905
- ? undefined
906
- : `${resultClass}(${outputs.map((output) =>
907
- output.many
908
- ? `${output.field}: _nmsAffectedReferences<${output.dartType}>(change, ${JSON.stringify(output.aggregateId)})`
909
- : `${output.field}: _nmsAffectedReference<${output.dartType}>(change, ${JSON.stringify(output.aggregateId)})`).join(", ")})`;
1293
+ const resultExpression =
1294
+ outputs.length === 0
1295
+ ? undefined
1296
+ : `${resultClass}(${outputs
1297
+ .map((output) =>
1298
+ output.many
1299
+ ? `${output.field}: _nmsAffectedReferences<${
1300
+ output.dartType
1301
+ }>(change, ${JSON.stringify(output.aggregateId)})`
1302
+ : output.optional
1303
+ ? `${output.field}: _nmsAffectedReferenceOrNull<${
1304
+ output.dartType
1305
+ }>(change, ${JSON.stringify(output.aggregateId)})`
1306
+ : `${output.field}: _nmsAffectedReference<${
1307
+ output.dartType
1308
+ }>(change, ${JSON.stringify(output.aggregateId)})`
1309
+ )
1310
+ .join(", ")})`;
910
1311
  if (isBusinessIntent(directive)) {
911
1312
  claimApplicationMember(method, `business intent '${directive.id}'`);
912
1313
  const sourceTrust = sourceTrustAction(directive);
@@ -922,7 +1323,7 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
922
1323
  ...afterBusinessWrite(directive),
923
1324
  ` return ${resultExpression};`,
924
1325
  ` }`,
925
- ``,
1326
+ ``
926
1327
  );
927
1328
  } else {
928
1329
  appLines.push(
@@ -935,27 +1336,25 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
935
1336
  ` await ${sourceTrust.call};`,
936
1337
  ...afterBusinessWrite(directive),
937
1338
  ` }`,
938
- ``,
1339
+ ``
939
1340
  );
940
1341
  }
941
1342
  continue;
942
1343
  }
943
1344
  const sourceTrust = sourceTrustAction(directive);
944
1345
  if (resultExpression !== undefined) {
945
- addCollectionMember(
946
- directive.aggregateId,
947
- method,
948
- [
949
- ` /// ${cap(camel(directive.id))}. Returns every affected typed business record reference.`,
950
- ` Future<${resultClass}> ${method}(${payload} facts) async {`,
951
- ...referenceChecks,
952
- ...sourceTrust.prelude,
953
- ` final change = await ${sourceTrust.call};`,
954
- ` return ${resultExpression};`,
955
- ` }`,
956
- ``,
957
- ],
958
- );
1346
+ addCollectionMember(directive.aggregateId, method, [
1347
+ ` /// ${cap(
1348
+ camel(directive.id)
1349
+ )}. Returns every affected typed business record reference.`,
1350
+ ` Future<${resultClass}> ${method}(${payload} facts) async {`,
1351
+ ...referenceChecks,
1352
+ ...sourceTrust.prelude,
1353
+ ` final change = await ${sourceTrust.call};`,
1354
+ ` return ${resultExpression};`,
1355
+ ` }`,
1356
+ ``,
1357
+ ]);
959
1358
  } else {
960
1359
  addCollectionMember(directive.aggregateId, method, [
961
1360
  ` /// ${cap(camel(directive.id))}.`,
@@ -972,7 +1371,8 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
972
1371
  const publicQueries = ((mod.queries ?? []) as QueryDecl[]).filter(
973
1372
  (candidate) =>
974
1373
  !isFrameworkQuery(candidate) &&
975
- (candidate as { businessBirthInternal?: unknown }).businessBirthInternal !== true,
1374
+ (candidate as { businessBirthInternal?: unknown })
1375
+ .businessBirthInternal !== true
976
1376
  );
977
1377
  const signedInSubjectKey = (query: QueryDecl): string | undefined => {
978
1378
  if (query.key.length !== 1) return undefined;
@@ -985,31 +1385,46 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
985
1385
  if (signedInSubjectKey(query) !== undefined) {
986
1386
  mineQueriesByAggregate.set(
987
1387
  query.returns,
988
- (mineQueriesByAggregate.get(query.returns) ?? 0) + 1,
1388
+ (mineQueriesByAggregate.get(query.returns) ?? 0) + 1
989
1389
  );
990
1390
  }
991
1391
  }
992
1392
  for (const query of publicQueries) {
993
1393
  const model = readModelClassName(query.returns);
994
1394
  const aggregate = aggregates.get(query.returns);
995
- const params = query.key.map((key) => `required ${keyType(aggregate, key)} ${keyParam(key)}`).join(", ");
996
- const args = query.key.map((key) => `${keyParam(key)}: ${keyParam(key)}`).join(", ");
1395
+ const params = query.key
1396
+ .map((key) => `required ${keyType(aggregate, key)} ${keyParam(key)}`)
1397
+ .join(", ");
1398
+ const args = query.key
1399
+ .map((key) => `${keyParam(key)}: ${keyParam(key)}`)
1400
+ .join(", ");
997
1401
  const method = member(query.id);
998
1402
  addCollectionMember(query.returns, method, [
999
1403
  ` Future<List<${model}>> ${method}({${params}}) => _client.${method}(${args});`,
1000
- ` Stream<List<${model}>> watch${cap(camel(query.id))}({${params}}) => _client.watch${cap(camel(query.id))}(${args});`,
1404
+ ` Stream<List<${model}>> watch${cap(
1405
+ camel(query.id)
1406
+ )}({${params}}) => _client.watch${cap(camel(query.id))}(${args});`,
1001
1407
  ``,
1002
1408
  ]);
1003
1409
  const subjectKey = signedInSubjectKey(query);
1004
- if (subjectKey !== undefined && !birthCollectionsWithMine.has(query.returns)) {
1410
+ if (
1411
+ subjectKey !== undefined &&
1412
+ !birthCollectionsWithMine.has(query.returns)
1413
+ ) {
1005
1414
  const subjectParam = keyParam(subjectKey);
1006
1415
  const subjectQueryCount = mineQueriesByAggregate.get(query.returns) ?? 0;
1007
1416
  const role = cap(camel(subjectKey.replace(/Subject$/, "") || "subject"));
1008
1417
  const mineMethod = subjectQueryCount === 1 ? "mine" : `mineAs${role}`;
1009
- const watchMineMethod = subjectQueryCount === 1 ? "watchMine" : `watchMineAs${role}`;
1010
- const relationship = subjectQueryCount === 1 ? "belonging to" : `where they are the ${role.toLowerCase()}`;
1418
+ const watchMineMethod =
1419
+ subjectQueryCount === 1 ? "watchMine" : `watchMineAs${role}`;
1420
+ const relationship =
1421
+ subjectQueryCount === 1
1422
+ ? "belonging to"
1423
+ : `where they are the ${role.toLowerCase()}`;
1011
1424
  addCollectionMember(query.returns, mineMethod, [
1012
- ` /// ${cap(camel(query.returns))} records ${relationship} the signed-in person.`,
1425
+ ` /// ${cap(
1426
+ camel(query.returns)
1427
+ )} records ${relationship} the signed-in person.`,
1013
1428
  ` Future<List<${model}>> ${mineMethod}() {`,
1014
1429
  ` final subject = _client.actor;`,
1015
1430
  ` if (subject == null || subject.trim().isEmpty) {`,
@@ -1023,7 +1438,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1023
1438
  ` if (subject == null || subject.trim().isEmpty) {`,
1024
1439
  ` throw StateError('The signed-in person is not available.');`,
1025
1440
  ` }`,
1026
- ` return _client.watch${cap(camel(query.id))}(${subjectParam}: subject);`,
1441
+ ` return _client.watch${cap(
1442
+ camel(query.id)
1443
+ )}(${subjectParam}: subject);`,
1027
1444
  ` }`,
1028
1445
  ``,
1029
1446
  ]);
@@ -1036,7 +1453,8 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1036
1453
  for (const aggregate of mod.aggregates.filter(
1037
1454
  (candidate) =>
1038
1455
  !isFrameworkAggregate(candidate) &&
1039
- (candidate as { businessBirthInternal?: unknown }).businessBirthInternal !== true,
1456
+ (candidate as { businessBirthInternal?: unknown })
1457
+ .businessBirthInternal !== true
1040
1458
  )) {
1041
1459
  const sourceModel = readModelClassName(aggregate.id);
1042
1460
  for (const contractId of aggregate.implementsContracts ?? []) {
@@ -1049,7 +1467,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1049
1467
  ` /// the contract that this aggregate explicitly implements. This does not grant access.`,
1050
1468
  ` NomosRef<${contract}> ${method}(${sourceModel} record) {`,
1051
1469
  ` final reference = record.ref;`,
1052
- ` _nmsRequireReference(reference, ${JSON.stringify(aggregate.id)}, '');`,
1470
+ ` _nmsRequireReference(reference, ${JSON.stringify(
1471
+ aggregate.id
1472
+ )}, '');`,
1053
1473
  ` return NmsInternalNomosRefs.create<${contract}>(`,
1054
1474
  ` workspace: reference.workspace,`,
1055
1475
  ` type: ${JSON.stringify(contractId)},`,
@@ -1065,8 +1485,12 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1065
1485
  ` /// Use this after following a generated cross-application reference and opening`,
1066
1486
  ` /// its business application. Nomos checks the declared contract and access on read.`,
1067
1487
  ` Future<${sourceModel}?> ${resolveMethod}(ForeignRef<${contract}> reference) {`,
1068
- ` _nmsRequireReference(reference.ref, ${JSON.stringify(contractId)}, '');`,
1069
- ` return find(NmsInternalNomosRefs.create<${concreteClassName(aggregate.id)}>(`,
1488
+ ` _nmsRequireReference(reference.ref, ${JSON.stringify(
1489
+ contractId
1490
+ )}, '');`,
1491
+ ` return find(NmsInternalNomosRefs.create<${concreteClassName(
1492
+ aggregate.id
1493
+ )}>(`,
1070
1494
  ` workspace: reference.workspace,`,
1071
1495
  ` type: ${JSON.stringify(aggregate.id)},`,
1072
1496
  ` id: reference.id,`,
@@ -1078,8 +1502,12 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1078
1502
  addCollectionMember(aggregate.id, watchMethod, [
1079
1503
  ` /// React when the concrete record behind [reference] changes.`,
1080
1504
  ` Stream<${sourceModel}?> ${watchMethod}(ForeignRef<${contract}> reference) {`,
1081
- ` _nmsRequireReference(reference.ref, ${JSON.stringify(contractId)}, '');`,
1082
- ` return watchOne(NmsInternalNomosRefs.create<${concreteClassName(aggregate.id)}>(`,
1505
+ ` _nmsRequireReference(reference.ref, ${JSON.stringify(
1506
+ contractId
1507
+ )}, '');`,
1508
+ ` return watchOne(NmsInternalNomosRefs.create<${concreteClassName(
1509
+ aggregate.id
1510
+ )}>(`,
1083
1511
  ` workspace: reference.workspace,`,
1084
1512
  ` type: ${JSON.stringify(aggregate.id)},`,
1085
1513
  ` id: reference.id,`,
@@ -1088,20 +1516,31 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1088
1516
  ``,
1089
1517
  ]);
1090
1518
  }
1091
- const sameDomainRefs = Object.entries(aggregate.fields).filter(([, field]) =>
1092
- field.kind === "ref" &&
1093
- field.foreignRef === undefined &&
1094
- field.refAggregateId !== undefined &&
1095
- field.refAggregateId !== aggregate.id &&
1096
- aggregates.has(field.refAggregateId),
1519
+ const sameDomainRefs = Object.entries(aggregate.fields).filter(
1520
+ ([, field]) =>
1521
+ field.kind === "ref" &&
1522
+ field.foreignRef === undefined &&
1523
+ field.refAggregateId !== undefined &&
1524
+ field.refAggregateId !== aggregate.id &&
1525
+ aggregates.has(field.refAggregateId)
1526
+ );
1527
+ const crossDomainRefs = Object.entries(aggregate.fields).filter(
1528
+ ([, field]) => {
1529
+ if (
1530
+ field.kind !== "ref" ||
1531
+ field.foreignRef !== undefined ||
1532
+ field.refAggregateId === undefined
1533
+ )
1534
+ return false;
1535
+ if (aggregates.has(field.refAggregateId)) return false;
1536
+ const owner = packageDomains[field.refAggregateId];
1537
+ return owner !== undefined && owner.domain !== domain;
1538
+ }
1097
1539
  );
1098
- const crossDomainRefs = Object.entries(aggregate.fields).filter(([, field]) => {
1099
- if (field.kind !== "ref" || field.foreignRef !== undefined || field.refAggregateId === undefined) return false;
1100
- if (aggregates.has(field.refAggregateId)) return false;
1101
- const owner = packageDomains[field.refAggregateId];
1102
- return owner !== undefined && owner.domain !== domain;
1103
- });
1104
- for (const [fieldName, field] of Object.entries(aggregate.fields) as [string, Field][]) {
1540
+ for (const [fieldName, field] of Object.entries(aggregate.fields) as [
1541
+ string,
1542
+ Field
1543
+ ][]) {
1105
1544
  const foreign = field.foreignRef;
1106
1545
  if (field.kind !== "ref") continue;
1107
1546
  if (foreign === undefined) {
@@ -1116,13 +1555,19 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1116
1555
  const sourceFieldValue = admitsNull(field)
1117
1556
  ? `record.${sourceFieldMember}.valueOrNull`
1118
1557
  : `record.${sourceFieldMember}`;
1119
- const isSameDomain = sameDomainRefs.some(([name]) => name === fieldName);
1120
- const isCrossDomain = crossDomainRefs.some(([name]) => name === fieldName);
1558
+ const isSameDomain = sameDomainRefs.some(
1559
+ ([name]) => name === fieldName
1560
+ );
1561
+ const isCrossDomain = crossDomainRefs.some(
1562
+ ([name]) => name === fieldName
1563
+ );
1121
1564
  if (!isSameDomain && !isCrossDomain) continue;
1122
1565
 
1123
1566
  if (isSameDomain) {
1124
1567
  const targetModel = readModelClassName(targetAggregateId);
1125
- const targetById = safe(`${lcFirst(camel(concreteClassName(targetAggregateId)))}ById`);
1568
+ const targetById = safe(
1569
+ `${lcFirst(camel(concreteClassName(targetAggregateId)))}ById`
1570
+ );
1126
1571
  addCollectionMember(aggregate.id, method, [
1127
1572
  ` /// Resolve [record]'s ${fieldName} inside this business application.`,
1128
1573
  ` Future<${targetModel}?> ${method}(${sourceModel} record) async {`,
@@ -1132,7 +1577,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1132
1577
  ` if (id == null) return null;`,
1133
1578
  ` final rows = await _client.${targetById}(NmsInternalNomosRefs.id(id));`,
1134
1579
  ]
1135
- : [` final rows = await _client.${targetById}(NmsInternalNomosRefs.id(record.${sourceFieldMember}));`]),
1580
+ : [
1581
+ ` final rows = await _client.${targetById}(NmsInternalNomosRefs.id(record.${sourceFieldMember}));`,
1582
+ ]),
1136
1583
  ` return rows.isEmpty ? null : rows.first;`,
1137
1584
  ` }`,
1138
1585
  ``,
@@ -1143,13 +1590,18 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1143
1590
  const owner = packageDomains[targetAggregateId];
1144
1591
  if (owner === undefined) continue;
1145
1592
  const targetModel = readModelClassName(targetAggregateId);
1146
- const targetCollection = member(plural(concreteClassName(targetAggregateId)));
1593
+ const targetCollection = member(
1594
+ plural(concreteClassName(targetAggregateId))
1595
+ );
1147
1596
  const targetApplication = dartApplicationClassName(owner.domain);
1148
1597
  const opener = `nomosInternalOpen${targetApplication}`;
1149
1598
  // Flat output needs the sibling type file. Layered output's ordinary
1150
1599
  // types barrel already exports every domain; the explicit-barrel mode
1151
1600
  // supplies direct package imports in explicitTypeImportUris.
1152
- if (opts.typesImportUri === undefined || explicitTypeImportUris.size > 0) {
1601
+ if (
1602
+ opts.typesImportUri === undefined ||
1603
+ explicitTypeImportUris.size > 0
1604
+ ) {
1153
1605
  explicitTypeImportUris.add(typeImportUriFor(owner.name));
1154
1606
  }
1155
1607
  addSibling(`${owner.name}.application.dart`, targetApplication);
@@ -1211,14 +1663,17 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1211
1663
  ]);
1212
1664
  continue;
1213
1665
  }
1214
- const target = foreign.targetIsContract === true
1215
- ? contractTypeName(foreign.targetAggregateId)
1216
- : concreteClassName(foreign.targetAggregateId);
1666
+ const target =
1667
+ foreign.targetIsContract === true
1668
+ ? contractTypeName(foreign.targetAggregateId)
1669
+ : concreteClassName(foreign.targetAggregateId);
1217
1670
  const fieldMember = member(fieldName);
1218
1671
  const fieldValue = admitsNull(field)
1219
1672
  ? `record.${fieldMember}.valueOrNull`
1220
1673
  : `record.${fieldMember}`;
1221
- const stem = fieldMember.endsWith("Id") ? fieldMember.slice(0, -2) : fieldMember;
1674
+ const stem = fieldMember.endsWith("Id")
1675
+ ? fieldMember.slice(0, -2)
1676
+ : fieldMember;
1222
1677
  const method = member(`${stem}Of`);
1223
1678
  addCollectionMember(aggregate.id, method, [
1224
1679
  ` /// Follow [record]'s ${fieldName} to its typed business record.`,
@@ -1227,15 +1682,18 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1227
1682
  ` if (reference == null) return null;`,
1228
1683
  ` final target = await NmsInternalForeignRefs.resolve(reference, _client.bridge.foreignReader());`,
1229
1684
  ` if (target == null) return null;`,
1230
- ` return NmsInternalRecordOrigins.remember(target, workspace: NmsInternalNomosRefs.workspace(reference.ref), type: ${JSON.stringify(foreign.targetAggregateId)}, id: NmsInternalNomosRefs.id(reference.ref));`,
1685
+ ` return NmsInternalRecordOrigins.remember(target, workspace: NmsInternalNomosRefs.workspace(reference.ref), type: ${JSON.stringify(
1686
+ foreign.targetAggregateId
1687
+ )}, id: NmsInternalNomosRefs.id(reference.ref));`,
1231
1688
  ` }`,
1232
1689
  ``,
1233
1690
  ]);
1234
- const targetOwner = packageDomains[foreign.targetAggregateId] ??
1691
+ const targetOwner =
1692
+ packageDomains[foreign.targetAggregateId] ??
1235
1693
  (foreign.targetDomain === undefined
1236
1694
  ? undefined
1237
1695
  : Object.values(packageDomains).find(
1238
- (candidate) => candidate.domain === foreign.targetDomain,
1696
+ (candidate) => candidate.domain === foreign.targetDomain
1239
1697
  ));
1240
1698
  if (targetOwner !== undefined && targetOwner.domain !== domain) {
1241
1699
  const targetApplication = dartApplicationClassName(targetOwner.domain);
@@ -1262,21 +1720,35 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1262
1720
 
1263
1721
  for (const count of (mod.counts ?? []).map((raw) => finishCount(raw))) {
1264
1722
  const method = member(count.id);
1265
- const groupType = count.by == null ? "" : keyType(aggregates.get(count.of), count.by);
1266
- addCollectionMember(count.of, method, [count.by == null
1267
- ? ` Future<int> ${method}() => _client.${method}();`
1268
- : ` Future<int> ${method}(${groupType} ${keyParam(count.by)}) => _client.${method}(${keyParam(count.by)});`]);
1723
+ const groupType =
1724
+ count.by == null ? "" : keyType(aggregates.get(count.of), count.by);
1725
+ addCollectionMember(count.of, method, [
1726
+ count.by == null
1727
+ ? ` Future<int> ${method}() => _client.${method}();`
1728
+ : ` Future<int> ${method}(${groupType} ${keyParam(
1729
+ count.by
1730
+ )}) => _client.${method}(${keyParam(count.by)});`,
1731
+ ]);
1269
1732
  }
1270
1733
  for (const sum of (mod.sums ?? []).map((raw) => finishSum(raw))) {
1271
1734
  const method = member(sum.id);
1272
- const groupType = sum.by == null ? "" : keyType(aggregates.get(sum.of), sum.by);
1273
- addCollectionMember(sum.of, method, [sum.by == null
1274
- ? ` Future<int> ${method}() => _client.${method}();`
1275
- : ` Future<int> ${method}(${groupType} ${keyParam(sum.by)}) => _client.${method}(${keyParam(sum.by)});`]);
1735
+ const groupType =
1736
+ sum.by == null ? "" : keyType(aggregates.get(sum.of), sum.by);
1737
+ addCollectionMember(sum.of, method, [
1738
+ sum.by == null
1739
+ ? ` Future<int> ${method}() => _client.${method}();`
1740
+ : ` Future<int> ${method}(${groupType} ${keyParam(
1741
+ sum.by
1742
+ )}) => _client.${method}(${keyParam(sum.by)});`,
1743
+ ]);
1276
1744
  }
1277
1745
 
1278
1746
  for (const aggregate of mod.aggregates) {
1279
- if ((aggregate as { businessBirthInternal?: unknown }).businessBirthInternal === true) continue;
1747
+ if (
1748
+ (aggregate as { businessBirthInternal?: unknown })
1749
+ .businessBirthInternal === true
1750
+ )
1751
+ continue;
1280
1752
  if (isFrameworkAggregate(aggregate)) continue;
1281
1753
  const concrete = concreteClassName(aggregate.id);
1282
1754
  const model = readModelClassName(aggregate.id);
@@ -1284,6 +1756,27 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1284
1756
  const Cap = cap(camel(concrete));
1285
1757
  const aggregateRefType = `NomosRef<${concrete}>`;
1286
1758
  const clientList = safe(`list${Cap}s`);
1759
+ addCollectionMember(aggregate.id, "ref", [
1760
+ ` /// Qualify [id] as a typed ${concrete} reference in this application workspace.`,
1761
+ ` ///`,
1762
+ ` /// Use this at UI boundaries such as route parameters that intentionally retain only`,
1763
+ ` /// the local aggregate id. Use [fromAddress] when the workspace is part of the value.`,
1764
+ ` ${aggregateRefType} ref(String id) {`,
1765
+ ` final workspace = _client.session?.workspace ?? '';`,
1766
+ ` if (workspace.isEmpty) {`,
1767
+ ` throw StateError('Cannot qualify a ${concrete} id before its application workspace is open.');`,
1768
+ ` }`,
1769
+ ` if (id.trim().isEmpty) {`,
1770
+ ` throw ArgumentError.value(id, 'id', 'A business id cannot be empty.');`,
1771
+ ` }`,
1772
+ ` return NmsInternalNomosRefs.create<${concrete}>(`,
1773
+ ` workspace: workspace,`,
1774
+ ` type: ${JSON.stringify(aggregate.id)},`,
1775
+ ` id: id,`,
1776
+ ` );`,
1777
+ ` }`,
1778
+ ``,
1779
+ ]);
1287
1780
  addCollectionMember(aggregate.id, "fromAddress", [
1288
1781
  ` /// Recover a typed ${concrete} reference from its stable public address.`,
1289
1782
  ` ///`,
@@ -1291,14 +1784,18 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1291
1784
  ` /// still verify the signed-in person's access when the reference is used.`,
1292
1785
  ` ${aggregateRefType} fromAddress(String address) {`,
1293
1786
  ` final reference = NmsInternalNomosRefs.fromUri<${concrete}>(address);`,
1294
- ` _nmsRequireReference(reference, ${JSON.stringify(aggregate.id)}, '');`,
1787
+ ` _nmsRequireReference(reference, ${JSON.stringify(
1788
+ aggregate.id
1789
+ )}, '');`,
1295
1790
  ` return reference;`,
1296
1791
  ` }`,
1297
1792
  ``,
1298
1793
  ]);
1299
1794
  addCollectionMember(aggregate.id, "find", [
1300
1795
  ` Future<${model}?> find(${aggregateRefType} reference) async {`,
1301
- ` _nmsRequireReference(reference, ${JSON.stringify(aggregate.id)}, '');`,
1796
+ ` _nmsRequireReference(reference, ${JSON.stringify(
1797
+ aggregate.id
1798
+ )}, '');`,
1302
1799
  ` final targetWorkspace = NmsInternalNomosRefs.workspace(reference);`,
1303
1800
  ` final currentWorkspace = _client.session?.workspace ?? '';`,
1304
1801
  ` if (currentWorkspace.isNotEmpty && targetWorkspace != currentWorkspace) {`,
@@ -1307,21 +1804,27 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1307
1804
  ` );`,
1308
1805
  ` return application.${member(plural(concrete))}.find(reference);`,
1309
1806
  ` }`,
1310
- ` final matches = await _client.${safe(`${stem}ById`)}(NmsInternalNomosRefs.id(reference));`,
1807
+ ` final matches = await _client.${safe(
1808
+ `${stem}ById`
1809
+ )}(NmsInternalNomosRefs.id(reference));`,
1311
1810
  ` return matches.isEmpty ? null : matches.single;`,
1312
1811
  ` }`,
1313
1812
  ``,
1314
1813
  ]);
1315
1814
  addCollectionMember(aggregate.id, "watchOne", [
1316
1815
  ` Stream<${model}?> watchOne(${aggregateRefType} reference) {`,
1317
- ` _nmsRequireReference(reference, ${JSON.stringify(aggregate.id)}, '');`,
1816
+ ` _nmsRequireReference(reference, ${JSON.stringify(
1817
+ aggregate.id
1818
+ )}, '');`,
1318
1819
  ` final targetWorkspace = NmsInternalNomosRefs.workspace(reference);`,
1319
1820
  ` final currentWorkspace = _client.session?.workspace ?? '';`,
1320
1821
  ` if (currentWorkspace.isNotEmpty && targetWorkspace != currentWorkspace) {`,
1321
1822
  ` return Stream.fromFuture(nomosInternalOpen${application}(`,
1322
1823
  ` _client.bridge, reference, _client.actor,`,
1323
1824
  ` )).asyncExpand(`,
1324
- ` (application) => application.${member(plural(concrete))}.watchOne(reference),`,
1825
+ ` (application) => application.${member(
1826
+ plural(concrete)
1827
+ )}.watchOne(reference),`,
1325
1828
  ` );`,
1326
1829
  ` }`,
1327
1830
  ` return _client.watch${Cap}ById(NmsInternalNomosRefs.id(reference))`,
@@ -1343,12 +1846,14 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1343
1846
  const initializers: string[] = [];
1344
1847
  for (const [aggregateId, members] of collectionLines) {
1345
1848
  const aggregate = aggregates.get(aggregateId);
1346
- const concrete = aggregate === undefined
1347
- ? concreteClassName(aggregateId)
1348
- : concreteClassName(aggregate.id);
1349
- const collection = aggregate === undefined
1350
- ? `${concrete}Collection`
1351
- : dartAggregateCollectionClassName(aggregate);
1849
+ const concrete =
1850
+ aggregate === undefined
1851
+ ? concreteClassName(aggregateId)
1852
+ : concreteClassName(aggregate.id);
1853
+ const collection =
1854
+ aggregate === undefined
1855
+ ? `${concrete}Collection`
1856
+ : dartAggregateCollectionClassName(aggregate);
1352
1857
  const property = member(plural(concrete));
1353
1858
  claimApplicationMember(property, `aggregate collection '${aggregateId}'`);
1354
1859
  appLines.push(` final ${collection} ${property};`);
@@ -1365,39 +1870,65 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1365
1870
  ``,
1366
1871
  ...members,
1367
1872
  `}`,
1368
- ``,
1873
+ ``
1369
1874
  );
1370
1875
  }
1371
1876
 
1372
1877
  const imports = [...siblingImports.entries()]
1373
1878
  .sort(([a], [b]) => a.localeCompare(b))
1374
- .map(([file, names]) => `import '${file}' show ${[...names].sort().join(", ")};`);
1879
+ .map(
1880
+ ([file, names]) =>
1881
+ `import '${file}' show ${[...names].sort().join(", ")};`
1882
+ );
1375
1883
  const typesImport = opts.typesImportUri ?? `${mod.name}.dart`;
1376
- const runtimeImport = opts.supportImport === "package"
1377
- ? "package:nomos_client/src/nms_generated_runtime.dart"
1378
- : "nomos_client.dart";
1884
+ const runtimeImport =
1885
+ opts.supportImport === "package"
1886
+ ? "package:nomos_client/src/nms_generated_runtime.dart"
1887
+ : "nomos_client.dart";
1379
1888
  const applicationInitializers = [
1380
1889
  "_client = client",
1381
1890
  ...(tenantRoot ? ["currentUser = currentUser"] : []),
1382
1891
  `activity = ${activity}._(client)`,
1383
- ...(resolverBindings.length > 0 ? [`conflicts = ${cap(camel(domain))}Conflicts._(client)`] : []),
1892
+ ...(resolverBindings.length > 0
1893
+ ? [`conflicts = ${cap(camel(domain))}Conflicts._(client)`]
1894
+ : []),
1384
1895
  ...initializers,
1385
1896
  ];
1386
1897
  return [
1387
1898
  `// AUTO-GENERATED by nomos-compile — the tenant application surface for "${domain}".`,
1388
1899
  `// Business facts in, business records out. Runtime lifecycle is deliberately absent.`,
1389
- ...(sharingPolicies.length > 0 ? [`import 'dart:async' show StreamSubscription, unawaited;`] : []),
1390
- `import '${runtimeImport}' show NmsInternalForeignRefs, NmsInternalNomosRefs, NmsInternalRecordOrigins, NomosBridge, NomosOfferOutcome, NomosRef, NomosSession${usesWorkspaceName ? ", deriveWorkspaceName" : ""}${sharingPolicies.length > 0 ? ", Invitation, Recipient, SyncPhase, User" : ""}${tenantRoot ? ", NmsInternalUsers, nmsInternalReceivingGroupNameOf" : ""};`,
1900
+ ...(sharingPolicies.length > 0
1901
+ ? [`import 'dart:async' show StreamSubscription, unawaited;`]
1902
+ : []),
1903
+ `import '${runtimeImport}' show NmsInternalForeignRefs, NmsInternalNomosRefs, NmsInternalRecordOrigins, NomosBridge, NomosOfferOutcome, NomosRef, NomosSession${
1904
+ usesWorkspaceName ? ", deriveWorkspaceName" : ""
1905
+ }${
1906
+ sharingPolicies.length > 0
1907
+ ? ", Invitation, Recipient, SyncPhase, User"
1908
+ : ""
1909
+ }${
1910
+ tenantRoot ? ", NmsInternalUsers, nmsInternalReceivingGroupNameOf" : ""
1911
+ };`,
1391
1912
  ...(opts.flutterHost === true || sharingPolicies.length > 0
1392
1913
  ? [
1393
1914
  ...(tenantRoot ? [`import 'package:flutter/widgets.dart';`] : []),
1394
1915
  ...(sharingPolicies.length > 0
1395
- ? [`import 'package:nomos_flutter/src/application_share.dart' show ApplicationShareResult;`]
1916
+ ? [
1917
+ `import 'package:nomos_flutter/src/application_share.dart' show ApplicationShareResult;`,
1918
+ ]
1396
1919
  : []),
1397
1920
  ...(opts.flutterHost === true && tenantRoot
1398
- ? [`import 'package:nomos_flutter/src/application_status.dart' show ApplicationSignIn, ApplicationStatus;`]
1921
+ ? [
1922
+ `import 'package:nomos_flutter/src/application_status.dart' show ApplicationSignIn, ApplicationStatus;`,
1923
+ ]
1399
1924
  : []),
1400
- `import 'package:nomos_flutter/src/generated_application_host.dart' show NmsInternalApplicationUserBinding${tenantRoot || sharingPolicies.length > 0 ? ", NmsInternalApplicationHost" : ""}${sharingPolicies.length > 0 ? ", NmsInternalInvitationPolicy" : ""};`,
1925
+ `import 'package:nomos_flutter/src/generated_application_host.dart' show NmsInternalApplicationUserBinding${
1926
+ tenantRoot || sharingPolicies.length > 0
1927
+ ? ", NmsInternalApplicationHost"
1928
+ : ""
1929
+ }${
1930
+ sharingPolicies.length > 0 ? ", NmsInternalInvitationPolicy" : ""
1931
+ };`,
1401
1932
  ]
1402
1933
  : []),
1403
1934
  `import '${typesImport}';`,
@@ -1406,9 +1937,7 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1406
1937
  ...imports,
1407
1938
  ``,
1408
1939
  ...(tenantRoot
1409
- ? [
1410
- `/// The canonical stateful entry point for this business domain.`,
1411
- ]
1940
+ ? [`/// The canonical stateful entry point for this business domain.`]
1412
1941
  : [
1413
1942
  `/// A business domain handle owned by the generated package application.`,
1414
1943
  ]),
@@ -1440,7 +1969,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1440
1969
  ...(tenantRoot ? [` final User currentUser;`] : []),
1441
1970
  ` final ${activity} activity;`,
1442
1971
  ...appLines,
1443
- ` ${application}._(${client} client${tenantRoot ? ", User currentUser" : ""}) : ${applicationInitializers.join(", ")};`,
1972
+ ` ${application}._(${client} client${
1973
+ tenantRoot ? ", User currentUser" : ""
1974
+ }) : ${applicationInitializers.join(", ")};`,
1444
1975
  ...(opts.flutterHost === true && tenantRoot
1445
1976
  ? [
1446
1977
  ``,
@@ -1473,14 +2004,23 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1473
2004
  : []),
1474
2005
  `}`,
1475
2006
  ``,
1476
- `/// The verified business history of this application, newest first.`,
2007
+ `/// The verified recent business activity of this application, newest first.`,
1477
2008
  `final class ${activity} {`,
1478
2009
  ` final ${client} _client;`,
1479
2010
  ` const ${activity}._(this._client);`,
1480
2011
  ``,
1481
- ` Future<List<ActivityEvent>> all({int? limit}) => _client.activityLog(limit: limit);`,
2012
+ ` Future<List<ActivityEvent>> recent({required int limit}) =>`,
2013
+ ` _client.recentActivity(limit: limit);`,
2014
+ ``,
2015
+ ` Stream<List<ActivityEvent>> watchRecent({required int limit}) =>`,
2016
+ ` _client.watchRecentActivity(limit: limit);`,
2017
+ ``,
2018
+ ` @Deprecated('Reads ledger history. Use recent with an explicit limit, or a DSL managed count for quantities.')`,
2019
+ ` Future<List<ActivityEvent>> all({int? limit}) =>`,
2020
+ ` recent(limit: limit ?? 50);`,
1482
2021
  ``,
1483
- ` Stream<List<ActivityEvent>> watchAll() => _client.watchActivity();`,
2022
+ ` @Deprecated('Reads ledger history. Use watchRecent with an explicit limit.')`,
2023
+ ` Stream<List<ActivityEvent>> watchAll() => watchRecent(limit: 50);`,
1484
2024
  `}`,
1485
2025
  ``,
1486
2026
  ...actionResultDeclarations,
@@ -1518,6 +2058,26 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1518
2058
  ` return NmsInternalNomosRefs.create<T>(workspace: workspace, type: affected.type, id: affected.id);`,
1519
2059
  `}`,
1520
2060
  ``,
2061
+ ...(usesAffectedReferenceOrNull
2062
+ ? [
2063
+ `NomosRef<T>? _nmsAffectedReferenceOrNull<T>(NomosOfferOutcome change, String expectedType) {`,
2064
+ ` final matches = change.affected`,
2065
+ ` .where((affected) => _nmsReferenceTypeMatches(affected.type, expectedType))`,
2066
+ ` .toList(growable: false);`,
2067
+ ` if (matches.length > 1) {`,
2068
+ ` throw StateError('The business action expected at most one affected $expectedType record, received \${matches.length}');`,
2069
+ ` }`,
2070
+ ` if (matches.isEmpty) return null;`,
2071
+ ` final affected = matches.single;`,
2072
+ ` final workspace = affected.workspace.isNotEmpty ? affected.workspace : change.workspace;`,
2073
+ ` if (workspace.isEmpty) {`,
2074
+ ` throw StateError('The affected record did not carry its business workspace');`,
2075
+ ` }`,
2076
+ ` return NmsInternalNomosRefs.create<T>(workspace: workspace, type: affected.type, id: affected.id);`,
2077
+ `}`,
2078
+ ``,
2079
+ ]
2080
+ : []),
1521
2081
  ...(usesAffectedReferences
1522
2082
  ? [
1523
2083
  `List<NomosRef<T>> _nmsAffectedReferences<T>(NomosOfferOutcome change, String expectedType) {`,
@@ -1542,9 +2102,13 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1542
2102
  `Future<${application}> nomosInternalBind${application}(`,
1543
2103
  ` NomosSession session, NmsInternalApplicationUserBinding user) async {`,
1544
2104
  ` final application = ${application}._(`,
1545
- ` await ${client}.bind(session, actor: user.nmsInternalActor)${tenantRoot ? "," : ""}`,
2105
+ ` await ${client}.bind(session, actor: user.nmsInternalActor)${
2106
+ tenantRoot ? "," : ""
2107
+ }`,
1546
2108
  ...(tenantRoot
1547
- ? [` NmsInternalUsers.create(identityId: user.nmsInternalIdentityId, identityAddress: user.nmsInternalIdentityAddress),`]
2109
+ ? [
2110
+ ` NmsInternalUsers.create(identityId: user.nmsInternalIdentityId, identityAddress: user.nmsInternalIdentityAddress),`,
2111
+ ]
1548
2112
  : []),
1549
2113
  ` );`,
1550
2114
  ]
@@ -1552,7 +2116,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1552
2116
  `Future<${application}> nomosInternalBind${application}(NomosSession session, String? actor) async {`,
1553
2117
  ` final application = ${application}._(await ${client}.bind(session, actor: actor));`,
1554
2118
  ]),
1555
- ...(sharingPolicies.length > 0 ? [` await application._nmsStartWorkspaceSharing();`] : []),
2119
+ ...(sharingPolicies.length > 0
2120
+ ? [` await application._nmsStartWorkspaceSharing();`]
2121
+ : []),
1556
2122
  ` return application;`,
1557
2123
  `}`,
1558
2124
  ``,
@@ -1570,8 +2136,12 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1570
2136
  ` ),`,
1571
2137
  ` );`,
1572
2138
  ]
1573
- : [` final application = ${application}._(await ${client}.open(bridge, ref: reference, actor: actor));`]),
1574
- ...(sharingPolicies.length > 0 ? [` await application._nmsStartWorkspaceSharing();`] : []),
2139
+ : [
2140
+ ` final application = ${application}._(await ${client}.open(bridge, ref: reference, actor: actor));`,
2141
+ ]),
2142
+ ...(sharingPolicies.length > 0
2143
+ ? [` await application._nmsStartWorkspaceSharing();`]
2144
+ : []),
1575
2145
  ` return application;`,
1576
2146
  `}`,
1577
2147
  ``,