@githolon/dsl 0.108.0 → 0.108.2

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 (95) hide show
  1. package/dart/CHANGELOG.md +5 -0
  2. package/dart/lib/src/application_framework_calls.dart +299 -0
  3. package/dart/lib/src/generated_call.dart +96 -0
  4. package/dart/lib/src/nms_generated_runtime.dart +29 -0
  5. package/dart/lib/src/node_runner_transport.dart +77 -1
  6. package/dart/lib/src/result_usda.dart +487 -0
  7. package/dart/lib/src/runner/node-entry.mjs +6401 -2102
  8. package/dart/lib/src/runner/offline-install.mjs +1538 -163
  9. package/dart/lib/src/runtime_v2_gateway.dart +54 -0
  10. package/dart/lib/src/runtime_v2_transport.dart +117 -0
  11. package/dart/lib/src/session_bound.dart +7 -2
  12. package/dart/lib/src/test_harness.dart +193 -112
  13. package/dart/lib/src/webview_bridge.dart +2120 -569
  14. package/dart/pubspec.yaml +2 -2
  15. package/dart/test/acquire_session_credentials_test.dart +1 -1
  16. package/dart/test/acquire_session_test.dart +68 -14
  17. package/dart/test/adv_realm_interference_test.dart +6 -4
  18. package/dart/test/cluster_births_test.dart +11 -8
  19. package/dart/test/cluster_local_autosync_test.dart +1 -1
  20. package/dart/test/cluster_scale_test.dart +3 -2
  21. package/dart/test/dlq_session_surface_test.dart +52 -35
  22. package/dart/test/first_app_gauntlet_test.dart +1 -1
  23. package/dart/test/local_sync_inert_test.dart +23 -26
  24. package/dart/test/offer_authentication_proof_test.dart +193 -0
  25. package/dart/test/result_usda_test.dart +49 -0
  26. package/dart/test/session_bound_test.dart +8 -11
  27. package/dart/test/stale_bundle_test.dart +18 -3
  28. package/dart/test/test_harness_test.dart +14 -27
  29. package/dart/test/webview_bridge_test.dart +504 -83
  30. package/dart_types/CHANGELOG.md +7 -0
  31. package/dart_types/lib/nomos_kernel_protocol.dart +3 -2
  32. package/dart_types/lib/src/custody.dart +64 -19
  33. package/dart_types/lib/src/local_work_rejected.dart +30 -45
  34. package/dart_types/lib/src/proto/nomos/custody/v1/snapshot.pb.dart +169 -0
  35. package/dart_types/lib/src/proto/nomos/custody/v1/snapshot.pbenum.dart +11 -0
  36. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +2021 -234
  37. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +175 -8
  38. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +8664 -0
  39. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pbenum.dart +369 -0
  40. package/dart_types/lib/src/surfaced.dart +30 -25
  41. package/dart_types/pubspec.yaml +1 -1
  42. package/dart_types/test/surfaced_test.dart +2 -1
  43. package/package.json +1 -1
  44. package/runtime/generated-application-host.js +5736 -2047
  45. package/src/authoring.ts +3 -2
  46. package/src/births.ts +21 -0
  47. package/src/build_package.ts +30 -0
  48. package/src/business_birth.ts +39 -35
  49. package/src/codegen_call_usda.ts +1111 -0
  50. package/src/codegen_dart.ts +207 -282
  51. package/src/codegen_dart_application.ts +195 -252
  52. package/src/codegen_dart_client.ts +1978 -593
  53. package/src/codegen_dart_framework_calls.ts +291 -0
  54. package/src/codegen_dart_scenario.ts +7 -0
  55. package/src/codegen_proof.ts +3 -4
  56. package/src/codegen_provider_dart.ts +21 -7
  57. package/src/codegen_query_usda.ts +214 -0
  58. package/src/codegen_result_usda.ts +685 -0
  59. package/src/codegen_ts.ts +3945 -1111
  60. package/src/compile_package_main.ts +360 -32
  61. package/src/directive.ts +19 -4
  62. package/src/engine_entry.ts +45 -4
  63. package/src/framework/application_home.ts +2 -0
  64. package/src/framework/auth_provider_law.ts +262 -20
  65. package/src/framework/home.ts +32 -28
  66. package/src/framework/owned_custody.ts +19 -11
  67. package/src/framework/owned_custody_value.ts +51 -0
  68. package/src/framework/relations.ts +57 -21
  69. package/src/framework/signing_identity.ts +34 -0
  70. package/src/framework/workspaces.ts +31 -9
  71. package/src/framework_call_definitions.ts +53 -0
  72. package/src/framework_read_definitions.ts +45 -0
  73. package/src/identity_frontier.ts +58 -0
  74. package/src/index.ts +20 -1
  75. package/src/intent.ts +216 -16
  76. package/src/law_check.ts +19 -1
  77. package/src/law_graph.ts +57 -8
  78. package/src/law_graph_usda.ts +73 -5
  79. package/src/law_value_type.ts +106 -19
  80. package/src/read.ts +47 -0
  81. package/src/read_projection_namespace.ts +7 -0
  82. package/src/shape_compat.ts +38 -39
  83. package/src/stable_ids.ts +37 -2
  84. package/src/stable_ids_for_module.ts +64 -4
  85. package/src/test_call_usda.ts +83 -0
  86. package/src/wire.ts +36 -2
  87. package/src/wire_encode.ts +45 -1
  88. package/src/workspace_sharing.ts +27 -10
  89. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbjson.dart +0 -6096
  90. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbserver.dart +0 -50
  91. package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pbjson.dart +0 -178
  92. package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pb.dart +0 -828
  93. package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbenum.dart +0 -465
  94. package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbjson.dart +0 -573
  95. package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbserver.dart +0 -50
@@ -14,7 +14,7 @@
14
14
  * [NomosBridge] (the WebView runner) with:
15
15
  *
16
16
  * * a typed OFFER method per directive — takes the generated input/implementation DTO, serialises via its
17
- * `nmsInternalToPayloadJson()`, auto-stamps omitted `…At` timestamps (the same caller-clock sugar the TS
17
+ * canonical USDA, auto-stamps omitted `…At` timestamps (the same caller-clock sugar the TS
18
18
  * client applies), returns the new local head;
19
19
  * * a typed declared-QUERY accessor per `query(...)` — one-shot `Future<List<…ReadModel>>` + a
20
20
  * reactive `watch…` `Stream<List<…ReadModel>>`, parsed off the bridge's `{type,id,data}` rows;
@@ -35,7 +35,11 @@ import {
35
35
  OWNED_PREMINTED_RECORDS_PAYLOAD_FIELD,
36
36
  type AggregateHandle,
37
37
  } from "./aggregate.js";
38
- import { isGitObjectIdSchema, isSha256DigestSchema, type Field } from "./fields.js";
38
+ import {
39
+ isGitObjectIdSchema,
40
+ isSha256DigestSchema,
41
+ type Field,
42
+ } from "./fields.js";
39
43
  import type { Directive } from "./directive.js";
40
44
  import type { QueryDecl } from "./query.js";
41
45
  import type { DomainModule } from "./codegen_dart.js";
@@ -47,11 +51,30 @@ import {
47
51
  registerDartNames,
48
52
  concreteClassName,
49
53
  dartBusinessRecordInputs,
54
+ dartForeignReferenceInputs,
50
55
  dartActionInputClassName,
51
56
  dartMemberName,
52
57
  collectDartEnums,
53
58
  dartEnumTypeForField,
59
+ dartEnumLookupKey,
60
+ introspectPayload,
61
+ aggregateFieldSpecs,
62
+ collectOwnIdFieldsByAggregate,
63
+ refIdClassResolver,
64
+ zodNumberIsInt,
65
+ type AggFieldSpec,
66
+ type PayloadFieldSpec,
54
67
  } from "./codegen_dart.js";
68
+ import type { LawGraph } from "./law_graph.js";
69
+ import {
70
+ DART_CALL_USDA_PRELUDE,
71
+ emitDartCallUsdaEncoder,
72
+ type DartCallUsdaFieldAccess,
73
+ } from "./codegen_call_usda.js";
74
+ import {
75
+ emitDartQueryUsdaEncoder,
76
+ type QueryScalarKind,
77
+ } from "./codegen_query_usda.js";
55
78
  import { finishCount } from "./count.js";
56
79
  import { finishSum } from "./sum.js";
57
80
  import {
@@ -73,15 +96,24 @@ import {
73
96
  type NomosDescribeModel,
74
97
  } from "./describe_model.js";
75
98
  import { assertModelClientMemberAvailable } from "./generated_client_names.js";
76
- import { ownedRecordActionDirectiveId } from "./framework/owned_custody.js";
99
+ import {
100
+ OWNED_DEVICE_APPROVAL_DIRECTIVE,
101
+ ownedRecordActionDirectiveId,
102
+ } from "./framework/owned_custody.js";
77
103
  import { contractTypeName } from "./contract.js";
78
104
  import { admitsNull } from "./null_meta.js";
105
+ import { zodDef, zodKind } from "./zod_introspection.js";
106
+ import type { WorkspaceSharingPolicy } from "./workspace_sharing.js";
79
107
 
80
108
  const camel = (s: string) =>
81
109
  s.replace(/[_\s-]+(\w)/g, (_m, c: string) => c.toUpperCase());
82
110
  const lcFirst = (s: string) =>
83
111
  s.length ? s[0]!.toLowerCase() + s.slice(1) : s;
84
112
 
113
+ export const dartInternalWorkspaceSharingReceiptMethod = (
114
+ directiveId: string
115
+ ): string => `nmsInternal${cap(camel(directiveId))}`;
116
+
85
117
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
86
118
  type AnyDirective = Directive<any>;
87
119
 
@@ -127,6 +159,11 @@ const safeMethod = (s: string): string => (DART_RESERVED.has(s) ? `${s}_` : s);
127
159
  /** `guestbook` / `tool-library` / `Tool Library` → `GuestbookClient` / `ToolLibraryClient`. */
128
160
  export const dartClientClassName = (domain: string): string =>
129
161
  `${cap(camel(domain))}Client`;
162
+
163
+ /** Compiler-private, but cross-library, entry point used by the generated application
164
+ * when a business action first has to author one hidden source-trust directive. */
165
+ export const dartInternalSourceTrustMethod = (directiveId: string): string =>
166
+ `nmsInternalOffer${cap(camel(directiveId))}`;
130
167
  /** `guestbook` → `guestbookDomainHash` — the Dart top-level const (lowerCamel by lint). */
131
168
  export const dartHashConstName = (fileBase: string): string =>
132
169
  `${lcFirst(camel(fileBase))}DomainHash`;
@@ -148,15 +185,88 @@ function dartRawJson(value: unknown): string {
148
185
  const dartStr = (s: string) =>
149
186
  `'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
150
187
 
188
+ const dartSealedValueName = (field: string): string =>
189
+ `nmsInternalSealed${cap(camel(field))}`;
190
+
191
+ const dartSealKeyName = (field: string): string =>
192
+ `nmsInternalSealKey${cap(camel(field))}`;
193
+
194
+ /** A schema-specific Dart expression for one public payload field. The USDA
195
+ * encoder consumes typed values directly; any legacy arbitrary-data shape is a
196
+ * compiler refusal, never a route back through JSON. */
197
+ function dartCallUsdaFieldAccess(
198
+ domain: string,
199
+ directive: string,
200
+ spec: PayloadFieldSpec
201
+ ): DartCallUsdaFieldAccess {
202
+ const member = `payload.${dartMemberName(spec.name)}`;
203
+ if (spec.nullAdmits === true) {
204
+ return { value: member, slot: true };
205
+ }
206
+ const value = spec.optional ? `${member}!` : member;
207
+ switch (spec.shape) {
208
+ case "scalar":
209
+ case "enum":
210
+ case "strList":
211
+ case "wrappedList":
212
+ case "refList":
213
+ case "record":
214
+ case "recordList":
215
+ case "stringMap":
216
+ case "vo":
217
+ case "voList":
218
+ return {
219
+ value,
220
+ ...(spec.optional ? { present: `${member} != null` } : {}),
221
+ };
222
+ case "wrapped":
223
+ return {
224
+ value: `${value}.value`,
225
+ ...(spec.optional ? { present: `${member} != null` } : {}),
226
+ };
227
+ case "ref":
228
+ // Optional aggregate references are deliberately not nullable NomosRef<T> values at the
229
+ // application boundary. They are required NomosOptionalRef<T> choices, so lower only the
230
+ // present arm; absence omits the USDA field. Passing the wrapper itself to the internal ref
231
+ // codec loses both the sum's meaning and Dart's type safety.
232
+ if (spec.optional) {
233
+ const reference = `${member}.referenceOrNull`;
234
+ return {
235
+ value: `NmsInternalNomosRefs.id(${reference}!)`,
236
+ present: `${reference} != null`,
237
+ };
238
+ }
239
+ return {
240
+ value: `NmsInternalNomosRefs.id(${value})`,
241
+ };
242
+ case "addressRef":
243
+ return {
244
+ value: `(workspace: NmsInternalNomosRefs.workspace(${value}), type: NmsInternalNomosRefs.type(${value}), id: NmsInternalNomosRefs.id(${value}))`,
245
+ ...(spec.optional ? { present: `${member} != null` } : {}),
246
+ };
247
+ case "versionedRef":
248
+ return {
249
+ value: `(pinned: (workspace: NmsInternalNomosRefs.workspace(${value}.pinned), type: NmsInternalNomosRefs.type(${value}.pinned), id: NmsInternalNomosRefs.id(${value}.pinned)), series: (workspace: NmsInternalNomosRefs.workspace(${value}.series), type: NmsInternalNomosRefs.type(${value}.series), id: NmsInternalNomosRefs.id(${value}.series)))`,
250
+ ...(spec.optional ? { present: `${member} != null` } : {}),
251
+ };
252
+ case "recipient":
253
+ return {
254
+ value: `NmsInternalRecipients.address(${value})`,
255
+ ...(spec.optional ? { present: `${member} != null` } : {}),
256
+ };
257
+ }
258
+ }
259
+
151
260
  /** The Dart param type for a query index key. Aggregate relations keep their canonical typed ref. */
152
261
  function dartKeyType(
153
262
  agg: AggregateHandle | undefined,
154
263
  key: string,
155
- enumDartTypes: ReadonlyMap<string, string>,
264
+ enumDartTypes: ReadonlyMap<string, string>
156
265
  ): string {
157
266
  const lead = key.includes(".") ? key.split(".")[0]! : key;
158
267
  const f = agg?.fields[lead] as Field | undefined;
159
268
  if (f && (f.kind === "int" || f.kind === "counter")) return "int";
269
+ if (f?.kind === "bool") return "bool";
160
270
  if (f?.kind === "ref") {
161
271
  if (f.foreignRef !== undefined) {
162
272
  const target =
@@ -179,6 +289,29 @@ function dartKeyType(
179
289
  return "String";
180
290
  }
181
291
 
292
+ function queryScalarKind(
293
+ agg: AggregateHandle | undefined,
294
+ key: string
295
+ ): QueryScalarKind {
296
+ // A nested index is a scalar leaf selected from a declared structural path. The current DSL
297
+ // exposes that leaf as String; its exact path still remains law-authenticated in query USDA.
298
+ if (key.includes(".")) return "string";
299
+ const field = agg?.fields[key] as Field | undefined;
300
+ switch (field?.kind) {
301
+ case "int":
302
+ case "counter":
303
+ return "integer";
304
+ case "bool":
305
+ return "boolean";
306
+ case "enum":
307
+ return "token";
308
+ case "ref":
309
+ return "reference";
310
+ default:
311
+ return "string";
312
+ }
313
+ }
314
+
182
315
  function dartKeyExpr(agg: AggregateHandle | undefined, key: string): string {
183
316
  const lead = key.includes(".") ? key.split(".")[0]! : key;
184
317
  const field = agg?.fields[lead] as Field | undefined;
@@ -225,6 +358,339 @@ function rowToModel(model: string, rowVar: string, workspace = "_ws"): string {
225
358
  return `NmsInternalRecordOrigins.remember(${model}.fromProjected((${rowVar}['data'] as Map).cast<String, dynamic>(), (${rowVar}['id'] ?? '').toString(), ${workspace}, (${rowVar}['type'] ?? '').toString()), workspace: ${workspace}, type: (${rowVar}['type'] ?? '').toString(), id: (${rowVar}['id'] ?? '').toString())`;
226
359
  }
227
360
 
361
+ interface DartResultField {
362
+ readonly spec: AggFieldSpec;
363
+ readonly lawPath: string;
364
+ readonly foreignWorkspacePath?: string;
365
+ }
366
+
367
+ function dartUsdaJsonMap(expression: string): string {
368
+ return `(${expression}.jsonValue() as Map).cast<String, Object?>()`;
369
+ }
370
+
371
+ function dartUsdaBasePresentValue(
372
+ spec: AggFieldSpec,
373
+ expression: string,
374
+ workspace: string,
375
+ foreignWorkspaceExpression?: string
376
+ ): string {
377
+ switch (spec.kind) {
378
+ case "string":
379
+ case "position": {
380
+ const value = `${expression}.stringValue()`;
381
+ if (spec.valueClass !== undefined) {
382
+ return `NmsInternalNomosRefs.create<${spec.valueClass}>(workspace: ${workspace}, type: '${spec.valueAggregateType}', id: ${value})`;
383
+ }
384
+ return spec.semanticString === "sha256"
385
+ ? `Sha256Digest(${value})`
386
+ : spec.semanticString === "git-object-id"
387
+ ? `GitObjectId(${value})`
388
+ : value;
389
+ }
390
+ case "ref": {
391
+ const value = `${expression}.stringValue()`;
392
+ if (spec.foreignRefTarget !== undefined) {
393
+ if (foreignWorkspaceExpression === undefined) {
394
+ throw new Error(
395
+ `Dart result USDA codegen: foreign reference '${spec.wireName}' has no workspace-field decoder`
396
+ );
397
+ }
398
+ return `NmsInternalForeignRefs.create<${spec.foreignRefTarget}>(id: ${value}, workspace: ${foreignWorkspaceExpression}, type: '${spec.foreignRefAggregateType}', fromJson: ${spec.foreignRefTarget}.fromJson)`;
399
+ }
400
+ if (spec.refIdClass !== undefined) {
401
+ return `NmsInternalNomosRefs.create<${spec.refIdClass}>(workspace: ${workspace}, type: '${spec.refAggregateType}', id: ${value})`;
402
+ }
403
+ if (spec.nomosRefTarget !== undefined) {
404
+ return `NmsInternalNomosRefs.fromJson<${spec.nomosRefTarget}>(${expression}.jsonValue())`;
405
+ }
406
+ if (spec.versionedRefTarget !== undefined) {
407
+ return `() { final value = ${dartUsdaJsonMap(
408
+ expression
409
+ )}; return NmsInternalVersionedNomosRefs.create<${
410
+ spec.versionedRefTarget
411
+ }, ${spec.versionedRefSeries}>(pinned: NmsInternalNomosRefs.fromJson<${
412
+ spec.versionedRefTarget
413
+ }>(value['pinned']), series: NmsInternalNomosRefs.fromJson<${
414
+ spec.versionedRefSeries
415
+ }>(value['series']), currentField: ${JSON.stringify(
416
+ spec.versionedRefCurrentField
417
+ )}); }()`;
418
+ }
419
+ return value;
420
+ }
421
+ case "int":
422
+ case "counter":
423
+ return `${expression}.numberValue().toInt()`;
424
+ case "bool":
425
+ return `${expression}.boolValue()`;
426
+ case "enum":
427
+ return `${spec.dartType}.fromWire(${expression}.stringValue())`;
428
+ case "json":
429
+ if (spec.voClass !== undefined)
430
+ return `${spec.voClass}.fromProjected(${dartUsdaJsonMap(expression)})`;
431
+ return spec.jsonShape === "object"
432
+ ? dartUsdaJsonMap(expression)
433
+ : `${expression}.jsonValue()`;
434
+ case "evidence":
435
+ return `EvidenceRef.fromProjected(${dartUsdaJsonMap(expression)})`;
436
+ case "set":
437
+ return `${expression}.children.map((value) => value.stringValue()).toSet()`;
438
+ case "map": {
439
+ if (spec.mapValueVoClass !== undefined) {
440
+ return `${expression}.mapValue<${spec.mapValueVoClass}>((value) => ${
441
+ spec.mapValueVoClass
442
+ }.fromProjected(${dartUsdaJsonMap("value")}))`;
443
+ }
444
+ return spec.mapValueKind === "json"
445
+ ? `${expression}.mapValue<Object?>((value) => value.jsonValue())`
446
+ : `${expression}.mapValue<String>((value) => value.stringValue())`;
447
+ }
448
+ }
449
+ }
450
+
451
+ function dartUsdaPresentValue(
452
+ spec: AggFieldSpec,
453
+ expression: string,
454
+ workspace: string,
455
+ foreignWorkspaceExpression?: string
456
+ ): string {
457
+ if (spec.surfaced !== true) {
458
+ return dartUsdaBasePresentValue(
459
+ spec,
460
+ expression,
461
+ workspace,
462
+ foreignWorkspaceExpression
463
+ );
464
+ }
465
+ const base = (value: string) =>
466
+ dartUsdaBasePresentValue(
467
+ spec,
468
+ value,
469
+ workspace,
470
+ foreignWorkspaceExpression
471
+ );
472
+ return `() {
473
+ final encoded = ${expression};
474
+ if (encoded.typeName != 'NomosRecordValue') {
475
+ return SurfacedResolved<${spec.dartType}>(${base("encoded")});
476
+ }
477
+ final surfaced = encoded.jsonMember('__surfaced');
478
+ if (surfaced != null) {
479
+ final pending = surfaced.requiredJsonMember('pending');
480
+ if (pending.typeName != 'NomosJsonArray' || pending.children.length < 2) {
481
+ throw const FormatException('Surfaced pending value needs at least two observations');
482
+ }
483
+ return SurfacedPending<${
484
+ spec.dartType
485
+ }>(List.unmodifiable(pending.children.map((observation) {
486
+ if (observation.typeName != 'NomosJsonObject') {
487
+ throw const FormatException('Malformed Surfaced observation');
488
+ }
489
+ final occurredAt = observation.requiredJsonMember('occurredAt');
490
+ final author = observation.jsonMember('author');
491
+ return SurfacedObservation<${spec.dartType}>(
492
+ value: ${base("observation.requiredJsonMember('value')")},
493
+ occurredAt: Hlc(
494
+ physical: occurredAt.requiredJsonMember('physical').intValue(),
495
+ logical: occurredAt.requiredJsonMember('logical').intValue(),
496
+ replica: int.parse(occurredAt.requiredJsonMember('replica').stringValue()),
497
+ ),
498
+ author: author == null || author.typeName == 'NomosNullValue' ? null : author.stringValue(),
499
+ );
500
+ })));
501
+ }
502
+ final evolution = encoded.jsonMember('__evolution');
503
+ if (evolution != null) {
504
+ final source = evolution.jsonMember('sourceSchema');
505
+ return SurfacedEvolutionRequired<${spec.dartType}>(
506
+ targetSchema: evolution.requiredJsonMember('targetSchema').stringValue(),
507
+ sourceSchemas: source == null || source.typeName == 'NomosNullValue'
508
+ ? const <String>[]
509
+ : <String>[source.stringValue()],
510
+ authoredUnder: const <String>[],
511
+ reason: evolution.requiredJsonMember('reason').stringValue(),
512
+ );
513
+ }
514
+ throw const FormatException('Structured Surfaced value has no recognised framework envelope');
515
+ }()`;
516
+ }
517
+
518
+ function dartUsdaFieldValue(
519
+ field: DartResultField,
520
+ row = "row",
521
+ workspace = "workspace"
522
+ ): string {
523
+ const selected = `${row}.field(${JSON.stringify(field.lawPath)})`;
524
+ const required = `${row}.requiredField(${JSON.stringify(field.lawPath)})`;
525
+ const foreignWorkspaceExpression = field.foreignWorkspacePath === undefined
526
+ ? undefined
527
+ : `(${row}.field(${JSON.stringify(
528
+ field.foreignWorkspacePath
529
+ )})?.stringValue() ?? '')`;
530
+ const present = dartUsdaPresentValue(
531
+ field.spec,
532
+ field.spec.optional ? `${selected}!` : required,
533
+ workspace,
534
+ foreignWorkspaceExpression
535
+ );
536
+ if (field.spec.nullAdmits === true) {
537
+ const slotType = field.spec.dartType.replace(/\?$/, "");
538
+ return `() { final value = ${selected}; if (value == null) return const NomosSlot<${slotType}>.omitted(); if (value.typeName == 'NomosNullValue') return const NomosSlot<${slotType}>.explicitNull(); return NomosSlot<${slotType}>.of(${dartUsdaPresentValue(
539
+ field.spec,
540
+ "value",
541
+ workspace,
542
+ foreignWorkspaceExpression
543
+ )}); }()`;
544
+ }
545
+ if (field.spec.kind === "set") {
546
+ return `(${selected} == null ? const <String>{} : ${dartUsdaPresentValue(
547
+ field.spec,
548
+ `${selected}!`,
549
+ workspace,
550
+ foreignWorkspaceExpression
551
+ )})`;
552
+ }
553
+ if (field.spec.kind === "map") {
554
+ return `(${selected} == null ? const {} : ${dartUsdaPresentValue(
555
+ field.spec,
556
+ `${selected}!`,
557
+ workspace,
558
+ foreignWorkspaceExpression
559
+ )})`;
560
+ }
561
+ return field.spec.optional
562
+ ? `(${selected} == null || ${selected}!.typeName == 'NomosNullValue' ? null : ${present})`
563
+ : present;
564
+ }
565
+
566
+ function dartUsdaProjectionValue(
567
+ schema: z.ZodTypeAny,
568
+ expression: string
569
+ ): string {
570
+ let inner = schema;
571
+ while (
572
+ zodKind(inner) === "optional" ||
573
+ zodKind(inner) === "default" ||
574
+ zodKind(inner) === "nullable"
575
+ ) {
576
+ inner = zodDef(inner).innerType!;
577
+ }
578
+ const value = `${expression}.jsonValue()`;
579
+ switch (zodKind(inner)) {
580
+ case "string":
581
+ case "enum":
582
+ return `${value} as String`;
583
+ case "boolean":
584
+ return `${value} as bool`;
585
+ case "number":
586
+ return zodNumberIsInt(inner)
587
+ ? `(${value} as num).toInt()`
588
+ : `(${value} as num).toDouble()`;
589
+ case "object":
590
+ case "record":
591
+ return `(${value} as Map).cast<String, Object?>()`;
592
+ case "array":
593
+ return `(${value} as List).cast<Object?>()`;
594
+ default:
595
+ return value;
596
+ }
597
+ }
598
+
599
+ interface DartUsdaResultDecoder {
600
+ readonly model: string;
601
+ readonly refType: string;
602
+ readonly aggregatePath: string;
603
+ readonly runtimeType: string;
604
+ readonly baseFields: readonly {
605
+ readonly name: string;
606
+ readonly path: string;
607
+ readonly spec: AggFieldSpec;
608
+ readonly required: boolean;
609
+ readonly foreignWorkspacePath?: string;
610
+ }[];
611
+ readonly computedFields: readonly {
612
+ readonly name: string;
613
+ readonly path: string;
614
+ readonly schema: z.ZodTypeAny;
615
+ readonly required: boolean;
616
+ readonly enumType?: string;
617
+ }[];
618
+ readonly attributedFields: readonly string[];
619
+ }
620
+
621
+ function emitDartUsdaResultDecoder(
622
+ decoder: DartUsdaResultDecoder
623
+ ): readonly string[] {
624
+ const functionName = `_nmsDecode${decoder.model}Rows`;
625
+ const constructorFields = decoder.baseFields.map(
626
+ (field) =>
627
+ ` ${field.spec.name}: ${dartUsdaFieldValue({
628
+ spec: field.spec,
629
+ lawPath: field.path,
630
+ ...(field.foreignWorkspacePath === undefined
631
+ ? {}
632
+ : { foreignWorkspacePath: field.foreignWorkspacePath }),
633
+ })},`
634
+ );
635
+ const computedFields = decoder.computedFields.map((field) => {
636
+ const selected = `row.field(${JSON.stringify(field.path)})`;
637
+ const required = `row.requiredField(${JSON.stringify(field.path)})`;
638
+ const decode = (value: string) => field.enumType === undefined
639
+ ? dartUsdaProjectionValue(field.schema, value)
640
+ : `${field.enumType}.fromWire(${value}.jsonValue() as String)`;
641
+ return field.required
642
+ ? ` ${dartMemberName(field.name)}: ${decode(required)},`
643
+ : ` ${dartMemberName(
644
+ field.name
645
+ )}: ${selected} == null ? null : ${decode(`${selected}!`)},`;
646
+ });
647
+ const provenanceFields = decoder.attributedFields.map((name) => {
648
+ const field = decoder.baseFields.find(
649
+ (candidate) => candidate.name === name
650
+ );
651
+ if (field === undefined) {
652
+ throw new Error(
653
+ `Dart result USDA codegen: attributed field '${decoder.model}.${name}' is not stored`
654
+ );
655
+ }
656
+ const selected = `row.field(${JSON.stringify(field.path)})`;
657
+ return ` if (${selected} != null) ${JSON.stringify(
658
+ name
659
+ )}: FieldProvenance(author: ${selected}!.property('nomos:author') is String ? ${selected}.property('nomos:author') as String : null, wasContested: ${selected}.property('nomos:wasContested') == true),`;
660
+ });
661
+ return [
662
+ `List<${decoder.model}> ${functionName}(Uint8List bytes, String workspace) {`,
663
+ ` final result = NmsUsdaResult.parse(bytes);`,
664
+ ` return result.rows.map((row) {`,
665
+ ` if (row.relationship('nomos:type') != ${JSON.stringify(
666
+ decoder.aggregatePath
667
+ )}) {`,
668
+ ` throw const FormatException('USDA row has the wrong law type for ${decoder.model}');`,
669
+ ` }`,
670
+ ` final id = row.property('nomos:id');`,
671
+ ` if (id is! String) throw const FormatException('USDA row has no string identity');`,
672
+ ` return ${decoder.model}(`,
673
+ ` ref: NmsInternalNomosRefs.create<${
674
+ decoder.refType
675
+ }>(workspace: workspace, type: ${JSON.stringify(
676
+ decoder.runtimeType
677
+ )}, id: id),`,
678
+ ...(provenanceFields.length > 0
679
+ ? [` \$provenance: {`, ...provenanceFields, ` },`]
680
+ : []),
681
+ ...constructorFields,
682
+ ...computedFields,
683
+ ` );`,
684
+ ` }).toList(growable: false);`,
685
+ `}`,
686
+ `NomosPage<${decoder.model}> _nmsDecode${decoder.model}Page(Uint8List bytes, String workspace) {`,
687
+ ` final result = NmsUsdaResult.parse(bytes);`,
688
+ ` return NomosPage(rows: ${functionName}(bytes, workspace), cursor: result.cursor, done: result.done);`,
689
+ `}`,
690
+ ``,
691
+ ];
692
+ }
693
+
228
694
  export interface DartBridgeClientOptions {
229
695
  /** Content identity of the built typed USDA closure (`.law.usdz`). */
230
696
  readonly domainHash: string;
@@ -294,6 +760,12 @@ export interface DartBridgeClientOptions {
294
760
  * directly. Empty/omitted ⇒ the barrel carries everything (the 'all' / flat path, byte-identical).
295
761
  */
296
762
  readonly explicitTypeImports?: readonly string[];
763
+ /** Canonical typed-law graph and its rendered USDA paths. Required for any
764
+ * generated directive: Dart authors the same NomosCall USDA as TypeScript. */
765
+ readonly callUsda?: {
766
+ readonly graph: LawGraph;
767
+ readonly paths: ReadonlyMap<string, string>;
768
+ };
297
769
  }
298
770
 
299
771
  /** Generate the `<name>.client.dart` async bridge client for one domain module. */
@@ -301,6 +773,28 @@ export function generateDartBridgeClient(
301
773
  mod: DomainModule,
302
774
  opts: DartBridgeClientOptions
303
775
  ): string {
776
+ const callUsda = opts.callUsda;
777
+ if (callUsda === undefined) {
778
+ throw new Error(
779
+ `Dart client codegen for '${
780
+ mod.domain ?? mod.name
781
+ }' requires the composed law graph; ` +
782
+ "application calls and results may not fall back to generic maps"
783
+ );
784
+ }
785
+ const authoredDirectives = mod.directives as AnyDirective[];
786
+ const workspaceSharingPolicies: readonly WorkspaceSharingPolicy[] =
787
+ mod.applicationWorkspaceSharing ?? [];
788
+ const workspaceSharingReceipts = [
789
+ ...new Map(
790
+ workspaceSharingPolicies
791
+ .flatMap((policy) => [...policy.delivered, ...policy.refused])
792
+ .map((receipt) => [receipt.records.id, receipt.records] as const)
793
+ ).values(),
794
+ ] as AnyDirective[];
795
+ const workspaceSharingReceiptIds = new Set(
796
+ workspaceSharingReceipts.map((directive) => directive.id)
797
+ );
304
798
  const publicAggregates = mod.aggregates.filter(
305
799
  (a) =>
306
800
  (a as { businessBirthInternal?: unknown }).businessBirthInternal !== true
@@ -339,7 +833,7 @@ export function generateDartBridgeClient(
339
833
  mod.directives,
340
834
  domain,
341
835
  mod.valueObjects ?? [],
342
- mod.deriveds ?? [],
836
+ mod.deriveds ?? []
343
837
  ).dartTypes;
344
838
  assertModelClientMemberAvailable(mod);
345
839
  const describeModel =
@@ -359,6 +853,162 @@ export function generateDartBridgeClient(
359
853
  : "nomos_client.dart";
360
854
  const aggById = new Map<string, AggregateHandle>();
361
855
  for (const a of mod.aggregates) aggById.set(a.id, a);
856
+ const packageAggregateDomains = opts.packageAggregateDomains ?? {};
857
+ const refIdClassForAggregate = refIdClassResolver(mod);
858
+ const ownIdFieldsByAggregate = collectOwnIdFieldsByAggregate(
859
+ mod.aggregates,
860
+ authoredDirectives,
861
+ enumDartTypes
862
+ );
863
+ const aggregateWireTypeForAggregate = (aggregateType: string): string => {
864
+ const local = aggById.get(aggregateType);
865
+ if (local !== undefined && !isNamespacedAggregate(local))
866
+ return aggregateType;
867
+ const owner = packageAggregateDomains[aggregateType] as
868
+ | { domain: string; namespaced?: boolean }
869
+ | undefined;
870
+ if (owner?.namespaced === false) return aggregateType;
871
+ const ownerDomain =
872
+ owner?.domain ?? (local === undefined ? undefined : domain);
873
+ return ownerDomain === undefined
874
+ ? aggregateType
875
+ : qualifyReadProjectionType(ownerDomain, aggregateType);
876
+ };
877
+ const resultDecoderSources: string[] = [];
878
+ const resultDecoderByAggregate = new Map<string, DartUsdaResultDecoder>();
879
+ {
880
+ for (const aggregate of mod.aggregates) {
881
+ const aggregateNode = callUsda.graph.nodes.find(
882
+ (node) =>
883
+ node.kind === "aggregate" && node.entity.label === aggregate.id
884
+ );
885
+ if (aggregateNode === undefined) {
886
+ throw new Error(
887
+ `Dart result USDA codegen: composed law has no '${domain}.${aggregate.id}' aggregate`
888
+ );
889
+ }
890
+ const aggregatePath = callUsda.paths.get(aggregateNode.entity.id);
891
+ if (aggregatePath === undefined) {
892
+ throw new Error(
893
+ `Dart result USDA codegen: '${domain}.${aggregate.id}' has no canonical USDA path`
894
+ );
895
+ }
896
+ const aggregateEnumDartTypes = aggregate.framework === true
897
+ ? collectDartEnums([aggregate], [], "nomos_framework").dartTypes
898
+ : enumDartTypes;
899
+ const specs = aggregateFieldSpecs(
900
+ aggregate,
901
+ aggregateEnumDartTypes,
902
+ refIdClassForAggregate,
903
+ ownIdFieldsByAggregate.get(aggregate.id) ?? new Set(),
904
+ aggregateWireTypeForAggregate
905
+ );
906
+ const baseFields = specs.map((spec) => {
907
+ const fieldNode = callUsda.graph.nodes.find(
908
+ (node) =>
909
+ node.kind === "field" &&
910
+ node.owner.id === aggregateNode.entity.id &&
911
+ node.entity.label === spec.wireName
912
+ );
913
+ if (fieldNode === undefined) {
914
+ throw new Error(
915
+ `Dart result USDA codegen: composed law has no '${domain}.${aggregate.id}.${spec.wireName}' field`
916
+ );
917
+ }
918
+ const path = callUsda.paths.get(fieldNode.entity.id);
919
+ if (path === undefined) {
920
+ throw new Error(
921
+ `Dart result USDA codegen: '${domain}.${aggregate.id}.${spec.wireName}' has no canonical USDA path`
922
+ );
923
+ }
924
+ const field = aggregate.fields[spec.wireName] as Field;
925
+ let foreignWorkspacePath: string | undefined;
926
+ if (spec.foreignRefWorkspaceField !== undefined) {
927
+ const workspaceNode = callUsda.graph.nodes.find(
928
+ (node) =>
929
+ node.kind === "field" &&
930
+ node.owner.id === aggregateNode.entity.id &&
931
+ node.entity.label === spec.foreignRefWorkspaceField
932
+ );
933
+ if (workspaceNode === undefined) {
934
+ throw new Error(
935
+ `Dart result USDA codegen: '${domain}.${aggregate.id}.${spec.wireName}' has no declared workspace field '${spec.foreignRefWorkspaceField}'`
936
+ );
937
+ }
938
+ foreignWorkspacePath = callUsda.paths.get(workspaceNode.entity.id);
939
+ if (foreignWorkspacePath === undefined) {
940
+ throw new Error(
941
+ `Dart result USDA codegen: '${domain}.${aggregate.id}.${spec.foreignRefWorkspaceField}' has no canonical USDA path`
942
+ );
943
+ }
944
+ }
945
+ return {
946
+ name: spec.wireName,
947
+ path,
948
+ spec,
949
+ required: field.isFromBirth === true,
950
+ ...(foreignWorkspacePath === undefined
951
+ ? {}
952
+ : { foreignWorkspacePath }),
953
+ };
954
+ });
955
+ const computedFields = [
956
+ ...(mod.deriveds ?? [])
957
+ .filter((read) => read.of === aggregate.id)
958
+ .map((read) => ({
959
+ name: read.id,
960
+ schema: read.returns,
961
+ required: "kind" in read && read.kind === "temporalEnum",
962
+ })),
963
+ ...(mod.combineds ?? [])
964
+ .filter((read) => read.of === aggregate.id)
965
+ .map((read) => ({
966
+ name: read.id,
967
+ schema: read.returns,
968
+ required: false,
969
+ })),
970
+ ].map((field) => {
971
+ const readNode = callUsda.graph.nodes.find(
972
+ (node) => node.kind === "read" && node.entity.label === field.name
973
+ );
974
+ if (readNode === undefined) {
975
+ throw new Error(
976
+ `Dart result USDA codegen: composed law has no '${domain}.${field.name}' read field`
977
+ );
978
+ }
979
+ const path = callUsda.paths.get(readNode.entity.id);
980
+ if (path === undefined) {
981
+ throw new Error(
982
+ `Dart result USDA codegen: '${domain}.${field.name}' has no canonical USDA path`
983
+ );
984
+ }
985
+ return { ...field, path };
986
+ }).map((field) => {
987
+ const inner = (() => {
988
+ let schema = field.schema;
989
+ while (["optional", "default", "nullable"].includes(zodKind(schema))) {
990
+ schema = zodDef(schema).innerType!;
991
+ }
992
+ return schema;
993
+ })();
994
+ const enumType = field.required && zodKind(inner) === "enum"
995
+ ? enumDartTypes.get(dartEnumLookupKey(inner))
996
+ : undefined;
997
+ return enumType === undefined ? field : { ...field, enumType };
998
+ });
999
+ const decoder = {
1000
+ model: readModelClassName(aggregate.id),
1001
+ refType: concreteClassName(aggregate.id),
1002
+ aggregatePath,
1003
+ runtimeType: aggregateWireTypeForAggregate(aggregate.id),
1004
+ baseFields,
1005
+ computedFields,
1006
+ attributedFields: aggregate.attributedFields ?? [],
1007
+ } satisfies DartUsdaResultDecoder;
1008
+ resultDecoderByAggregate.set(aggregate.id, decoder);
1009
+ resultDecoderSources.push(...emitDartUsdaResultDecoder(decoder));
1010
+ }
1011
+ }
362
1012
  const conflictAggregateType = (aggregateId: string): string => {
363
1013
  const aggregate = aggById.get(aggregateId);
364
1014
  if (aggregate === undefined) {
@@ -371,9 +1021,9 @@ export function generateDartBridgeClient(
371
1021
  : aggregateId;
372
1022
  };
373
1023
 
374
- // ROLE SUGAR — the `canEdit<Agg>`/`canView<Agg>` UI-hint helpers live HERE (the client package, which may
375
- // import runtime), NOT in the pure types file: they `jsonDecode` the folded RelationTuple leaf, and the
376
- // layered types-package purity gate forbids `dart:convert`. Empty ⇒ no roles (era-safe; no extra import).
1024
+ // ROLE SUGAR — the `canEdit<Agg>`/`canView<Agg>` UI-hint helpers consume the generated
1025
+ // RelationTuple/RelationshipSubject types from the composed framework graph. They are informational;
1026
+ // the kernel still owns authorization.
377
1027
  const roleHelpers = dartRoleHelpers(mod);
378
1028
 
379
1029
  // THE E2E-SEALING MAP (the sealing client) — non-null iff a field is `.encrypted()`. When null NOTHING
@@ -407,9 +1057,7 @@ export function generateDartBridgeClient(
407
1057
  `[${scopes
408
1058
  .map(
409
1059
  (s) =>
410
- `{${dartStr("scope")}: ${dartStr(s.scope)}${
411
- s.owned === true ? `, ${dartStr("owned")}: true` : ""
412
- }}`
1060
+ `(scope: ${dartStr(s.scope)}, owned: ${s.owned === true})`
413
1061
  )
414
1062
  .join(", ")}]`;
415
1063
  const needsReadPrincipal = (typeId: string): boolean => {
@@ -499,16 +1147,6 @@ export function generateDartBridgeClient(
499
1147
  ];
500
1148
  })
501
1149
  );
502
- const ownedActionsDart = `{${Object.entries(ownedActions)
503
- .map(
504
- ([type, actions]) =>
505
- `${dartStr(type)}: {${Object.entries(actions)
506
- .map(
507
- ([action, directive]) => `${dartStr(action)}: ${dartStr(directive)}`
508
- )
509
- .join(", ")}}`
510
- )
511
- .join(", ")}}`;
512
1150
  if (ownedScope !== undefined && Object.keys(ownedActions).length > 0) {
513
1151
  methods.push(
514
1152
  ` /// Share one record. The peer can read its history and future changes; Nomos owns the keys and policy.`,
@@ -531,9 +1169,8 @@ export function generateDartBridgeClient(
531
1169
  )?.interfaceHash;
532
1170
 
533
1171
  // ── directives → typed offer ──
534
- // A `.births()` directive (birthsChild === true) is NOT a same-workspace write — it is a signed
535
- // cross-workspace governance offer to a PARENT. It gets a typed `bridge.createWorkspace(...)` wrapper
536
- // (below), NOT an `offerOutcome`; excluded here so a frontend dev can never call it as a local write.
1172
+ // A `.births()` directive is still authored on its bound parent workspace. It therefore uses the
1173
+ // same typed USDA/Protobuf offer door as every other directive; only the law's effect crosses custody.
537
1174
  const allDirs = (mod.directives as AnyDirective[]).filter(
538
1175
  (d) =>
539
1176
  d &&
@@ -547,6 +1184,410 @@ export function generateDartBridgeClient(
547
1184
  const dirs = allDirs.filter(
548
1185
  (d) => (d as { birthsChild?: boolean }).birthsChild !== true
549
1186
  );
1187
+ const authenticationRecoveryDirs = dirs.filter(
1188
+ (directive) =>
1189
+ isBusinessIntent(directive) &&
1190
+ directive.offerAuthenticationProof === "enrolledDevice"
1191
+ );
1192
+ if (authenticationRecoveryDirs.length > 1) {
1193
+ throw new Error(
1194
+ `Dart client codegen: '${domain}' declares more than one enrolled-device authentication recovery intent; ` +
1195
+ "the framework cannot choose a unique law-authored recovery operation"
1196
+ );
1197
+ }
1198
+ const authenticationRecovery = authenticationRecoveryDirs[0];
1199
+ if (
1200
+ authenticationRecovery !== undefined &&
1201
+ introspectPayload(
1202
+ authenticationRecovery.payloadSchema as z.ZodTypeAny,
1203
+ enumDartTypes
1204
+ ).length !== 0
1205
+ ) {
1206
+ throw new Error(
1207
+ `Dart client codegen: '${domain}/${authenticationRecovery.id}' is the automatic enrolled-device ` +
1208
+ "authentication recovery intent and therefore must accept an empty payload"
1209
+ );
1210
+ }
1211
+ const localOfferDirs = [...dirs, ...birthDirs];
1212
+ const sourceTrustViaIds = new Set(
1213
+ dirs.flatMap((directive) =>
1214
+ directive.businessSourceTrust === undefined
1215
+ ? []
1216
+ : [directive.businessSourceTrust.viaDirectiveId]
1217
+ )
1218
+ );
1219
+ const sourceTrustDirectives = authoredDirectives.filter((directive) =>
1220
+ sourceTrustViaIds.has(directive.id)
1221
+ );
1222
+ for (const viaDirectiveId of sourceTrustViaIds) {
1223
+ if (
1224
+ !sourceTrustDirectives.some(
1225
+ (directive) => directive.id === viaDirectiveId
1226
+ )
1227
+ ) {
1228
+ throw new Error(
1229
+ `Dart source-trust codegen: '${domain}' has no hidden directive '${viaDirectiveId}'`
1230
+ );
1231
+ }
1232
+ }
1233
+ const encoderDirs = [
1234
+ ...new Map(
1235
+ [
1236
+ ...localOfferDirs,
1237
+ ...sourceTrustDirectives,
1238
+ ...workspaceSharingReceipts,
1239
+ ].map((directive) => [directive.id, directive] as const)
1240
+ ).values(),
1241
+ ];
1242
+ const callEncoderSources: string[] = [];
1243
+ const callEncoderByDirective = new Map<
1244
+ string,
1245
+ { readonly functionName: string; readonly arguments: readonly string[] }
1246
+ >();
1247
+ const internalRecordTypes = new Map<string, string>();
1248
+ for (const directive of encoderDirs) {
1249
+ const compilerInternal =
1250
+ sourceTrustViaIds.has(directive.id) ||
1251
+ workspaceSharingReceiptIds.has(directive.id);
1252
+ const businessBirth = (
1253
+ directive as unknown as {
1254
+ businessBirth?: { publicPayloadSchema: z.ZodTypeAny };
1255
+ }
1256
+ ).businessBirth;
1257
+ const specs = introspectPayload(
1258
+ businessBirth?.publicPayloadSchema ??
1259
+ (directive.payloadSchema as z.ZodTypeAny),
1260
+ enumDartTypes
1261
+ );
1262
+ const payloadClass = compilerInternal
1263
+ ? `({${specs
1264
+ .map((spec) => `${spec.dartType}${spec.optional ? "?" : ""} ${dartMemberName(spec.name)}`)
1265
+ .join(", ")}})`
1266
+ : dartActionInputClassName(directive);
1267
+ if (compilerInternal) internalRecordTypes.set(directive.id, payloadClass);
1268
+ const specsByName = new Map(specs.map((spec) => [spec.name, spec]));
1269
+ const access = new Map<string, DartCallUsdaFieldAccess>();
1270
+ for (const spec of specs) {
1271
+ access.set(
1272
+ spec.name,
1273
+ dartCallUsdaFieldAccess(domain, directive.id, spec)
1274
+ );
1275
+ }
1276
+ const extraParameters: string[] = [];
1277
+ const arguments_: string[] = [];
1278
+ const targetBinding = directive.targetBinding;
1279
+ if (targetBinding !== undefined) {
1280
+ const targetType = `NomosRef<${concreteClassName(
1281
+ targetBinding.aggregateId
1282
+ )}>`;
1283
+ extraParameters.push(`${targetType} nmsInternalTarget`);
1284
+ arguments_.push("target");
1285
+ access.set(targetBinding.field, {
1286
+ value: "NmsInternalNomosRefs.id(nmsInternalTarget)",
1287
+ });
1288
+ }
1289
+ if (directive.businessPrincipalField !== undefined) {
1290
+ extraParameters.push("String nmsInternalPrincipal");
1291
+ arguments_.push("nmsInternalPrincipal");
1292
+ access.set(directive.businessPrincipalField, {
1293
+ value: "nmsInternalPrincipal",
1294
+ });
1295
+ }
1296
+ if (businessBirth !== undefined) {
1297
+ extraParameters.push(
1298
+ "String nmsInternalBirthPrincipal",
1299
+ "String nmsInternalBornAt"
1300
+ );
1301
+ arguments_.push("principal", "bornAt");
1302
+ access.set("principal", { value: "nmsInternalBirthPrincipal" });
1303
+ access.set("bornAt", { value: "nmsInternalBornAt" });
1304
+ }
1305
+ for (const input of dartBusinessRecordInputs(directive, mod.aggregates)) {
1306
+ const origin = `NmsInternalRecordOrigins.require(payload.${input.argument})`;
1307
+ access.set(input.idField, { value: `${origin}.id` });
1308
+ access.set(input.workspaceField, { value: `${origin}.workspace` });
1309
+ }
1310
+ for (const input of dartForeignReferenceInputs(directive, mod.aggregates)) {
1311
+ const member = `payload.${dartMemberName(input.idField)}`;
1312
+ const reference = input.optional ? `${member}.referenceOrNull!` : member;
1313
+ const present = input.optional
1314
+ ? { present: `${member}.referenceOrNull != null` }
1315
+ : {};
1316
+ access.set(input.idField, {
1317
+ value: `NmsInternalNomosRefs.id(${reference})`,
1318
+ ...present,
1319
+ });
1320
+ access.set(input.workspaceField, {
1321
+ value: `NmsInternalNomosRefs.workspace(${reference})`,
1322
+ ...(input.optional
1323
+ ? present
1324
+ : specsByName.get(input.workspaceField)?.optional === true
1325
+ ? { present: "true" }
1326
+ : {}),
1327
+ });
1328
+ }
1329
+ const stamps = autoStampFields(
1330
+ directive.payloadSchema as z.ZodTypeAny
1331
+ ).filter(
1332
+ (stamp) =>
1333
+ !(
1334
+ (directive as { businessBirth?: unknown }).businessBirth !==
1335
+ undefined && stamp === "bornAt"
1336
+ )
1337
+ );
1338
+ if (stamps.length > 0) {
1339
+ extraParameters.push("String nmsInternalNow");
1340
+ arguments_.push("now");
1341
+ for (const stamp of stamps) {
1342
+ access.set(stamp, {
1343
+ // Some compiler-owned birth payloads deliberately retain a required
1344
+ // timestamp constructor even though ordinary generated action inputs
1345
+ // expose caller-clock fields as optional sugar. Widen explicitly so
1346
+ // the one canonical encoder is analyzer-clean in both cases.
1347
+ value: `(payload.${dartMemberName(
1348
+ stamp
1349
+ )} as String?) ?? nmsInternalNow`,
1350
+ present: "true",
1351
+ });
1352
+ }
1353
+ }
1354
+ const directiveEncryption = encMap?.directives?.[directive.id];
1355
+ if (directiveEncryption !== undefined) {
1356
+ for (const field of Object.keys(directiveEncryption).sort()) {
1357
+ const spec = specsByName.get(field);
1358
+ if (spec === undefined) {
1359
+ throw new Error(
1360
+ `Dart call USDA codegen: encrypted field '${domain}/${directive.id}.${field}' is absent from its typed payload`
1361
+ );
1362
+ }
1363
+ if (spec.dartType.replace("?", "") !== "String") {
1364
+ throw new Error(
1365
+ `Dart call USDA codegen: encrypted field '${domain}/${directive.id}.${field}' has Dart type '${spec.dartType}'. ` +
1366
+ "Ciphertext is a string, so the law must declare a string-valued encrypted boundary."
1367
+ );
1368
+ }
1369
+ const sealed = dartSealedValueName(field);
1370
+ extraParameters.push(
1371
+ `${spec.optional ? "String?" : "String"} ${sealed}`
1372
+ );
1373
+ arguments_.push(sealed);
1374
+ access.set(field, {
1375
+ value: spec.optional ? `${sealed}!` : sealed,
1376
+ ...(spec.optional ? { present: `${sealed} != null` } : {}),
1377
+ });
1378
+ }
1379
+ if (
1380
+ Object.values(directiveEncryption).some((field) => field.owned === true)
1381
+ ) {
1382
+ extraParameters.push(
1383
+ "String? nmsInternalOwnedKeySubject",
1384
+ "OwnedCustody? nmsInternalOwnedCustody",
1385
+ "List<({String type, String id})>? nmsInternalOwnedPreMintedRecords"
1386
+ );
1387
+ arguments_.push(
1388
+ "nmsInternalOwnedKeySubject",
1389
+ "nmsInternalOwnedCustody",
1390
+ "nmsInternalOwnedPreMintedRecords"
1391
+ );
1392
+ access.set(OWNED_KEY_PAYLOAD_FIELD, {
1393
+ value: "nmsInternalOwnedKeySubject!",
1394
+ present: "nmsInternalOwnedKeySubject != null",
1395
+ });
1396
+ access.set(OWNED_CUSTODY_PAYLOAD_FIELD, {
1397
+ value: "nmsInternalOwnedCustody!",
1398
+ present: "nmsInternalOwnedCustody != null",
1399
+ });
1400
+ access.set(OWNED_PREMINTED_RECORDS_PAYLOAD_FIELD, {
1401
+ value: "nmsInternalOwnedPreMintedRecords!",
1402
+ present: "nmsInternalOwnedPreMintedRecords != null",
1403
+ });
1404
+ }
1405
+ if (businessBirth !== undefined) {
1406
+ const carrierFields = [
1407
+ ...specs.map(
1408
+ (spec) => `${spec.dartType} ${dartMemberName(spec.name)}`
1409
+ ),
1410
+ "String principal",
1411
+ "String bornAt",
1412
+ `String? ${dartMemberName(OWNED_KEY_PAYLOAD_FIELD)}`,
1413
+ `OwnedCustody? ${dartMemberName(OWNED_CUSTODY_PAYLOAD_FIELD)}`,
1414
+ `List<({String type, String id})>? ${dartMemberName(
1415
+ OWNED_PREMINTED_RECORDS_PAYLOAD_FIELD
1416
+ )}`,
1417
+ ];
1418
+ extraParameters.push(
1419
+ `({${carrierFields.join(", ")}}) nmsInternalBirthSeed`
1420
+ );
1421
+ arguments_.push("nmsInternalBirthSeed");
1422
+ access.set(BUSINESS_BIRTH_SEED_PAYLOAD_FIELD, {
1423
+ value: "nmsInternalBirthSeed",
1424
+ });
1425
+ }
1426
+ }
1427
+ const encoder = emitDartCallUsdaEncoder(
1428
+ callUsda.graph,
1429
+ callUsda.paths,
1430
+ domain,
1431
+ directive.id,
1432
+ payloadClass,
1433
+ access,
1434
+ extraParameters
1435
+ );
1436
+ callEncoderSources.push(...encoder.source);
1437
+ callEncoderByDirective.set(directive.id, {
1438
+ functionName: encoder.functionName,
1439
+ arguments: arguments_,
1440
+ });
1441
+ }
1442
+ for (const directive of sourceTrustDirectives) {
1443
+ const encoder = callEncoderByDirective.get(directive.id);
1444
+ const payloadType = internalRecordTypes.get(directive.id);
1445
+ if (
1446
+ encoder === undefined ||
1447
+ payloadType === undefined ||
1448
+ encoder.arguments.length !== 0
1449
+ ) {
1450
+ throw new Error(
1451
+ `Dart source-trust codegen: hidden directive '${domain}/${directive.id}' is not a closed typed record`
1452
+ );
1453
+ }
1454
+ methods.push(
1455
+ ` /// @internal Compiler-generated source-trust offer. Domain code supplies one exact typed record.`,
1456
+ ` Future<NomosOfferOutcome> ${dartInternalSourceTrustMethod(
1457
+ directive.id
1458
+ )}(${payloadType} payload) =>`,
1459
+ ` _offer(${encoder.functionName}(payload));`,
1460
+ ``
1461
+ );
1462
+ }
1463
+ for (const directive of workspaceSharingReceipts) {
1464
+ const encoder = callEncoderByDirective.get(directive.id);
1465
+ const payloadType = internalRecordTypes.get(directive.id);
1466
+ if (
1467
+ encoder === undefined ||
1468
+ payloadType === undefined ||
1469
+ encoder.arguments.some((argument) => argument !== "now")
1470
+ ) {
1471
+ throw new Error(
1472
+ `Dart workspace-sharing codegen: hidden receipt '${domain}/${directive.id}' is not a closed typed record`
1473
+ );
1474
+ }
1475
+ const method = dartInternalWorkspaceSharingReceiptMethod(directive.id);
1476
+ const implementation =
1477
+ encoder.arguments.length === 0
1478
+ ? `=>\n _offer(${encoder.functionName}(payload));`
1479
+ : `{\n final now = DateTime.now().toUtc().toIso8601String();\n return _offer(${encoder.functionName}(payload, now));\n }`;
1480
+ methods.push(
1481
+ ` /// @internal Compiler-generated workspace-sharing receipt. Not tenant application vocabulary.`,
1482
+ ` Future<NomosOfferOutcome> ${method}(${payloadType} payload) ${implementation}`,
1483
+ ``
1484
+ );
1485
+ }
1486
+ const ownedActionCallEncoders = new Map<
1487
+ string,
1488
+ {
1489
+ readonly action: "share" | "unshare" | "transfer";
1490
+ readonly functionName: string;
1491
+ readonly payloadType: string;
1492
+ }
1493
+ >();
1494
+ let ownedDeviceApprovalCallEncoder: string | undefined;
1495
+ if (ownedScope !== undefined && Object.keys(ownedActions).length > 0) {
1496
+ const ownedAccess = (
1497
+ fields: readonly string[]
1498
+ ): Map<string, DartCallUsdaFieldAccess> => {
1499
+ const access = new Map<string, DartCallUsdaFieldAccess>();
1500
+ for (const field of fields) {
1501
+ access.set(field, { value: `payload.${dartMemberName(field)}` });
1502
+ }
1503
+ access.set(OWNED_KEY_PAYLOAD_FIELD, {
1504
+ value: "payload.ownedKeySubject",
1505
+ present: "true",
1506
+ });
1507
+ access.set(OWNED_CUSTODY_PAYLOAD_FIELD, {
1508
+ value: "payload.ownedCustody!",
1509
+ present: "payload.ownedCustody != null",
1510
+ });
1511
+ access.set(OWNED_PREMINTED_RECORDS_PAYLOAD_FIELD, {
1512
+ value: "const <({String type, String id})>[]",
1513
+ omit: true,
1514
+ });
1515
+ return access;
1516
+ };
1517
+ const payloadTypes = {
1518
+ share: "NomosOwnedSharePreparation",
1519
+ unshare: "NomosOwnedUnsharePreparation",
1520
+ transfer: "NomosOwnedTransferPreparation",
1521
+ } as const;
1522
+ const payloadFields = {
1523
+ share: ["recordId", "peer", "epoch", "sharedAt", "wraps"],
1524
+ unshare: ["recordId", "peer", "epoch", "changedAt", "wraps"],
1525
+ transfer: [
1526
+ "recordId",
1527
+ "newOwner",
1528
+ "previousReaders",
1529
+ "epoch",
1530
+ "changedAt",
1531
+ "historicWraps",
1532
+ ],
1533
+ } as const;
1534
+ for (const actions of Object.values(ownedActions)) {
1535
+ for (const action of ["share", "unshare", "transfer"] as const) {
1536
+ const directiveId = actions[action];
1537
+ const encoder = emitDartCallUsdaEncoder(
1538
+ callUsda.graph,
1539
+ callUsda.paths,
1540
+ domain,
1541
+ directiveId,
1542
+ payloadTypes[action],
1543
+ ownedAccess(payloadFields[action])
1544
+ );
1545
+ callEncoderSources.push(...encoder.source);
1546
+ ownedActionCallEncoders.set(directiveId, {
1547
+ action,
1548
+ functionName: encoder.functionName,
1549
+ payloadType: payloadTypes[action],
1550
+ });
1551
+ }
1552
+ }
1553
+ const approvalEncoder = emitDartCallUsdaEncoder(
1554
+ callUsda.graph,
1555
+ callUsda.paths,
1556
+ domain,
1557
+ OWNED_DEVICE_APPROVAL_DIRECTIVE,
1558
+ "NomosOwnedDeviceApprovalPreparation",
1559
+ new Map<string, DartCallUsdaFieldAccess>([
1560
+ ["newKeySubject", { value: "payload.newKeySubject" }],
1561
+ ["ownedCustody", { value: "payload.ownedCustody" }],
1562
+ ["verifiedVia", { value: "payload.verifiedVia" }],
1563
+ ["validFromEpoch", { value: "payload.validFromEpoch" }],
1564
+ ["approvedAt", { value: "payload.approvedAt" }],
1565
+ [
1566
+ OWNED_KEY_PAYLOAD_FIELD,
1567
+ {
1568
+ value: "payload.ownedKeySubject",
1569
+ present: "true",
1570
+ },
1571
+ ],
1572
+ [
1573
+ OWNED_CUSTODY_PAYLOAD_FIELD,
1574
+ {
1575
+ value: "payload.ownedCustody",
1576
+ present: "true",
1577
+ },
1578
+ ],
1579
+ [
1580
+ OWNED_PREMINTED_RECORDS_PAYLOAD_FIELD,
1581
+ {
1582
+ value: "const <({String type, String id})>[]",
1583
+ omit: true,
1584
+ },
1585
+ ],
1586
+ ])
1587
+ );
1588
+ callEncoderSources.push(...approvalEncoder.source);
1589
+ ownedDeviceApprovalCallEncoder = approvalEncoder.functionName;
1590
+ }
550
1591
  const correctionActions = dirs.filter(
551
1592
  (d) => (d as { businessBirth?: unknown }).businessBirth === undefined
552
1593
  );
@@ -597,13 +1638,17 @@ export function generateDartBridgeClient(
597
1638
  // targetBinding is declared on AnyDirective (directive.ts) — honour it for EVERY
598
1639
  // directive, exactly as the application surface does (a split here emits a 2-arg
599
1640
  // application call against a 1-arg client method: generated Dart that cannot compile).
600
- const targetBinding = (d as { targetBinding?: import("./directive.js").DirectiveTargetBinding }).targetBinding;
601
- const targetType = targetBinding === undefined
602
- ? undefined
603
- : `NomosRef<${concreteClassName(targetBinding.aggregateId)}>`;
604
- const actionParameters = targetBinding === undefined
605
- ? `${payloadClass} payload`
606
- : `${targetType} target, ${payloadClass} payload`;
1641
+ const targetBinding = (
1642
+ d as { targetBinding?: import("./directive.js").DirectiveTargetBinding }
1643
+ ).targetBinding;
1644
+ const targetType =
1645
+ targetBinding === undefined
1646
+ ? undefined
1647
+ : `NomosRef<${concreteClassName(targetBinding.aggregateId)}>`;
1648
+ const actionParameters =
1649
+ targetBinding === undefined
1650
+ ? `${payloadClass} payload`
1651
+ : `${targetType} target, ${payloadClass} payload`;
607
1652
  const stamps = autoStampFields(
608
1653
  (d as unknown as { payloadSchema: z.ZodTypeAny }).payloadSchema
609
1654
  );
@@ -638,7 +1683,6 @@ export function generateDartBridgeClient(
638
1683
  (d as { aggregateId?: string }).aggregateId ?? "aggregate"
639
1684
  } id inside the sealed intent — it rides back on \`outcome.created\` (no id in the payload, no query-after).`
640
1685
  : "";
641
- const recordInputs = dartBusinessRecordInputs(d, mod.aggregates);
642
1686
  const principalField = d.businessPrincipalField;
643
1687
  const principalPrelude =
644
1688
  principalField === undefined
@@ -653,24 +1697,21 @@ export function generateDartBridgeClient(
653
1697
  )}, requires: 'the signed-in Nomos principal');`,
654
1698
  ` }`,
655
1699
  ];
656
- const payloadJsonArgs = [
657
- ...(targetBinding === undefined
658
- ? []
659
- : ["nmsInternalTarget: target"]),
660
- ...(principalField === undefined
661
- ? []
662
- : ["nmsInternalPrincipal: nmsInternalPrincipal"]),
663
- ...(recordInputs.length === 0
664
- ? []
665
- : [
666
- "nmsInternalLocationOf: (record) => NmsInternalRecordOrigins.require(record).workspace",
667
- "nmsInternalIdOf: (record) => NmsInternalRecordOrigins.require(record).id",
668
- ]),
669
- ];
670
- const payloadJson =
671
- payloadJsonArgs.length === 0
672
- ? "payload.nmsInternalToPayloadJson()"
673
- : `payload.nmsInternalToPayloadJson(${payloadJsonArgs.join(", ")})`;
1700
+ const callEncoder = callEncoderByDirective.get(d.id);
1701
+ if (callEncoder === undefined) {
1702
+ throw new Error(
1703
+ `Dart call USDA codegen: '${domain}/${d.id}' has no generated encoder`
1704
+ );
1705
+ }
1706
+ const callUsda = `${callEncoder.functionName}(${[
1707
+ "payload",
1708
+ ...callEncoder.arguments,
1709
+ ].join(", ")})`;
1710
+ const offerCall = `_offer(${callUsda}${
1711
+ isBusinessIntent(d) && d.offerAuthenticationProof === "enrolledDevice"
1712
+ ? ", authenticationProof: NomosOfferAuthenticationProof.enrolledDevice"
1713
+ : ""
1714
+ })`;
674
1715
  const stampsDoc =
675
1716
  stamps.length > 0
676
1717
  ? `\n /// Omitted ${stamps
@@ -701,6 +1742,11 @@ export function generateDartBridgeClient(
701
1742
  : isNamespacedAggregate(targetAggregate)
702
1743
  ? qualifyReadProjectionType(domain, targetAggregate.id)
703
1744
  : targetAggregate.id;
1745
+ const encryptedSpecs = new Map(
1746
+ introspectPayload(d.payloadSchema as z.ZodTypeAny, enumDartTypes).map(
1747
+ (spec) => [spec.name, spec]
1748
+ )
1749
+ );
704
1750
  const recordPrelude =
705
1751
  recordTarget === undefined || runtimeType === undefined
706
1752
  ? []
@@ -713,9 +1759,18 @@ export function generateDartBridgeClient(
713
1759
  : recordTarget.kind === "payload"
714
1760
  ? [
715
1761
  ` final ownedRecordType = ${dartStr(runtimeType)};`,
716
- ` final ownedRecordId = (data[${dartStr(
717
- recordTarget.field
718
- )}] ?? '').toString();`,
1762
+ ` final ownedRecordId = ${
1763
+ dartCallUsdaFieldAccess(
1764
+ domain,
1765
+ d.id,
1766
+ encryptedSpecs.get(recordTarget.field) ??
1767
+ (() => {
1768
+ throw new Error(
1769
+ `dart codegen: owned record field '${domain}/${d.id}.${recordTarget.field}' is absent from the typed payload`
1770
+ );
1771
+ })()
1772
+ ).value
1773
+ }.toString();`,
719
1774
  ` const ownedRecordPreMinted = false;`,
720
1775
  ]
721
1776
  : [
@@ -723,6 +1778,97 @@ export function generateDartBridgeClient(
723
1778
  ` final ownedRecordId = ${dartStr(recordTarget.id)};`,
724
1779
  ` const ownedRecordPreMinted = false;`,
725
1780
  ];
1781
+ const ownedKeyPrelude = ownedSeal
1782
+ ? [
1783
+ ...recordPrelude,
1784
+ ` final nmsInternalOwnedKey = await bridge.nomosOwnedScopeKey(`,
1785
+ ` ${dartStr(Object.values(directiveEncryption)[0]!.scope)},`,
1786
+ ` recordType: ownedRecordType, recordId: ownedRecordId);`,
1787
+ ` final nmsInternalOwnedKeySubject = nmsInternalOwnedKey.ownedKeySubject;`,
1788
+ ` if (nmsInternalOwnedKeySubject == null || nmsInternalOwnedKeySubject.isEmpty) {`,
1789
+ ` throw StateError('Nomos did not return the framework-owned device identity for .owned() custody');`,
1790
+ ` }`,
1791
+ ` final nmsInternalOwnedCustody = nmsInternalOwnedKey.ownedCustody;`,
1792
+ ...(recordTarget?.kind === "premint"
1793
+ ? [
1794
+ ` final nmsInternalOwnedPreMintedRecords = <({String type, String id})>[`,
1795
+ ` (type: ownedRecordType, id: ownedRecordId),`,
1796
+ ` ];`,
1797
+ ]
1798
+ : [
1799
+ ` const List<({String type, String id})>? nmsInternalOwnedPreMintedRecords = null;`,
1800
+ ]),
1801
+ ]
1802
+ : [];
1803
+ const sealPrelude = Object.entries(directiveEncryption)
1804
+ .sort(([left], [right]) => left.localeCompare(right))
1805
+ .flatMap(([field, descriptor]) => {
1806
+ const spec = encryptedSpecs.get(field);
1807
+ if (spec === undefined) {
1808
+ throw new Error(
1809
+ `dart codegen: encrypted field '${domain}/${d.id}.${field}' is absent from the typed payload`
1810
+ );
1811
+ }
1812
+ if (spec.dartType.replace("?", "") !== "String") {
1813
+ throw new Error(
1814
+ `dart codegen: encrypted field '${domain}/${d.id}.${field}' must be String, got '${spec.dartType}'`
1815
+ );
1816
+ }
1817
+ const member = `payload.${dartMemberName(field)}`;
1818
+ const sealed = dartSealedValueName(field);
1819
+ const key = dartSealKeyName(field);
1820
+ const sealValue = [
1821
+ ` final ${key} = ${
1822
+ descriptor.owned === true
1823
+ ? "nmsInternalOwnedKey"
1824
+ : `keyStore == null ? null : await keyStore!.scopeKey(${dartStr(
1825
+ descriptor.scope
1826
+ )})`
1827
+ };`,
1828
+ ` if (${key} == null) throw EncryptionKeyUnavailable(${dartStr(
1829
+ descriptor.scope
1830
+ )}, ${dartStr(field)});`,
1831
+ ` ${sealed} = await bridge.cryptoSealField(`,
1832
+ ` scopeKey: ${key}.key, scope: ${
1833
+ descriptor.owned === true
1834
+ ? `${key}.scope`
1835
+ : dartStr(descriptor.scope)
1836
+ }, epoch: ${key}.epoch,`,
1837
+ ` aggSid: ${dartStr(descriptor.aggSid)}, fieldSid: ${dartStr(
1838
+ descriptor.fieldSid
1839
+ )},`,
1840
+ ` plaintext: ${member},`,
1841
+ ` );`,
1842
+ ];
1843
+ if (spec.optional) {
1844
+ return [
1845
+ ` String? ${sealed};`,
1846
+ ` if (${member} != null) {`,
1847
+ ...(ownedSeal
1848
+ ? sealValue
1849
+ : [
1850
+ ` if (!seal) {`,
1851
+ ` ${sealed} = ${member};`,
1852
+ ` } else {`,
1853
+ ...sealValue.map((line) => ` ${line}`),
1854
+ ` }`,
1855
+ ]),
1856
+ ` }`,
1857
+ ];
1858
+ }
1859
+ return [
1860
+ ` late final String ${sealed};`,
1861
+ ...(ownedSeal
1862
+ ? [` {`, ...sealValue, ` }`]
1863
+ : [
1864
+ ` if (!seal) {`,
1865
+ ` ${sealed} = ${member};`,
1866
+ ` } else {`,
1867
+ ...sealValue.map((line) => ` ${line}`),
1868
+ ` }`,
1869
+ ]),
1870
+ ];
1871
+ });
726
1872
  methods.push(
727
1873
  ` /// Author \`${domain}/${d.id}\` locally under the installed law; returns the typed [NomosOfferOutcome].${createsDoc}`,
728
1874
  ` /// ${
@@ -736,24 +1882,12 @@ export function generateDartBridgeClient(
736
1882
  }) async {`,
737
1883
  ...(gateGuard ? [gateGuard] : []),
738
1884
  ...principalPrelude,
739
- ` final data = ${payloadJson};`,
740
1885
  ...(stamps.length > 0
741
- ? [
742
- ` final now = DateTime.now().toUtc().toIso8601String();`,
743
- ...stamps.map(
744
- (s) => ` data.putIfAbsent(${dartStr(s)}, () => now);`
745
- ),
746
- ]
1886
+ ? [` final now = DateTime.now().toUtc().toIso8601String();`]
747
1887
  : []),
748
- ...recordPrelude,
749
- ` await _sealPayload(data, ${dartStr(d.id)}, ${
750
- ownedSeal ? "true" : "seal"
751
- }${
752
- ownedSeal
753
- ? ", null, ownedRecordType, ownedRecordId, ownedRecordPreMinted"
754
- : ""
755
- });`,
756
- ` return _offer(${dartStr(d.id)}, data);`,
1888
+ ...ownedKeyPrelude,
1889
+ ...sealPrelude,
1890
+ ` return ${offerCall};`,
757
1891
  ` }`,
758
1892
  ``
759
1893
  );
@@ -766,10 +1900,8 @@ export function generateDartBridgeClient(
766
1900
  ` Future<NomosOfferOutcome> ${method}(${actionParameters}) {`,
767
1901
  ...(gateGuard ? [gateGuard] : []),
768
1902
  ...principalPrelude,
769
- ` final data = ${payloadJson};`,
770
1903
  ` final now = DateTime.now().toUtc().toIso8601String();`,
771
- ...stamps.map((s) => ` data.putIfAbsent(${dartStr(s)}, () => now);`),
772
- ` return _offer(${dartStr(d.id)}, data);`,
1904
+ ` return ${offerCall};`,
773
1905
  ` }`,
774
1906
  ``
775
1907
  );
@@ -782,7 +1914,7 @@ export function generateDartBridgeClient(
782
1914
  ` Future<NomosOfferOutcome> ${method}(${actionParameters}) {`,
783
1915
  ...(gateGuard ? [gateGuard] : []),
784
1916
  ...principalPrelude,
785
- ` return _offer(${dartStr(d.id)}, ${payloadJson});`,
1917
+ ` return ${offerCall};`,
786
1918
  ` }`,
787
1919
  ``
788
1920
  );
@@ -791,7 +1923,7 @@ export function generateDartBridgeClient(
791
1923
  ` /// Author \`${domain}/${d.id}\` locally under the installed law; returns the typed [NomosOfferOutcome].${createsDoc}`,
792
1924
  ...(gateDoc ? [gateDoc] : []),
793
1925
  ` Future<NomosOfferOutcome> ${method}(${actionParameters}) =>`,
794
- ` _offer(${dartStr(d.id)}, ${payloadJson});`,
1926
+ ` ${offerCall};`,
795
1927
  ``
796
1928
  );
797
1929
  }
@@ -823,6 +1955,16 @@ export function generateDartBridgeClient(
823
1955
  ).businessBirth;
824
1956
  const payloadClass = dartActionInputClassName(d);
825
1957
  if (businessBirth !== undefined) {
1958
+ const birthCallEncoder = callEncoderByDirective.get(d.id);
1959
+ if (birthCallEncoder === undefined) {
1960
+ throw new Error(
1961
+ `Dart call USDA codegen: '${domain}/${d.id}' has no generated birth encoder`
1962
+ );
1963
+ }
1964
+ const birthCallUsda = `${birthCallEncoder.functionName}(${[
1965
+ "payload",
1966
+ ...birthCallEncoder.arguments,
1967
+ ].join(", ")})`;
826
1968
  const seedDirectiveId =
827
1969
  businessBirth.seedDirectiveId ??
828
1970
  `nomosSeed${businessBirth.aggregateId.replace(/[^A-Za-z0-9]/g, "")}`;
@@ -839,16 +1981,131 @@ export function generateDartBridgeClient(
839
1981
  const birthAggregate = (mod.aggregates as AggregateHandle[]).find(
840
1982
  (aggregate) => aggregate.id === businessBirth.aggregateId
841
1983
  );
842
- const birthRuntimeType = birthAggregate === undefined
843
- ? undefined
844
- : isNamespacedAggregate(birthAggregate)
1984
+ const birthRuntimeType =
1985
+ birthAggregate === undefined
1986
+ ? undefined
1987
+ : isNamespacedAggregate(birthAggregate)
845
1988
  ? qualifyReadProjectionType(domain, birthAggregate.id)
846
1989
  : birthAggregate.id;
847
- if (ownedBirthDescriptor !== undefined && birthRuntimeType === undefined) {
1990
+ if (
1991
+ ownedBirthDescriptor !== undefined &&
1992
+ birthRuntimeType === undefined
1993
+ ) {
848
1994
  throw new Error(
849
1995
  `codegen: cannot derive the record identity for encrypted owned birth '${domain}/${d.id}'`
850
1996
  );
851
1997
  }
1998
+ const birthSpecs = introspectPayload(
1999
+ businessBirth.publicPayloadSchema,
2000
+ enumDartTypes
2001
+ );
2002
+ const birthSpecsByName = new Map(
2003
+ birthSpecs.map((spec) => [spec.name, spec])
2004
+ );
2005
+ const ownedBirthPrelude =
2006
+ ownedBirthDescriptor === undefined
2007
+ ? []
2008
+ : [
2009
+ ` final nmsInternalOwnedKey = await bridge.nomosOwnedBirthScopeKey(`,
2010
+ ` ${dartStr(ownedBirthDescriptor.scope)}, ${dartStr(
2011
+ businessBirth.namespace
2012
+ )}, payload.${dartMemberName(businessBirth.keyField)}, ${dartStr(
2013
+ birthRuntimeType!
2014
+ )}, ${dartStr(businessBirth.rootId)});`,
2015
+ ` final nmsInternalOwnedKeySubject = nmsInternalOwnedKey.ownedKeySubject;`,
2016
+ ` if (nmsInternalOwnedKeySubject == null || nmsInternalOwnedKeySubject.isEmpty) {`,
2017
+ ` throw StateError('Nomos did not return the framework-owned device identity for .owned() custody');`,
2018
+ ` }`,
2019
+ ` final nmsInternalOwnedCustody = nmsInternalOwnedKey.ownedCustody;`,
2020
+ ` const List<({String type, String id})>? nmsInternalOwnedPreMintedRecords = null;`,
2021
+ ];
2022
+ const birthSealPrelude =
2023
+ birthEncryption === undefined
2024
+ ? []
2025
+ : Object.entries(birthEncryption)
2026
+ .sort(([left], [right]) => left.localeCompare(right))
2027
+ .flatMap(([field, descriptor]) => {
2028
+ const spec = birthSpecsByName.get(field);
2029
+ if (
2030
+ spec === undefined ||
2031
+ spec.dartType.replace("?", "") !== "String"
2032
+ ) {
2033
+ throw new Error(
2034
+ `dart codegen: encrypted birth field '${domain}/${d.id}.${field}' must be a typed String payload field`
2035
+ );
2036
+ }
2037
+ const member = `payload.${dartMemberName(field)}`;
2038
+ const sealed = dartSealedValueName(field);
2039
+ const key = dartSealKeyName(field);
2040
+ const sealValue = [
2041
+ ` final ${key} = ${
2042
+ descriptor.owned === true
2043
+ ? "nmsInternalOwnedKey"
2044
+ : `keyStore == null ? null : await keyStore!.scopeKey(${dartStr(
2045
+ descriptor.scope
2046
+ )})`
2047
+ };`,
2048
+ ` if (${key} == null) throw EncryptionKeyUnavailable(${dartStr(
2049
+ descriptor.scope
2050
+ )}, ${dartStr(field)});`,
2051
+ ` ${sealed} = await bridge.cryptoSealField(`,
2052
+ ` scopeKey: ${key}.key, scope: ${
2053
+ descriptor.owned === true
2054
+ ? `${key}.scope`
2055
+ : dartStr(descriptor.scope)
2056
+ }, epoch: ${key}.epoch,`,
2057
+ ` aggSid: ${dartStr(
2058
+ descriptor.aggSid
2059
+ )}, fieldSid: ${dartStr(descriptor.fieldSid)},`,
2060
+ ` plaintext: ${member},`,
2061
+ ` );`,
2062
+ ];
2063
+ if (spec.optional) {
2064
+ return [
2065
+ ` String? ${sealed};`,
2066
+ ` if (${member} != null) {`,
2067
+ ...sealValue,
2068
+ ` }`,
2069
+ ];
2070
+ }
2071
+ return [
2072
+ ` late final String ${sealed};`,
2073
+ ` {`,
2074
+ ...sealValue,
2075
+ ` }`,
2076
+ ];
2077
+ });
2078
+ const birthSeedFields =
2079
+ birthEncryption === undefined
2080
+ ? []
2081
+ : [
2082
+ ...birthSpecs.map((spec) => {
2083
+ const value =
2084
+ birthEncryption[spec.name] === undefined
2085
+ ? `payload.${dartMemberName(spec.name)}`
2086
+ : dartSealedValueName(spec.name);
2087
+ return ` ${dartMemberName(spec.name)}: ${value},`;
2088
+ }),
2089
+ ` principal: principal,`,
2090
+ ` bornAt: bornAt,`,
2091
+ ` ${dartMemberName(OWNED_KEY_PAYLOAD_FIELD)}: ${
2092
+ ownedBirthDescriptor === undefined
2093
+ ? "null"
2094
+ : "nmsInternalOwnedKeySubject"
2095
+ },`,
2096
+ ` ${dartMemberName(OWNED_CUSTODY_PAYLOAD_FIELD)}: ${
2097
+ ownedBirthDescriptor === undefined
2098
+ ? "null"
2099
+ : "nmsInternalOwnedCustody"
2100
+ },`,
2101
+ ` ${dartMemberName(
2102
+ OWNED_PREMINTED_RECORDS_PAYLOAD_FIELD
2103
+ )}: ${
2104
+ ownedBirthDescriptor === undefined
2105
+ ? "null"
2106
+ : "nmsInternalOwnedPreMintedRecords"
2107
+ },`,
2108
+ ];
852
2109
  methods.push(
853
2110
  ` /// Give one ${businessBirth.aggregateId} its own durable life. Nomos owns identity, authorship,`,
854
2111
  ` /// provenance, persistence and recovery; the app supplies only business facts.`,
@@ -884,38 +2141,17 @@ export function generateDartBridgeClient(
884
2141
  )}, packageHash: ${hashConst});`
885
2142
  ),
886
2143
  ]),
887
- ` final data = payload.nmsInternalToPayloadJson();`,
888
- ...(ownedBirthDescriptor === undefined
889
- ? []
890
- : [
891
- ` final ownedBirthKey = await bridge.nomosOwnedBirthScopeKey(`,
892
- ` ${dartStr(ownedBirthDescriptor.scope)}, ${dartStr(
893
- businessBirth.namespace
894
- )}, data[${dartStr(businessBirth.keyField)}], ${dartStr(
895
- birthRuntimeType!
896
- )}, ${dartStr(businessBirth.rootId)});`,
897
- ]),
2144
+ ` final bornAt = DateTime.now().toUtc().toIso8601String();`,
2145
+ ...ownedBirthPrelude,
2146
+ ...birthSealPrelude,
898
2147
  ...(birthEncryption === undefined
899
2148
  ? []
900
2149
  : [
901
- ` final sealedGenesis = Map<String, dynamic>.from(data);`,
902
- ` await _sealPayload(sealedGenesis, ${dartStr(d.id)}, true${
903
- ownedBirthDescriptor === undefined ? "" : ", ownedBirthKey"
904
- });`,
905
- ` data[${dartStr(
906
- BUSINESS_BIRTH_SEED_PAYLOAD_FIELD
907
- )}] = jsonEncode(sealedGenesis);`,
908
- ...(ownedBirthDescriptor === undefined
909
- ? []
910
- : [
911
- ` data[${dartStr(
912
- OWNED_KEY_PAYLOAD_FIELD
913
- )}] = ownedBirthKey.ownedKeySubject;`,
914
- ]),
2150
+ ` final nmsInternalBirthSeed = (`,
2151
+ ...birthSeedFields,
2152
+ ` );`,
915
2153
  ]),
916
- ` data['principal'] = principal;`,
917
- ` data['bornAt'] = DateTime.now().toUtc().toIso8601String();`,
918
- ` final outcome = await _offer(${dartStr(d.id)}, data);`,
2154
+ ` final outcome = await _offer(${birthCallUsda});`,
919
2155
  ` if (outcome.born.length != 1) {`,
920
2156
  ` throw StateError('birth() committed without returning exactly one born workspace '
921
2157
  '(born: \${outcome.born}, birthOutcomes: \${outcome.birthOutcomes}, head: \${outcome.head})');`,
@@ -923,7 +2159,7 @@ export function generateDartBridgeClient(
923
2159
  ...(ownedBirthDescriptor === undefined
924
2160
  ? []
925
2161
  : [
926
- ` if (ownedBirthKey.workspace == null || outcome.born.single != ownedBirthKey.workspace) {`,
2162
+ ` if (nmsInternalOwnedKey.workspace == null || outcome.born.single != nmsInternalOwnedKey.workspace) {`,
927
2163
  ` throw StateError('birth() returned a workspace different from the deterministic owned-custody address');`,
928
2164
  ` }`,
929
2165
  ]),
@@ -940,78 +2176,38 @@ export function generateDartBridgeClient(
940
2176
  );
941
2177
  continue;
942
2178
  }
2179
+ const ordinaryBirthEncoder = callEncoderByDirective.get(d.id);
2180
+ if (ordinaryBirthEncoder === undefined) {
2181
+ throw new Error(
2182
+ `Dart call USDA codegen: '${domain}/${d.id}' has no generated birth encoder`
2183
+ );
2184
+ }
2185
+ const ordinaryBirthCallUsda = `${ordinaryBirthEncoder.functionName}(${[
2186
+ "payload",
2187
+ ...ordinaryBirthEncoder.arguments,
2188
+ ].join(", ")})`;
2189
+ const ordinaryBirthStamps = autoStampFields(
2190
+ d.payloadSchema as z.ZodTypeAny
2191
+ );
943
2192
  methods.push(
944
- ` /// Births a CHILD WORKSPACE named by \`${payloadClass}\` — a \`.births()\` offer on THIS client's`,
945
- ` /// session, whose workspace IS the parent (session_bound_clients.md ask 4: no loose parent string).`,
946
- ` /// Returns the typed [NomosOfferOutcome]: \`outcome.born\` names the child, \`outcome.bornRefs\` are`,
947
- ` /// followable — \`final child = await engine.follow(outcome.bornRefs.single);\` mounts it, typed`,
948
- ` /// client construction is then \`await ChildClient.bind(child)\`. [actor] (a [NomosPrincipal])`,
949
- ` /// overrides this client's own \`actor\` for the offer.`,
950
- ` ///`,
951
- ` /// COMPATIBILITY SHIM (one minor version): passing the deprecated [parent] with a workspace that is`,
952
- ` /// NOT this session's routes the old SIGNED cross-workspace gateway relay`,
953
- ` /// (POST /v2/workspaces/<parent>/${d.id}); its verdict rides \`outcome.gatewayVerdict\`. [authorSecret]/`,
954
- ` /// [authToken] only apply on that legacy lane.`,
955
- ` Future<NomosOfferOutcome> ${method}({`,
956
- ` required ${payloadClass} payload,`,
957
- ` NomosPrincipal? actor,`,
958
- ` @Deprecated('the birth parent IS the session workspace — bind this client to the PARENT session '`,
959
- ` '(session_bound_clients.md ask 4); an explicit different parent rides the legacy gateway relay '`,
960
- ` 'one more minor version') String? parent,`,
961
- ` NomosDeviceSecret? authorSecret,`,
962
- ` String? authToken,`,
963
- ` }) async {`,
964
- ` final ws = _ws;`,
965
- ` final explicitParent = parent;`,
966
- ` if (explicitParent != null && explicitParent.isNotEmpty && explicitParent != ws) {`,
967
- ` // LEGACY GATEWAY SHIM — the pre-session-bound cross-workspace relay, delegated unchanged.`,
968
- ` if (actor == null) {`,
969
- ` throw MissingOfferAuthority(domain: ${dartStr(
970
- domain
971
- )}, directiveId: ${dartStr(
972
- d.id
973
- )}, requires: 'a requesting principal (actor) on the legacy parent-relay lane');`,
974
- ` }`,
975
- ` final verdict = await bridge.createWorkspace(`,
976
- ` domain: ${dartStr(domain)},`,
977
- ` directiveId: ${dartStr(d.id)},`,
978
- ` domainHash: domainHash,`,
979
- ` parent: explicitParent,`,
980
- ` actor: actor.wire,`,
981
- ` authorSecret: authorSecret?.wire,`,
982
- ` payload: payload.nmsInternalToPayloadJson(),`,
983
- ` authToken: authToken,`,
984
- ` );`,
985
- ` return NomosOfferOutcome(`,
986
- ` head: (verdict['head'] ?? '').toString(),`,
987
- ` created: const [],`,
988
- ` workspace: explicitParent,`,
989
- ` gatewayVerdict: verdict);`,
990
- ` }`,
991
- ` if (ws.isEmpty) {`,
992
- ` throw StateError(`,
993
- ` '${method}: bind this client to the PARENT NomosSession (${className}.bind(session)) — '`,
994
- ` 'the birth parent IS the session workspace, never a loose string');`,
995
- ` }`,
996
- ` final effActor = actor?.wire ?? this.actor;`,
997
- ...(gate !== undefined
998
- ? [
999
- ` if (effActor == null || effActor.trim().isEmpty) {`,
2193
+ ` /// Birth a child from this client's bound parent session through the same typed offer door as`,
2194
+ ` /// every other directive. Parent address, signing authority and custody stay session-bound.`,
2195
+ ` Future<NomosOfferOutcome> ${method}(${payloadClass} payload) async {`,
2196
+ ...(gate === undefined
2197
+ ? []
2198
+ : [
2199
+ ` if (actor == null || actor!.trim().isEmpty) {`,
1000
2200
  ` throw MissingOfferAuthority(domain: ${dartStr(
1001
2201
  domain
1002
2202
  )}, directiveId: ${dartStr(d.id)}, requires: ${dartStr(
1003
2203
  gate.relation
1004
2204
  )});`,
1005
2205
  ` }`,
1006
- ]
1007
- : []),
1008
- ` await _preflightOnce();`,
1009
- ` await _prepareCommand(${dartStr(d.id)});`,
1010
- ` return bridge.offerOutcome(domain: ${dartStr(
1011
- domain
1012
- )}, directiveId: ${dartStr(
1013
- d.id
1014
- )}, payload: payload.nmsInternalToPayloadJson(), actor: effActor, domainHash: domainHash);`,
2206
+ ]),
2207
+ ...(ordinaryBirthStamps.length === 0
2208
+ ? []
2209
+ : [` final now = DateTime.now().toUtc().toIso8601String();`]),
2210
+ ` return _offer(${ordinaryBirthCallUsda});`,
1015
2211
  ` }`,
1016
2212
  ``
1017
2213
  );
@@ -1022,27 +2218,56 @@ export function generateDartBridgeClient(
1022
2218
  const model = readModelClassName(q.returns);
1023
2219
  const agg = aggById.get(q.returns);
1024
2220
  const params = q.key
1025
- .map((k) => `required ${dartKeyType(agg, k, enumDartTypes)} ${dartKeyParam(k)}`)
2221
+ .map(
2222
+ (k) =>
2223
+ `required ${dartKeyType(agg, k, enumDartTypes)} ${dartKeyParam(k)}`
2224
+ )
1026
2225
  .join(", ");
1027
- const argMap = `{${q.key
1028
- .map((k) => `${dartStr(k)}: ${dartKeyExpr(agg, k)}`)
1029
- .join(", ")}}`;
2226
+ const queryEncoder = emitDartQueryUsdaEncoder(
2227
+ callUsda.graph,
2228
+ callUsda.paths,
2229
+ domain,
2230
+ q.id,
2231
+ q.key.map((key) => ({
2232
+ authoredPath: key,
2233
+ name: dartKeyParam(key),
2234
+ type:
2235
+ dartKeyType(agg, key, enumDartTypes) === "int"
2236
+ ? "int"
2237
+ : dartKeyType(agg, key, enumDartTypes) === "bool"
2238
+ ? "bool"
2239
+ : "String",
2240
+ value: dartKeyExpr(agg, key),
2241
+ kind: queryScalarKind(agg, key),
2242
+ }))
2243
+ );
2244
+ callEncoderSources.push(...queryEncoder.source);
2245
+ const queryCall = queryEncoder.call;
1030
2246
  const m = safeMethod(lcFirst(camel(q.id)));
1031
2247
  const qScopes = readScopesFor(q.returns);
1032
2248
  const qOwned = qScopes.some((scope) => scope.owned === true);
1033
2249
  const qPrivate = needsReadPrincipal(q.returns);
1034
2250
  const qScopesExpr = scopesExpr(qScopes);
2251
+ const nominalWatch = qOwned
2252
+ ? `bridge.nmsWatchUsda(${queryCall}${
2253
+ qPrivate ? ", principal: actor" : ""
2254
+ }).asyncMap((_) => _readWithOwnedKeys(${qScopesExpr}, (keys) async => _nmsDecode${model}Rows(await bridge.nmsQueryUsda(${queryCall}, keys${
2255
+ qPrivate ? ", actor" : ""
2256
+ }), _ws), (row) => row.ref))`
2257
+ : qScopes.length
2258
+ ? `Stream.fromFuture(_readKeys(${qScopesExpr})).asyncExpand((keys) => bridge.nmsWatchUsda(${queryCall}, keys: keys${
2259
+ qPrivate ? ", principal: actor" : ""
2260
+ }).map((usda) => _nmsDecode${model}Rows(usda, _ws)))`
2261
+ : `bridge.nmsWatchUsda(${queryCall}${
2262
+ qPrivate ? ", principal: actor" : ""
2263
+ }).map((usda) => _nmsDecode${model}Rows(usda, _ws))`;
1035
2264
  const pageRead = qOwned
1036
- ? `_readWithOwnedKeys(${qScopesExpr}, (keys) => bridge.queryPage(${dartStr(
1037
- q.id
1038
- )}, params: ${argMap}, pageSize: pageSize, cursor: cursor, keys: keys${
2265
+ ? `_readPageWithOwnedKeys(${qScopesExpr}, (keys) async => _nmsDecode${model}Page(await bridge.nmsQueryPageUsda(${queryCall}, pageSize: pageSize, cursor: cursor, keys: keys${
1039
2266
  qPrivate ? ", principal: actor" : ""
1040
- }))`
1041
- : `bridge.queryPage(${dartStr(
1042
- q.id
1043
- )}, params: ${argMap}, pageSize: pageSize, cursor: cursor${
2267
+ }), _ws), (row) => row.ref)`
2268
+ : `_nmsDecode${model}Page(await bridge.nmsQueryPageUsda(${queryCall}, pageSize: pageSize, cursor: cursor${
1044
2269
  qScopes.length ? `, keys: await _readKeys(${qScopesExpr})` : ""
1045
- }${qPrivate ? ", principal: actor" : ""})`;
2270
+ }${qPrivate ? ", principal: actor" : ""}), _ws)`;
1046
2271
  methods.push(
1047
2272
  ` /// Declared query \`${q.id}\` as one complete result set.${
1048
2273
  readScopesFor(q.returns).length
@@ -1052,64 +2277,36 @@ export function generateDartBridgeClient(
1052
2277
  ` @Deprecated('Whole-result reads scale with result size; use ${m}Page().')`,
1053
2278
  ` Future<List<${model}>> ${m}({${params}}) async =>`,
1054
2279
  qOwned
1055
- ? ` (await _readWithOwnedKeys(${qScopesExpr}, (keys) => bridge.query(${dartStr(
1056
- q.id
1057
- )}, ${argMap}, keys${
2280
+ ? ` _readWithOwnedKeys(${qScopesExpr}, (keys) async => _nmsDecode${model}Rows(await bridge.nmsQueryUsda(${queryCall}, keys${
1058
2281
  qPrivate ? ", actor" : ""
1059
- }))).map((r) => ${rowToModel(model, "r")}).toList();`
1060
- : ` (await bridge.query(${dartStr(q.id)}, ${argMap}${keysArg(
2282
+ }), _ws), (row) => row.ref);`
2283
+ : ` _nmsDecode${model}Rows(await bridge.nmsQueryUsda(${queryCall}${keysArg(
1061
2284
  q.returns
1062
- )})).map((r) => ${rowToModel(model, "r")}).toList();`,
2285
+ )}), _ws);`,
1063
2286
  ``,
1064
2287
  ` /// One bounded \`${q.id}\` page. The opaque [cursor] is returned unchanged by [NomosPage].`,
1065
2288
  ` Future<NomosPage<${model}>> ${m}Page({${params}${
1066
2289
  params ? ", " : ""
1067
2290
  }int pageSize = 100, String? cursor}) async {`,
1068
2291
  ` final page = await ${pageRead};`,
1069
- ` return NomosPage<${model}>(`,
1070
- ` rows: page.rows.map((r) => ${rowToModel(model, "r")}).toList(),`,
1071
- ` cursor: page.cursor,`,
1072
- ` done: page.done,`,
1073
- ` );`,
2292
+ ` return page;`,
1074
2293
  ` }`,
1075
2294
  ``,
1076
2295
  ` /// Reactive \`${q.id}\` — re-emits on every local change (cancel the subscription to stop).`,
1077
2296
  ` Stream<List<${model}>> watch${cap(camel(q.id))}({${params}}) =>`,
1078
- qOwned
1079
- ? ` bridge.watch(${dartStr(q.id)}, ${argMap}, null${
1080
- qPrivate ? ", actor" : ""
1081
- }).asyncMap((_) => _readWithOwnedKeys(${qScopesExpr}, (keys) => bridge.query(${dartStr(
1082
- q.id
1083
- )}, ${argMap}, keys${
1084
- qPrivate ? ", actor" : ""
1085
- }))).map((rows) => rows.map((r) => ${rowToModel(
1086
- model,
1087
- "r"
1088
- )}).toList());`
1089
- : ` bridge.watch(${dartStr(q.id)}, ${argMap}${
1090
- qPrivate ? ", null, actor" : ""
1091
- }).map((rows) => rows.map((r) => ${rowToModel(
1092
- model,
1093
- "r"
1094
- )}).toList());`,
2297
+ ` ${nominalWatch};`,
1095
2298
  ``
1096
2299
  );
1097
2300
  pastMethods.push(
1098
2301
  ` /// Declared query \`${q.id}\` in the verified world at [moment].`,
1099
2302
  ` Future<List<${model}>> ${m}({${params}}) async =>`,
1100
2303
  qOwned
1101
- ? ` (await _client._readWithOwnedKeys(${qScopesExpr}, (keys) => _client.bridge.queryAt(moment, ${dartStr(
1102
- q.id
1103
- )}, ${argMap}, keys${
2304
+ ? ` _client._readWithOwnedKeys(${qScopesExpr}, (keys) async => _nmsDecode${model}Rows(await _client.bridge.nmsQueryAtUsda(moment, ${queryCall}, keys${
1104
2305
  qPrivate ? ", _client.actor" : ""
1105
- }))).map((r) => ${rowToModel(model, "r", "_client._ws")}).toList();`
1106
- : ` (await _client.bridge.queryAt(moment, ${dartStr(
1107
- q.id
1108
- )}, ${argMap}${pastKeysArg(q.returns)})).map((r) => ${rowToModel(
1109
- model,
1110
- "r",
1111
- "_client._ws"
1112
- )}).toList();`,
2306
+ }), _client._ws), (row) => row.ref);`
2307
+ : ` _nmsDecode${model}Rows(await _client.bridge.nmsQueryAtUsda(moment, ${queryCall}${pastKeysArg(
2308
+ q.returns
2309
+ )}), _client._ws);`,
1113
2310
  ``
1114
2311
  );
1115
2312
  }
@@ -1126,20 +2323,17 @@ export function generateDartBridgeClient(
1126
2323
  ` /// Declared spatial index \`${sp.id}\` — rows of \`${sp.of}\` whose \`${sp.on}\` geometry`,
1127
2324
  ` /// intersects [bbox] (an R*Tree probe on the local projection, never a scan).`,
1128
2325
  ` Future<List<${model}>> ${m}({required NomosBbox bbox}) async =>`,
1129
- ` (await bridge.spatialWithin(${dartStr(
2326
+ ` _nmsDecode${model}Rows(await bridge.nmsSpatialWithinUsda(${dartStr(
1130
2327
  sp.id
1131
- )}, bbox)).map((r) => ${rowToModel(model, "r")}).toList();`,
2328
+ )}, bbox), _ws);`,
1132
2329
  ``,
1133
2330
  ` /// Reactive \`${sp.id}\` — re-emits when the probe's rows change locally.`,
1134
2331
  ` Stream<List<${model}>> watch${cap(
1135
2332
  camel(sp.id)
1136
2333
  )}({required NomosBbox bbox}) =>`,
1137
- ` bridge.watchSpatial(${dartStr(
2334
+ ` bridge.nmsWatchSpatialUsda(${dartStr(
1138
2335
  sp.id
1139
- )}, bbox).map((rows) => rows.map((r) => ${rowToModel(
1140
- model,
1141
- "r"
1142
- )}).toList());`,
2336
+ )}, bbox).map((usda) => _nmsDecode${model}Rows(usda, _ws));`,
1143
2337
  ``
1144
2338
  );
1145
2339
  }
@@ -1157,10 +2351,10 @@ export function generateDartBridgeClient(
1157
2351
  c.by
1158
2352
  }\`.\n Future<int> ${m}(${groupType} ${dartKeyParam(
1159
2353
  c.by
1160
- )}) => bridge.count(${dartStr(c.id)}, ${groupValue});`
2354
+ )}) => bridge.nmsCount(${dartStr(c.id)}, ${groupValue});`
1161
2355
  : ` /// Declared count \`${
1162
2356
  c.id
1163
- }\` — the maintained O(1) grand total.\n Future<int> ${m}() => bridge.count(${dartStr(
2357
+ }\` — the maintained O(1) grand total.\n Future<int> ${m}() => bridge.nmsCount(${dartStr(
1164
2358
  c.id
1165
2359
  )});`,
1166
2360
  ``
@@ -1171,12 +2365,12 @@ export function generateDartBridgeClient(
1171
2365
  c.id
1172
2366
  }\` at [moment].\n Future<int> ${m}(${groupType} ${dartKeyParam(
1173
2367
  c.by
1174
- )}) => _client.bridge.countAt(moment, ${dartStr(
2368
+ )}) => _client.bridge.nmsCountAt(moment, ${dartStr(
1175
2369
  c.id
1176
2370
  )}, ${groupValue});`
1177
2371
  : ` /// Declared count \`${
1178
2372
  c.id
1179
- }\` at [moment].\n Future<int> ${m}() => _client.bridge.countAt(moment, ${dartStr(
2373
+ }\` at [moment].\n Future<int> ${m}() => _client.bridge.nmsCountAt(moment, ${dartStr(
1180
2374
  c.id
1181
2375
  )});`,
1182
2376
  ``
@@ -1194,10 +2388,10 @@ export function generateDartBridgeClient(
1194
2388
  s.by
1195
2389
  }\`.\n Future<int> ${m}(${groupType} ${dartKeyParam(
1196
2390
  s.by
1197
- )}) => bridge.sum(${dartStr(s.id)}, ${groupValue});`
2391
+ )}) => bridge.nmsSum(${dartStr(s.id)}, ${groupValue});`
1198
2392
  : ` /// Declared sum \`${
1199
2393
  s.id
1200
- }\` — the maintained O(1) grand total.\n Future<int> ${m}() => bridge.sum(${dartStr(
2394
+ }\` — the maintained O(1) grand total.\n Future<int> ${m}() => bridge.nmsSum(${dartStr(
1201
2395
  s.id
1202
2396
  )});`,
1203
2397
  ``
@@ -1208,10 +2402,12 @@ export function generateDartBridgeClient(
1208
2402
  s.id
1209
2403
  }\` at [moment].\n Future<int> ${m}(${groupType} ${dartKeyParam(
1210
2404
  s.by
1211
- )}) => _client.bridge.sumAt(moment, ${dartStr(s.id)}, ${groupValue});`
2405
+ )}) => _client.bridge.nmsSumAt(moment, ${dartStr(
2406
+ s.id
2407
+ )}, ${groupValue});`
1212
2408
  : ` /// Declared sum \`${
1213
2409
  s.id
1214
- }\` at [moment].\n Future<int> ${m}() => _client.bridge.sumAt(moment, ${dartStr(
2410
+ }\` at [moment].\n Future<int> ${m}() => _client.bridge.nmsSumAt(moment, ${dartStr(
1215
2411
  s.id
1216
2412
  )});`,
1217
2413
  ``
@@ -1231,6 +2427,7 @@ export function generateDartBridgeClient(
1231
2427
  const conflictsClass = `${cap(camel(domain))}Conflicts`;
1232
2428
  const summaryClass = `${cap(camel(domain))}ConflictSummary`;
1233
2429
  const rowClass = `_${cap(camel(domain))}PendingConflictRow`;
2430
+ const evolutionDetailsClass = `${cap(camel(domain))}ConflictEvolution`;
1234
2431
  const aggregateConflictsClass = (aggregateId: string) => {
1235
2432
  const aggregateName = cap(camel(concreteClassName(aggregateId)));
1236
2433
  return aggregateName === cap(camel(domain))
@@ -1250,32 +2447,107 @@ export function generateDartBridgeClient(
1250
2447
  ``,
1251
2448
  ` /// Nomos-internal aggregate-filtered index used only by generated conflict references.`,
1252
2449
  ` Future<List<${rowClass}>> _pendingConflictRows(String aggregateType) async =>`,
1253
- ` (await bridge.pendingConflictsFor(aggregateType)).map(${rowClass}.fromJson).toList();`,
2450
+ ` NmsUsdaConflictResult.parse(await bridge.nmsPendingConflictsUsda(aggregateType))`,
2451
+ ` .rows.map(${rowClass}.fromUsda).toList(growable: false);`,
1254
2452
  ``
1255
2453
  );
1256
2454
  for (const b of resolverBindings) {
1257
2455
  if (b.valuePayloadField === undefined || b.idPayloadField === undefined)
1258
2456
  continue; // probe couldn't bind — dispatch the directive method directly.
1259
- const agg = aggById.get(b.aggregateId);
1260
- const fieldKind = (agg?.fields[b.field] as Field | undefined)?.kind;
1261
- const mergedType =
1262
- fieldKind === "string" || fieldKind === "ref" || fieldKind === "enum"
1263
- ? "String"
1264
- : fieldKind === "int"
1265
- ? "int"
1266
- : fieldKind === "bool"
1267
- ? "bool"
1268
- : "Object?";
1269
2457
  const helper = `_merge${cap(
1270
2458
  camel(concreteClassName(b.aggregateId))
1271
2459
  )}${cap(camel(b.field))}`;
1272
2460
  const dir = allDirs.find((d) => d.id === b.directiveId);
1273
- const gate = (
1274
- dir as { requiresRelationGate?: { relation: string } } | undefined
1275
- )?.requiresRelationGate;
2461
+ if (dir === undefined) {
2462
+ throw new Error(
2463
+ `Dart conflict codegen: missing directive '${domain}/${b.directiveId}'`
2464
+ );
2465
+ }
2466
+ const gate = (dir as { requiresRelationGate?: { relation: string } })
2467
+ ?.requiresRelationGate;
2468
+ const targetBinding = dir.targetBinding;
2469
+ if (
2470
+ targetBinding === undefined ||
2471
+ targetBinding.field !== b.idPayloadField
2472
+ ) {
2473
+ throw new Error(
2474
+ `Dart conflict codegen: '${domain}/${b.directiveId}' must expose '${b.idPayloadField}' as its typed target`
2475
+ );
2476
+ }
2477
+ const payloadClass = dartActionInputClassName(dir);
2478
+ const payloadSpecs = introspectPayload(
2479
+ dir.payloadSchema as z.ZodTypeAny,
2480
+ enumDartTypes
2481
+ );
2482
+ const mergedSpec = payloadSpecs.find(
2483
+ (spec) => spec.name === b.valuePayloadField
2484
+ );
2485
+ if (mergedSpec === undefined) {
2486
+ throw new Error(
2487
+ `Dart conflict codegen: '${domain}/${b.directiveId}' has no typed '${b.valuePayloadField}' fact`
2488
+ );
2489
+ }
2490
+ const otherPublicFacts = payloadSpecs.filter(
2491
+ (spec) =>
2492
+ spec.name !== b.valuePayloadField &&
2493
+ spec.name !== b.idPayloadField &&
2494
+ !autoStampFields(dir.payloadSchema as z.ZodTypeAny).includes(spec.name)
2495
+ );
2496
+ const needsTypedDecision = otherPublicFacts.some(
2497
+ (spec) => !spec.optional
2498
+ );
2499
+ const decisionClass = `${cap(
2500
+ camel(concreteClassName(b.aggregateId))
2501
+ )}${cap(camel(b.field))}MergeDecision`;
2502
+ if (needsTypedDecision) {
2503
+ conflictDeclarations.push(
2504
+ `/// Complete typed facts required by \`${domain}/${b.directiveId}\` to resolve one conflict.`,
2505
+ `final class ${decisionClass} {`,
2506
+ ` final ${mergedSpec.dartType} merged;`,
2507
+ ...otherPublicFacts.map(
2508
+ (spec) =>
2509
+ ` final ${spec.dartType} ${dartMemberName(spec.name)};`
2510
+ ),
2511
+ ` const ${decisionClass}({required this.merged, ${otherPublicFacts
2512
+ .map((spec) => `required this.${dartMemberName(spec.name)}`)
2513
+ .join(", ")}});`,
2514
+ `}`,
2515
+ ``
2516
+ );
2517
+ }
2518
+ const encoder = callEncoderByDirective.get(b.directiveId);
2519
+ if (
2520
+ encoder === undefined ||
2521
+ !encoder.arguments.includes("target") ||
2522
+ encoder.arguments.some(
2523
+ (argument) => argument !== "target" && argument !== "now"
2524
+ )
2525
+ ) {
2526
+ throw new Error(
2527
+ `Dart conflict codegen: '${domain}/${b.directiveId}' cannot be reconstructed from one typed target and value`
2528
+ );
2529
+ }
2530
+ const targetClass = concreteClassName(targetBinding.aggregateId);
2531
+ const conflictCall = `${encoder.functionName}(${[
2532
+ "payload",
2533
+ ...encoder.arguments,
2534
+ ].join(", ")})`;
2535
+ const mergedExpression = needsTypedDecision ? "decision.merged" : "merged";
2536
+ const payloadArguments = [
2537
+ `${dartMemberName(b.valuePayloadField)}: ${mergedExpression}`,
2538
+ ...(needsTypedDecision
2539
+ ? otherPublicFacts.map(
2540
+ (spec) =>
2541
+ `${dartMemberName(spec.name)}: decision.${dartMemberName(
2542
+ spec.name
2543
+ )}`
2544
+ )
2545
+ : []),
2546
+ ];
1276
2547
  methods.push(
1277
- ` Future<NomosOfferOutcome> ${helper}(${rowClass} conflict, ${mergedType} merged,`,
1278
- ` {Map<String, Object?> rest = const {}}) {`,
2548
+ ` Future<NomosOfferOutcome> ${helper}(${rowClass} conflict, ${
2549
+ needsTypedDecision ? decisionClass : mergedSpec.dartType
2550
+ } ${needsTypedDecision ? "decision" : "merged"}) {`,
1279
2551
  ...(gate !== undefined
1280
2552
  ? [
1281
2553
  ` if (actor == null || actor!.trim().isEmpty) {`,
@@ -1287,56 +2559,101 @@ export function generateDartBridgeClient(
1287
2559
  ` }`,
1288
2560
  ]
1289
2561
  : []),
1290
- ` final data = <String, Object?>{...rest, ${dartStr(
1291
- b.idPayloadField
1292
- )}: conflict.aggregateId, ${dartStr(b.valuePayloadField)}: merged};`,
1293
- ` return _offer(${dartStr(
1294
- b.directiveId
1295
- )}, data, conflictSnapshots: [<String, Object?>{`,
1296
- ` 'aggregateType': conflict.aggregateType, 'aggregateId': conflict.aggregateId,`,
1297
- ` 'field': conflict.field, 'targetSchema': conflict.targetSchema, 'token': conflict.snapshot,`,
1298
- ` }]);`,
2562
+ ` final target = NmsInternalNomosRefs.create<${targetClass}>(`,
2563
+ ` workspace: _ws, type: ${dartStr(
2564
+ conflictAggregateType(b.aggregateId)
2565
+ )}, id: conflict.aggregateId);`,
2566
+ ` final payload = ${payloadClass}(${payloadArguments.join(", ")});`,
2567
+ ...(encoder.arguments.includes("now")
2568
+ ? [` final now = DateTime.now().toUtc().toIso8601String();`]
2569
+ : []),
2570
+ ` return _offer(${conflictCall},`,
2571
+ ` conflictSnapshots: [NomosConflictSnapshot(`,
2572
+ ` aggregateType: ${dartStr(
2573
+ conflictAggregateType(b.aggregateId)
2574
+ )}, aggregateId: conflict.aggregateId,`,
2575
+ ` field_3: ${dartStr(
2576
+ b.field
2577
+ )}, targetSchema: conflict.targetSchema, token: conflict.snapshot,`,
2578
+ ` )],`,
2579
+ ` );`,
1299
2580
  ` }`,
1300
2581
  ``
1301
2582
  );
1302
2583
  }
1303
2584
 
2585
+ const summaryAggregates = [...byAggregate.keys()].map((aggregateId) => {
2586
+ const decoder = resultDecoderByAggregate.get(aggregateId);
2587
+ if (decoder === undefined) {
2588
+ throw new Error(
2589
+ `Dart conflict codegen: '${domain}.${aggregateId}' has no nominal result decoder`
2590
+ );
2591
+ }
2592
+ return {
2593
+ property: safeMethod(lcFirst(camel(concreteClassName(aggregateId)))),
2594
+ aggregatePath: decoder.aggregatePath,
2595
+ };
2596
+ });
1304
2597
  conflictDeclarations.push(
1305
- `/// Framework conflict row decoded by this generated client. It is local to the`,
1306
- `/// generated release so schema-aware conflict DX never waits on a core package cast.`,
2598
+ `/// Exact model-evolution evidence for a conflict whose older values cannot yet be`,
2599
+ `/// interpreted as the current generated field type.`,
2600
+ `final class ${evolutionDetailsClass} {`,
2601
+ ` final List<String> sourceSchemas;`,
2602
+ ` final List<String> authoredUnder;`,
2603
+ ` final String reason;`,
2604
+ ` const ${evolutionDetailsClass}({required this.sourceSchemas, required this.authoredUnder, required this.reason});`,
2605
+ `}`,
2606
+ ``,
2607
+ `/// Framework conflict metadata decoded from authenticated USDA. Application values`,
2608
+ `/// remain inside [source] until a generated field decoder consumes them.`,
1307
2609
  `class ${rowClass} {`,
1308
- ` final String aggregateType;`,
2610
+ ` final String aggregatePath;`,
2611
+ ` final String fieldPath;`,
1309
2612
  ` final String aggregateId;`,
1310
- ` final String field;`,
1311
2613
  ` final String status;`,
1312
2614
  ` final String targetSchema;`,
1313
- ` final List<SurfacedObservation<Object?>> observations;`,
1314
- ` final Map<String, dynamic>? evolution;`,
2615
+ ` final ${evolutionDetailsClass}? evolution;`,
1315
2616
  ` final String snapshot;`,
1316
- ` const ${rowClass}({required this.aggregateType, required this.aggregateId, required this.field, required this.status, required this.targetSchema, required this.observations, required this.evolution, required this.snapshot});`,
1317
- ` factory ${rowClass}.fromJson(Map<String, dynamic> json) {`,
1318
- ` final snapshot = json['snapshot'];`,
1319
- ` final targetSchema = json['targetSchema'];`,
1320
- ` if (snapshot is! String || snapshot.isEmpty || targetSchema is! String || targetSchema.isEmpty) {`,
1321
- ` throw StateError('conflicts need a Nomos runtime with schema-pinned immutable snapshots');`,
2617
+ ` final NmsUsdaPrim source;`,
2618
+ ` const ${rowClass}({required this.aggregatePath, required this.fieldPath, required this.aggregateId, required this.status, required this.targetSchema, required this.evolution, required this.snapshot, required this.source});`,
2619
+ ` factory ${rowClass}.fromUsda(NmsUsdaPrim row) {`,
2620
+ ` if (row.typeName != 'NomosPendingConflict') throw const FormatException('Expected NomosPendingConflict');`,
2621
+ ` final aggregatePath = row.relationship('nomos:type');`,
2622
+ ` final fieldPath = row.relationship('nomos:field');`,
2623
+ ` if (aggregatePath == null || fieldPath == null) throw const FormatException('Conflict has no authenticated law identity');`,
2624
+ ` final status = row.requiredStringProperty('nomos:status');`,
2625
+ ` ${evolutionDetailsClass}? evolution;`,
2626
+ ` if (status == 'evolutionRequired') {`,
2627
+ ` final value = row.requiredChild('Evolution');`,
2628
+ ` evolution = ${evolutionDetailsClass}(`,
2629
+ ` sourceSchemas: value.requiredStringArrayProperty('nomos:sourceSchemas'),`,
2630
+ ` authoredUnder: value.requiredStringArrayProperty('nomos:authoredUnder'),`,
2631
+ ` reason: value.requiredStringProperty('nomos:reason'),`,
2632
+ ` );`,
2633
+ ` } else if (status != 'ready') {`,
2634
+ ` throw FormatException('Unknown conflict status: $status');`,
1322
2635
  ` }`,
1323
2636
  ` return ${rowClass}(`,
1324
- ` aggregateType: json['aggregateType'] as String, aggregateId: json['aggregateId'] as String,`,
1325
- ` field: json['field'] as String, status: json['status'] as String? ?? 'evolutionRequired',`,
1326
- ` targetSchema: targetSchema, snapshot: snapshot,`,
1327
- ` evolution: (json['evolution'] as Map?)?.cast<String, dynamic>(),`,
1328
- ` observations: List.unmodifiable((json['observations'] as List? ?? const []).map((o) => SurfacedObservation<Object?>.fromJson((o as Map).cast<String, dynamic>(), (v) => v))),`,
2637
+ ` aggregatePath: aggregatePath, fieldPath: fieldPath,`,
2638
+ ` aggregateId: row.requiredStringProperty('nomos:aggregateId'),`,
2639
+ ` status: status,`,
2640
+ ` targetSchema: row.requiredStringProperty('nomos:targetSchema'),`,
2641
+ ` snapshot: row.requiredStringProperty('nomos:snapshot'),`,
2642
+ ` evolution: evolution, source: row,`,
1329
2643
  ` );`,
1330
2644
  ` }`,
1331
2645
  `}`,
1332
2646
  ``,
1333
- `/// Workspace-wide pending-merge totals, derived from Nomos's maintained conflict index.`,
2647
+ `/// Workspace-wide pending-merge totals with generated aggregate identities.`,
1334
2648
  `class ${summaryClass} {`,
1335
2649
  ` final int total;`,
1336
2650
  ` final int ready;`,
1337
2651
  ` final int evolutionRequired;`,
1338
- ` final Map<String, int> byAggregate;`,
1339
- ` const ${summaryClass}({required this.total, required this.ready, required this.evolutionRequired, required this.byAggregate});`,
2652
+ ...summaryAggregates.map(({ property }) => ` final int ${property};`),
2653
+ ` const ${summaryClass}({required this.total, required this.ready, required this.evolutionRequired, ${summaryAggregates
2654
+ .map(({ property }) => `required this.${property}`)
2655
+ .join(", ")}});`,
2656
+ ` int get modelUpdatesRequired => evolutionRequired;`,
1340
2657
  `}`,
1341
2658
  ``,
1342
2659
  `/// Chained conflict API for the \`${domain}\` domain.`,
@@ -1345,14 +2662,21 @@ export function generateDartBridgeClient(
1345
2662
  ` const ${conflictsClass}(this._client);`,
1346
2663
  ``,
1347
2664
  ` Future<${summaryClass}> summary() async {`,
1348
- ` final summary = await _client.bridge.pendingConflictSummary();`,
1349
- ` final byAggregate = (summary['byAggregate'] as Map? ?? const {}).map(`,
1350
- ` (key, value) => MapEntry(key.toString(), (value as num).toInt()));`,
2665
+ ` final summary = NmsUsdaConflictSummaryResult.parse(`,
2666
+ ` await _client.bridge.nmsPendingConflictSummaryUsda());`,
2667
+ ` int count(String aggregatePath) {`,
2668
+ ` for (final entry in summary.byAggregate) {`,
2669
+ ` if (entry.aggregatePath == aggregatePath) return entry.count;`,
2670
+ ` }`,
2671
+ ` return 0;`,
2672
+ ` }`,
1351
2673
  ` return ${summaryClass}(`,
1352
- ` total: (summary['total'] as num? ?? 0).toInt(),`,
1353
- ` ready: (summary['ready'] as num? ?? 0).toInt(),`,
1354
- ` evolutionRequired: (summary['evolutionRequired'] as num? ?? 0).toInt(),`,
1355
- ` byAggregate: Map.unmodifiable(byAggregate),`,
2674
+ ` total: summary.total, ready: summary.ready,`,
2675
+ ` evolutionRequired: summary.evolutionRequired,`,
2676
+ ...summaryAggregates.map(
2677
+ ({ property, aggregatePath }) =>
2678
+ ` ${property}: count(${dartStr(aggregatePath)}),`
2679
+ ),
1356
2680
  ` );`,
1357
2681
  ` }`,
1358
2682
  ...[...byAggregate.keys()].map(
@@ -1369,53 +2693,120 @@ export function generateDartBridgeClient(
1369
2693
  const runtimeAggregateType = conflictAggregateType(aggregateId);
1370
2694
  const aggregateName = cap(camel(concreteClassName(aggregateId)));
1371
2695
  const aggregateConflict = `${aggregateName}PendingConflict`;
2696
+ const aggregateDecoder = resultDecoderByAggregate.get(aggregateId);
2697
+ if (aggregateDecoder === undefined) {
2698
+ throw new Error(
2699
+ `Dart conflict codegen: '${domain}.${aggregateId}' has no nominal decoder`
2700
+ );
2701
+ }
1372
2702
  conflictDeclarations.push(
1373
2703
  `/// One pending merge on \`${aggregateId}\`; narrow by subtype to call its typed merge.`,
1374
2704
  `sealed class ${aggregateConflict} {`,
1375
2705
  ` const ${aggregateConflict}();`,
2706
+ ` String get aggregateType => ${dartStr(runtimeAggregateType)};`,
1376
2707
  ` String get aggregateId;`,
1377
2708
  ` String get field;`,
2709
+ ` String get targetSchema;`,
2710
+ ` String get snapshot;`,
1378
2711
  `}`,
1379
2712
  ``
1380
2713
  );
1381
2714
  for (const b of bindings) {
1382
2715
  if (b.valuePayloadField === undefined || b.idPayloadField === undefined)
1383
2716
  continue;
1384
- const agg = aggById.get(b.aggregateId);
1385
- const fieldKind = (agg?.fields[b.field] as Field | undefined)?.kind;
1386
- const mergedType =
1387
- fieldKind === "string" || fieldKind === "ref" || fieldKind === "enum"
1388
- ? "String"
1389
- : fieldKind === "int"
1390
- ? "int"
1391
- : fieldKind === "bool"
1392
- ? "bool"
1393
- : "Object?";
2717
+ const fieldDecoder = aggregateDecoder.baseFields.find(
2718
+ (field) => field.name === b.field
2719
+ );
2720
+ if (fieldDecoder === undefined) {
2721
+ throw new Error(
2722
+ `Dart conflict codegen: '${domain}.${aggregateId}.${b.field}' has no nominal field decoder`
2723
+ );
2724
+ }
2725
+ const mergedType = fieldDecoder.spec.dartType;
1394
2726
  const typeName = `${aggregateName}${cap(camel(b.field))}Conflict`;
2727
+ const decisionClass = `${aggregateName}${cap(
2728
+ camel(b.field)
2729
+ )}MergeDecision`;
2730
+ const dir = allDirs.find((candidate) => candidate.id === b.directiveId);
2731
+ if (dir === undefined) {
2732
+ throw new Error(
2733
+ `Dart conflict codegen: missing directive '${domain}/${b.directiveId}'`
2734
+ );
2735
+ }
2736
+ const decisionSpecs = introspectPayload(
2737
+ dir.payloadSchema as z.ZodTypeAny,
2738
+ enumDartTypes
2739
+ );
2740
+ const needsTypedDecision = decisionSpecs.some(
2741
+ (spec) =>
2742
+ spec.name !== b.valuePayloadField &&
2743
+ spec.name !== b.idPayloadField &&
2744
+ !autoStampFields(dir.payloadSchema as z.ZodTypeAny).includes(
2745
+ spec.name
2746
+ ) &&
2747
+ !spec.optional
2748
+ );
1395
2749
  const evolutionTypeName = `${aggregateName}${cap(
1396
2750
  camel(b.field)
1397
2751
  )}ConflictEvolutionRequired`;
1398
2752
  const helper = `_merge${aggregateName}${cap(camel(b.field))}`;
2753
+ const versionsDecoder = `_nmsDecode${aggregateName}${cap(
2754
+ camel(b.field)
2755
+ )}ConflictVersions`;
1399
2756
  conflictDeclarations.push(
2757
+ `List<SurfacedObservation<${mergedType}>> ${versionsDecoder}(${rowClass} details, String workspace) {`,
2758
+ ` if (details.status != 'ready' || details.fieldPath != ${dartStr(
2759
+ fieldDecoder.path
2760
+ )}) {`,
2761
+ ` throw const FormatException('Conflict does not match the generated field contract');`,
2762
+ ` }`,
2763
+ ` final observations = details.source.requiredChild('Observations');`,
2764
+ ` if (observations.typeName != 'NomosListValue') throw const FormatException('Conflict observations are not a list');`,
2765
+ ` return List.unmodifiable(observations.children.map((observation) {`,
2766
+ ` if (observation.typeName != 'NomosConflictObservation') throw const FormatException('Malformed conflict observation');`,
2767
+ ` final value = observation.requiredChild('Value');`,
2768
+ ` if (value.relationship('nomos:field') != ${dartStr(
2769
+ fieldDecoder.path
2770
+ )}) throw const FormatException('Conflict value has the wrong law field');`,
2771
+ ` return SurfacedObservation<${mergedType}>(`,
2772
+ ` value: ${dartUsdaBasePresentValue(
2773
+ fieldDecoder.spec,
2774
+ "value",
2775
+ "workspace"
2776
+ )},`,
2777
+ ` occurredAt: Hlc(`,
2778
+ ` physical: observation.requiredIntProperty('nomos:physical'),`,
2779
+ ` logical: observation.requiredIntProperty('nomos:logical'),`,
2780
+ ` replica: observation.requiredIntProperty('nomos:replica'),`,
2781
+ ` ),`,
2782
+ ` author: observation.optionalStringProperty('nomos:author'),`,
2783
+ ` );`,
2784
+ ` }));`,
2785
+ `}`,
2786
+ ``,
1400
2787
  `/// Pending versions of \`${aggregateId}.${b.field}\`. [merge] commits a result that may`,
1401
2788
  `/// equal either version or be a new reconciled business value.`,
1402
2789
  `final class ${typeName} extends ${aggregateConflict} {`,
1403
2790
  ` final ${rowClass} _details;`,
1404
2791
  ` final ${className} _client;`,
1405
- ` const ${typeName}._(this._details, this._client);`,
2792
+ ` final List<SurfacedObservation<${mergedType}>> versions;`,
2793
+ ` const ${typeName}._(this._details, this._client, this.versions);`,
1406
2794
  ` @override`,
1407
2795
  ` String get aggregateId => _details.aggregateId;`,
1408
2796
  ` @override`,
1409
- ` String get field => _details.field;`,
1410
- ``,
1411
- ` List<SurfacedObservation<${mergedType}>> get versions => List.unmodifiable(`,
1412
- ` _details.observations.map((o) => SurfacedObservation<${mergedType}>(`,
1413
- ` value: o.value as ${mergedType}, occurredAt: o.occurredAt, author: o.author)));`,
2797
+ ` String get field => ${dartStr(b.field)};`,
2798
+ ` @override`,
2799
+ ` String get targetSchema => _details.targetSchema;`,
2800
+ ` @override`,
2801
+ ` String get snapshot => _details.snapshot;`,
1414
2802
  ``,
1415
- ` Future<NomosOfferOutcome> merge(`,
1416
- ` ${mergedType} Function(List<SurfacedObservation<${mergedType}>> versions) reconcile,`,
1417
- ` {Map<String, Object?> rest = const {}}) =>`,
1418
- ` _client.${helper}(_details, reconcile(versions), rest: rest);`,
2803
+ ` Future<void> merge(`,
2804
+ ` ${
2805
+ needsTypedDecision ? decisionClass : mergedType
2806
+ } Function(List<SurfacedObservation<${mergedType}>> versions) reconcile,`,
2807
+ ` ) async {`,
2808
+ ` await _client.${helper}(_details, reconcile(versions));`,
2809
+ ` }`,
1419
2810
  `}`,
1420
2811
  ``,
1421
2812
  `/// The pending values span field schemas. No merge method exists until Nomos can`,
@@ -1426,8 +2817,12 @@ export function generateDartBridgeClient(
1426
2817
  ` @override`,
1427
2818
  ` String get aggregateId => _details.aggregateId;`,
1428
2819
  ` @override`,
1429
- ` String get field => _details.field;`,
1430
- ` Map<String, dynamic> get evolution => _details.evolution ?? const {};`,
2820
+ ` String get field => ${dartStr(b.field)};`,
2821
+ ` @override`,
2822
+ ` String get targetSchema => _details.targetSchema;`,
2823
+ ` @override`,
2824
+ ` String get snapshot => _details.snapshot;`,
2825
+ ` ${evolutionDetailsClass} get evolution => _details.evolution!;`,
1431
2826
  `}`,
1432
2827
  ``
1433
2828
  );
@@ -1444,10 +2839,12 @@ export function generateDartBridgeClient(
1444
2839
  )});`,
1445
2840
  ` final out = <${aggregateConflict}>[];`,
1446
2841
  ` for (final row in rows) {`,
1447
- ` if (row.aggregateType != ${dartStr(runtimeAggregateType)}) {`,
2842
+ ` if (row.aggregatePath != ${dartStr(
2843
+ aggregateDecoder.aggregatePath
2844
+ )}) {`,
1448
2845
  ` throw StateError('Nomos returned a conflict outside the generated aggregate filter');`,
1449
2846
  ` }`,
1450
- ` switch (row.field) {`,
2847
+ ` switch (row.fieldPath) {`,
1451
2848
  ...bindings.flatMap((b) => {
1452
2849
  if (
1453
2850
  b.valuePayloadField === undefined ||
@@ -1458,10 +2855,21 @@ export function generateDartBridgeClient(
1458
2855
  const evolutionTypeName = `${aggregateName}${cap(
1459
2856
  camel(b.field)
1460
2857
  )}ConflictEvolutionRequired`;
2858
+ const fieldDecoder = aggregateDecoder.baseFields.find(
2859
+ (field) => field.name === b.field
2860
+ );
2861
+ if (fieldDecoder === undefined) {
2862
+ throw new Error(
2863
+ `Dart conflict codegen: '${domain}.${aggregateId}.${b.field}' has no nominal field decoder`
2864
+ );
2865
+ }
2866
+ const versionsDecoder = `_nmsDecode${aggregateName}${cap(
2867
+ camel(b.field)
2868
+ )}ConflictVersions`;
1461
2869
  return [
1462
- ` case ${dartStr(b.field)}:`,
2870
+ ` case ${dartStr(fieldDecoder.path)}:`,
1463
2871
  ` if (row.status == 'ready') {`,
1464
- ` out.add(${typeName}._(row, _client));`,
2872
+ ` out.add(${typeName}._(row, _client, ${versionsDecoder}(row, _client._ws)));`,
1465
2873
  ` } else if (row.status == 'evolutionRequired') {`,
1466
2874
  ` out.add(${evolutionTypeName}._(row));`,
1467
2875
  ` } else {`,
@@ -1469,6 +2877,8 @@ export function generateDartBridgeClient(
1469
2877
  ` }`,
1470
2878
  ];
1471
2879
  }),
2880
+ ` default:`,
2881
+ ` throw StateError('Nomos returned a conflict outside the generated field set');`,
1472
2882
  ` }`,
1473
2883
  ` }`,
1474
2884
  ` return List.unmodifiable(out);`,
@@ -1500,9 +2910,9 @@ export function generateDartBridgeClient(
1500
2910
  ? ", null, actor"
1501
2911
  : ""
1502
2912
  : aOwned
1503
- ? `, await _readKeys(${aScopesExpr}, [{${dartStr("type")}: ${dartStr(
2913
+ ? `, await _readKeys(${aScopesExpr}, [(type: ${dartStr(
1504
2914
  runtimeType
1505
- )}, ${dartStr("id")}: id}])${aPrivate ? ", actor" : ""}`
2915
+ )}, id: id)])${aPrivate ? ", actor" : ""}`
1506
2916
  : keysArg(agg.id);
1507
2917
  const pastByIdKeys =
1508
2918
  aScopes.length === 0
@@ -1510,12 +2920,40 @@ export function generateDartBridgeClient(
1510
2920
  ? ", null, _client.actor"
1511
2921
  : ""
1512
2922
  : aOwned
1513
- ? `, await _client._readKeys(${aScopesExpr}, [{${dartStr(
1514
- "type"
1515
- )}: ${dartStr(runtimeType)}, ${dartStr("id")}: id}])${
2923
+ ? `, await _client._readKeys(${aScopesExpr}, [(type: ${dartStr(
2924
+ runtimeType
2925
+ )}, id: id)])${
1516
2926
  aPrivate ? ", _client.actor" : ""
1517
2927
  }`
1518
2928
  : pastKeysArg(agg.id);
2929
+ const nominalByIdWatch = aScopes.length
2930
+ ? `Stream.fromFuture(_readKeys(${aScopesExpr}${
2931
+ aOwned
2932
+ ? `, [(type: ${dartStr(runtimeType)}, id: id)]`
2933
+ : ""
2934
+ })).asyncExpand((keys) => bridge.nmsWatchByIdUsda(${dartStr(
2935
+ runtimeType
2936
+ )}, id, keys: keys${
2937
+ aPrivate ? ", principal: actor" : ""
2938
+ }).map((usda) => _nmsDecode${model}Rows(usda, _ws)))`
2939
+ : `bridge.nmsWatchByIdUsda(${dartStr(runtimeType)}, id${
2940
+ aPrivate ? ", principal: actor" : ""
2941
+ }).map((usda) => _nmsDecode${model}Rows(usda, _ws))`;
2942
+ const nominalByTypeWatch = aOwned
2943
+ ? `bridge.nmsWatchByTypeUsda(${dartStr(runtimeType)}${
2944
+ aPrivate ? ", principal: actor" : ""
2945
+ }).asyncMap((_) => _readWithOwnedKeys(${aScopesExpr}, (keys) async => _nmsDecode${model}Rows(await bridge.nmsQueryByTypeUsda(${dartStr(
2946
+ runtimeType
2947
+ )}, keys${aPrivate ? ", actor" : ""}), _ws), (row) => row.ref))`
2948
+ : aScopes.length
2949
+ ? `Stream.fromFuture(_readKeys(${aScopesExpr})).asyncExpand((keys) => bridge.nmsWatchByTypeUsda(${dartStr(
2950
+ runtimeType
2951
+ )}, keys: keys${
2952
+ aPrivate ? ", principal: actor" : ""
2953
+ }).map((usda) => _nmsDecode${model}Rows(usda, _ws)))`
2954
+ : `bridge.nmsWatchByTypeUsda(${dartStr(runtimeType)}${
2955
+ aPrivate ? ", principal: actor" : ""
2956
+ }).map((usda) => _nmsDecode${model}Rows(usda, _ws))`;
1519
2957
  methods.push(
1520
2958
  ` /// Read one ${agg.id} by aggregate id.${
1521
2959
  readScopesFor(agg.id).length
@@ -1523,96 +2961,45 @@ export function generateDartBridgeClient(
1523
2961
  : ""
1524
2962
  }`,
1525
2963
  ` Future<List<${model}>> ${safeMethod(`${lc}ById`)}(String id) async =>`,
1526
- ` (await bridge.queryById(id${byIdKeys})).map((r) => ${rowToModel(
1527
- model,
1528
- "r"
1529
- )}).toList();`,
2964
+ ` _nmsDecode${model}Rows(await bridge.nmsQueryByIdUsda(${dartStr(
2965
+ runtimeType
2966
+ )}, id${byIdKeys}), _ws);`,
1530
2967
  ``,
1531
2968
  ` /// Watch one ${agg.id} by aggregate id (local reactive read).`,
1532
2969
  ` Stream<List<${model}>> watch${Cap}ById(String id) =>`,
1533
- aOwned
1534
- ? ` bridge.watchById(id, null${
1535
- aPrivate ? ", actor" : ""
1536
- }).asyncMap((_) async => bridge.queryById(id, await _readKeys(${aScopesExpr}, [{${dartStr(
1537
- "type"
1538
- )}: ${dartStr(runtimeType)}, ${dartStr("id")}: id}])${
1539
- aPrivate ? ", actor" : ""
1540
- })).map((rows) => rows.map((r) => ${rowToModel(
1541
- model,
1542
- "r"
1543
- )}).toList());`
1544
- : ` bridge.watchById(id${
1545
- aPrivate ? ", null, actor" : ""
1546
- }).map((rows) => rows.map((r) => ${rowToModel(
1547
- model,
1548
- "r"
1549
- )}).toList());`,
2970
+ ` ${nominalByIdWatch};`,
1550
2971
  ``,
1551
2972
  ` /// Every ${agg.id} (the type-index probe — O(result), not a scan).`,
1552
2973
  ` Future<List<${model}>> ${safeMethod(`list${Cap}s`)}() async =>`,
1553
2974
  aOwned
1554
- ? ` (await _readWithOwnedKeys(${aScopesExpr}, (keys) => bridge.query('aggregatesByType', {'__type': ${dartStr(
2975
+ ? ` _readWithOwnedKeys(${aScopesExpr}, (keys) async => _nmsDecode${model}Rows(await bridge.nmsQueryByTypeUsda(${dartStr(
1555
2976
  runtimeType
1556
- )}}, keys${aPrivate ? ", actor" : ""}))).map((r) => ${rowToModel(
1557
- model,
1558
- "r"
1559
- )}).toList();`
1560
- : ` (await bridge.query('aggregatesByType', {'__type': ${dartStr(
2977
+ )}, keys${aPrivate ? ", actor" : ""}), _ws), (row) => row.ref);`
2978
+ : ` _nmsDecode${model}Rows(await bridge.nmsQueryByTypeUsda(${dartStr(
1561
2979
  runtimeType
1562
- )}}${keysArg(agg.id)})).map((r) => ${rowToModel(
1563
- model,
1564
- "r"
1565
- )}).toList();`,
2980
+ )}${keysArg(agg.id)}), _ws);`,
1566
2981
  ``,
1567
2982
  ` /// Watch every ${agg.id} (the same indexed local read, re-emitted after each change).`,
1568
2983
  ` Stream<List<${model}>> ${safeMethod(`watchAll${Cap}s`)}() =>`,
1569
- aOwned
1570
- ? ` bridge.watch('aggregatesByType', {'__type': ${dartStr(
1571
- runtimeType
1572
- )}}, null${
1573
- aPrivate ? ", actor" : ""
1574
- }).asyncMap((_) => _readWithOwnedKeys(${aScopesExpr}, (keys) => bridge.query('aggregatesByType', {'__type': ${dartStr(
1575
- runtimeType
1576
- )}}, keys${
1577
- aPrivate ? ", actor" : ""
1578
- }))).map((rows) => rows.map((r) => ${rowToModel(
1579
- model,
1580
- "r"
1581
- )}).toList());`
1582
- : ` bridge.watch('aggregatesByType', {'__type': ${dartStr(
1583
- runtimeType
1584
- )}}${
1585
- aPrivate ? ", null, actor" : ""
1586
- }).map((rows) => rows.map((r) => ${rowToModel(
1587
- model,
1588
- "r"
1589
- )}).toList());`,
2984
+ ` ${nominalByTypeWatch};`,
1590
2985
  ``
1591
2986
  );
1592
2987
  pastMethods.push(
1593
2988
  ` /// Read one ${agg.id} by aggregate id in the verified world at [moment].`,
1594
2989
  ` Future<List<${model}>> ${safeMethod(`${lc}ById`)}(String id) async =>`,
1595
- ` (await _client.bridge.queryByIdAt(moment, id${pastByIdKeys})).map((r) => ${rowToModel(
1596
- model,
1597
- "r",
1598
- "_client._ws"
1599
- )}).toList();`,
2990
+ ` _nmsDecode${model}Rows(await _client.bridge.nmsQueryByIdAtUsda(moment, ${dartStr(
2991
+ runtimeType
2992
+ )}, id${pastByIdKeys}), _client._ws);`,
1600
2993
  ``,
1601
2994
  ` /// Every ${agg.id} in the verified world at [moment].`,
1602
2995
  ` Future<List<${model}>> ${safeMethod(`list${Cap}s`)}() async =>`,
1603
2996
  aOwned
1604
- ? ` (await _client._readWithOwnedKeys(${aScopesExpr}, (keys) => _client.bridge.queryAt(moment, 'aggregatesByType', {'__type': ${dartStr(
2997
+ ? ` _client._readWithOwnedKeys(${aScopesExpr}, (keys) async => _nmsDecode${model}Rows(await _client.bridge.nmsQueryByTypeAtUsda(moment, ${dartStr(
1605
2998
  runtimeType
1606
- )}}, keys${
1607
- aPrivate ? ", _client.actor" : ""
1608
- }))).map((r) => ${rowToModel(model, "r", "_client._ws")}).toList();`
1609
- : ` (await _client.bridge.queryAt(moment, 'aggregatesByType', {'__type': ${dartStr(
2999
+ )}, keys${aPrivate ? ", _client.actor" : ""}), _client._ws), (row) => row.ref);`
3000
+ : ` _nmsDecode${model}Rows(await _client.bridge.nmsQueryByTypeAtUsda(moment, ${dartStr(
1610
3001
  runtimeType
1611
- )}}${pastKeysArg(agg.id)})).map((r) => ${rowToModel(
1612
- model,
1613
- "r",
1614
- "_client._ws"
1615
- )}).toList();`,
3002
+ )}${pastKeysArg(agg.id)}), _client._ws);`,
1616
3003
  ``
1617
3004
  );
1618
3005
  }
@@ -1623,73 +3010,48 @@ export function generateDartBridgeClient(
1623
3010
  ` /// difference returns a decision whose accept(reason) re-proves and records the reason.`,
1624
3011
  ` NomosModel get model => NomosModel(_adoptGeneratedModel, _restorePreviousGeneratedModel);`,
1625
3012
  ``,
1626
- ` NomosModelReview _nomosModelReview(Map<String, Object?>? report) {`,
1627
- ` final differences = <NomosModelDifference>[];`,
1628
- ` for (final raw in ((report?['divergences'] as List?) ?? const [])) {`,
1629
- ` final difference = (raw as Map).cast<String, Object?>();`,
1630
- ` final rawArea = difference['domain'];`,
1631
- ` final area = rawArea is String && !rawArea.contains('nomos_internal') ? rawArea : null;`,
1632
- ` final rawAction = difference['directiveId'];`,
1633
- ` final action = rawAction is String && !rawAction.contains('nomos_internal') ? rawAction : null;`,
1634
- ` final rawKind = difference['kind']?.toString();`,
1635
- ` final kind = rawKind == 'plan-divergence'`,
1636
- ` ? NomosModelDifferenceKind.outcome`,
1637
- ` : rawKind == 'unsupported-premise' || rawKind == 'read-conflict'`,
1638
- ` ? NomosModelDifferenceKind.evidence`,
1639
- ` : NomosModelDifferenceKind.rules;`,
1640
- ` final subject = action ?? (kind == NomosModelDifferenceKind.outcome ? 'A business action' : 'A historical action');`,
1641
- ` final summary = kind == NomosModelDifferenceKind.outcome`,
1642
- ` ? '\$subject would produce a different result under this model.'`,
1643
- ` : kind == NomosModelDifferenceKind.evidence`,
1644
- ` ? 'Nomos could not reproduce all evidence needed by \$subject.'`,
1645
- ` : "\$subject is not accepted by this model's rules.";`,
1646
- ` differences.add(NomosModelDifference(kind: kind, area: area, action: action, summary: summary));`,
1647
- ` }`,
1648
- ` final evolve = (report?['evolve'] as Map?)?.cast<String, Object?>();`,
1649
- ` for (final raw in ((evolve?['violations'] as List?) ?? const [])) {`,
1650
- ` if (raw is! Map) continue;`,
1651
- ` final violation = raw.cast<String, Object?>();`,
1652
- ` final rawDomain = violation['domain']?.toString().trim();`,
1653
- ` final rawDetail = violation['detail']?.toString().trim();`,
1654
- ` differences.add(NomosModelDifference(`,
1655
- ` kind: NomosModelDifferenceKind.rules,`,
1656
- ` area: rawDomain == null || rawDomain.isEmpty ? null : rawDomain,`,
1657
- ` summary: rawDetail == null || rawDetail.isEmpty`,
1658
- ` ? 'This model does not declare how to interpret an existing value.'`,
1659
- ` : rawDetail,`,
1660
- ` ));`,
1661
- ` }`,
1662
- ` if (report != null && evolve?['ok'] != true && !differences.any((d) => d.kind == NomosModelDifferenceKind.rules)) {`,
1663
- ` differences.add(const NomosModelDifference(kind: NomosModelDifferenceKind.rules, summary: 'This model does not yet explain how to interpret every existing value.'));`,
1664
- ` }`,
1665
- ` if (report != null && (report['sourceVerified'] != true || report['complete'] != true)`,
1666
- ` && !differences.any((d) => d.kind == NomosModelDifferenceKind.evidence)) {`,
1667
- ` differences.add(const NomosModelDifference(kind: NomosModelDifferenceKind.evidence, summary: 'Nomos could not re-prove every premise in the current business history.'));`,
1668
- ` }`,
1669
- ` final projection = (report?['projection'] as Map?)?.cast<String, Object?>();`,
1670
- ` int number(String name) => (report?[name] as num?)?.toInt() ?? 0;`,
3013
+ ` NomosModelReview _nomosModelReview(ModelLifecycleResponse result) {`,
3014
+ ` final report = result.hasReview() ? result.review : null;`,
1671
3015
  ` return NomosModelReview(`,
1672
- ` checkedActions: number('replayedIntents'), candidateActions: number('candidateIntents'),`,
1673
- ` sameOutcome: projection?['equal'] as bool?, differences: differences);`,
3016
+ ` checkedActions: report?.checkedActions.toInt() ?? 0,`,
3017
+ ` candidateActions: report?.candidateActions.toInt() ?? 0,`,
3018
+ ` sameOutcome: report?.hasSameOutcome() == true ? report!.sameOutcome : null,`,
3019
+ ` differences: [`,
3020
+ ` for (final difference in report?.differences ?? const [])`,
3021
+ ` NomosModelDifference(`,
3022
+ ` kind: switch (difference.kind) {`,
3023
+ ` ModelDifferenceKind.MODEL_DIFFERENCE_KIND_RULES => NomosModelDifferenceKind.rules,`,
3024
+ ` ModelDifferenceKind.MODEL_DIFFERENCE_KIND_OUTCOME => NomosModelDifferenceKind.outcome,`,
3025
+ ` ModelDifferenceKind.MODEL_DIFFERENCE_KIND_EVIDENCE => NomosModelDifferenceKind.evidence,`,
3026
+ ` _ => NomosModelDifferenceKind.rules,`,
3027
+ ` },`,
3028
+ ` area: difference.hasArea() ? difference.area : null,`,
3029
+ ` action: difference.hasAction() ? difference.action : null,`,
3030
+ ` summary: difference.summary,`,
3031
+ ` ),`,
3032
+ ` ],`,
3033
+ ` );`,
1674
3034
  ` }`,
1675
3035
  ``,
1676
3036
  ` Future<NomosModelAdoption> _adoptGeneratedModel([String? because]) async {`,
1677
- ` final raw = await bridge.nomosFrameworkAdoptGeneratedModel(`,
3037
+ ` final result = await bridge.nomosFrameworkAdoptGeneratedModel(`,
1678
3038
  ` domain: ${dartStr(
1679
3039
  domain
1680
3040
  )}, candidateHash: domainHash, because: because);`,
1681
- ` final reportRaw = raw['report'];`,
1682
- ` final report = reportRaw is Map ? reportRaw.cast<String, Object?>() : null;`,
1683
- ` final review = _nomosModelReview(report);`,
1684
- ` final stage = raw['stage']?.toString();`,
1685
- ` if (stage == 'promoted' && raw['ok'] == true) {`,
3041
+ ` final review = _nomosModelReview(result);`,
3042
+ ` final stage = result.stage;`,
3043
+ ` if ((stage == ModelLifecycleStage.MODEL_LIFECYCLE_STAGE_PROMOTED ||`,
3044
+ ` stage == ModelLifecycleStage.MODEL_LIFECYCLE_STAGE_ALREADY_CURRENT) && result.accepted) {`,
1686
3045
  ` return NomosModelAdoption.adopted(`,
1687
- ` summary: 'Nomos proved this model against current history and made it active.', review: review);`,
3046
+ ` summary: stage == ModelLifecycleStage.MODEL_LIFECYCLE_STAGE_PROMOTED`,
3047
+ ` ? 'Nomos proved this model against current history and made it active.'`,
3048
+ ` : 'This workspace already runs this model or a server-side successor.',`,
3049
+ ` review: review);`,
1688
3050
  ` }`,
1689
3051
  ...(opts.lawAssetKey === undefined
1690
3052
  ? []
1691
3053
  : [
1692
- ` if (stage == 'staging-required') {`,
3054
+ ` if (stage == ModelLifecycleStage.MODEL_LIFECYCLE_STAGE_STAGING_REQUIRED) {`,
1693
3055
  ` return NomosModelAdoption.needsStaging(`,
1694
3056
  ` summary: 'This workspace is running an older model. Stage this generated USDA candidate through the signed workspace gate before reviewing it.',`,
1695
3057
  ` review: review,`,
@@ -1700,24 +3062,17 @@ export function generateDartBridgeClient(
1700
3062
  ` );`,
1701
3063
  ` }`,
1702
3064
  ]),
1703
- ` if (stage == 'acceptance') {`,
3065
+ ` if (stage == ModelLifecycleStage.MODEL_LIFECYCLE_STAGE_ACCEPTANCE_REQUIRED) {`,
1704
3066
  ` return NomosModelAdoption.needsDecision(`,
1705
3067
  ` summary: 'This model would change prior business outcomes. Review the differences and record why that is intended.',`,
1706
3068
  ` review: review, accept: (reason) => _adoptGeneratedModel(reason));`,
1707
3069
  ` }`,
1708
- ` final evolve = (report?['evolve'] as Map?)?.cast<String, Object?>();`,
1709
- ` final rawError = raw['error']?.toString().trim();`,
1710
- ` final summary = rawError != null && rawError.isNotEmpty`,
3070
+ ` final rawError = result.hasMessage() ? result.message.trim() : '';`,
3071
+ ` final summary = rawError.isNotEmpty`,
1711
3072
  ` ? rawError`,
1712
- ` : stage == 'identity'`,
3073
+ ` : stage == ModelLifecycleStage.MODEL_LIFECYCLE_STAGE_IDENTITY_REQUIRED`,
1713
3074
  ` ? 'Sign in before adopting this model so the decision is accountable.'`,
1714
- ` : report != null && report['sourceVerified'] != true`,
1715
- ` ? 'The current history could not be verified. Nothing changed and the current model remains active.'`,
1716
- ` : report != null && report['complete'] != true`,
1717
- ` ? 'Nomos could not re-prove every historical dependency. Nothing changed and the current model remains active.'`,
1718
- ` : report != null && evolve?['ok'] != true`,
1719
- ` ? 'This model cannot yet interpret every existing value. Nothing changed and the current model remains active.'`,
1720
- ` : 'The workspace changed or refused this adoption. Nothing changed; ask Nomos to try again.';`,
3075
+ ` : 'The workspace changed or refused this adoption. Nothing changed; ask Nomos to try again.';`,
1721
3076
  ` return NomosModelAdoption.blocked(summary: summary, review: review);`,
1722
3077
  ` }`,
1723
3078
  ``,
@@ -1725,7 +3080,9 @@ export function generateDartBridgeClient(
1725
3080
  ? []
1726
3081
  : [
1727
3082
  ` Future<void> _stageGeneratedModel() async {`,
1728
- ` final archive = await rootBundle.load(${dartStr(opts.lawAssetKey)});`,
3083
+ ` final archive = await rootBundle.load(${dartStr(
3084
+ opts.lawAssetKey
3085
+ )});`,
1729
3086
  ` await bridge.nomosInternalStageGeneratedModel(`,
1730
3087
  ` domain: ${dartStr(domain)},`,
1731
3088
  ` candidateHash: domainHash,`,
@@ -1736,38 +3093,31 @@ export function generateDartBridgeClient(
1736
3093
  ``,
1737
3094
  ]),
1738
3095
  ` Future<NomosModelRestoration> _restorePreviousGeneratedModel([String? because]) async {`,
1739
- ` final raw = await bridge.nomosFrameworkRestorePreviousGeneratedModel(`,
3096
+ ` final result = await bridge.nomosFrameworkRestorePreviousGeneratedModel(`,
1740
3097
  ` domain: ${dartStr(
1741
3098
  domain
1742
3099
  )}, currentHash: domainHash, because: because);`,
1743
- ` final reportRaw = raw['report'];`,
1744
- ` final report = reportRaw is Map ? reportRaw.cast<String, Object?>() : null;`,
1745
- ` final review = _nomosModelReview(report);`,
1746
- ` final stage = raw['stage']?.toString();`,
1747
- ` if (stage == 'restored' && raw['ok'] == true) {`,
3100
+ ` final review = _nomosModelReview(result);`,
3101
+ ` final stage = result.stage;`,
3102
+ ` if (stage == ModelLifecycleStage.MODEL_LIFECYCLE_STAGE_RESTORED && result.accepted) {`,
1748
3103
  ` return NomosModelRestoration.restored(`,
1749
3104
  ` summary: 'Nomos re-proved current history and restored the previous model.', review: review);`,
1750
3105
  ` }`,
1751
- ` if (stage == 'acceptance') {`,
3106
+ ` if (stage == ModelLifecycleStage.MODEL_LIFECYCLE_STAGE_ACCEPTANCE_REQUIRED) {`,
1752
3107
  ` return NomosModelRestoration.needsDecision(`,
1753
3108
  ` summary: 'Restoring the previous model changes which rules govern future work. Review the effects and record why restoration is intended.',`,
1754
3109
  ` review: review, accept: (reason) => _restorePreviousGeneratedModel(reason));`,
1755
3110
  ` }`,
1756
- ` final evolve = (report?['evolve'] as Map?)?.cast<String, Object?>();`,
1757
- ` final error = raw['error']?.toString();`,
1758
- ` final summary = stage == 'identity'`,
3111
+ ` final error = result.hasMessage() ? result.message : '';`,
3112
+ ` final summary = stage == ModelLifecycleStage.MODEL_LIFECYCLE_STAGE_IDENTITY_REQUIRED`,
1759
3113
  ` ? 'Sign in before restoring a model so the decision is accountable.'`,
1760
- ` : error?.contains('restore-no-previous') == true`,
3114
+ ` : error.contains('restore-no-previous')`,
1761
3115
  ` ? 'This is the first model for this business; there is no previous model to restore.'`,
1762
- ` : error?.contains('restore-current-changed') == true`,
3116
+ ` : error.contains('restore-current-changed')`,
1763
3117
  ` ? 'The current model changed before restoration could finish. Nothing changed; ask Nomos to review again.'`,
1764
- ` : report != null && report['sourceVerified'] != true`,
1765
- ` ? 'The current history could not be verified. Nothing changed and the current model remains active.'`,
1766
- ` : report != null && report['complete'] != true`,
1767
- ` ? 'Nomos could not re-prove every historical dependency. Nothing changed and the current model remains active.'`,
1768
- ` : report != null && evolve?['ok'] != true`,
1769
- ` ? 'The previous model cannot safely interpret every existing value. Nothing changed and the current model remains active.'`,
1770
- ` : 'The workspace refused restoration. Nothing changed and the current model remains active.';`,
3118
+ ` : error.isNotEmpty`,
3119
+ ` ? error`,
3120
+ ` : 'The workspace refused restoration. Nothing changed and the current model remains active.';`,
1771
3121
  ` return NomosModelRestoration.blocked(summary: summary, review: review);`,
1772
3122
  ` }`,
1773
3123
  ``
@@ -2153,13 +3503,48 @@ export function generateDartBridgeClient(
2153
3503
  ` refs: eventRefs(event))).toList();`,
2154
3504
  ` final aggregateTypes = ((model['aggregateTypes'] as List?) ?? const [])`,
2155
3505
  ` .map((raw) => (raw as Map).cast<String, dynamic>()).toList();`,
2156
- ` for (final conflict in await bridge.pendingConflicts()) {`,
3506
+ ` String? aggregateTypeForConflictPath(String aggregatePath) {`,
3507
+ ` switch (aggregatePath) {`,
3508
+ ...[...resultDecoderByAggregate.values()].map(
3509
+ (decoder) =>
3510
+ ` case ${dartStr(decoder.aggregatePath)}: return ${dartStr(
3511
+ decoder.runtimeType
3512
+ )};`
3513
+ ),
3514
+ ` }`,
3515
+ ` return null;`,
3516
+ ` }`,
3517
+ ` String? fieldNameForConflictPath(String aggregatePath, String fieldPath) {`,
3518
+ ` switch (aggregatePath) {`,
3519
+ ...[...resultDecoderByAggregate.values()].flatMap((decoder) => [
3520
+ ` case ${dartStr(decoder.aggregatePath)}:`,
3521
+ ` switch (fieldPath) {`,
3522
+ ...decoder.baseFields.map(
3523
+ (field) =>
3524
+ ` case ${dartStr(field.path)}: return ${dartStr(
3525
+ field.name
3526
+ )};`
3527
+ ),
3528
+ ` }`,
3529
+ ` return null;`,
3530
+ ]),
3531
+ ` }`,
3532
+ ` return null;`,
3533
+ ` }`,
3534
+ ` final pendingConflicts = NmsUsdaConflictResult.parse(`,
3535
+ ` await bridge.nmsPendingConflictsUsda(null));`,
3536
+ ` for (final conflict in pendingConflicts.rows) {`,
3537
+ ` final aggregatePath = conflict.relationship('nomos:type');`,
3538
+ ` final fieldPath = conflict.relationship('nomos:field');`,
3539
+ ` if (aggregatePath == null || fieldPath == null) continue;`,
3540
+ ` final conflictAggregateType = aggregateTypeForConflictPath(aggregatePath);`,
3541
+ ` final field = fieldNameForConflictPath(aggregatePath, fieldPath);`,
3542
+ ` if (conflictAggregateType == null || field == null) continue;`,
2157
3543
  ` Map<String, dynamic>? aggregate;`,
2158
3544
  ` for (final candidate in aggregateTypes) {`,
2159
- ` if (candidate['queryType'] == conflict['aggregateType']) { aggregate = candidate; break; }`,
3545
+ ` if (candidate['queryType'] == conflictAggregateType) { aggregate = candidate; break; }`,
2160
3546
  ` }`,
2161
3547
  ` if (aggregate == null) continue;`,
2162
- ` final field = (conflict['field'] ?? '').toString();`,
2163
3548
  ` final pages = (model['pages'] as Map).cast<String, dynamic>();`,
2164
3549
  ` final ruleItems = (((pages['rules'] as Map)['items'] as List?) ?? const [])`,
2165
3550
  ` .map((raw) => (raw as Map).cast<String, dynamic>());`,
@@ -2167,32 +3552,32 @@ export function generateDartBridgeClient(
2167
3552
  ` final ruleId = '\${aggregate['domain']}.\${aggregate['type']}.$field.merge';`,
2168
3553
  ` for (final candidate in ruleItems) { if (candidate['id'] == ruleId) { rule = candidate; break; } }`,
2169
3554
  ` final subject = rule?['label']?.toString() ?? '\${aggregate['label']} $field';`,
2170
- ` final status = conflict['status']?.toString() ?? 'ready';`,
3555
+ ` final status = conflict.requiredStringProperty('nomos:status');`,
2171
3556
  ` final versions = status == 'ready'`,
2172
- ` ? ((conflict['observations'] as List?)?.length ?? 0)`,
2173
- ` : ((((conflict['evolution'] as Map?)?['sourceSchemas']) as List?)?.length ?? 0);`,
3557
+ ` ? conflict.requiredChild('Observations').children.length`,
3558
+ ` : conflict.requiredChild('Evolution').requiredStringArrayProperty('nomos:sourceSchemas').length;`,
2174
3559
  ` final conflictSummary = status == 'ready'`,
2175
3560
  ` ? '$subject has $versions competing versions waiting for one business merge.'`,
2176
3561
  ` : '$subject cannot yet be compared because the model does not explain every earlier value. The disagreement remains pending while unrelated work continues.';`,
2177
3562
  ` items.add(NomosDescribeItem(`,
2178
3563
  ` kind: status == 'ready' ? 'pending-merge' : 'evolution-required',`,
2179
- ` id: 'decision:\${aggregate['domain']}.\${aggregate['type']}:\${conflict['aggregateId']}:$field',`,
3564
+ ` id: 'decision:\${aggregate['domain']}.\${aggregate['type']}:\${conflict.requiredStringProperty('nomos:aggregateId')}:$field',`,
2180
3565
  ` label: 'Review $subject', summary: conflictSummary,`,
2181
3566
  ` details: {'domain': aggregate['domain'], 'type': aggregate['type'], 'field': field, 'status': status, 'versions': versions, 'next': status == 'ready' ? 'merge' : 'update-model'},`,
2182
- ` refs: [NmsInternalNomosRefs.create<Object?>(workspace: _ws, type: aggregate['type'].toString(), id: conflict['aggregateId'].toString())]));`,
3567
+ ` refs: [NmsInternalNomosRefs.create<Object?>(workspace: _ws, type: aggregate['type'].toString(), id: conflict.requiredStringProperty('nomos:aggregateId'))]));`,
2183
3568
  ` }`,
2184
3569
  ` final modelPageItems = ((((model['pages'] as Map)['model'] as Map)['items'] as List?) ?? const [])`,
2185
3570
  ` .map((raw) => (raw as Map).cast<String, dynamic>()).where((item) => item['kind'] == 'verb').toList();`,
2186
3571
  ` for (final entry in await bridge.deadLetters()) {`,
2187
- ` final domain = entry['domain']; final directive = entry['directiveId'];`,
2188
- ` if (domain is! String || directive is! String) continue;`,
3572
+ ` if (!entry.hasDomain() || !entry.hasDirective()) continue;`,
3573
+ ` final domain = entry.domain; final directive = entry.directive;`,
2189
3574
  ` Map<String, dynamic>? action;`,
2190
3575
  ` for (final candidate in modelPageItems) { if (candidate['id'] == '$domain.$directive') { action = candidate; break; } }`,
2191
3576
  ` if (action == null) continue;`,
2192
- ` final reason = refusalReason(entry['error']); final actionLabel = action['label'].toString();`,
2193
- ` items.add(NomosDescribeItem(kind: 'rejected-action', id: 'rejected:\${entry['id'] ?? entry['oid']}',`,
3577
+ ` final reason = refusalReason(entry.message); final actionLabel = action['label'].toString();`,
3578
+ ` items.add(NomosDescribeItem(kind: 'rejected-action', id: 'rejected:\${entry.hasId() ? entry.id : entry.commitOid}',`,
2194
3579
  ` label: 'Review $actionLabel', summary: refusalSummary(reason, actionLabel),`,
2195
- ` details: {'domain': domain, 'action': directive, 'status': 'retained', 'reason': reason, 'refusedAt': entry['at'], 'canRetry': true}));`,
3580
+ ` details: {'domain': domain, 'action': directive, 'status': 'retained', 'reason': reason, 'refusedAt': entry.recordedAt, 'canRetry': true}));`,
2196
3581
  ` }`,
2197
3582
  ` final pages = (model['pages'] as Map).cast<String, dynamic>();`,
2198
3583
  ` final rules = ((pages['rules'] as Map)['items'] as List?) ?? const [];`,
@@ -2237,15 +3622,21 @@ export function generateDartBridgeClient(
2237
3622
  // hand-written code, so this file never adds noise to a tenant's analyzer output (co2 feedback).
2238
3623
  // (deprecated_member_use*: the compatibility shims intentionally delegate to the deprecated
2239
3624
  // engine-level lanes for one minor version.)
2240
- `// ignore_for_file: avoid_dynamic_calls, unnecessary_this, prefer_const_constructors, unused_import, type_annotate_public_apis, always_specify_types, deprecated_member_use, deprecated_member_use_from_same_package, invalid_use_of_internal_member`,
3625
+ `// ignore_for_file: avoid_dynamic_calls, unnecessary_this, unnecessary_cast, prefer_const_constructors, unused_import, unused_local_variable, type_annotate_public_apis, always_specify_types, deprecated_member_use, deprecated_member_use_from_same_package, invalid_use_of_internal_member`,
2241
3626
  ``,
2242
3627
  // `dart:convert` is needed ONLY for the interface block's `jsonDecode` — the offer lane no longer encodes
2243
3628
  // any envelope (it routes through `bridge.offerDirective` → THE ONE ENCODER). Omit it when no interface.
2244
3629
  ...(clientInterface !== undefined ||
2245
- roleHelpers !== "" ||
2246
- opts.emitActivity === true
3630
+ opts.emitActivity === true ||
3631
+ localOfferDirs.length > 0 ||
3632
+ callEncoderSources.length > 0
2247
3633
  ? [`import 'dart:convert';`, ``]
2248
3634
  : []),
3635
+ ...(localOfferDirs.length > 0 ||
3636
+ resultDecoderSources.length > 0 ||
3637
+ callEncoderSources.length > 0
3638
+ ? [`import 'dart:typed_data';`, ``]
3639
+ : []),
2249
3640
  `import '${supportImport}';`,
2250
3641
  ...(opts.lawAssetKey !== undefined
2251
3642
  ? [`import 'package:flutter/services.dart' show rootBundle;`]
@@ -2327,12 +3718,16 @@ export function generateDartBridgeClient(
2327
3718
  ``,
2328
3719
  ]
2329
3720
  : []),
3721
+ ...(callEncoderSources.length > 0
3722
+ ? [...DART_CALL_USDA_PRELUDE, ...callEncoderSources]
3723
+ : []),
3724
+ ...resultDecoderSources,
2330
3725
  ...pastDeclarations,
2331
3726
  ...conflictDeclarations,
2332
3727
  `/// Typed async client for the \`${domain}\` domain, bound to a connected [NomosBridge].`,
2333
3728
  `///`,
2334
3729
  `/// Pass \`actor\` (the verified principal uid) to author AS that user — the bridge's \`offerDirective\` op`,
2335
- `/// carries it through THE ONE ENCODER (\`encodeIntentOfferBytes\` in @githolon/client) as \`security.actor\`,`,
3730
+ `/// carries it with the compiler-generated canonical USDA call as the captured principal,`,
2336
3731
  `/// so the kernel seals it as the captured principal and the ReBAC \`.requires(...)\` gate judges the real`,
2337
3732
  `/// author. A GATED directive (one whose law declares \`.requires(relation)\`) authored with no actor throws`,
2338
3733
  `/// [MissingOfferAuthority] BEFORE any submit — an actorless gated offer can't be constructed. The bridge`,
@@ -2362,7 +3757,14 @@ export function generateDartBridgeClient(
2362
3757
  ` /// structural interface (#72). Consumed by [bind] and by \`session.assertCompatible(${className}.interface)\`.`,
2363
3758
  ` /// Standard OSGi Contract Namespace requirements derived from this law's shape.`,
2364
3759
  ` static const List<Map<String, String>> kernelContracts = [${kernelContracts
2365
- .map((contract) => `{${dartStr("namespace")}: ${dartStr(contract.namespace)}, ${dartStr("name")}: ${dartStr(contract.name)}, ${dartStr("version")}: ${dartStr(contract.version)}}`)
3760
+ .map(
3761
+ (contract) =>
3762
+ `{${dartStr("namespace")}: ${dartStr(contract.namespace)}, ${dartStr(
3763
+ "name"
3764
+ )}: ${dartStr(contract.name)}, ${dartStr("version")}: ${dartStr(
3765
+ contract.version
3766
+ )}}`
3767
+ )
2366
3768
  .join(", ")}];`,
2367
3769
  ``,
2368
3770
  ` static const NomosClientInterface interface = NomosClientInterface(`,
@@ -2494,7 +3896,9 @@ export function generateDartBridgeClient(
2494
3896
  ` if (bridge is! NomosSession) {`,
2495
3897
  ` throw StateError('Generated child model inheritance needs a bound parent session.');`,
2496
3898
  ` }`,
2497
- ` final archive = await rootBundle.load(${dartStr(opts.lawAssetKey)});`,
3899
+ ` final archive = await rootBundle.load(${dartStr(
3900
+ opts.lawAssetKey
3901
+ )});`,
2498
3902
  ` await bridge.nomosInternalEnsureGeneratedModelInChild(`,
2499
3903
  ` childWorkspace: ws,`,
2500
3904
  ` domain: interface.domain,`,
@@ -2555,7 +3959,17 @@ export function generateDartBridgeClient(
2555
3959
  ...(ownedScope === undefined || Object.keys(ownedActions).length === 0
2556
3960
  ? []
2557
3961
  : [
2558
- ` static const Map<String, Map<String, String>> _ownedRecordActions = ${ownedActionsDart};`,
3962
+ ` String? _ownedDirectiveId(String type, String action) => switch ((type, action)) {`,
3963
+ ...Object.entries(ownedActions).flatMap(([type, actions]) =>
3964
+ Object.entries(actions).map(
3965
+ ([action, directiveId]) =>
3966
+ ` (${dartStr(type)}, ${dartStr(action)}) => ${dartStr(
3967
+ directiveId
3968
+ )},`
3969
+ )
3970
+ ),
3971
+ ` _ => null,`,
3972
+ ` };`,
2559
3973
  ` bool _ownedIdentityReady = false;`,
2560
3974
  ` Future<void> _ensureOwnedIdentity() async {`,
2561
3975
  ` if (_ownedIdentityReady) return;`,
@@ -2569,39 +3983,55 @@ export function generateDartBridgeClient(
2569
3983
  ` await _preflightOnce();`,
2570
3984
  ` await _ensureOwnedIdentity();`,
2571
3985
  ` if (NmsInternalNomosRefs.workspace(record) != _ws) throw ArgumentError('$action() cannot use a record from another workspace');`,
2572
- ` final directiveId = _ownedRecordActions[NmsInternalNomosRefs.type(record)]?[action];`,
3986
+ ` final directiveId = _ownedDirectiveId(NmsInternalNomosRefs.type(record), action);`,
2573
3987
  ` if (directiveId == null) throw ArgumentError('$action() needs a generated owned record reference');`,
2574
3988
  ` await _prepareCommand(directiveId);`,
2575
3989
  ` if (peer.trim().isEmpty) throw ArgumentError('$action() needs the person to act with');`,
2576
3990
  ` final payload = await bridge.nomosPrepareOwnedRecordAction(action: action, scope: ${dartStr(
2577
3991
  ownedScope
2578
3992
  )}, record: record, peer: peer);`,
2579
- ` return bridge.offerOutcome(domain: ${dartStr(
2580
- domain
2581
- )}, directiveId: directiveId, payload: payload, actor: actor, domainHash: domainHash);`,
3993
+ ` final callUsda = switch (directiveId) {`,
3994
+ ...[...ownedActionCallEncoders.entries()].map(
3995
+ ([directiveId, encoder]) =>
3996
+ ` ${dartStr(directiveId)} => ${
3997
+ encoder.functionName
3998
+ }(payload as ${encoder.payloadType}),`
3999
+ ),
4000
+ ` _ => throw StateError('No generated USDA encoder for owned directive $directiveId'),`,
4001
+ ` };`,
4002
+ ` return _offer(callUsda);`,
2582
4003
  ` }`,
2583
4004
  ``,
2584
4005
  ]),
2585
- ...(dirs.length > 0 ||
2586
- birthDirs.some(
2587
- (d) => (d as { businessBirth?: unknown }).businessBirth !== undefined
2588
- )
4006
+ ...(callEncoderSources.length > 0
2589
4007
  ? [
2590
4008
  ` /// THE ONE DISPATCH LANE every generated directive method routes through: lazy preflight, then`,
2591
4009
  ` /// the session's offer (the generated-internal use of the engine-level lane).`,
2592
- ` Future<NomosOfferOutcome> _offer(String directiveId, Object? payload, {List<Map<String, Object?>>? conflictSnapshots}) async {`,
4010
+ ` Future<NomosOfferOutcome> _offer(NmsGeneratedCall call, {List<NomosConflictSnapshot> conflictSnapshots = const [], NomosOfferAuthenticationProof authenticationProof = NomosOfferAuthenticationProof.sessionCredentials}) async {`,
2593
4011
  ` await _preflightOnce();`,
2594
- ` await _prepareCommand(directiveId);`,
2595
- ` return bridge.offerOutcome(domain: ${dartStr(
2596
- domain
2597
- )}, directiveId: directiveId, payload: payload, actor: actor, domainHash: domainHash, nomosConflictSnapshots: conflictSnapshots);`,
4012
+ ` await _prepareCommand(call.directive);`,
4013
+ ` Future<NomosOfferOutcome> attempt() => bridge.offerCallUsdaOutcome(call: call, actor: actor, domainHash: domainHash, conflictSnapshots: conflictSnapshots, authenticationProof: authenticationProof);`,
4014
+ ...(authenticationRecovery === undefined
4015
+ ? [` return attempt();`]
4016
+ : [
4017
+ ` if (authenticationProof != NomosOfferAuthenticationProof.sessionCredentials) return attempt();`,
4018
+ ` try {`,
4019
+ ` return await attempt();`,
4020
+ ` } on NomosKernelCallFailure {`,
4021
+ ` final posture = await bridge.nmsAuthenticationPosture(principal: actor);`,
4022
+ ` if (posture.providerToken != NomosProviderTokenVerification.noMatchingKey) rethrow;`,
4023
+ ` await ${safeMethod(authenticationRecovery.id)}(const ${dartActionInputClassName(
4024
+ authenticationRecovery
4025
+ )}());`,
4026
+ ` await bridge.nmsAwaitAuthenticationReady(principal: actor);`,
4027
+ ` return attempt();`,
4028
+ ` }`,
4029
+ ]),
2598
4030
  ` }`,
2599
4031
  ``,
2600
4032
  ]
2601
4033
  : []),
2602
- ...(encPresent
2603
- ? dartEncClassMembers(encMap!, manualEncryptionPresent)
2604
- : []),
4034
+ ...(encPresent ? dartEncClassMembers(manualEncryptionPresent) : []),
2605
4035
  ...(ownedScope === undefined
2606
4036
  ? []
2607
4037
  : [
@@ -2617,7 +4047,10 @@ export function generateDartBridgeClient(
2617
4047
  domain
2618
4048
  )}, domainHash: domainHash, actor: actor, scope: ${dartStr(
2619
4049
  ownedScope
2620
- )});`,
4050
+ )},`,
4051
+ ` approvePrepared: (payload) => _offer(`,
4052
+ ` ${ownedDeviceApprovalCallEncoder!}(payload),`,
4053
+ ` ));`,
2621
4054
  ``,
2622
4055
  ]),
2623
4056
  // THE OWNER CEREMONY (slice 2): grantAndWrap / revokeAndRotate / rotate — the operator-blind key-epoch
@@ -2696,80 +4129,17 @@ export function generateDartBridgeClient(
2696
4129
  }
2697
4130
 
2698
4131
  /** The in-class E2E-sealing members: the baked map + the seal/read private helpers. */
2699
- function dartEncClassMembers(
2700
- encMap: EncDomainMap,
2701
- manualEncryptionPresent: boolean
2702
- ): string[] {
4132
+ function dartEncClassMembers(manualEncryptionPresent: boolean): string[] {
2703
4133
  return [
2704
- ` /// The baked E2E-sealing map (compile-time, from the SAME sids the law was compiled with): which`,
2705
- ` /// directive payload fields seal → { scope, aggSid, fieldSid }, and which read-model fields decrypt.`,
2706
- ` static final Map<String, dynamic> encryptionMap = (jsonDecode(${dartRawJson(
2707
- encMap
2708
- )}) as Map).cast<String, dynamic>();`,
2709
- ``,
2710
- ` /// Seal the mapped encrypted fields of \`data\` in place before dispatch. Ordinary encryption may`,
2711
- ` /// use the bound keyStore; .owned() keys are held internally by the connected Nomos client.`,
2712
- ` Future<void> _sealPayload(Map<String, dynamic> data, String directiveId, bool seal, [NomosScopeKey? ownedKeyOverride, String? ownedRecordType, String? ownedRecordId, bool ownedRecordPreMinted = false]) async {`,
2713
- ` final fields = (encryptionMap['directives'] as Map)[directiveId] as Map?;`,
2714
- ` if (fields == null) return;`,
2715
- ` if (!seal) {`,
2716
- ` if (fields.values.any((raw) => (raw as Map)['owned'] == true)) {`,
2717
- ` throw StateError('.owned() encryption cannot be disabled');`,
2718
- ` }`,
2719
- ` return;`,
2720
- ` }`,
2721
- ` NomosScopeKey? ownedKey = ownedKeyOverride;`,
2722
- ` Map? owned;`,
2723
- ` for (final raw in fields.values) {`,
2724
- ` final candidate = raw as Map;`,
2725
- ` if (candidate['owned'] == true) { owned = candidate; break; }`,
2726
- ` }`,
2727
- ` if (owned != null) {`,
2728
- ` if (ownedKey == null && (ownedRecordType == null || ownedRecordType.isEmpty || ownedRecordId == null || ownedRecordId.isEmpty)) {`,
2729
- ` throw StateError('Nomos could not derive the owned record identity before sealing; recompile the client and law together');`,
2730
- ` }`,
2731
- ` ownedKey ??= await bridge.nomosOwnedScopeKey(owned['scope'] as String, recordType: ownedRecordType, recordId: ownedRecordId);`,
2732
- ` final subject = ownedKey.ownedKeySubject;`,
2733
- ` if (subject == null || subject.isEmpty) {`,
2734
- ` throw StateError('Nomos did not return the framework-owned device identity for .owned() custody');`,
2735
- ` }`,
2736
- ` data[${JSON.stringify(OWNED_KEY_PAYLOAD_FIELD)}] = subject;`,
2737
- ` if (ownedKey.ownedCustody != null && ownedKey.ownedCustody!.isNotEmpty) {`,
2738
- ` data[${JSON.stringify(
2739
- OWNED_CUSTODY_PAYLOAD_FIELD
2740
- )}] = ownedKey.ownedCustody;`,
2741
- ` }`,
2742
- ` if (ownedRecordPreMinted) {`,
2743
- ` data[${JSON.stringify(
2744
- OWNED_PREMINTED_RECORDS_PAYLOAD_FIELD
2745
- )}] = [{'type': ownedRecordType, 'id': ownedRecordId}];`,
2746
- ` }`,
2747
- ` }`,
2748
- ` for (final entry in fields.entries) {`,
2749
- ` final field = entry.key as String;`,
2750
- ` final m = entry.value as Map;`,
2751
- ` if (data[field] == null) continue;`,
2752
- ` final scope = m['scope'] as String;`,
2753
- ` NomosScopeKey? sk = m['owned'] == true ? ownedKey : ${
2754
- manualEncryptionPresent
2755
- ? "keyStore == null ? null : await keyStore!.scopeKey(scope)"
2756
- : "null"
2757
- };`,
2758
- ` if (sk == null) throw EncryptionKeyUnavailable(scope, field);`,
2759
- ` data[field] = await bridge.cryptoSealField(`,
2760
- ` scopeKey: sk.key, scope: m['owned'] == true ? sk.scope : scope, epoch: sk.epoch,`,
2761
- ` aggSid: m['aggSid'] as String, fieldSid: m['fieldSid'] as String,`,
2762
- ` plaintext: data[field].toString(),`,
2763
- ` );`,
2764
- ` }`,
2765
- ` }`,
2766
- ``,
2767
4134
  ` /// Read keys for ordinary app-custodied scopes plus framework-custodied .owned() scopes.`,
2768
- ` Future<List<NomosScopeKey>?> _readKeys(List<Map<String, Object?>> scopes, [List<Map<String, String>> records = const []]) async {`,
4135
+ ` Future<List<NomosScopeKey>?> _readKeys(`,
4136
+ ` List<({String scope, bool owned})> scopes, [`,
4137
+ ` List<({String type, String id})> records = const [],`,
4138
+ ` ]) async {`,
2769
4139
  ` if (scopes.isEmpty) return null;`,
2770
4140
  ...(manualEncryptionPresent
2771
4141
  ? [
2772
- ` final ordinary = scopes.where((s) => s['owned'] != true).map((s) => s['scope']! as String).toSet().toList();`,
4142
+ ` final ordinary = scopes.where((scope) => !scope.owned).map((scope) => scope.scope).toSet().toList();`,
2773
4143
  ]
2774
4144
  : []),
2775
4145
  ` final keys = ${
@@ -2778,11 +4148,11 @@ function dartEncClassMembers(
2778
4148
  : "<NomosScopeKey>[]"
2779
4149
  };`,
2780
4150
  ` for (final descriptor in scopes) {`,
2781
- ` if (descriptor['owned'] != true) continue;`,
2782
- ` final scope = descriptor['scope']! as String;`,
4151
+ ` if (!descriptor.owned) continue;`,
4152
+ ` final scope = descriptor.scope;`,
2783
4153
  ` for (final record in records) {`,
2784
- ` final type = record['type']!;`,
2785
- ` final id = record['id']!;`,
4154
+ ` final type = record.type;`,
4155
+ ` final id = record.id;`,
2786
4156
  ` final held = await bridge.nomosOwnedScopeKeys(scope, recordType: type, recordId: id);`,
2787
4157
  ` for (final key in held) {`,
2788
4158
  ` if (!keys.any((candidate) => candidate.scope == key.scope && candidate.epoch == key.epoch)) keys.add(key);`,
@@ -2794,16 +4164,31 @@ function dartEncClassMembers(
2794
4164
  ``,
2795
4165
  ` /// Multi-row owned reads are two local indexed reads: discover only authorized ciphertext rows,`,
2796
4166
  ` /// derive those record keys, then repeat with decryption. No key or crypto concept reaches the app.`,
2797
- ` Future<List<Map<String, dynamic>>> _readWithOwnedKeys(`,
2798
- ` List<Map<String, Object?>> scopes,`,
2799
- ` Future<List<Map<String, dynamic>>> Function(List<NomosScopeKey>? keys) read,`,
4167
+ ` Future<List<TModel>> _readWithOwnedKeys<TModel, TAggregate>(`,
4168
+ ` List<({String scope, bool owned})> scopes,`,
4169
+ ` Future<List<TModel>> Function(List<NomosScopeKey>? keys) read,`,
4170
+ ` NomosRef<TAggregate> Function(TModel row) reference,`,
4171
+ ` ) async {`,
4172
+ ` final first = await read(await _readKeys(scopes));`,
4173
+ ` if (!scopes.any((scope) => scope.owned) || first.isEmpty) return first;`,
4174
+ ` final records = first.map(reference).map((record) => (`,
4175
+ ` type: NmsInternalNomosRefs.type(record),`,
4176
+ ` id: NmsInternalNomosRefs.id(record),`,
4177
+ ` )).toList(growable: false);`,
4178
+ ` return read(await _readKeys(scopes, records));`,
4179
+ ` }`,
4180
+ ``,
4181
+ ` Future<NomosPage<TModel>> _readPageWithOwnedKeys<TModel, TAggregate>(`,
4182
+ ` List<({String scope, bool owned})> scopes,`,
4183
+ ` Future<NomosPage<TModel>> Function(List<NomosScopeKey>? keys) read,`,
4184
+ ` NomosRef<TAggregate> Function(TModel row) reference,`,
2800
4185
  ` ) async {`,
2801
4186
  ` final first = await read(await _readKeys(scopes));`,
2802
- ` if (!scopes.any((scope) => scope['owned'] == true) || first.isEmpty) return first;`,
2803
- ` final records = first.map((row) => {`,
2804
- ` 'type': (row['type'] ?? row['__type'] ?? '').toString(),`,
2805
- ` 'id': (row['id'] ?? row['__id'] ?? '').toString(),`,
2806
- ` }).where((record) => record['type']!.isNotEmpty && record['id']!.isNotEmpty).toList();`,
4187
+ ` if (!scopes.any((scope) => scope.owned) || first.rows.isEmpty) return first;`,
4188
+ ` final records = first.rows.map(reference).map((record) => (`,
4189
+ ` type: NmsInternalNomosRefs.type(record),`,
4190
+ ` id: NmsInternalNomosRefs.id(record),`,
4191
+ ` )).toList(growable: false);`,
2807
4192
  ` return read(await _readKeys(scopes, records));`,
2808
4193
  ` }`,
2809
4194
  ``,