@githolon/dsl 0.108.0 → 0.108.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dart/CHANGELOG.md +5 -0
- package/dart/lib/src/application_framework_calls.dart +299 -0
- package/dart/lib/src/generated_call.dart +96 -0
- package/dart/lib/src/nms_generated_runtime.dart +29 -0
- package/dart/lib/src/node_runner_transport.dart +77 -1
- package/dart/lib/src/result_usda.dart +487 -0
- package/dart/lib/src/runner/node-entry.mjs +6401 -2102
- package/dart/lib/src/runner/offline-install.mjs +1538 -163
- package/dart/lib/src/runtime_v2_gateway.dart +54 -0
- package/dart/lib/src/runtime_v2_transport.dart +117 -0
- package/dart/lib/src/session_bound.dart +7 -2
- package/dart/lib/src/test_harness.dart +193 -112
- package/dart/lib/src/webview_bridge.dart +2120 -569
- package/dart/pubspec.yaml +2 -2
- package/dart/test/acquire_session_credentials_test.dart +1 -1
- package/dart/test/acquire_session_test.dart +68 -14
- package/dart/test/adv_realm_interference_test.dart +6 -4
- package/dart/test/cluster_births_test.dart +11 -8
- package/dart/test/cluster_local_autosync_test.dart +1 -1
- package/dart/test/cluster_scale_test.dart +3 -2
- package/dart/test/dlq_session_surface_test.dart +52 -35
- package/dart/test/first_app_gauntlet_test.dart +1 -1
- package/dart/test/local_sync_inert_test.dart +23 -26
- package/dart/test/offer_authentication_proof_test.dart +193 -0
- package/dart/test/result_usda_test.dart +49 -0
- package/dart/test/session_bound_test.dart +8 -11
- package/dart/test/stale_bundle_test.dart +18 -3
- package/dart/test/test_harness_test.dart +14 -27
- package/dart/test/webview_bridge_test.dart +504 -83
- package/dart_types/CHANGELOG.md +7 -0
- package/dart_types/lib/nomos_kernel_protocol.dart +3 -2
- package/dart_types/lib/src/custody.dart +64 -19
- package/dart_types/lib/src/local_work_rejected.dart +30 -45
- package/dart_types/lib/src/proto/nomos/custody/v1/snapshot.pb.dart +169 -0
- package/dart_types/lib/src/proto/nomos/custody/v1/snapshot.pbenum.dart +11 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +2021 -234
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +175 -8
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +8664 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pbenum.dart +369 -0
- package/dart_types/lib/src/surfaced.dart +30 -25
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/surfaced_test.dart +2 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +5736 -2047
- package/src/authoring.ts +3 -2
- package/src/births.ts +21 -0
- package/src/build_package.ts +30 -0
- package/src/business_birth.ts +39 -35
- package/src/codegen_call_usda.ts +1111 -0
- package/src/codegen_dart.ts +207 -282
- package/src/codegen_dart_application.ts +195 -252
- package/src/codegen_dart_client.ts +1978 -593
- package/src/codegen_dart_framework_calls.ts +291 -0
- package/src/codegen_dart_scenario.ts +7 -0
- package/src/codegen_proof.ts +3 -4
- package/src/codegen_provider_dart.ts +21 -7
- package/src/codegen_query_usda.ts +214 -0
- package/src/codegen_result_usda.ts +685 -0
- package/src/codegen_ts.ts +3945 -1111
- package/src/compile_package_main.ts +360 -32
- package/src/directive.ts +19 -4
- package/src/engine_entry.ts +45 -4
- package/src/framework/application_home.ts +2 -0
- package/src/framework/auth_provider_law.ts +262 -20
- package/src/framework/home.ts +32 -28
- package/src/framework/owned_custody.ts +19 -11
- package/src/framework/owned_custody_value.ts +51 -0
- package/src/framework/relations.ts +47 -21
- package/src/framework/signing_identity.ts +34 -0
- package/src/framework/workspaces.ts +31 -9
- package/src/framework_call_definitions.ts +53 -0
- package/src/framework_read_definitions.ts +45 -0
- package/src/identity_frontier.ts +58 -0
- package/src/index.ts +20 -1
- package/src/intent.ts +216 -16
- package/src/law_check.ts +19 -1
- package/src/law_graph.ts +57 -8
- package/src/law_graph_usda.ts +73 -5
- package/src/law_value_type.ts +106 -19
- package/src/read.ts +47 -0
- package/src/read_projection_namespace.ts +7 -0
- package/src/shape_compat.ts +38 -39
- package/src/stable_ids.ts +37 -2
- package/src/test_call_usda.ts +83 -0
- package/src/wire.ts +36 -2
- package/src/wire_encode.ts +45 -1
- package/src/workspace_sharing.ts +27 -10
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbjson.dart +0 -6096
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbserver.dart +0 -50
- package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pbjson.dart +0 -178
- package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pb.dart +0 -828
- package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbenum.dart +0 -465
- package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbjson.dart +0 -573
- 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
|
-
*
|
|
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 {
|
|
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 {
|
|
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
|
|
375
|
-
//
|
|
376
|
-
//
|
|
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
|
-
`
|
|
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
|
|
535
|
-
//
|
|
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 = (
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
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 =
|
|
717
|
-
|
|
718
|
-
|
|
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
|
-
...
|
|
749
|
-
|
|
750
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
`
|
|
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 =
|
|
843
|
-
|
|
844
|
-
|
|
1984
|
+
const birthRuntimeType =
|
|
1985
|
+
birthAggregate === undefined
|
|
1986
|
+
? undefined
|
|
1987
|
+
: isNamespacedAggregate(birthAggregate)
|
|
845
1988
|
? qualifyReadProjectionType(domain, birthAggregate.id)
|
|
846
1989
|
: birthAggregate.id;
|
|
847
|
-
if (
|
|
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
|
|
888
|
-
...
|
|
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
|
|
902
|
-
|
|
903
|
-
|
|
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
|
-
`
|
|
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 (
|
|
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
|
-
` ///
|
|
945
|
-
` ///
|
|
946
|
-
`
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
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
|
-
|
|
1009
|
-
|
|
1010
|
-
` return
|
|
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(
|
|
2221
|
+
.map(
|
|
2222
|
+
(k) =>
|
|
2223
|
+
`required ${dartKeyType(agg, k, enumDartTypes)} ${dartKeyParam(k)}`
|
|
2224
|
+
)
|
|
1026
2225
|
.join(", ");
|
|
1027
|
-
const
|
|
1028
|
-
.
|
|
1029
|
-
.
|
|
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
|
-
? `
|
|
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.
|
|
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
|
-
? `
|
|
1056
|
-
q.id
|
|
1057
|
-
)}, ${argMap}, keys${
|
|
2280
|
+
? ` _readWithOwnedKeys(${qScopesExpr}, (keys) async => _nmsDecode${model}Rows(await bridge.nmsQueryUsda(${queryCall}, keys${
|
|
1058
2281
|
qPrivate ? ", actor" : ""
|
|
1059
|
-
}))
|
|
1060
|
-
: ` (await bridge.
|
|
2282
|
+
}), _ws), (row) => row.ref);`
|
|
2283
|
+
: ` _nmsDecode${model}Rows(await bridge.nmsQueryUsda(${queryCall}${keysArg(
|
|
1061
2284
|
q.returns
|
|
1062
|
-
)})
|
|
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
|
|
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
|
-
|
|
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
|
-
? `
|
|
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
|
-
})
|
|
1106
|
-
: ` (await _client.bridge.
|
|
1107
|
-
q.
|
|
1108
|
-
)},
|
|
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.
|
|
2326
|
+
` _nmsDecode${model}Rows(await bridge.nmsSpatialWithinUsda(${dartStr(
|
|
1130
2327
|
sp.id
|
|
1131
|
-
)}, bbox)
|
|
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.
|
|
2334
|
+
` bridge.nmsWatchSpatialUsda(${dartStr(
|
|
1138
2335
|
sp.id
|
|
1139
|
-
)}, bbox).map((
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
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, ${
|
|
1278
|
-
|
|
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
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
)
|
|
1296
|
-
|
|
1297
|
-
|
|
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
|
-
`///
|
|
1306
|
-
`///
|
|
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
|
|
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
|
|
1314
|
-
` final Map<String, dynamic>? evolution;`,
|
|
2615
|
+
` final ${evolutionDetailsClass}? evolution;`,
|
|
1315
2616
|
` final String snapshot;`,
|
|
1316
|
-
`
|
|
1317
|
-
`
|
|
1318
|
-
`
|
|
1319
|
-
`
|
|
1320
|
-
`
|
|
1321
|
-
`
|
|
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
|
-
`
|
|
1325
|
-
`
|
|
1326
|
-
`
|
|
1327
|
-
`
|
|
1328
|
-
`
|
|
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
|
|
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
|
|
1339
|
-
` const ${summaryClass}({required this.total, required this.ready, required this.evolutionRequired,
|
|
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 =
|
|
1349
|
-
`
|
|
1350
|
-
`
|
|
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:
|
|
1353
|
-
`
|
|
1354
|
-
|
|
1355
|
-
|
|
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
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
:
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
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
|
-
`
|
|
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 =>
|
|
1410
|
-
|
|
1411
|
-
`
|
|
1412
|
-
`
|
|
1413
|
-
`
|
|
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<
|
|
1416
|
-
` ${
|
|
1417
|
-
|
|
1418
|
-
|
|
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 =>
|
|
1430
|
-
`
|
|
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.
|
|
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.
|
|
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(
|
|
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}, [
|
|
2913
|
+
? `, await _readKeys(${aScopesExpr}, [(type: ${dartStr(
|
|
1504
2914
|
runtimeType
|
|
1505
|
-
)},
|
|
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}, [
|
|
1514
|
-
|
|
1515
|
-
)}
|
|
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.
|
|
1527
|
-
|
|
1528
|
-
|
|
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
|
-
|
|
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
|
-
? `
|
|
2975
|
+
? ` _readWithOwnedKeys(${aScopesExpr}, (keys) async => _nmsDecode${model}Rows(await bridge.nmsQueryByTypeUsda(${dartStr(
|
|
1555
2976
|
runtimeType
|
|
1556
|
-
)}
|
|
1557
|
-
|
|
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
|
-
)}
|
|
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
|
-
|
|
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.
|
|
1596
|
-
|
|
1597
|
-
|
|
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
|
-
? `
|
|
2997
|
+
? ` _client._readWithOwnedKeys(${aScopesExpr}, (keys) async => _nmsDecode${model}Rows(await _client.bridge.nmsQueryByTypeAtUsda(moment, ${dartStr(
|
|
1605
2998
|
runtimeType
|
|
1606
|
-
)}
|
|
1607
|
-
|
|
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
|
-
)}
|
|
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(
|
|
1627
|
-
` final
|
|
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:
|
|
1673
|
-
`
|
|
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
|
|
3037
|
+
` final result = await bridge.nomosFrameworkAdoptGeneratedModel(`,
|
|
1678
3038
|
` domain: ${dartStr(
|
|
1679
3039
|
domain
|
|
1680
3040
|
)}, candidateHash: domainHash, because: because);`,
|
|
1681
|
-
` final
|
|
1682
|
-
` final
|
|
1683
|
-
`
|
|
1684
|
-
`
|
|
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:
|
|
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 ==
|
|
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 ==
|
|
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
|
|
1709
|
-
` final
|
|
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 ==
|
|
3073
|
+
` : stage == ModelLifecycleStage.MODEL_LIFECYCLE_STAGE_IDENTITY_REQUIRED`,
|
|
1713
3074
|
` ? 'Sign in before adopting this model so the decision is accountable.'`,
|
|
1714
|
-
` :
|
|
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(
|
|
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
|
|
3096
|
+
` final result = await bridge.nomosFrameworkRestorePreviousGeneratedModel(`,
|
|
1740
3097
|
` domain: ${dartStr(
|
|
1741
3098
|
domain
|
|
1742
3099
|
)}, currentHash: domainHash, because: because);`,
|
|
1743
|
-
` final
|
|
1744
|
-
` final
|
|
1745
|
-
`
|
|
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 ==
|
|
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
|
|
1757
|
-
` final
|
|
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
|
|
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
|
|
3116
|
+
` : error.contains('restore-current-changed')`,
|
|
1763
3117
|
` ? 'The current model changed before restoration could finish. Nothing changed; ask Nomos to review again.'`,
|
|
1764
|
-
` :
|
|
1765
|
-
` ?
|
|
1766
|
-
` :
|
|
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
|
-
`
|
|
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'] ==
|
|
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
|
|
3555
|
+
` final status = conflict.requiredStringProperty('nomos:status');`,
|
|
2171
3556
|
` final versions = status == 'ready'`,
|
|
2172
|
-
` ? (
|
|
2173
|
-
` : (
|
|
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
|
|
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
|
|
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
|
-
`
|
|
2188
|
-
`
|
|
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
|
|
2193
|
-
` items.add(NomosDescribeItem(kind: 'rejected-action', id: 'rejected:\${entry
|
|
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
|
|
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
|
-
|
|
2246
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
`
|
|
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 =
|
|
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
|
-
`
|
|
2580
|
-
|
|
2581
|
-
|
|
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
|
-
...(
|
|
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(
|
|
4010
|
+
` Future<NomosOfferOutcome> _offer(NmsGeneratedCall call, {List<NomosConflictSnapshot> conflictSnapshots = const [], NomosOfferAuthenticationProof authenticationProof = NomosOfferAuthenticationProof.sessionCredentials}) async {`,
|
|
2593
4011
|
` await _preflightOnce();`,
|
|
2594
|
-
` await _prepareCommand(
|
|
2595
|
-
`
|
|
2596
|
-
|
|
2597
|
-
|
|
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(
|
|
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((
|
|
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
|
|
2782
|
-
` final scope = descriptor
|
|
4151
|
+
` if (!descriptor.owned) continue;`,
|
|
4152
|
+
` final scope = descriptor.scope;`,
|
|
2783
4153
|
` for (final record in records) {`,
|
|
2784
|
-
` final type = record
|
|
2785
|
-
` final id = record
|
|
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<
|
|
2798
|
-
` List<
|
|
2799
|
-
` Future<List<
|
|
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
|
|
2803
|
-
` final records = first.map((
|
|
2804
|
-
`
|
|
2805
|
-
`
|
|
2806
|
-
`
|
|
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
|
``,
|