@githolon/dsl 0.102.4 → 0.103.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/compile_package.mjs +43 -2
  2. package/compile_proof.d.mts +47 -0
  3. package/compile_proof.mjs +343 -0
  4. package/dart/CHANGELOG.md +7 -0
  5. package/dart/lib/src/dev_cloud.dart +8 -8
  6. package/dart/lib/src/nms_generated_runtime.dart +2 -0
  7. package/dart/lib/src/runner/node-entry.mjs +5504 -1236
  8. package/dart/lib/src/runner/offline-install.mjs +4578 -528
  9. package/dart/lib/src/test_harness.dart +206 -152
  10. package/dart/lib/src/webview_bridge.dart +196 -89
  11. package/dart/pubspec.lock +1 -1
  12. package/dart/pubspec.yaml +2 -2
  13. package/dart/test/acquire_session_credentials_test.dart +4 -3
  14. package/dart/test/adv_realm_interference_test.dart +7 -6
  15. package/dart/test/cloud_bound_topology_test.dart +3 -3
  16. package/dart/test/cluster_births_test.dart +4 -3
  17. package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
  18. package/dart/test/cluster_local_autosync_test.dart +3 -2
  19. package/dart/test/cluster_scale_test.dart +3 -2
  20. package/dart/test/dlq_session_surface_test.dart +7 -6
  21. package/dart/test/first_app_gauntlet_test.dart +14 -21
  22. package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
  23. package/dart/test/four_layer_nested_topology_test.dart +4 -4
  24. package/dart/test/harness_phase_timeout_test.dart +2 -2
  25. package/dart/test/local_sync_inert_test.dart +1 -1
  26. package/dart/test/parent_signer_recovery_test.dart +2 -2
  27. package/dart/test/sealing_client_test.dart +4 -3
  28. package/dart/test/session_bound_test.dart +8 -7
  29. package/dart/test/test_harness_test.dart +13 -12
  30. package/dart/test/vo_read_typing_test.dart +4 -3
  31. package/dart/test/watch_after_offer_stall_test.dart +4 -3
  32. package/dart/test/webview_bridge_test.dart +102 -5
  33. package/dart_types/CHANGELOG.md +5 -0
  34. package/dart_types/lib/nomos_types.dart +9 -0
  35. package/dart_types/lib/src/nomos_ref.dart +20 -0
  36. package/dart_types/lib/src/optional_ref.dart +85 -0
  37. package/dart_types/lib/src/sha256_digest.dart +41 -0
  38. package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
  39. package/dart_types/pubspec.yaml +1 -1
  40. package/dart_types/test/optional_ref_test.dart +44 -0
  41. package/dart_types/test/sha256_digest_test.dart +18 -0
  42. package/package.json +9 -5
  43. package/runtime/generated-application-host.js +5365 -1105
  44. package/src/aggregate.ts +18 -0
  45. package/src/application_composition.ts +6 -32
  46. package/src/attested_core.ts +6 -3
  47. package/src/birth_meter.ts +1 -0
  48. package/src/birth_meter_ensure.ts +28 -2
  49. package/src/births.ts +1 -1
  50. package/src/build_package.ts +162 -538
  51. package/src/business_birth.ts +19 -3
  52. package/src/canonical_json.ts +21 -0
  53. package/src/check_package_main.ts +76 -35
  54. package/src/clear_directive.ts +19 -8
  55. package/src/codegen_dart.ts +305 -22
  56. package/src/codegen_dart_application.ts +844 -292
  57. package/src/codegen_dart_client.ts +1084 -407
  58. package/src/codegen_dart_scenario.ts +12 -12
  59. package/src/codegen_proof.ts +169 -39
  60. package/src/codegen_provider_dart.ts +17 -1
  61. package/src/codegen_ts.ts +298 -106
  62. package/src/compat_manifest.ts +2 -2
  63. package/src/compat_package_main.ts +8 -12
  64. package/src/compile_package_main.ts +1866 -597
  65. package/src/compose.ts +1 -1
  66. package/src/directive.ts +208 -15
  67. package/src/engine_entry.ts +165 -111
  68. package/src/evolve_preview.ts +43 -81
  69. package/src/extremum.ts +1 -1
  70. package/src/fields.ts +183 -4
  71. package/src/flow_gate.ts +75 -2
  72. package/src/framework/application_home.ts +2 -0
  73. package/src/framework/auth_provider_law.ts +2 -2
  74. package/src/framework/birth_meter.ts +7 -19
  75. package/src/framework/bootstrap.ts +0 -2
  76. package/src/framework/business_corrections.ts +9 -5
  77. package/src/framework/domain_lifecycle.ts +15 -29
  78. package/src/framework/encryption_scope.ts +2 -1
  79. package/src/framework/framework_aggregates.ts +2 -0
  80. package/src/framework/genesis.ts +16 -14
  81. package/src/framework/home.ts +20 -14
  82. package/src/framework/owned_custody.ts +9 -3
  83. package/src/framework/platform_admin.ts +3 -3
  84. package/src/framework/relations.ts +21 -13
  85. package/src/framework/signing_identity.ts +45 -40
  86. package/src/framework/workspace_invariant.ts +245 -0
  87. package/src/framework/workspaces.ts +23 -29
  88. package/src/index.ts +36 -16
  89. package/src/intent.ts +178 -6
  90. package/src/law_check.ts +32 -11
  91. package/src/law_graph.ts +2902 -0
  92. package/src/law_graph_usda.ts +1799 -0
  93. package/src/law_identity.ts +435 -0
  94. package/src/law_value_type.ts +230 -0
  95. package/src/lsp_server.ts +61 -31
  96. package/src/ops.ts +7 -0
  97. package/src/package_config.ts +141 -0
  98. package/src/package_dependencies.ts +108 -0
  99. package/src/package_dependency_usage.ts +150 -0
  100. package/src/package_source_imports.ts +71 -0
  101. package/src/plan_source_slice.ts +335 -0
  102. package/src/point_guard.ts +192 -0
  103. package/src/precondition.ts +44 -9
  104. package/src/read_projection_namespace.ts +1 -1
  105. package/src/runtime_zod.ts +44 -0
  106. package/src/secondary_ensure.ts +67 -14
  107. package/src/shape_compat.ts +1 -1
  108. package/src/stable_id_identity_bridge.ts +149 -0
  109. package/src/stable_id_lockfile.ts +278 -0
  110. package/src/stable_ids.ts +7 -2
  111. package/src/stable_ids_for_module.ts +348 -0
  112. package/src/stable_ids_types.ts +1 -1
  113. package/src/sum.ts +1 -1
  114. package/src/usd_state.ts +1 -1
  115. package/src/usda_relationship_integrity.ts +66 -0
  116. package/src/usda_text.ts +18 -0
  117. package/src/usdz.ts +78 -0
  118. package/src/value_object.ts +95 -0
  119. package/src/wire.ts +19 -0
  120. package/src/wire_encode.ts +22 -0
  121. package/src/workspace_sharding.ts +52 -36
  122. package/src/workspace_sharing.ts +28 -4
  123. package/src/zod_introspection.ts +1 -1
  124. package/dart/test/cluster_attested_test.dart +0 -201
  125. package/src/compile_engine.ts +0 -595
  126. package/src/manifest.ts +0 -1476
  127. package/src/read_projection_composition.ts +0 -117
  128. package/src/read_projection_manifest.ts +0 -115
  129. package/src/relation_composition.ts +0 -286
  130. package/src/usd.ts +0 -1967
  131. package/src/usd_layers.ts +0 -1776
@@ -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,22 @@ 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
+ ` final shares = await _client.bridge.query('sharedByObject', {'object': 'workspace:self'}, null, _client.actor);`,
844
1172
  ` for (final row in shares) {`,
845
1173
  ` final raw = row['data'];`,
846
1174
  ` if (raw is! Map) continue;`,
847
1175
  ` final workspace = (raw['asserterWorkspace'] ?? '').toString();`,
848
1176
  ` final object = (raw['object'] ?? '').toString();`,
849
1177
  ` final prunedAt = (raw['prunedAt'] ?? '').toString();`,
850
- ` if (!workspace.startsWith(${JSON.stringify(`${birth.namespace}-`)}) ||`,
1178
+ ` if (!workspace.startsWith(${JSON.stringify(
1179
+ `${birth.namespace}-`
1180
+ )}) ||`,
851
1181
  ` object != 'workspace:self' ||`,
852
1182
  ` prunedAt.isNotEmpty ||`,
853
1183
  ` !seen.add(workspace)) continue;`,
854
- ` final reference = NmsInternalNomosRefs.create<${rootType}>(workspace: workspace, type: ${JSON.stringify(birth.aggregateId)}, id: ${JSON.stringify(birth.rootId)});`,
1184
+ ` final reference = NmsInternalNomosRefs.create<${rootType}>(workspace: workspace, type: ${JSON.stringify(
1185
+ birth.aggregateId
1186
+ )}, id: ${JSON.stringify(birth.rootId)});`,
855
1187
  ` final application = await _nmsOpenReference(reference);`,
856
1188
  ` final record = await application.${rootCollection}.find(reference);`,
857
1189
  ` if (record != null) records.add(record);`,
@@ -861,15 +1193,48 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
861
1193
  ``,
862
1194
  ` /// React when this person's available ${birth.aggregateId} lives change.`,
863
1195
  ` Stream<List<${rootModel}>> watchAvailable() =>`,
864
- ` _client.bridge`,
865
- ` .watch('aggregatesByType', {'__type': 'SharedWithMe'}, null, _client.actor)`,
866
- ` .asyncMap((_) => available());`,
1196
+ ` Stream<List<${rootModel}>>.multi((events) {`,
1197
+ ` var reading = false;`,
1198
+ ` var reread = false;`,
1199
+ ` Future<void> refresh() async {`,
1200
+ ` if (reading) {`,
1201
+ ` reread = true;`,
1202
+ ` return;`,
1203
+ ` }`,
1204
+ ` reading = true;`,
1205
+ ` do {`,
1206
+ ` reread = false;`,
1207
+ ` try {`,
1208
+ ` final records = await available();`,
1209
+ ` if (!events.isClosed) events.add(records);`,
1210
+ ` } catch (error, stackTrace) {`,
1211
+ ` if (!events.isClosed) events.addError(error, stackTrace);`,
1212
+ ` }`,
1213
+ ` } while (reread && !events.isClosed);`,
1214
+ ` reading = false;`,
1215
+ ` }`,
1216
+ ` final birthChanges = _client.bridge`,
1217
+ ` .watch('birthRecordsByPrincipal', {'principal': _client.actor}, null, null, 60000)`,
1218
+ ` .skip(1)`,
1219
+ ` .listen((_) { refresh(); }, onError: events.addError);`,
1220
+ ` final shareChanges = _client.bridge`,
1221
+ ` .watch('sharedByObject', {'object': 'workspace:self'}, null, _client.actor, 60000)`,
1222
+ ` .skip(1)`,
1223
+ ` .listen((_) { refresh(); }, onError: events.addError);`,
1224
+ ` events.onCancel = () async {`,
1225
+ ` await birthChanges.cancel();`,
1226
+ ` await shareChanges.cancel();`,
1227
+ ` };`,
1228
+ ` refresh();`,
1229
+ ` });`,
867
1230
  ``,
868
1231
  ` /// Continue working in the business application that produced [record].`,
869
1232
  ` Future<${targetApplication}> applicationOf(${rootModel} record) {`,
870
1233
  ` final origin = NmsInternalRecordOrigins.require(record);`,
871
1234
  ` return _nmsOpenReference(`,
872
- ` NmsInternalNomosRefs.create<${rootType}>(workspace: origin.workspace, type: ${JSON.stringify(birth.aggregateId)}, id: origin.id),`,
1235
+ ` NmsInternalNomosRefs.create<${rootType}>(workspace: origin.workspace, type: ${JSON.stringify(
1236
+ birth.aggregateId
1237
+ )}, id: origin.id),`,
873
1238
  ` );`,
874
1239
  ` }`,
875
1240
  ``,
@@ -889,24 +1254,42 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
889
1254
  if (outputs.length > 0) {
890
1255
  usesAffectedReference = true;
891
1256
  usesAffectedReferences ||= outputs.some((output) => output.many);
1257
+ usesAffectedReferenceOrNull ||= outputs.some((output) => output.optional);
892
1258
  actionResultDeclarations.push(
893
1259
  `/// Every business record affected by ${cap(camel(directive.id))}.`,
894
1260
  `final class ${resultClass} {`,
895
1261
  ...outputs.map((output) =>
896
1262
  output.many
897
1263
  ? ` 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(", ")}});`,
1264
+ : output.optional
1265
+ ? ` final NomosRef<${output.dartType}>? ${output.field};`
1266
+ : ` final NomosRef<${output.dartType}> ${output.field};`
1267
+ ),
1268
+ ` const ${resultClass}({${outputs
1269
+ .map((output) => `required this.${output.field}`)
1270
+ .join(", ")}});`,
900
1271
  `}`,
901
- ``,
1272
+ ``
902
1273
  );
903
1274
  }
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(", ")})`;
1275
+ const resultExpression =
1276
+ outputs.length === 0
1277
+ ? undefined
1278
+ : `${resultClass}(${outputs
1279
+ .map((output) =>
1280
+ output.many
1281
+ ? `${output.field}: _nmsAffectedReferences<${
1282
+ output.dartType
1283
+ }>(change, ${JSON.stringify(output.aggregateId)})`
1284
+ : output.optional
1285
+ ? `${output.field}: _nmsAffectedReferenceOrNull<${
1286
+ output.dartType
1287
+ }>(change, ${JSON.stringify(output.aggregateId)})`
1288
+ : `${output.field}: _nmsAffectedReference<${
1289
+ output.dartType
1290
+ }>(change, ${JSON.stringify(output.aggregateId)})`
1291
+ )
1292
+ .join(", ")})`;
910
1293
  if (isBusinessIntent(directive)) {
911
1294
  claimApplicationMember(method, `business intent '${directive.id}'`);
912
1295
  const sourceTrust = sourceTrustAction(directive);
@@ -922,7 +1305,7 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
922
1305
  ...afterBusinessWrite(directive),
923
1306
  ` return ${resultExpression};`,
924
1307
  ` }`,
925
- ``,
1308
+ ``
926
1309
  );
927
1310
  } else {
928
1311
  appLines.push(
@@ -935,27 +1318,25 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
935
1318
  ` await ${sourceTrust.call};`,
936
1319
  ...afterBusinessWrite(directive),
937
1320
  ` }`,
938
- ``,
1321
+ ``
939
1322
  );
940
1323
  }
941
1324
  continue;
942
1325
  }
943
1326
  const sourceTrust = sourceTrustAction(directive);
944
1327
  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
- );
1328
+ addCollectionMember(directive.aggregateId, method, [
1329
+ ` /// ${cap(
1330
+ camel(directive.id)
1331
+ )}. Returns every affected typed business record reference.`,
1332
+ ` Future<${resultClass}> ${method}(${payload} facts) async {`,
1333
+ ...referenceChecks,
1334
+ ...sourceTrust.prelude,
1335
+ ` final change = await ${sourceTrust.call};`,
1336
+ ` return ${resultExpression};`,
1337
+ ` }`,
1338
+ ``,
1339
+ ]);
959
1340
  } else {
960
1341
  addCollectionMember(directive.aggregateId, method, [
961
1342
  ` /// ${cap(camel(directive.id))}.`,
@@ -972,7 +1353,8 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
972
1353
  const publicQueries = ((mod.queries ?? []) as QueryDecl[]).filter(
973
1354
  (candidate) =>
974
1355
  !isFrameworkQuery(candidate) &&
975
- (candidate as { businessBirthInternal?: unknown }).businessBirthInternal !== true,
1356
+ (candidate as { businessBirthInternal?: unknown })
1357
+ .businessBirthInternal !== true
976
1358
  );
977
1359
  const signedInSubjectKey = (query: QueryDecl): string | undefined => {
978
1360
  if (query.key.length !== 1) return undefined;
@@ -985,31 +1367,46 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
985
1367
  if (signedInSubjectKey(query) !== undefined) {
986
1368
  mineQueriesByAggregate.set(
987
1369
  query.returns,
988
- (mineQueriesByAggregate.get(query.returns) ?? 0) + 1,
1370
+ (mineQueriesByAggregate.get(query.returns) ?? 0) + 1
989
1371
  );
990
1372
  }
991
1373
  }
992
1374
  for (const query of publicQueries) {
993
1375
  const model = readModelClassName(query.returns);
994
1376
  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(", ");
1377
+ const params = query.key
1378
+ .map((key) => `required ${keyType(aggregate, key)} ${keyParam(key)}`)
1379
+ .join(", ");
1380
+ const args = query.key
1381
+ .map((key) => `${keyParam(key)}: ${keyParam(key)}`)
1382
+ .join(", ");
997
1383
  const method = member(query.id);
998
1384
  addCollectionMember(query.returns, method, [
999
1385
  ` 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});`,
1386
+ ` Stream<List<${model}>> watch${cap(
1387
+ camel(query.id)
1388
+ )}({${params}}) => _client.watch${cap(camel(query.id))}(${args});`,
1001
1389
  ``,
1002
1390
  ]);
1003
1391
  const subjectKey = signedInSubjectKey(query);
1004
- if (subjectKey !== undefined && !birthCollectionsWithMine.has(query.returns)) {
1392
+ if (
1393
+ subjectKey !== undefined &&
1394
+ !birthCollectionsWithMine.has(query.returns)
1395
+ ) {
1005
1396
  const subjectParam = keyParam(subjectKey);
1006
1397
  const subjectQueryCount = mineQueriesByAggregate.get(query.returns) ?? 0;
1007
1398
  const role = cap(camel(subjectKey.replace(/Subject$/, "") || "subject"));
1008
1399
  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()}`;
1400
+ const watchMineMethod =
1401
+ subjectQueryCount === 1 ? "watchMine" : `watchMineAs${role}`;
1402
+ const relationship =
1403
+ subjectQueryCount === 1
1404
+ ? "belonging to"
1405
+ : `where they are the ${role.toLowerCase()}`;
1011
1406
  addCollectionMember(query.returns, mineMethod, [
1012
- ` /// ${cap(camel(query.returns))} records ${relationship} the signed-in person.`,
1407
+ ` /// ${cap(
1408
+ camel(query.returns)
1409
+ )} records ${relationship} the signed-in person.`,
1013
1410
  ` Future<List<${model}>> ${mineMethod}() {`,
1014
1411
  ` final subject = _client.actor;`,
1015
1412
  ` if (subject == null || subject.trim().isEmpty) {`,
@@ -1023,7 +1420,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1023
1420
  ` if (subject == null || subject.trim().isEmpty) {`,
1024
1421
  ` throw StateError('The signed-in person is not available.');`,
1025
1422
  ` }`,
1026
- ` return _client.watch${cap(camel(query.id))}(${subjectParam}: subject);`,
1423
+ ` return _client.watch${cap(
1424
+ camel(query.id)
1425
+ )}(${subjectParam}: subject);`,
1027
1426
  ` }`,
1028
1427
  ``,
1029
1428
  ]);
@@ -1036,7 +1435,8 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1036
1435
  for (const aggregate of mod.aggregates.filter(
1037
1436
  (candidate) =>
1038
1437
  !isFrameworkAggregate(candidate) &&
1039
- (candidate as { businessBirthInternal?: unknown }).businessBirthInternal !== true,
1438
+ (candidate as { businessBirthInternal?: unknown })
1439
+ .businessBirthInternal !== true
1040
1440
  )) {
1041
1441
  const sourceModel = readModelClassName(aggregate.id);
1042
1442
  for (const contractId of aggregate.implementsContracts ?? []) {
@@ -1049,7 +1449,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1049
1449
  ` /// the contract that this aggregate explicitly implements. This does not grant access.`,
1050
1450
  ` NomosRef<${contract}> ${method}(${sourceModel} record) {`,
1051
1451
  ` final reference = record.ref;`,
1052
- ` _nmsRequireReference(reference, ${JSON.stringify(aggregate.id)}, '');`,
1452
+ ` _nmsRequireReference(reference, ${JSON.stringify(
1453
+ aggregate.id
1454
+ )}, '');`,
1053
1455
  ` return NmsInternalNomosRefs.create<${contract}>(`,
1054
1456
  ` workspace: reference.workspace,`,
1055
1457
  ` type: ${JSON.stringify(contractId)},`,
@@ -1065,8 +1467,12 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1065
1467
  ` /// Use this after following a generated cross-application reference and opening`,
1066
1468
  ` /// its business application. Nomos checks the declared contract and access on read.`,
1067
1469
  ` Future<${sourceModel}?> ${resolveMethod}(ForeignRef<${contract}> reference) {`,
1068
- ` _nmsRequireReference(reference.ref, ${JSON.stringify(contractId)}, '');`,
1069
- ` return find(NmsInternalNomosRefs.create<${concreteClassName(aggregate.id)}>(`,
1470
+ ` _nmsRequireReference(reference.ref, ${JSON.stringify(
1471
+ contractId
1472
+ )}, '');`,
1473
+ ` return find(NmsInternalNomosRefs.create<${concreteClassName(
1474
+ aggregate.id
1475
+ )}>(`,
1070
1476
  ` workspace: reference.workspace,`,
1071
1477
  ` type: ${JSON.stringify(aggregate.id)},`,
1072
1478
  ` id: reference.id,`,
@@ -1078,8 +1484,12 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1078
1484
  addCollectionMember(aggregate.id, watchMethod, [
1079
1485
  ` /// React when the concrete record behind [reference] changes.`,
1080
1486
  ` Stream<${sourceModel}?> ${watchMethod}(ForeignRef<${contract}> reference) {`,
1081
- ` _nmsRequireReference(reference.ref, ${JSON.stringify(contractId)}, '');`,
1082
- ` return watchOne(NmsInternalNomosRefs.create<${concreteClassName(aggregate.id)}>(`,
1487
+ ` _nmsRequireReference(reference.ref, ${JSON.stringify(
1488
+ contractId
1489
+ )}, '');`,
1490
+ ` return watchOne(NmsInternalNomosRefs.create<${concreteClassName(
1491
+ aggregate.id
1492
+ )}>(`,
1083
1493
  ` workspace: reference.workspace,`,
1084
1494
  ` type: ${JSON.stringify(aggregate.id)},`,
1085
1495
  ` id: reference.id,`,
@@ -1088,20 +1498,31 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1088
1498
  ``,
1089
1499
  ]);
1090
1500
  }
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),
1501
+ const sameDomainRefs = Object.entries(aggregate.fields).filter(
1502
+ ([, field]) =>
1503
+ field.kind === "ref" &&
1504
+ field.foreignRef === undefined &&
1505
+ field.refAggregateId !== undefined &&
1506
+ field.refAggregateId !== aggregate.id &&
1507
+ aggregates.has(field.refAggregateId)
1508
+ );
1509
+ const crossDomainRefs = Object.entries(aggregate.fields).filter(
1510
+ ([, field]) => {
1511
+ if (
1512
+ field.kind !== "ref" ||
1513
+ field.foreignRef !== undefined ||
1514
+ field.refAggregateId === undefined
1515
+ )
1516
+ return false;
1517
+ if (aggregates.has(field.refAggregateId)) return false;
1518
+ const owner = packageDomains[field.refAggregateId];
1519
+ return owner !== undefined && owner.domain !== domain;
1520
+ }
1097
1521
  );
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][]) {
1522
+ for (const [fieldName, field] of Object.entries(aggregate.fields) as [
1523
+ string,
1524
+ Field
1525
+ ][]) {
1105
1526
  const foreign = field.foreignRef;
1106
1527
  if (field.kind !== "ref") continue;
1107
1528
  if (foreign === undefined) {
@@ -1116,13 +1537,19 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1116
1537
  const sourceFieldValue = admitsNull(field)
1117
1538
  ? `record.${sourceFieldMember}.valueOrNull`
1118
1539
  : `record.${sourceFieldMember}`;
1119
- const isSameDomain = sameDomainRefs.some(([name]) => name === fieldName);
1120
- const isCrossDomain = crossDomainRefs.some(([name]) => name === fieldName);
1540
+ const isSameDomain = sameDomainRefs.some(
1541
+ ([name]) => name === fieldName
1542
+ );
1543
+ const isCrossDomain = crossDomainRefs.some(
1544
+ ([name]) => name === fieldName
1545
+ );
1121
1546
  if (!isSameDomain && !isCrossDomain) continue;
1122
1547
 
1123
1548
  if (isSameDomain) {
1124
1549
  const targetModel = readModelClassName(targetAggregateId);
1125
- const targetById = safe(`${lcFirst(camel(concreteClassName(targetAggregateId)))}ById`);
1550
+ const targetById = safe(
1551
+ `${lcFirst(camel(concreteClassName(targetAggregateId)))}ById`
1552
+ );
1126
1553
  addCollectionMember(aggregate.id, method, [
1127
1554
  ` /// Resolve [record]'s ${fieldName} inside this business application.`,
1128
1555
  ` Future<${targetModel}?> ${method}(${sourceModel} record) async {`,
@@ -1132,7 +1559,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1132
1559
  ` if (id == null) return null;`,
1133
1560
  ` final rows = await _client.${targetById}(NmsInternalNomosRefs.id(id));`,
1134
1561
  ]
1135
- : [` final rows = await _client.${targetById}(NmsInternalNomosRefs.id(record.${sourceFieldMember}));`]),
1562
+ : [
1563
+ ` final rows = await _client.${targetById}(NmsInternalNomosRefs.id(record.${sourceFieldMember}));`,
1564
+ ]),
1136
1565
  ` return rows.isEmpty ? null : rows.first;`,
1137
1566
  ` }`,
1138
1567
  ``,
@@ -1143,13 +1572,18 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1143
1572
  const owner = packageDomains[targetAggregateId];
1144
1573
  if (owner === undefined) continue;
1145
1574
  const targetModel = readModelClassName(targetAggregateId);
1146
- const targetCollection = member(plural(concreteClassName(targetAggregateId)));
1575
+ const targetCollection = member(
1576
+ plural(concreteClassName(targetAggregateId))
1577
+ );
1147
1578
  const targetApplication = dartApplicationClassName(owner.domain);
1148
1579
  const opener = `nomosInternalOpen${targetApplication}`;
1149
1580
  // Flat output needs the sibling type file. Layered output's ordinary
1150
1581
  // types barrel already exports every domain; the explicit-barrel mode
1151
1582
  // supplies direct package imports in explicitTypeImportUris.
1152
- if (opts.typesImportUri === undefined || explicitTypeImportUris.size > 0) {
1583
+ if (
1584
+ opts.typesImportUri === undefined ||
1585
+ explicitTypeImportUris.size > 0
1586
+ ) {
1153
1587
  explicitTypeImportUris.add(typeImportUriFor(owner.name));
1154
1588
  }
1155
1589
  addSibling(`${owner.name}.application.dart`, targetApplication);
@@ -1211,14 +1645,17 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1211
1645
  ]);
1212
1646
  continue;
1213
1647
  }
1214
- const target = foreign.targetIsContract === true
1215
- ? contractTypeName(foreign.targetAggregateId)
1216
- : concreteClassName(foreign.targetAggregateId);
1648
+ const target =
1649
+ foreign.targetIsContract === true
1650
+ ? contractTypeName(foreign.targetAggregateId)
1651
+ : concreteClassName(foreign.targetAggregateId);
1217
1652
  const fieldMember = member(fieldName);
1218
1653
  const fieldValue = admitsNull(field)
1219
1654
  ? `record.${fieldMember}.valueOrNull`
1220
1655
  : `record.${fieldMember}`;
1221
- const stem = fieldMember.endsWith("Id") ? fieldMember.slice(0, -2) : fieldMember;
1656
+ const stem = fieldMember.endsWith("Id")
1657
+ ? fieldMember.slice(0, -2)
1658
+ : fieldMember;
1222
1659
  const method = member(`${stem}Of`);
1223
1660
  addCollectionMember(aggregate.id, method, [
1224
1661
  ` /// Follow [record]'s ${fieldName} to its typed business record.`,
@@ -1227,15 +1664,18 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1227
1664
  ` if (reference == null) return null;`,
1228
1665
  ` final target = await NmsInternalForeignRefs.resolve(reference, _client.bridge.foreignReader());`,
1229
1666
  ` 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));`,
1667
+ ` return NmsInternalRecordOrigins.remember(target, workspace: NmsInternalNomosRefs.workspace(reference.ref), type: ${JSON.stringify(
1668
+ foreign.targetAggregateId
1669
+ )}, id: NmsInternalNomosRefs.id(reference.ref));`,
1231
1670
  ` }`,
1232
1671
  ``,
1233
1672
  ]);
1234
- const targetOwner = packageDomains[foreign.targetAggregateId] ??
1673
+ const targetOwner =
1674
+ packageDomains[foreign.targetAggregateId] ??
1235
1675
  (foreign.targetDomain === undefined
1236
1676
  ? undefined
1237
1677
  : Object.values(packageDomains).find(
1238
- (candidate) => candidate.domain === foreign.targetDomain,
1678
+ (candidate) => candidate.domain === foreign.targetDomain
1239
1679
  ));
1240
1680
  if (targetOwner !== undefined && targetOwner.domain !== domain) {
1241
1681
  const targetApplication = dartApplicationClassName(targetOwner.domain);
@@ -1262,21 +1702,35 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1262
1702
 
1263
1703
  for (const count of (mod.counts ?? []).map((raw) => finishCount(raw))) {
1264
1704
  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)});`]);
1705
+ const groupType =
1706
+ count.by == null ? "" : keyType(aggregates.get(count.of), count.by);
1707
+ addCollectionMember(count.of, method, [
1708
+ count.by == null
1709
+ ? ` Future<int> ${method}() => _client.${method}();`
1710
+ : ` Future<int> ${method}(${groupType} ${keyParam(
1711
+ count.by
1712
+ )}) => _client.${method}(${keyParam(count.by)});`,
1713
+ ]);
1269
1714
  }
1270
1715
  for (const sum of (mod.sums ?? []).map((raw) => finishSum(raw))) {
1271
1716
  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)});`]);
1717
+ const groupType =
1718
+ sum.by == null ? "" : keyType(aggregates.get(sum.of), sum.by);
1719
+ addCollectionMember(sum.of, method, [
1720
+ sum.by == null
1721
+ ? ` Future<int> ${method}() => _client.${method}();`
1722
+ : ` Future<int> ${method}(${groupType} ${keyParam(
1723
+ sum.by
1724
+ )}) => _client.${method}(${keyParam(sum.by)});`,
1725
+ ]);
1276
1726
  }
1277
1727
 
1278
1728
  for (const aggregate of mod.aggregates) {
1279
- if ((aggregate as { businessBirthInternal?: unknown }).businessBirthInternal === true) continue;
1729
+ if (
1730
+ (aggregate as { businessBirthInternal?: unknown })
1731
+ .businessBirthInternal === true
1732
+ )
1733
+ continue;
1280
1734
  if (isFrameworkAggregate(aggregate)) continue;
1281
1735
  const concrete = concreteClassName(aggregate.id);
1282
1736
  const model = readModelClassName(aggregate.id);
@@ -1284,6 +1738,27 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1284
1738
  const Cap = cap(camel(concrete));
1285
1739
  const aggregateRefType = `NomosRef<${concrete}>`;
1286
1740
  const clientList = safe(`list${Cap}s`);
1741
+ addCollectionMember(aggregate.id, "ref", [
1742
+ ` /// Qualify [id] as a typed ${concrete} reference in this application workspace.`,
1743
+ ` ///`,
1744
+ ` /// Use this at UI boundaries such as route parameters that intentionally retain only`,
1745
+ ` /// the local aggregate id. Use [fromAddress] when the workspace is part of the value.`,
1746
+ ` ${aggregateRefType} ref(String id) {`,
1747
+ ` final workspace = _client.session?.workspace ?? '';`,
1748
+ ` if (workspace.isEmpty) {`,
1749
+ ` throw StateError('Cannot qualify a ${concrete} id before its application workspace is open.');`,
1750
+ ` }`,
1751
+ ` if (id.trim().isEmpty) {`,
1752
+ ` throw ArgumentError.value(id, 'id', 'A business id cannot be empty.');`,
1753
+ ` }`,
1754
+ ` return NmsInternalNomosRefs.create<${concrete}>(`,
1755
+ ` workspace: workspace,`,
1756
+ ` type: ${JSON.stringify(aggregate.id)},`,
1757
+ ` id: id,`,
1758
+ ` );`,
1759
+ ` }`,
1760
+ ``,
1761
+ ]);
1287
1762
  addCollectionMember(aggregate.id, "fromAddress", [
1288
1763
  ` /// Recover a typed ${concrete} reference from its stable public address.`,
1289
1764
  ` ///`,
@@ -1291,14 +1766,18 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1291
1766
  ` /// still verify the signed-in person's access when the reference is used.`,
1292
1767
  ` ${aggregateRefType} fromAddress(String address) {`,
1293
1768
  ` final reference = NmsInternalNomosRefs.fromUri<${concrete}>(address);`,
1294
- ` _nmsRequireReference(reference, ${JSON.stringify(aggregate.id)}, '');`,
1769
+ ` _nmsRequireReference(reference, ${JSON.stringify(
1770
+ aggregate.id
1771
+ )}, '');`,
1295
1772
  ` return reference;`,
1296
1773
  ` }`,
1297
1774
  ``,
1298
1775
  ]);
1299
1776
  addCollectionMember(aggregate.id, "find", [
1300
1777
  ` Future<${model}?> find(${aggregateRefType} reference) async {`,
1301
- ` _nmsRequireReference(reference, ${JSON.stringify(aggregate.id)}, '');`,
1778
+ ` _nmsRequireReference(reference, ${JSON.stringify(
1779
+ aggregate.id
1780
+ )}, '');`,
1302
1781
  ` final targetWorkspace = NmsInternalNomosRefs.workspace(reference);`,
1303
1782
  ` final currentWorkspace = _client.session?.workspace ?? '';`,
1304
1783
  ` if (currentWorkspace.isNotEmpty && targetWorkspace != currentWorkspace) {`,
@@ -1307,21 +1786,27 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1307
1786
  ` );`,
1308
1787
  ` return application.${member(plural(concrete))}.find(reference);`,
1309
1788
  ` }`,
1310
- ` final matches = await _client.${safe(`${stem}ById`)}(NmsInternalNomosRefs.id(reference));`,
1789
+ ` final matches = await _client.${safe(
1790
+ `${stem}ById`
1791
+ )}(NmsInternalNomosRefs.id(reference));`,
1311
1792
  ` return matches.isEmpty ? null : matches.single;`,
1312
1793
  ` }`,
1313
1794
  ``,
1314
1795
  ]);
1315
1796
  addCollectionMember(aggregate.id, "watchOne", [
1316
1797
  ` Stream<${model}?> watchOne(${aggregateRefType} reference) {`,
1317
- ` _nmsRequireReference(reference, ${JSON.stringify(aggregate.id)}, '');`,
1798
+ ` _nmsRequireReference(reference, ${JSON.stringify(
1799
+ aggregate.id
1800
+ )}, '');`,
1318
1801
  ` final targetWorkspace = NmsInternalNomosRefs.workspace(reference);`,
1319
1802
  ` final currentWorkspace = _client.session?.workspace ?? '';`,
1320
1803
  ` if (currentWorkspace.isNotEmpty && targetWorkspace != currentWorkspace) {`,
1321
1804
  ` return Stream.fromFuture(nomosInternalOpen${application}(`,
1322
1805
  ` _client.bridge, reference, _client.actor,`,
1323
1806
  ` )).asyncExpand(`,
1324
- ` (application) => application.${member(plural(concrete))}.watchOne(reference),`,
1807
+ ` (application) => application.${member(
1808
+ plural(concrete)
1809
+ )}.watchOne(reference),`,
1325
1810
  ` );`,
1326
1811
  ` }`,
1327
1812
  ` return _client.watch${Cap}ById(NmsInternalNomosRefs.id(reference))`,
@@ -1343,12 +1828,14 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1343
1828
  const initializers: string[] = [];
1344
1829
  for (const [aggregateId, members] of collectionLines) {
1345
1830
  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);
1831
+ const concrete =
1832
+ aggregate === undefined
1833
+ ? concreteClassName(aggregateId)
1834
+ : concreteClassName(aggregate.id);
1835
+ const collection =
1836
+ aggregate === undefined
1837
+ ? `${concrete}Collection`
1838
+ : dartAggregateCollectionClassName(aggregate);
1352
1839
  const property = member(plural(concrete));
1353
1840
  claimApplicationMember(property, `aggregate collection '${aggregateId}'`);
1354
1841
  appLines.push(` final ${collection} ${property};`);
@@ -1365,39 +1852,65 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1365
1852
  ``,
1366
1853
  ...members,
1367
1854
  `}`,
1368
- ``,
1855
+ ``
1369
1856
  );
1370
1857
  }
1371
1858
 
1372
1859
  const imports = [...siblingImports.entries()]
1373
1860
  .sort(([a], [b]) => a.localeCompare(b))
1374
- .map(([file, names]) => `import '${file}' show ${[...names].sort().join(", ")};`);
1861
+ .map(
1862
+ ([file, names]) =>
1863
+ `import '${file}' show ${[...names].sort().join(", ")};`
1864
+ );
1375
1865
  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";
1866
+ const runtimeImport =
1867
+ opts.supportImport === "package"
1868
+ ? "package:nomos_client/src/nms_generated_runtime.dart"
1869
+ : "nomos_client.dart";
1379
1870
  const applicationInitializers = [
1380
1871
  "_client = client",
1381
1872
  ...(tenantRoot ? ["currentUser = currentUser"] : []),
1382
1873
  `activity = ${activity}._(client)`,
1383
- ...(resolverBindings.length > 0 ? [`conflicts = ${cap(camel(domain))}Conflicts._(client)`] : []),
1874
+ ...(resolverBindings.length > 0
1875
+ ? [`conflicts = ${cap(camel(domain))}Conflicts._(client)`]
1876
+ : []),
1384
1877
  ...initializers,
1385
1878
  ];
1386
1879
  return [
1387
1880
  `// AUTO-GENERATED by nomos-compile — the tenant application surface for "${domain}".`,
1388
1881
  `// 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" : ""};`,
1882
+ ...(sharingPolicies.length > 0
1883
+ ? [`import 'dart:async' show StreamSubscription, unawaited;`]
1884
+ : []),
1885
+ `import '${runtimeImport}' show NmsInternalForeignRefs, NmsInternalNomosRefs, NmsInternalRecordOrigins, NomosBridge, NomosOfferOutcome, NomosRef, NomosSession${
1886
+ usesWorkspaceName ? ", deriveWorkspaceName" : ""
1887
+ }${
1888
+ sharingPolicies.length > 0
1889
+ ? ", Invitation, Recipient, SyncPhase, User"
1890
+ : ""
1891
+ }${
1892
+ tenantRoot ? ", NmsInternalUsers, nmsInternalReceivingGroupNameOf" : ""
1893
+ };`,
1391
1894
  ...(opts.flutterHost === true || sharingPolicies.length > 0
1392
1895
  ? [
1393
1896
  ...(tenantRoot ? [`import 'package:flutter/widgets.dart';`] : []),
1394
1897
  ...(sharingPolicies.length > 0
1395
- ? [`import 'package:nomos_flutter/src/application_share.dart' show ApplicationShareResult;`]
1898
+ ? [
1899
+ `import 'package:nomos_flutter/src/application_share.dart' show ApplicationShareResult;`,
1900
+ ]
1396
1901
  : []),
1397
1902
  ...(opts.flutterHost === true && tenantRoot
1398
- ? [`import 'package:nomos_flutter/src/application_status.dart' show ApplicationSignIn, ApplicationStatus;`]
1903
+ ? [
1904
+ `import 'package:nomos_flutter/src/application_status.dart' show ApplicationSignIn, ApplicationStatus;`,
1905
+ ]
1399
1906
  : []),
1400
- `import 'package:nomos_flutter/src/generated_application_host.dart' show NmsInternalApplicationUserBinding${tenantRoot || sharingPolicies.length > 0 ? ", NmsInternalApplicationHost" : ""}${sharingPolicies.length > 0 ? ", NmsInternalInvitationPolicy" : ""};`,
1907
+ `import 'package:nomos_flutter/src/generated_application_host.dart' show NmsInternalApplicationUserBinding${
1908
+ tenantRoot || sharingPolicies.length > 0
1909
+ ? ", NmsInternalApplicationHost"
1910
+ : ""
1911
+ }${
1912
+ sharingPolicies.length > 0 ? ", NmsInternalInvitationPolicy" : ""
1913
+ };`,
1401
1914
  ]
1402
1915
  : []),
1403
1916
  `import '${typesImport}';`,
@@ -1406,9 +1919,7 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1406
1919
  ...imports,
1407
1920
  ``,
1408
1921
  ...(tenantRoot
1409
- ? [
1410
- `/// The canonical stateful entry point for this business domain.`,
1411
- ]
1922
+ ? [`/// The canonical stateful entry point for this business domain.`]
1412
1923
  : [
1413
1924
  `/// A business domain handle owned by the generated package application.`,
1414
1925
  ]),
@@ -1440,7 +1951,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1440
1951
  ...(tenantRoot ? [` final User currentUser;`] : []),
1441
1952
  ` final ${activity} activity;`,
1442
1953
  ...appLines,
1443
- ` ${application}._(${client} client${tenantRoot ? ", User currentUser" : ""}) : ${applicationInitializers.join(", ")};`,
1954
+ ` ${application}._(${client} client${
1955
+ tenantRoot ? ", User currentUser" : ""
1956
+ }) : ${applicationInitializers.join(", ")};`,
1444
1957
  ...(opts.flutterHost === true && tenantRoot
1445
1958
  ? [
1446
1959
  ``,
@@ -1473,14 +1986,23 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1473
1986
  : []),
1474
1987
  `}`,
1475
1988
  ``,
1476
- `/// The verified business history of this application, newest first.`,
1989
+ `/// The verified recent business activity of this application, newest first.`,
1477
1990
  `final class ${activity} {`,
1478
1991
  ` final ${client} _client;`,
1479
1992
  ` const ${activity}._(this._client);`,
1480
1993
  ``,
1481
- ` Future<List<ActivityEvent>> all({int? limit}) => _client.activityLog(limit: limit);`,
1994
+ ` Future<List<ActivityEvent>> recent({required int limit}) =>`,
1995
+ ` _client.recentActivity(limit: limit);`,
1482
1996
  ``,
1483
- ` Stream<List<ActivityEvent>> watchAll() => _client.watchActivity();`,
1997
+ ` Stream<List<ActivityEvent>> watchRecent({required int limit}) =>`,
1998
+ ` _client.watchRecentActivity(limit: limit);`,
1999
+ ``,
2000
+ ` @Deprecated('Reads ledger history. Use recent with an explicit limit, or a DSL managed count for quantities.')`,
2001
+ ` Future<List<ActivityEvent>> all({int? limit}) =>`,
2002
+ ` recent(limit: limit ?? 50);`,
2003
+ ``,
2004
+ ` @Deprecated('Reads ledger history. Use watchRecent with an explicit limit.')`,
2005
+ ` Stream<List<ActivityEvent>> watchAll() => watchRecent(limit: 50);`,
1484
2006
  `}`,
1485
2007
  ``,
1486
2008
  ...actionResultDeclarations,
@@ -1518,6 +2040,26 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1518
2040
  ` return NmsInternalNomosRefs.create<T>(workspace: workspace, type: affected.type, id: affected.id);`,
1519
2041
  `}`,
1520
2042
  ``,
2043
+ ...(usesAffectedReferenceOrNull
2044
+ ? [
2045
+ `NomosRef<T>? _nmsAffectedReferenceOrNull<T>(NomosOfferOutcome change, String expectedType) {`,
2046
+ ` final matches = change.affected`,
2047
+ ` .where((affected) => _nmsReferenceTypeMatches(affected.type, expectedType))`,
2048
+ ` .toList(growable: false);`,
2049
+ ` if (matches.length > 1) {`,
2050
+ ` throw StateError('The business action expected at most one affected $expectedType record, received \${matches.length}');`,
2051
+ ` }`,
2052
+ ` if (matches.isEmpty) return null;`,
2053
+ ` final affected = matches.single;`,
2054
+ ` final workspace = affected.workspace.isNotEmpty ? affected.workspace : change.workspace;`,
2055
+ ` if (workspace.isEmpty) {`,
2056
+ ` throw StateError('The affected record did not carry its business workspace');`,
2057
+ ` }`,
2058
+ ` return NmsInternalNomosRefs.create<T>(workspace: workspace, type: affected.type, id: affected.id);`,
2059
+ `}`,
2060
+ ``,
2061
+ ]
2062
+ : []),
1521
2063
  ...(usesAffectedReferences
1522
2064
  ? [
1523
2065
  `List<NomosRef<T>> _nmsAffectedReferences<T>(NomosOfferOutcome change, String expectedType) {`,
@@ -1542,9 +2084,13 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1542
2084
  `Future<${application}> nomosInternalBind${application}(`,
1543
2085
  ` NomosSession session, NmsInternalApplicationUserBinding user) async {`,
1544
2086
  ` final application = ${application}._(`,
1545
- ` await ${client}.bind(session, actor: user.nmsInternalActor)${tenantRoot ? "," : ""}`,
2087
+ ` await ${client}.bind(session, actor: user.nmsInternalActor)${
2088
+ tenantRoot ? "," : ""
2089
+ }`,
1546
2090
  ...(tenantRoot
1547
- ? [` NmsInternalUsers.create(identityId: user.nmsInternalIdentityId, identityAddress: user.nmsInternalIdentityAddress),`]
2091
+ ? [
2092
+ ` NmsInternalUsers.create(identityId: user.nmsInternalIdentityId, identityAddress: user.nmsInternalIdentityAddress),`,
2093
+ ]
1548
2094
  : []),
1549
2095
  ` );`,
1550
2096
  ]
@@ -1552,7 +2098,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1552
2098
  `Future<${application}> nomosInternalBind${application}(NomosSession session, String? actor) async {`,
1553
2099
  ` final application = ${application}._(await ${client}.bind(session, actor: actor));`,
1554
2100
  ]),
1555
- ...(sharingPolicies.length > 0 ? [` await application._nmsStartWorkspaceSharing();`] : []),
2101
+ ...(sharingPolicies.length > 0
2102
+ ? [` await application._nmsStartWorkspaceSharing();`]
2103
+ : []),
1556
2104
  ` return application;`,
1557
2105
  `}`,
1558
2106
  ``,
@@ -1570,8 +2118,12 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1570
2118
  ` ),`,
1571
2119
  ` );`,
1572
2120
  ]
1573
- : [` final application = ${application}._(await ${client}.open(bridge, ref: reference, actor: actor));`]),
1574
- ...(sharingPolicies.length > 0 ? [` await application._nmsStartWorkspaceSharing();`] : []),
2121
+ : [
2122
+ ` final application = ${application}._(await ${client}.open(bridge, ref: reference, actor: actor));`,
2123
+ ]),
2124
+ ...(sharingPolicies.length > 0
2125
+ ? [` await application._nmsStartWorkspaceSharing();`]
2126
+ : []),
1575
2127
  ` return application;`,
1576
2128
  `}`,
1577
2129
  ``,