@githolon/dsl 0.102.4 → 0.103.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/compile_package.mjs +43 -2
- package/compile_proof.d.mts +47 -0
- package/compile_proof.mjs +343 -0
- package/dart/CHANGELOG.md +7 -0
- package/dart/lib/src/dev_cloud.dart +8 -8
- package/dart/lib/src/nms_generated_runtime.dart +2 -0
- package/dart/lib/src/runner/node-entry.mjs +5504 -1236
- package/dart/lib/src/runner/offline-install.mjs +4578 -528
- package/dart/lib/src/test_harness.dart +206 -152
- package/dart/lib/src/webview_bridge.dart +196 -89
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/acquire_session_credentials_test.dart +4 -3
- package/dart/test/adv_realm_interference_test.dart +7 -6
- package/dart/test/cloud_bound_topology_test.dart +3 -3
- package/dart/test/cluster_births_test.dart +4 -3
- package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
- package/dart/test/cluster_local_autosync_test.dart +3 -2
- package/dart/test/cluster_scale_test.dart +3 -2
- package/dart/test/dlq_session_surface_test.dart +7 -6
- package/dart/test/first_app_gauntlet_test.dart +14 -21
- package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
- package/dart/test/four_layer_nested_topology_test.dart +4 -4
- package/dart/test/harness_phase_timeout_test.dart +2 -2
- package/dart/test/local_sync_inert_test.dart +1 -1
- package/dart/test/parent_signer_recovery_test.dart +2 -2
- package/dart/test/sealing_client_test.dart +4 -3
- package/dart/test/session_bound_test.dart +8 -7
- package/dart/test/test_harness_test.dart +13 -12
- package/dart/test/vo_read_typing_test.dart +4 -3
- package/dart/test/watch_after_offer_stall_test.dart +4 -3
- package/dart/test/webview_bridge_test.dart +102 -5
- package/dart_types/CHANGELOG.md +5 -0
- package/dart_types/lib/nomos_types.dart +9 -0
- package/dart_types/lib/src/nomos_ref.dart +20 -0
- package/dart_types/lib/src/optional_ref.dart +85 -0
- package/dart_types/lib/src/sha256_digest.dart +41 -0
- package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/optional_ref_test.dart +44 -0
- package/dart_types/test/sha256_digest_test.dart +18 -0
- package/package.json +9 -5
- package/runtime/generated-application-host.js +5365 -1105
- package/src/aggregate.ts +18 -0
- package/src/application_composition.ts +6 -32
- package/src/attested_core.ts +6 -3
- package/src/birth_meter.ts +1 -0
- package/src/birth_meter_ensure.ts +28 -2
- package/src/births.ts +1 -1
- package/src/build_package.ts +162 -538
- package/src/business_birth.ts +19 -3
- package/src/canonical_json.ts +21 -0
- package/src/check_package_main.ts +76 -35
- package/src/clear_directive.ts +19 -8
- package/src/codegen_dart.ts +305 -22
- package/src/codegen_dart_application.ts +844 -292
- package/src/codegen_dart_client.ts +1084 -407
- package/src/codegen_dart_scenario.ts +12 -12
- package/src/codegen_proof.ts +169 -39
- package/src/codegen_provider_dart.ts +17 -1
- package/src/codegen_ts.ts +298 -106
- package/src/compat_manifest.ts +2 -2
- package/src/compat_package_main.ts +8 -12
- package/src/compile_package_main.ts +1866 -597
- package/src/compose.ts +1 -1
- package/src/directive.ts +208 -15
- package/src/engine_entry.ts +165 -111
- package/src/evolve_preview.ts +43 -81
- package/src/extremum.ts +1 -1
- package/src/fields.ts +183 -4
- package/src/flow_gate.ts +75 -2
- package/src/framework/application_home.ts +2 -0
- package/src/framework/auth_provider_law.ts +2 -2
- package/src/framework/birth_meter.ts +7 -19
- package/src/framework/bootstrap.ts +0 -2
- package/src/framework/business_corrections.ts +9 -5
- package/src/framework/domain_lifecycle.ts +15 -29
- package/src/framework/encryption_scope.ts +2 -1
- package/src/framework/framework_aggregates.ts +2 -0
- package/src/framework/genesis.ts +16 -14
- package/src/framework/home.ts +20 -14
- package/src/framework/owned_custody.ts +9 -3
- package/src/framework/platform_admin.ts +3 -3
- package/src/framework/relations.ts +21 -13
- package/src/framework/signing_identity.ts +45 -40
- package/src/framework/workspace_invariant.ts +245 -0
- package/src/framework/workspaces.ts +23 -29
- package/src/index.ts +36 -16
- package/src/intent.ts +178 -6
- package/src/law_check.ts +32 -11
- package/src/law_graph.ts +2902 -0
- package/src/law_graph_usda.ts +1799 -0
- package/src/law_identity.ts +435 -0
- package/src/law_value_type.ts +230 -0
- package/src/lsp_server.ts +61 -31
- package/src/ops.ts +7 -0
- package/src/package_config.ts +141 -0
- package/src/package_dependencies.ts +108 -0
- package/src/package_dependency_usage.ts +150 -0
- package/src/package_source_imports.ts +71 -0
- package/src/plan_source_slice.ts +335 -0
- package/src/point_guard.ts +192 -0
- package/src/precondition.ts +44 -9
- package/src/read_projection_namespace.ts +1 -1
- package/src/runtime_zod.ts +44 -0
- package/src/secondary_ensure.ts +67 -14
- package/src/shape_compat.ts +1 -1
- package/src/stable_id_identity_bridge.ts +149 -0
- package/src/stable_id_lockfile.ts +278 -0
- package/src/stable_ids.ts +7 -2
- package/src/stable_ids_for_module.ts +348 -0
- package/src/stable_ids_types.ts +1 -1
- package/src/sum.ts +1 -1
- package/src/usd_state.ts +1 -1
- package/src/usda_relationship_integrity.ts +66 -0
- package/src/usda_text.ts +18 -0
- package/src/usdz.ts +78 -0
- package/src/value_object.ts +95 -0
- package/src/wire.ts +19 -0
- package/src/wire_encode.ts +22 -0
- package/src/workspace_sharding.ts +52 -36
- package/src/workspace_sharing.ts +28 -4
- package/src/zod_introspection.ts +1 -1
- package/dart/test/cluster_attested_test.dart +0 -201
- package/src/compile_engine.ts +0 -595
- package/src/manifest.ts +0 -1476
- package/src/read_projection_composition.ts +0 -117
- package/src/read_projection_manifest.ts +0 -115
- package/src/relation_composition.ts +0 -286
- package/src/usd.ts +0 -1967
- package/src/usd_layers.ts +0 -1776
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
OWNED_PREMINTED_RECORDS_PAYLOAD_FIELD,
|
|
36
36
|
type AggregateHandle,
|
|
37
37
|
} from "./aggregate.js";
|
|
38
|
-
import type
|
|
38
|
+
import { isSha256DigestSchema, type Field } from "./fields.js";
|
|
39
39
|
import type { Directive } from "./directive.js";
|
|
40
40
|
import type { QueryDecl } from "./query.js";
|
|
41
41
|
import type { DomainModule } from "./codegen_dart.js";
|
|
@@ -52,42 +52,90 @@ import {
|
|
|
52
52
|
} from "./codegen_dart.js";
|
|
53
53
|
import { finishCount } from "./count.js";
|
|
54
54
|
import { finishSum } from "./sum.js";
|
|
55
|
-
import {
|
|
56
|
-
|
|
55
|
+
import {
|
|
56
|
+
autoStampFields,
|
|
57
|
+
domainEncryptionMap,
|
|
58
|
+
type EncDomainMap,
|
|
59
|
+
type InterfaceAggShape,
|
|
60
|
+
} from "./codegen_ts.js";
|
|
61
|
+
import {
|
|
62
|
+
isNamespacedAggregate,
|
|
63
|
+
qualifyReadProjectionType,
|
|
64
|
+
} from "./read_projection_namespace.js";
|
|
57
65
|
import { isBusinessIntent } from "./intent.js";
|
|
58
66
|
import { publicClientInterface, type DomainInterface } from "./shape_compat.js";
|
|
59
67
|
import { BUSINESS_CORRECTION_DIRECTIVE } from "./framework/business_corrections.js";
|
|
60
68
|
import { deriveDirectiveRecordTarget } from "./workspace_routing.js";
|
|
61
|
-
import {
|
|
69
|
+
import {
|
|
70
|
+
buildNomosDescribeModel,
|
|
71
|
+
type NomosDescribeModel,
|
|
72
|
+
} from "./describe_model.js";
|
|
62
73
|
import { assertModelClientMemberAvailable } from "./generated_client_names.js";
|
|
63
74
|
import { ownedRecordActionDirectiveId } from "./framework/owned_custody.js";
|
|
64
75
|
import { contractTypeName } from "./contract.js";
|
|
65
76
|
import { admitsNull } from "./null_meta.js";
|
|
66
77
|
|
|
67
|
-
const camel = (s: string) =>
|
|
68
|
-
|
|
78
|
+
const camel = (s: string) =>
|
|
79
|
+
s.replace(/[_\s-]+(\w)/g, (_m, c: string) => c.toUpperCase());
|
|
80
|
+
const lcFirst = (s: string) =>
|
|
81
|
+
s.length ? s[0]!.toLowerCase() + s.slice(1) : s;
|
|
69
82
|
|
|
70
83
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
71
84
|
type AnyDirective = Directive<any>;
|
|
72
85
|
|
|
73
86
|
/** Dart reserved words that can't be method names — suffixed `_` if one slips through. */
|
|
74
87
|
const DART_RESERVED = new Set([
|
|
75
|
-
"assert",
|
|
76
|
-
"
|
|
77
|
-
"
|
|
88
|
+
"assert",
|
|
89
|
+
"break",
|
|
90
|
+
"case",
|
|
91
|
+
"catch",
|
|
92
|
+
"class",
|
|
93
|
+
"const",
|
|
94
|
+
"continue",
|
|
95
|
+
"default",
|
|
96
|
+
"do",
|
|
97
|
+
"else",
|
|
98
|
+
"enum",
|
|
99
|
+
"extends",
|
|
100
|
+
"false",
|
|
101
|
+
"final",
|
|
102
|
+
"finally",
|
|
103
|
+
"for",
|
|
104
|
+
"if",
|
|
105
|
+
"in",
|
|
106
|
+
"is",
|
|
107
|
+
"new",
|
|
108
|
+
"null",
|
|
109
|
+
"rethrow",
|
|
110
|
+
"return",
|
|
111
|
+
"super",
|
|
112
|
+
"switch",
|
|
113
|
+
"this",
|
|
114
|
+
"throw",
|
|
115
|
+
"true",
|
|
116
|
+
"try",
|
|
117
|
+
"var",
|
|
118
|
+
"void",
|
|
119
|
+
"while",
|
|
120
|
+
"with",
|
|
121
|
+
"dynamic",
|
|
78
122
|
]);
|
|
79
123
|
const safeMethod = (s: string): string => (DART_RESERVED.has(s) ? `${s}_` : s);
|
|
80
124
|
|
|
81
125
|
/** `guestbook` / `tool-library` / `Tool Library` → `GuestbookClient` / `ToolLibraryClient`. */
|
|
82
|
-
export const dartClientClassName = (domain: string): string =>
|
|
126
|
+
export const dartClientClassName = (domain: string): string =>
|
|
127
|
+
`${cap(camel(domain))}Client`;
|
|
83
128
|
/** `guestbook` → `guestbookDomainHash` — the Dart top-level const (lowerCamel by lint). */
|
|
84
|
-
export const dartHashConstName = (fileBase: string): string =>
|
|
129
|
+
export const dartHashConstName = (fileBase: string): string =>
|
|
130
|
+
`${lcFirst(camel(fileBase))}DomainHash`;
|
|
85
131
|
|
|
86
132
|
/** `todo-nomos` → `todoNomosInterfaceJson` — the baked structural-interface JSON const (#72). */
|
|
87
|
-
export const interfaceConstNameDart = (fileBase: string): string =>
|
|
133
|
+
export const interfaceConstNameDart = (fileBase: string): string =>
|
|
134
|
+
`${lcFirst(camel(fileBase))}InterfaceJson`;
|
|
88
135
|
|
|
89
136
|
/** `todo-nomos` → `todoNomosCompatJson` — the baked client/law compat manifest JSON const (§5). */
|
|
90
|
-
export const compatConstNameDart = (fileBase: string): string =>
|
|
137
|
+
export const compatConstNameDart = (fileBase: string): string =>
|
|
138
|
+
`${lcFirst(camel(fileBase))}CompatJson`;
|
|
91
139
|
|
|
92
140
|
/** Emit a Dart RAW triple-quoted string literal of an object's JSON (the interface is JSON-only
|
|
93
141
|
* data — no `'`/`$`/backslash escaping concerns under `r'''…'''`; JSON never contains `'''`). */
|
|
@@ -95,7 +143,8 @@ function dartRawJson(value: unknown): string {
|
|
|
95
143
|
return `r'''${JSON.stringify(value)}'''`;
|
|
96
144
|
}
|
|
97
145
|
|
|
98
|
-
const dartStr = (s: string) =>
|
|
146
|
+
const dartStr = (s: string) =>
|
|
147
|
+
`'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
99
148
|
|
|
100
149
|
/** The Dart param type for a query index key. Aggregate relations keep their canonical typed ref. */
|
|
101
150
|
function dartKeyType(agg: AggregateHandle | undefined, key: string): string {
|
|
@@ -104,12 +153,17 @@ function dartKeyType(agg: AggregateHandle | undefined, key: string): string {
|
|
|
104
153
|
if (f && (f.kind === "int" || f.kind === "counter")) return "int";
|
|
105
154
|
if (f?.kind === "ref") {
|
|
106
155
|
if (f.foreignRef !== undefined) {
|
|
107
|
-
const target =
|
|
108
|
-
|
|
109
|
-
|
|
156
|
+
const target =
|
|
157
|
+
f.foreignRef.targetIsContract === true
|
|
158
|
+
? contractTypeName(f.foreignRef.targetAggregateId)
|
|
159
|
+
: concreteClassName(f.foreignRef.targetAggregateId);
|
|
110
160
|
return `ForeignRef<${target}>`;
|
|
111
161
|
}
|
|
112
|
-
if (f.refAggregateId !== undefined)
|
|
162
|
+
if (f.refAggregateId !== undefined)
|
|
163
|
+
return `NomosRef<${concreteClassName(f.refAggregateId)}>`;
|
|
164
|
+
}
|
|
165
|
+
if (f?.kind === "string" && isSha256DigestSchema(f.zod as z.ZodTypeAny)) {
|
|
166
|
+
return "Sha256Digest";
|
|
113
167
|
}
|
|
114
168
|
return "String";
|
|
115
169
|
}
|
|
@@ -118,6 +172,12 @@ function dartKeyExpr(agg: AggregateHandle | undefined, key: string): string {
|
|
|
118
172
|
const lead = key.includes(".") ? key.split(".")[0]! : key;
|
|
119
173
|
const field = agg?.fields[lead] as Field | undefined;
|
|
120
174
|
const param = dartKeyParam(key);
|
|
175
|
+
if (
|
|
176
|
+
field?.kind === "string" &&
|
|
177
|
+
isSha256DigestSchema(field.zod as z.ZodTypeAny)
|
|
178
|
+
) {
|
|
179
|
+
return `${param}.value`;
|
|
180
|
+
}
|
|
121
181
|
if (field?.kind !== "ref") return param;
|
|
122
182
|
return field.foreignRef !== undefined
|
|
123
183
|
? `NmsInternalNomosRefs.id(${param}.ref)`
|
|
@@ -127,7 +187,14 @@ function dartKeyExpr(agg: AggregateHandle | undefined, key: string): string {
|
|
|
127
187
|
/** A Dart-safe param identifier from a (possibly dotted) index key: `a.b.c` → `aBC`. */
|
|
128
188
|
function dartKeyParam(key: string): string {
|
|
129
189
|
const parts = key.split(".").filter((p) => p !== "");
|
|
130
|
-
const id =
|
|
190
|
+
const id =
|
|
191
|
+
parts.length === 0
|
|
192
|
+
? "key"
|
|
193
|
+
: parts[0]! +
|
|
194
|
+
parts
|
|
195
|
+
.slice(1)
|
|
196
|
+
.map((p) => cap(p))
|
|
197
|
+
.join("");
|
|
131
198
|
const cleaned = id.replace(/[^A-Za-z0-9_]/g, "_");
|
|
132
199
|
const safe = /^[0-9]/.test(cleaned) ? `v${cleaned}` : cleaned;
|
|
133
200
|
return safeMethod(safe);
|
|
@@ -141,7 +208,7 @@ function rowToModel(model: string, rowVar: string, workspace = "_ws"): string {
|
|
|
141
208
|
}
|
|
142
209
|
|
|
143
210
|
export interface DartBridgeClientOptions {
|
|
144
|
-
/**
|
|
211
|
+
/** Content identity of the built typed USDA closure (`.law.usdz`). */
|
|
145
212
|
readonly domainHash: string;
|
|
146
213
|
/** This domain's STRUCTURAL INTERFACE (#72) — directives (payload shapes) + aggregates
|
|
147
214
|
* (read shapes, sid-keyed). Baked in as JSON so the app can negotiate shape-compatibility
|
|
@@ -193,7 +260,9 @@ export interface DartBridgeClientOptions {
|
|
|
193
260
|
* Omit (or supply only self-owned aggregates) ⇒ NO cross-domain accessors are emitted — a package
|
|
194
261
|
* whose refs are all same-domain / foreign is byte-identical to before this existed.
|
|
195
262
|
*/
|
|
196
|
-
readonly packageAggregateDomains?: Readonly<
|
|
263
|
+
readonly packageAggregateDomains?: Readonly<
|
|
264
|
+
Record<string, { domain: string; name: string }>
|
|
265
|
+
>;
|
|
197
266
|
/**
|
|
198
267
|
* LAYERED-PACKAGES `barrelExports: 'explicit'` MODE: the full package URIs of the per-domain TYPES
|
|
199
268
|
* files this client references (e.g. `package:advl_types/src/generated/estate.dart`). Under 'explicit'
|
|
@@ -206,9 +275,13 @@ export interface DartBridgeClientOptions {
|
|
|
206
275
|
}
|
|
207
276
|
|
|
208
277
|
/** Generate the `<name>.client.dart` async bridge client for one domain module. */
|
|
209
|
-
export function generateDartBridgeClient(
|
|
278
|
+
export function generateDartBridgeClient(
|
|
279
|
+
mod: DomainModule,
|
|
280
|
+
opts: DartBridgeClientOptions
|
|
281
|
+
): string {
|
|
210
282
|
const publicAggregates = mod.aggregates.filter(
|
|
211
|
-
(a) =>
|
|
283
|
+
(a) =>
|
|
284
|
+
(a as { businessBirthInternal?: unknown }).businessBirthInternal !== true
|
|
212
285
|
);
|
|
213
286
|
const publicAggregateIds = new Set(publicAggregates.map((a) => a.id));
|
|
214
287
|
const publicDirectives = mod.applicationIntents ?? mod.directives;
|
|
@@ -216,25 +289,38 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
216
289
|
...mod,
|
|
217
290
|
aggregates: publicAggregates,
|
|
218
291
|
directives: publicDirectives.filter(
|
|
219
|
-
(d) =>
|
|
292
|
+
(d) =>
|
|
293
|
+
(d as { businessBirthInternal?: unknown }).businessBirthInternal !==
|
|
294
|
+
true
|
|
220
295
|
),
|
|
221
296
|
...(mod.queries === undefined
|
|
222
297
|
? {}
|
|
223
|
-
: {
|
|
298
|
+
: {
|
|
299
|
+
queries: mod.queries.filter(
|
|
300
|
+
(q) =>
|
|
301
|
+
(q as { businessBirthInternal?: unknown })
|
|
302
|
+
.businessBirthInternal !== true
|
|
303
|
+
),
|
|
304
|
+
}),
|
|
224
305
|
...(mod.readModelAggregates === undefined
|
|
225
306
|
? {}
|
|
226
|
-
: {
|
|
307
|
+
: {
|
|
308
|
+
readModelAggregates: mod.readModelAggregates.filter((id) =>
|
|
309
|
+
publicAggregateIds.has(id)
|
|
310
|
+
),
|
|
311
|
+
}),
|
|
227
312
|
};
|
|
228
313
|
registerDartNames(mod.aggregates); // `.dartName` overrides for read-model class names in the client.
|
|
229
314
|
const domain = mod.domain ?? mod.name;
|
|
230
315
|
assertModelClientMemberAvailable(mod);
|
|
231
|
-
const describeModel =
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
316
|
+
const describeModel =
|
|
317
|
+
opts.describeModel ?? buildNomosDescribeModel([mod], domain);
|
|
318
|
+
const clientInterface =
|
|
319
|
+
opts.interface === undefined
|
|
320
|
+
? undefined
|
|
321
|
+
: publicClientInterface([mod], {
|
|
322
|
+
[domain]: opts.interface as unknown as DomainInterface,
|
|
323
|
+
})[domain];
|
|
238
324
|
const className = dartClientClassName(domain);
|
|
239
325
|
const hashConst = dartHashConstName(mod.name);
|
|
240
326
|
const requiresKernelFeats = requiredKernelFeatures([mod]);
|
|
@@ -247,7 +333,9 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
247
333
|
const conflictAggregateType = (aggregateId: string): string => {
|
|
248
334
|
const aggregate = aggById.get(aggregateId);
|
|
249
335
|
if (aggregate === undefined) {
|
|
250
|
-
throw new Error(
|
|
336
|
+
throw new Error(
|
|
337
|
+
`generated conflict policy refers to unknown aggregate '${aggregateId}' in domain '${domain}'`
|
|
338
|
+
);
|
|
251
339
|
}
|
|
252
340
|
return isNamespacedAggregate(aggregate)
|
|
253
341
|
? qualifyReadProjectionType(domain, aggregateId)
|
|
@@ -264,23 +352,37 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
264
352
|
// the TS client (one source of truth: `domainEncryptionMap`); requires the baked interface for the sids.
|
|
265
353
|
const encMap: EncDomainMap | null =
|
|
266
354
|
clientInterface !== undefined
|
|
267
|
-
? domainEncryptionMap(
|
|
355
|
+
? domainEncryptionMap(
|
|
356
|
+
mod,
|
|
357
|
+
clientInterface as { aggregates?: Record<string, InterfaceAggShape> }
|
|
358
|
+
)
|
|
268
359
|
: null;
|
|
269
360
|
const encPresent = encMap !== null;
|
|
270
|
-
const manualEncryptionPresent =
|
|
271
|
-
|
|
272
|
-
|
|
361
|
+
const manualEncryptionPresent =
|
|
362
|
+
encMap !== null &&
|
|
363
|
+
[...Object.values(encMap.directives), ...Object.values(encMap.reads)].some(
|
|
364
|
+
(fields) => Object.values(fields).some((field) => field.owned !== true)
|
|
273
365
|
);
|
|
274
366
|
/** The distinct scopes a read of `typeId` must inject keys for (empty ⇒ nothing encrypted on it). */
|
|
275
367
|
const readScopesFor = (typeId: string): { scope: string; owned?: true }[] => {
|
|
276
368
|
const r = encMap?.reads?.[typeId];
|
|
277
369
|
if (r === undefined) return [];
|
|
278
370
|
const byKey = new Map<string, { scope: string; owned?: true }>();
|
|
279
|
-
for (const x of Object.values(r))
|
|
371
|
+
for (const x of Object.values(r))
|
|
372
|
+
byKey.set(`${x.scope}:${x.owned === true}`, x);
|
|
280
373
|
return [...byKey.values()].sort((a, b) => a.scope.localeCompare(b.scope));
|
|
281
374
|
};
|
|
282
|
-
const scopesExpr = (
|
|
283
|
-
|
|
375
|
+
const scopesExpr = (
|
|
376
|
+
scopes: readonly { scope: string; owned?: true }[]
|
|
377
|
+
): string =>
|
|
378
|
+
`[${scopes
|
|
379
|
+
.map(
|
|
380
|
+
(s) =>
|
|
381
|
+
`{${dartStr("scope")}: ${dartStr(s.scope)}${
|
|
382
|
+
s.owned === true ? `, ${dartStr("owned")}: true` : ""
|
|
383
|
+
}}`
|
|
384
|
+
)
|
|
385
|
+
.join(", ")}]`;
|
|
284
386
|
const needsReadPrincipal = (typeId: string): boolean => {
|
|
285
387
|
const audience = aggById.get(typeId)?.audience;
|
|
286
388
|
return audience?.kind === "readableBy" || audience?.kind === "owned";
|
|
@@ -288,14 +390,18 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
288
390
|
const keysArg = (typeId: string): string => {
|
|
289
391
|
const scopes = readScopesFor(typeId);
|
|
290
392
|
if (scopes.length) {
|
|
291
|
-
return `, await _readKeys(${scopesExpr(scopes)})${
|
|
393
|
+
return `, await _readKeys(${scopesExpr(scopes)})${
|
|
394
|
+
needsReadPrincipal(typeId) ? ", actor" : ""
|
|
395
|
+
}`;
|
|
292
396
|
}
|
|
293
397
|
return needsReadPrincipal(typeId) ? ", null, actor" : "";
|
|
294
398
|
};
|
|
295
399
|
const pastKeysArg = (typeId: string): string => {
|
|
296
400
|
const scopes = readScopesFor(typeId);
|
|
297
401
|
if (scopes.length) {
|
|
298
|
-
return `, await _client._readKeys(${scopesExpr(scopes)})${
|
|
402
|
+
return `, await _client._readKeys(${scopesExpr(scopes)})${
|
|
403
|
+
needsReadPrincipal(typeId) ? ", _client.actor" : ""
|
|
404
|
+
}`;
|
|
299
405
|
}
|
|
300
406
|
return needsReadPrincipal(typeId) ? ", null, _client.actor" : "";
|
|
301
407
|
};
|
|
@@ -304,23 +410,46 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
304
410
|
const domScopes: string[] = (() => {
|
|
305
411
|
if (encMap === null) return [];
|
|
306
412
|
const s = new Set<string>();
|
|
307
|
-
for (const r of Object.values(encMap.reads))
|
|
308
|
-
|
|
413
|
+
for (const r of Object.values(encMap.reads))
|
|
414
|
+
for (const f of Object.values(r)) if (f.owned !== true) s.add(f.scope);
|
|
415
|
+
for (const dir of Object.values(encMap.directives))
|
|
416
|
+
for (const f of Object.values(dir)) if (f.owned !== true) s.add(f.scope);
|
|
309
417
|
return [...s].sort();
|
|
310
418
|
})();
|
|
311
419
|
const ownedScopes: string[] = (() => {
|
|
312
420
|
if (encMap === null) return [];
|
|
313
421
|
const scopes = new Set<string>();
|
|
314
|
-
for (const r of Object.values(encMap.reads))
|
|
315
|
-
|
|
422
|
+
for (const r of Object.values(encMap.reads))
|
|
423
|
+
for (const f of Object.values(r))
|
|
424
|
+
if (f.owned === true) scopes.add(f.scope);
|
|
425
|
+
for (const dir of Object.values(encMap.directives))
|
|
426
|
+
for (const f of Object.values(dir))
|
|
427
|
+
if (f.owned === true) scopes.add(f.scope);
|
|
316
428
|
return [...scopes].sort();
|
|
317
429
|
})();
|
|
318
430
|
if (ownedScopes.length > 1) {
|
|
319
|
-
throw new Error(
|
|
431
|
+
throw new Error(
|
|
432
|
+
`dart codegen: owned business state in domain '${domain}' unexpectedly uses more than one framework scope`
|
|
433
|
+
);
|
|
320
434
|
}
|
|
321
435
|
const ownedScope = ownedScopes[0];
|
|
322
436
|
|
|
323
437
|
const methods: string[] = [];
|
|
438
|
+
const hasVersionedRefs = mod.aggregates.some((aggregate) =>
|
|
439
|
+
Object.values(aggregate.fields as Record<string, Field>).some(
|
|
440
|
+
(field) => field.versionedForeignRef !== undefined
|
|
441
|
+
)
|
|
442
|
+
);
|
|
443
|
+
if (hasVersionedRefs) {
|
|
444
|
+
methods.push(
|
|
445
|
+
` /// Check only whether an immutable publication pin is still the stable series' current`,
|
|
446
|
+
` /// publication. The kernel reads one compiled current pointer; it does not resolve history.`,
|
|
447
|
+
` Future<ReferenceFreshness<TVersion>> referenceFreshness<TVersion, TSeries>(`,
|
|
448
|
+
` VersionedNomosRef<TVersion, TSeries> reference) =>`,
|
|
449
|
+
` bridge.checkReferenceFreshness(reference);`,
|
|
450
|
+
``
|
|
451
|
+
);
|
|
452
|
+
}
|
|
324
453
|
const pastMethods: string[] = [];
|
|
325
454
|
const pastDeclarations: string[] = [];
|
|
326
455
|
const conflictDeclarations: string[] = [];
|
|
@@ -328,17 +457,29 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
328
457
|
(mod.aggregates as AggregateHandle[])
|
|
329
458
|
.filter((aggregate) => aggregate.audience?.kind === "owned")
|
|
330
459
|
.map((aggregate) => {
|
|
331
|
-
const type = isNamespacedAggregate(aggregate)
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
460
|
+
const type = isNamespacedAggregate(aggregate)
|
|
461
|
+
? qualifyReadProjectionType(domain, aggregate.id)
|
|
462
|
+
: aggregate.id;
|
|
463
|
+
return [
|
|
464
|
+
type,
|
|
465
|
+
{
|
|
466
|
+
share: ownedRecordActionDirectiveId("share", aggregate.id),
|
|
467
|
+
unshare: ownedRecordActionDirectiveId("unshare", aggregate.id),
|
|
468
|
+
transfer: ownedRecordActionDirectiveId("transfer", aggregate.id),
|
|
469
|
+
},
|
|
470
|
+
];
|
|
471
|
+
})
|
|
338
472
|
);
|
|
339
|
-
const ownedActionsDart = `{${Object.entries(ownedActions)
|
|
340
|
-
|
|
341
|
-
|
|
473
|
+
const ownedActionsDart = `{${Object.entries(ownedActions)
|
|
474
|
+
.map(
|
|
475
|
+
([type, actions]) =>
|
|
476
|
+
`${dartStr(type)}: {${Object.entries(actions)
|
|
477
|
+
.map(
|
|
478
|
+
([action, directive]) => `${dartStr(action)}: ${dartStr(directive)}`
|
|
479
|
+
)
|
|
480
|
+
.join(", ")}}`
|
|
481
|
+
)
|
|
482
|
+
.join(", ")}}`;
|
|
342
483
|
if (ownedScope !== undefined && Object.keys(ownedActions).length > 0) {
|
|
343
484
|
methods.push(
|
|
344
485
|
` /// Share one record. The peer can read its history and future changes; Nomos owns the keys and policy.`,
|
|
@@ -349,28 +490,36 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
349
490
|
``,
|
|
350
491
|
` /// Hand ownership, readable history and future authority for one record to another person.`,
|
|
351
492
|
` Future<NomosOfferOutcome> transfer<TAggregate>(NomosRef<TAggregate> record, String peer) => _ownedRecordAction('transfer', record, peer);`,
|
|
352
|
-
|
|
493
|
+
``
|
|
353
494
|
);
|
|
354
495
|
}
|
|
355
496
|
|
|
356
497
|
// THE STATIC INTERFACE DESCRIPTOR (`XClient.interface`, session_bound_clients.md asks 5/9) —
|
|
357
498
|
// what this client EXPECTS of a workspace: the domain key, the law hash it compiled under, and
|
|
358
499
|
// the structural interface (#72). `bind(session)` / `session.assertCompatible(...)` consume it.
|
|
359
|
-
const compatInterfaceHash = (
|
|
500
|
+
const compatInterfaceHash = (
|
|
501
|
+
opts.compat as { interfaceHash?: string } | undefined
|
|
502
|
+
)?.interfaceHash;
|
|
360
503
|
|
|
361
504
|
// ── directives → typed offer ──
|
|
362
505
|
// A `.births()` directive (birthsChild === true) is NOT a same-workspace write — it is a signed
|
|
363
506
|
// cross-workspace governance offer to a PARENT. It gets a typed `bridge.createWorkspace(...)` wrapper
|
|
364
507
|
// (below), NOT an `offerOutcome`; excluded here so a frontend dev can never call it as a local write.
|
|
365
508
|
const allDirs = (mod.directives as AnyDirective[]).filter(
|
|
366
|
-
(d) =>
|
|
367
|
-
|
|
368
|
-
|
|
509
|
+
(d) =>
|
|
510
|
+
d &&
|
|
511
|
+
typeof d.id === "string" &&
|
|
512
|
+
typeof (d as { payloadSchema?: unknown }).payloadSchema === "object" &&
|
|
513
|
+
(d as { businessBirthInternal?: unknown }).businessBirthInternal !== true
|
|
514
|
+
);
|
|
515
|
+
const birthDirs = allDirs.filter(
|
|
516
|
+
(d) => (d as { birthsChild?: boolean }).birthsChild === true
|
|
517
|
+
);
|
|
518
|
+
const dirs = allDirs.filter(
|
|
519
|
+
(d) => (d as { birthsChild?: boolean }).birthsChild !== true
|
|
369
520
|
);
|
|
370
|
-
const birthDirs = allDirs.filter((d) => (d as { birthsChild?: boolean }).birthsChild === true);
|
|
371
|
-
const dirs = allDirs.filter((d) => (d as { birthsChild?: boolean }).birthsChild !== true);
|
|
372
521
|
const correctionActions = dirs.filter(
|
|
373
|
-
(d) => (d as { businessBirth?: unknown }).businessBirth === undefined
|
|
522
|
+
(d) => (d as { businessBirth?: unknown }).businessBirth === undefined
|
|
374
523
|
);
|
|
375
524
|
if (correctionActions.length > 0) {
|
|
376
525
|
const correctionsClass = `${className}Corrections`;
|
|
@@ -378,7 +527,7 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
378
527
|
methods.push(
|
|
379
528
|
` /// Correct this principal's latest business change. The framework owns target selection and replay.`,
|
|
380
529
|
` ${correctionsClass} get corrections => ${correctionsClass}(this);`,
|
|
381
|
-
|
|
530
|
+
``
|
|
382
531
|
);
|
|
383
532
|
pastDeclarations.push(
|
|
384
533
|
`/// Result of a reasoned business correction. Framework intent and testimony ids remain private.`,
|
|
@@ -404,57 +553,71 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
404
553
|
` if (reason.trim().isEmpty) throw ArgumentError('corrections.\$kind() needs a reason');`,
|
|
405
554
|
` final result = await _client.bridge.correctMostRecent(`,
|
|
406
555
|
` kind: kind, domain: ${dartStr(domain)},`,
|
|
407
|
-
` actions: const [${correctionActions
|
|
556
|
+
` actions: const [${correctionActions
|
|
557
|
+
.map((d) => dartStr(d.id))
|
|
558
|
+
.join(", ")}],`,
|
|
408
559
|
` reason: reason, domainHash: _client.domainHash, actor: _client.actor);`,
|
|
409
560
|
` return ${outcomeClass}.fromJson(result);`,
|
|
410
561
|
` }`,
|
|
411
562
|
`}`,
|
|
412
|
-
|
|
563
|
+
``
|
|
413
564
|
);
|
|
414
565
|
}
|
|
415
566
|
for (const d of dirs) {
|
|
416
567
|
const payloadClass = dartActionInputClassName(d);
|
|
417
|
-
const stamps = autoStampFields(
|
|
568
|
+
const stamps = autoStampFields(
|
|
569
|
+
(d as unknown as { payloadSchema: z.ZodTypeAny }).payloadSchema
|
|
570
|
+
);
|
|
418
571
|
const method = safeMethod(d.id);
|
|
419
572
|
// THE AUTHORITY GATE (codegen-time): a directive carrying `.requires(relation)` is GATED — its method
|
|
420
573
|
// throws MissingOfferAuthority when `actor` is null/empty, BEFORE any submit, so an actorless gated
|
|
421
574
|
// offer is impossible to GENERATE (the war: the gate is structural, not just kernel-side). Secrets never
|
|
422
575
|
// appear — only the actor identity (the bridge owns signing).
|
|
423
|
-
const gate = (d as { requiresRelationGate?: { relation: string } })
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
576
|
+
const gate = (d as { requiresRelationGate?: { relation: string } })
|
|
577
|
+
.requiresRelationGate;
|
|
578
|
+
const gateGuard =
|
|
579
|
+
gate !== undefined
|
|
580
|
+
? ` if (actor == null || actor!.trim().isEmpty) {\n throw MissingOfferAuthority(domain: ${dartStr(
|
|
581
|
+
domain
|
|
582
|
+
)}, directiveId: ${dartStr(d.id)}, requires: ${dartStr(
|
|
583
|
+
gate.relation
|
|
584
|
+
)});\n }`
|
|
585
|
+
: "";
|
|
586
|
+
const gateDoc =
|
|
587
|
+
gate !== undefined
|
|
588
|
+
? ` /// GATED: needs an \`actor\` (the \`${gate.relation}\` relation) — throws MissingOfferAuthority otherwise.`
|
|
589
|
+
: "";
|
|
437
590
|
// E2E SEALING (the sealing client): a directive with `.encrypted()` payload fields SEALS them via the
|
|
438
591
|
// bound [keyStore] before dispatch (a missing key throws [EncryptionKeyUnavailable] — never a plaintext
|
|
439
592
|
// write). One async block covers creates + mutate + gated + stamps; `seal: false` is the escape hatch.
|
|
440
593
|
// THE ONE WRITE OUTCOME (architecture/nomos_ref.md): EVERY directive method returns Future<NomosOfferOutcome>
|
|
441
594
|
// via the ONE lane `bridge.offerOutcome` — `outcome.created` carries every minted aggregate (each with its
|
|
442
595
|
// typed `.ref`), `outcome.head` the new local head. The breaking cut: creates AND non-creates alike.
|
|
443
|
-
const createsDoc =
|
|
444
|
-
|
|
445
|
-
|
|
596
|
+
const createsDoc =
|
|
597
|
+
(d as { marker?: string }).marker === "creates"
|
|
598
|
+
? ` The LAW mints the ${
|
|
599
|
+
(d as { aggregateId?: string }).aggregateId ?? "aggregate"
|
|
600
|
+
} id inside the sealed intent — it rides back on \`outcome.created\` (no id in the payload, no query-after).`
|
|
601
|
+
: "";
|
|
446
602
|
const recordInputs = dartBusinessRecordInputs(d, mod.aggregates);
|
|
447
603
|
const principalField = d.businessPrincipalField;
|
|
448
|
-
const principalPrelude =
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
604
|
+
const principalPrelude =
|
|
605
|
+
principalField === undefined
|
|
606
|
+
? []
|
|
607
|
+
: [
|
|
608
|
+
` final nmsInternalPrincipal = actor ?? bridge.principal;`,
|
|
609
|
+
` if (nmsInternalPrincipal == null || nmsInternalPrincipal.trim().isEmpty) {`,
|
|
610
|
+
` throw MissingOfferAuthority(domain: ${dartStr(
|
|
611
|
+
domain
|
|
612
|
+
)}, directiveId: ${dartStr(
|
|
613
|
+
d.id
|
|
614
|
+
)}, requires: 'the signed-in Nomos principal');`,
|
|
615
|
+
` }`,
|
|
616
|
+
];
|
|
456
617
|
const payloadJsonArgs = [
|
|
457
|
-
...(principalField === undefined
|
|
618
|
+
...(principalField === undefined
|
|
619
|
+
? []
|
|
620
|
+
: ["nmsInternalPrincipal: nmsInternalPrincipal"]),
|
|
458
621
|
...(recordInputs.length === 0
|
|
459
622
|
? []
|
|
460
623
|
: [
|
|
@@ -462,64 +625,95 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
462
625
|
"nmsInternalIdOf: (record) => NmsInternalRecordOrigins.require(record).id",
|
|
463
626
|
]),
|
|
464
627
|
];
|
|
465
|
-
const payloadJson =
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
628
|
+
const payloadJson =
|
|
629
|
+
payloadJsonArgs.length === 0
|
|
630
|
+
? "payload.nmsInternalToPayloadJson()"
|
|
631
|
+
: `payload.nmsInternalToPayloadJson(${payloadJsonArgs.join(", ")})`;
|
|
632
|
+
const stampsDoc =
|
|
633
|
+
stamps.length > 0
|
|
634
|
+
? `\n /// Omitted ${stamps
|
|
635
|
+
.map((s) => `\`${s}\``)
|
|
636
|
+
.join(" / ")} auto-stamp(s) ISO now() at offer.`
|
|
637
|
+
: "";
|
|
469
638
|
const directiveEncryption = encMap?.directives?.[d.id];
|
|
470
639
|
if (directiveEncryption !== undefined) {
|
|
471
|
-
const ownedSeal = Object.values(directiveEncryption).every(
|
|
472
|
-
|
|
473
|
-
|
|
640
|
+
const ownedSeal = Object.values(directiveEncryption).every(
|
|
641
|
+
(field) => field.owned === true
|
|
642
|
+
);
|
|
643
|
+
const targetAggregate = ownedSeal
|
|
644
|
+
? aggById.get((d as { aggregateId?: string }).aggregateId ?? "")
|
|
645
|
+
: undefined;
|
|
646
|
+
const recordTarget =
|
|
647
|
+
targetAggregate === undefined
|
|
648
|
+
? undefined
|
|
649
|
+
: deriveDirectiveRecordTarget(d, targetAggregate);
|
|
474
650
|
if (ownedSeal && recordTarget === undefined) {
|
|
475
651
|
throw new Error(
|
|
476
652
|
`dart codegen: cannot derive the record targeted by encrypted owned directive '${domain}/${d.id}'. ` +
|
|
477
|
-
`Address it from one string payload field, one deterministic instance, or create it with create(Aggregate)
|
|
653
|
+
`Address it from one string payload field, one deterministic instance, or create it with create(Aggregate).`
|
|
478
654
|
);
|
|
479
655
|
}
|
|
480
|
-
const runtimeType =
|
|
481
|
-
|
|
482
|
-
|
|
656
|
+
const runtimeType =
|
|
657
|
+
targetAggregate === undefined
|
|
658
|
+
? undefined
|
|
659
|
+
: isNamespacedAggregate(targetAggregate)
|
|
483
660
|
? qualifyReadProjectionType(domain, targetAggregate.id)
|
|
484
661
|
: targetAggregate.id;
|
|
485
|
-
const recordPrelude =
|
|
486
|
-
|
|
487
|
-
|
|
662
|
+
const recordPrelude =
|
|
663
|
+
recordTarget === undefined || runtimeType === undefined
|
|
664
|
+
? []
|
|
665
|
+
: recordTarget.kind === "premint"
|
|
488
666
|
? [
|
|
489
667
|
` final ownedRecordType = ${dartStr(runtimeType)};`,
|
|
490
668
|
` final ownedRecordId = await bridge.mintId(ownedRecordType);`,
|
|
491
669
|
` const ownedRecordPreMinted = true;`,
|
|
492
670
|
]
|
|
493
671
|
: recordTarget.kind === "payload"
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
]
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
672
|
+
? [
|
|
673
|
+
` final ownedRecordType = ${dartStr(runtimeType)};`,
|
|
674
|
+
` final ownedRecordId = (data[${dartStr(
|
|
675
|
+
recordTarget.field
|
|
676
|
+
)}] ?? '').toString();`,
|
|
677
|
+
` const ownedRecordPreMinted = false;`,
|
|
678
|
+
]
|
|
679
|
+
: [
|
|
680
|
+
` final ownedRecordType = ${dartStr(runtimeType)};`,
|
|
681
|
+
` final ownedRecordId = ${dartStr(recordTarget.id)};`,
|
|
682
|
+
` const ownedRecordPreMinted = false;`,
|
|
683
|
+
];
|
|
504
684
|
methods.push(
|
|
505
685
|
` /// Author \`${domain}/${d.id}\` locally under the installed law; returns the typed [NomosOfferOutcome].${createsDoc}`,
|
|
506
|
-
` /// ${
|
|
686
|
+
` /// ${
|
|
687
|
+
ownedSeal
|
|
688
|
+
? "Owned content is encrypted automatically by Nomos."
|
|
689
|
+
: "Encrypted field(s) are SEALED via the bound [keyStore] before dispatch (pass `seal: false` to skip)."
|
|
690
|
+
}${stampsDoc}`,
|
|
507
691
|
...(gateDoc ? [gateDoc] : []),
|
|
508
|
-
` Future<NomosOfferOutcome> ${method}(${payloadClass} payload${
|
|
692
|
+
` Future<NomosOfferOutcome> ${method}(${payloadClass} payload${
|
|
693
|
+
ownedSeal ? "" : ", {bool seal = true}"
|
|
694
|
+
}) async {`,
|
|
509
695
|
...(gateGuard ? [gateGuard] : []),
|
|
510
696
|
...principalPrelude,
|
|
511
697
|
` final data = ${payloadJson};`,
|
|
512
698
|
...(stamps.length > 0
|
|
513
699
|
? [
|
|
514
700
|
` final now = DateTime.now().toUtc().toIso8601String();`,
|
|
515
|
-
...stamps.map(
|
|
701
|
+
...stamps.map(
|
|
702
|
+
(s) => ` data.putIfAbsent(${dartStr(s)}, () => now);`
|
|
703
|
+
),
|
|
516
704
|
]
|
|
517
705
|
: []),
|
|
518
706
|
...recordPrelude,
|
|
519
|
-
` await _sealPayload(data, ${dartStr(d.id)}, ${
|
|
520
|
-
|
|
707
|
+
` await _sealPayload(data, ${dartStr(d.id)}, ${
|
|
708
|
+
ownedSeal ? "true" : "seal"
|
|
709
|
+
}${
|
|
710
|
+
ownedSeal
|
|
711
|
+
? ", null, ownedRecordType, ownedRecordId, ownedRecordPreMinted"
|
|
712
|
+
: ""
|
|
713
|
+
});`,
|
|
714
|
+
` return _offer(${dartStr(d.id)}, data);`,
|
|
521
715
|
` }`,
|
|
522
|
-
|
|
716
|
+
``
|
|
523
717
|
);
|
|
524
718
|
continue;
|
|
525
719
|
}
|
|
@@ -527,15 +721,15 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
527
721
|
methods.push(
|
|
528
722
|
` /// Author \`${domain}/${d.id}\` locally under the installed law; returns the typed [NomosOfferOutcome].${createsDoc}${stampsDoc}`,
|
|
529
723
|
...(gateDoc ? [gateDoc] : []),
|
|
530
|
-
` Future<NomosOfferOutcome> ${method}(${payloadClass} payload
|
|
724
|
+
` Future<NomosOfferOutcome> ${method}(${payloadClass} payload) {`,
|
|
531
725
|
...(gateGuard ? [gateGuard] : []),
|
|
532
726
|
...principalPrelude,
|
|
533
727
|
` final data = ${payloadJson};`,
|
|
534
728
|
` final now = DateTime.now().toUtc().toIso8601String();`,
|
|
535
729
|
...stamps.map((s) => ` data.putIfAbsent(${dartStr(s)}, () => now);`),
|
|
536
|
-
` return _offer(${dartStr(d.id)}, data
|
|
730
|
+
` return _offer(${dartStr(d.id)}, data);`,
|
|
537
731
|
` }`,
|
|
538
|
-
|
|
732
|
+
``
|
|
539
733
|
);
|
|
540
734
|
} else if (gateGuard || principalPrelude.length > 0) {
|
|
541
735
|
// A gated or principal-bound directive with no timestamp sugar still needs a block body
|
|
@@ -543,20 +737,20 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
543
737
|
methods.push(
|
|
544
738
|
` /// Author \`${domain}/${d.id}\` locally under the installed law; returns the typed [NomosOfferOutcome].${createsDoc}`,
|
|
545
739
|
gateDoc,
|
|
546
|
-
` Future<NomosOfferOutcome> ${method}(${payloadClass} payload
|
|
740
|
+
` Future<NomosOfferOutcome> ${method}(${payloadClass} payload) {`,
|
|
547
741
|
...(gateGuard ? [gateGuard] : []),
|
|
548
742
|
...principalPrelude,
|
|
549
|
-
` return _offer(${dartStr(d.id)}, ${payloadJson}
|
|
743
|
+
` return _offer(${dartStr(d.id)}, ${payloadJson});`,
|
|
550
744
|
` }`,
|
|
551
|
-
|
|
745
|
+
``
|
|
552
746
|
);
|
|
553
747
|
} else {
|
|
554
748
|
methods.push(
|
|
555
749
|
` /// Author \`${domain}/${d.id}\` locally under the installed law; returns the typed [NomosOfferOutcome].${createsDoc}`,
|
|
556
750
|
...(gateDoc ? [gateDoc] : []),
|
|
557
|
-
` Future<NomosOfferOutcome> ${method}(${payloadClass} payload
|
|
558
|
-
` _offer(${dartStr(d.id)}, ${payloadJson}
|
|
559
|
-
|
|
751
|
+
` Future<NomosOfferOutcome> ${method}(${payloadClass} payload) =>`,
|
|
752
|
+
` _offer(${dartStr(d.id)}, ${payloadJson});`,
|
|
753
|
+
``
|
|
560
754
|
);
|
|
561
755
|
}
|
|
562
756
|
}
|
|
@@ -569,46 +763,120 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
569
763
|
for (const d of birthDirs) {
|
|
570
764
|
const method = safeMethod(d.id);
|
|
571
765
|
const publicMethod = isBusinessIntent(d) ? method : "birth";
|
|
572
|
-
const gate = (d as { requiresRelationGate?: { relation: string } })
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
766
|
+
const gate = (d as { requiresRelationGate?: { relation: string } })
|
|
767
|
+
.requiresRelationGate;
|
|
768
|
+
const businessBirth = (
|
|
769
|
+
d as unknown as {
|
|
770
|
+
businessBirth?: {
|
|
771
|
+
aggregateId: string;
|
|
772
|
+
domain: string;
|
|
773
|
+
rootId: string;
|
|
774
|
+
namespace: string;
|
|
775
|
+
keyField: string;
|
|
776
|
+
seedDirectiveId?: string;
|
|
777
|
+
genesisDirectiveIds?: readonly string[];
|
|
778
|
+
publicPayloadSchema: z.ZodTypeAny;
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
).businessBirth;
|
|
576
782
|
const payloadClass = dartActionInputClassName(d);
|
|
577
783
|
if (businessBirth !== undefined) {
|
|
784
|
+
const seedDirectiveId =
|
|
785
|
+
businessBirth.seedDirectiveId ??
|
|
786
|
+
`nomosSeed${businessBirth.aggregateId.replace(/[^A-Za-z0-9]/g, "")}`;
|
|
787
|
+
const genesisDirectiveIds = businessBirth.genesisDirectiveIds ?? [
|
|
788
|
+
"enrollSigner",
|
|
789
|
+
];
|
|
578
790
|
const birthEncryption = encMap?.directives?.[d.id];
|
|
579
|
-
const ownedBirthDescriptor =
|
|
791
|
+
const ownedBirthDescriptor =
|
|
792
|
+
birthEncryption === undefined
|
|
793
|
+
? undefined
|
|
794
|
+
: Object.values(birthEncryption).find(
|
|
795
|
+
(field) => field.owned === true
|
|
796
|
+
);
|
|
797
|
+
const birthAggregate = (mod.aggregates as AggregateHandle[]).find(
|
|
798
|
+
(aggregate) => aggregate.id === businessBirth.aggregateId
|
|
799
|
+
);
|
|
800
|
+
const birthRuntimeType = birthAggregate === undefined
|
|
580
801
|
? undefined
|
|
581
|
-
:
|
|
802
|
+
: isNamespacedAggregate(birthAggregate)
|
|
803
|
+
? qualifyReadProjectionType(domain, birthAggregate.id)
|
|
804
|
+
: birthAggregate.id;
|
|
805
|
+
if (ownedBirthDescriptor !== undefined && birthRuntimeType === undefined) {
|
|
806
|
+
throw new Error(
|
|
807
|
+
`codegen: cannot derive the record identity for encrypted owned birth '${domain}/${d.id}'`
|
|
808
|
+
);
|
|
809
|
+
}
|
|
582
810
|
methods.push(
|
|
583
811
|
` /// Give one ${businessBirth.aggregateId} its own durable life. Nomos owns identity, authorship,`,
|
|
584
812
|
` /// provenance, persistence and recovery; the app supplies only business facts.`,
|
|
585
|
-
` Future<NomosRef<${concreteClassName(
|
|
813
|
+
` Future<NomosRef<${concreteClassName(
|
|
814
|
+
businessBirth.aggregateId
|
|
815
|
+
)}>> ${publicMethod}(${payloadClass} payload) async {`,
|
|
586
816
|
` final principal = actor ?? bridge.principal;`,
|
|
587
817
|
` if (principal == null || principal.trim().isEmpty) {`,
|
|
588
|
-
` throw MissingOfferAuthority(domain: ${dartStr(
|
|
818
|
+
` throw MissingOfferAuthority(domain: ${dartStr(
|
|
819
|
+
domain
|
|
820
|
+
)}, directiveId: ${dartStr(
|
|
821
|
+
d.id
|
|
822
|
+
)}, requires: 'a signed-in Nomos session');`,
|
|
589
823
|
` }`,
|
|
824
|
+
` await _preflightOnce();`,
|
|
825
|
+
` await _prepareCommand(${dartStr(d.id)});`,
|
|
826
|
+
...(businessBirth.domain === domain
|
|
827
|
+
? []
|
|
828
|
+
: [
|
|
829
|
+
` // This parent offer synchronously runs the child genesis plan. Name that exact`,
|
|
830
|
+
` // authenticated slice now so birth never composes an unrelated or cold domain.`,
|
|
831
|
+
` await bridge.prepareDomain(domain: ${dartStr(
|
|
832
|
+
businessBirth.domain
|
|
833
|
+
)}, directiveId: ${dartStr(
|
|
834
|
+
seedDirectiveId
|
|
835
|
+
)}, packageHash: ${hashConst});`,
|
|
836
|
+
...genesisDirectiveIds.map(
|
|
837
|
+
(directiveId) =>
|
|
838
|
+
` await bridge.prepareDomain(domain: ${dartStr(
|
|
839
|
+
businessBirth.domain
|
|
840
|
+
)}, directiveId: ${dartStr(
|
|
841
|
+
directiveId
|
|
842
|
+
)}, packageHash: ${hashConst});`
|
|
843
|
+
),
|
|
844
|
+
]),
|
|
590
845
|
` final data = payload.nmsInternalToPayloadJson();`,
|
|
591
846
|
...(ownedBirthDescriptor === undefined
|
|
592
847
|
? []
|
|
593
848
|
: [
|
|
594
849
|
` final ownedBirthKey = await bridge.nomosOwnedBirthScopeKey(`,
|
|
595
|
-
` ${dartStr(ownedBirthDescriptor.scope)}, ${dartStr(
|
|
850
|
+
` ${dartStr(ownedBirthDescriptor.scope)}, ${dartStr(
|
|
851
|
+
businessBirth.namespace
|
|
852
|
+
)}, data[${dartStr(businessBirth.keyField)}], ${dartStr(
|
|
853
|
+
birthRuntimeType!
|
|
854
|
+
)}, ${dartStr(businessBirth.rootId)});`,
|
|
596
855
|
]),
|
|
597
856
|
...(birthEncryption === undefined
|
|
598
857
|
? []
|
|
599
858
|
: [
|
|
600
859
|
` final sealedGenesis = Map<String, dynamic>.from(data);`,
|
|
601
|
-
` await _sealPayload(sealedGenesis, ${dartStr(d.id)}, true${
|
|
602
|
-
|
|
860
|
+
` await _sealPayload(sealedGenesis, ${dartStr(d.id)}, true${
|
|
861
|
+
ownedBirthDescriptor === undefined ? "" : ", ownedBirthKey"
|
|
862
|
+
});`,
|
|
863
|
+
` data[${dartStr(
|
|
864
|
+
BUSINESS_BIRTH_SEED_PAYLOAD_FIELD
|
|
865
|
+
)}] = jsonEncode(sealedGenesis);`,
|
|
603
866
|
...(ownedBirthDescriptor === undefined
|
|
604
867
|
? []
|
|
605
|
-
: [
|
|
868
|
+
: [
|
|
869
|
+
` data[${dartStr(
|
|
870
|
+
OWNED_KEY_PAYLOAD_FIELD
|
|
871
|
+
)}] = ownedBirthKey.ownedKeySubject;`,
|
|
872
|
+
]),
|
|
606
873
|
]),
|
|
607
874
|
` data['principal'] = principal;`,
|
|
608
875
|
` data['bornAt'] = DateTime.now().toUtc().toIso8601String();`,
|
|
609
876
|
` final outcome = await _offer(${dartStr(d.id)}, data);`,
|
|
610
877
|
` if (outcome.born.length != 1) {`,
|
|
611
|
-
` throw StateError('birth() committed without returning exactly one born workspace'
|
|
878
|
+
` throw StateError('birth() committed without returning exactly one born workspace '
|
|
879
|
+
'(born: \${outcome.born}, birthOutcomes: \${outcome.birthOutcomes}, head: \${outcome.head})');`,
|
|
612
880
|
` }`,
|
|
613
881
|
...(ownedBirthDescriptor === undefined
|
|
614
882
|
? []
|
|
@@ -620,9 +888,13 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
620
888
|
// Birth is a local, durable business commit. The application host's continuous custody lifecycle
|
|
621
889
|
// pushes it immediately when online and retries when offline; awaiting a network round here makes
|
|
622
890
|
// the supposedly offline-first business action hang behind transport availability.
|
|
623
|
-
` return NmsInternalNomosRefs.create<${concreteClassName(
|
|
891
|
+
` return NmsInternalNomosRefs.create<${concreteClassName(
|
|
892
|
+
businessBirth.aggregateId
|
|
893
|
+
)}>(workspace: outcome.born.single, type: ${dartStr(
|
|
894
|
+
businessBirth.aggregateId
|
|
895
|
+
)}, id: ${dartStr(businessBirth.rootId)});`,
|
|
624
896
|
` }`,
|
|
625
|
-
|
|
897
|
+
``
|
|
626
898
|
);
|
|
627
899
|
continue;
|
|
628
900
|
}
|
|
@@ -652,7 +924,11 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
652
924
|
` if (explicitParent != null && explicitParent.isNotEmpty && explicitParent != ws) {`,
|
|
653
925
|
` // LEGACY GATEWAY SHIM — the pre-session-bound cross-workspace relay, delegated unchanged.`,
|
|
654
926
|
` if (actor == null) {`,
|
|
655
|
-
` throw MissingOfferAuthority(domain: ${dartStr(
|
|
927
|
+
` throw MissingOfferAuthority(domain: ${dartStr(
|
|
928
|
+
domain
|
|
929
|
+
)}, directiveId: ${dartStr(
|
|
930
|
+
d.id
|
|
931
|
+
)}, requires: 'a requesting principal (actor) on the legacy parent-relay lane');`,
|
|
656
932
|
` }`,
|
|
657
933
|
` final verdict = await bridge.createWorkspace(`,
|
|
658
934
|
` domain: ${dartStr(domain)},`,
|
|
@@ -679,14 +955,23 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
679
955
|
...(gate !== undefined
|
|
680
956
|
? [
|
|
681
957
|
` if (effActor == null || effActor.trim().isEmpty) {`,
|
|
682
|
-
` throw MissingOfferAuthority(domain: ${dartStr(
|
|
958
|
+
` throw MissingOfferAuthority(domain: ${dartStr(
|
|
959
|
+
domain
|
|
960
|
+
)}, directiveId: ${dartStr(d.id)}, requires: ${dartStr(
|
|
961
|
+
gate.relation
|
|
962
|
+
)});`,
|
|
683
963
|
` }`,
|
|
684
964
|
]
|
|
685
965
|
: []),
|
|
686
966
|
` await _preflightOnce();`,
|
|
687
|
-
`
|
|
967
|
+
` await _prepareCommand(${dartStr(d.id)});`,
|
|
968
|
+
` return bridge.offerOutcome(domain: ${dartStr(
|
|
969
|
+
domain
|
|
970
|
+
)}, directiveId: ${dartStr(
|
|
971
|
+
d.id
|
|
972
|
+
)}, payload: payload.nmsInternalToPayloadJson(), actor: effActor, domainHash: domainHash);`,
|
|
688
973
|
` }`,
|
|
689
|
-
|
|
974
|
+
``
|
|
690
975
|
);
|
|
691
976
|
}
|
|
692
977
|
|
|
@@ -694,51 +979,126 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
694
979
|
for (const q of (mod.queries ?? []) as QueryDecl[]) {
|
|
695
980
|
const model = readModelClassName(q.returns);
|
|
696
981
|
const agg = aggById.get(q.returns);
|
|
697
|
-
const params = q.key
|
|
698
|
-
|
|
982
|
+
const params = q.key
|
|
983
|
+
.map((k) => `required ${dartKeyType(agg, k)} ${dartKeyParam(k)}`)
|
|
984
|
+
.join(", ");
|
|
985
|
+
const argMap = `{${q.key
|
|
986
|
+
.map((k) => `${dartStr(k)}: ${dartKeyExpr(agg, k)}`)
|
|
987
|
+
.join(", ")}}`;
|
|
699
988
|
const m = safeMethod(lcFirst(camel(q.id)));
|
|
700
989
|
const qScopes = readScopesFor(q.returns);
|
|
701
990
|
const qOwned = qScopes.some((scope) => scope.owned === true);
|
|
702
991
|
const qPrivate = needsReadPrincipal(q.returns);
|
|
703
992
|
const qScopesExpr = scopesExpr(qScopes);
|
|
993
|
+
const pageRead = qOwned
|
|
994
|
+
? `_readWithOwnedKeys(${qScopesExpr}, (keys) => bridge.queryPage(${dartStr(
|
|
995
|
+
q.id
|
|
996
|
+
)}, params: ${argMap}, pageSize: pageSize, cursor: cursor, keys: keys${
|
|
997
|
+
qPrivate ? ", principal: actor" : ""
|
|
998
|
+
}))`
|
|
999
|
+
: `bridge.queryPage(${dartStr(
|
|
1000
|
+
q.id
|
|
1001
|
+
)}, params: ${argMap}, pageSize: pageSize, cursor: cursor${
|
|
1002
|
+
qScopes.length ? `, keys: await _readKeys(${qScopesExpr})` : ""
|
|
1003
|
+
}${qPrivate ? ", principal: actor" : ""})`;
|
|
704
1004
|
methods.push(
|
|
705
|
-
` /// Declared query \`${q.id}\`
|
|
1005
|
+
` /// Declared query \`${q.id}\` as one complete result set.${
|
|
1006
|
+
readScopesFor(q.returns).length
|
|
1007
|
+
? " Encrypted field(s) decrypt when the bound [keyStore] holds the scope key; else they stay opaque envelopes."
|
|
1008
|
+
: ""
|
|
1009
|
+
}`,
|
|
1010
|
+
` @Deprecated('Whole-result reads scale with result size; use ${m}Page().')`,
|
|
706
1011
|
` Future<List<${model}>> ${m}({${params}}) async =>`,
|
|
707
1012
|
qOwned
|
|
708
|
-
? ` (await _readWithOwnedKeys(${qScopesExpr}, (keys) => bridge.query(${dartStr(
|
|
709
|
-
|
|
1013
|
+
? ` (await _readWithOwnedKeys(${qScopesExpr}, (keys) => bridge.query(${dartStr(
|
|
1014
|
+
q.id
|
|
1015
|
+
)}, ${argMap}, keys${
|
|
1016
|
+
qPrivate ? ", actor" : ""
|
|
1017
|
+
}))).map((r) => ${rowToModel(model, "r")}).toList();`
|
|
1018
|
+
: ` (await bridge.query(${dartStr(q.id)}, ${argMap}${keysArg(
|
|
1019
|
+
q.returns
|
|
1020
|
+
)})).map((r) => ${rowToModel(model, "r")}).toList();`,
|
|
1021
|
+
``,
|
|
1022
|
+
` /// One bounded \`${q.id}\` page. The opaque [cursor] is returned unchanged by [NomosPage].`,
|
|
1023
|
+
` Future<NomosPage<${model}>> ${m}Page({${params}${
|
|
1024
|
+
params ? ", " : ""
|
|
1025
|
+
}int pageSize = 100, String? cursor}) async {`,
|
|
1026
|
+
` final page = await ${pageRead};`,
|
|
1027
|
+
` return NomosPage<${model}>(`,
|
|
1028
|
+
` rows: page.rows.map((r) => ${rowToModel(model, "r")}).toList(),`,
|
|
1029
|
+
` cursor: page.cursor,`,
|
|
1030
|
+
` done: page.done,`,
|
|
1031
|
+
` );`,
|
|
1032
|
+
` }`,
|
|
710
1033
|
``,
|
|
711
1034
|
` /// Reactive \`${q.id}\` — re-emits on every local change (cancel the subscription to stop).`,
|
|
712
1035
|
` Stream<List<${model}>> watch${cap(camel(q.id))}({${params}}) =>`,
|
|
713
1036
|
qOwned
|
|
714
|
-
? ` bridge.watch(${dartStr(q.id)}, ${argMap}, null${
|
|
715
|
-
|
|
716
|
-
|
|
1037
|
+
? ` bridge.watch(${dartStr(q.id)}, ${argMap}, null${
|
|
1038
|
+
qPrivate ? ", actor" : ""
|
|
1039
|
+
}).asyncMap((_) => _readWithOwnedKeys(${qScopesExpr}, (keys) => bridge.query(${dartStr(
|
|
1040
|
+
q.id
|
|
1041
|
+
)}, ${argMap}, keys${
|
|
1042
|
+
qPrivate ? ", actor" : ""
|
|
1043
|
+
}))).map((rows) => rows.map((r) => ${rowToModel(
|
|
1044
|
+
model,
|
|
1045
|
+
"r"
|
|
1046
|
+
)}).toList());`
|
|
1047
|
+
: ` bridge.watch(${dartStr(q.id)}, ${argMap}${
|
|
1048
|
+
qPrivate ? ", null, actor" : ""
|
|
1049
|
+
}).map((rows) => rows.map((r) => ${rowToModel(
|
|
1050
|
+
model,
|
|
1051
|
+
"r"
|
|
1052
|
+
)}).toList());`,
|
|
1053
|
+
``
|
|
717
1054
|
);
|
|
718
1055
|
pastMethods.push(
|
|
719
1056
|
` /// Declared query \`${q.id}\` in the verified world at [moment].`,
|
|
720
1057
|
` Future<List<${model}>> ${m}({${params}}) async =>`,
|
|
721
1058
|
qOwned
|
|
722
|
-
? ` (await _client._readWithOwnedKeys(${qScopesExpr}, (keys) => _client.bridge.queryAt(moment, ${dartStr(
|
|
723
|
-
|
|
724
|
-
|
|
1059
|
+
? ` (await _client._readWithOwnedKeys(${qScopesExpr}, (keys) => _client.bridge.queryAt(moment, ${dartStr(
|
|
1060
|
+
q.id
|
|
1061
|
+
)}, ${argMap}, keys${
|
|
1062
|
+
qPrivate ? ", _client.actor" : ""
|
|
1063
|
+
}))).map((r) => ${rowToModel(model, "r", "_client._ws")}).toList();`
|
|
1064
|
+
: ` (await _client.bridge.queryAt(moment, ${dartStr(
|
|
1065
|
+
q.id
|
|
1066
|
+
)}, ${argMap}${pastKeysArg(q.returns)})).map((r) => ${rowToModel(
|
|
1067
|
+
model,
|
|
1068
|
+
"r",
|
|
1069
|
+
"_client._ws"
|
|
1070
|
+
)}).toList();`,
|
|
1071
|
+
``
|
|
725
1072
|
);
|
|
726
1073
|
}
|
|
727
1074
|
|
|
728
1075
|
// ── declared spatial indexes → typed bbox reads (lane-2 read-serving) ──
|
|
729
|
-
for (const sp of (mod.spatials ?? []) as {
|
|
1076
|
+
for (const sp of (mod.spatials ?? []) as {
|
|
1077
|
+
id: string;
|
|
1078
|
+
of: string;
|
|
1079
|
+
on: string;
|
|
1080
|
+
}[]) {
|
|
730
1081
|
const model = readModelClassName(sp.of);
|
|
731
1082
|
const m = safeMethod(lcFirst(camel(sp.id)));
|
|
732
1083
|
methods.push(
|
|
733
1084
|
` /// Declared spatial index \`${sp.id}\` — rows of \`${sp.of}\` whose \`${sp.on}\` geometry`,
|
|
734
1085
|
` /// intersects [bbox] (an R*Tree probe on the local projection, never a scan).`,
|
|
735
1086
|
` Future<List<${model}>> ${m}({required NomosBbox bbox}) async =>`,
|
|
736
|
-
` (await bridge.spatialWithin(${dartStr(
|
|
1087
|
+
` (await bridge.spatialWithin(${dartStr(
|
|
1088
|
+
sp.id
|
|
1089
|
+
)}, bbox)).map((r) => ${rowToModel(model, "r")}).toList();`,
|
|
737
1090
|
``,
|
|
738
1091
|
` /// Reactive \`${sp.id}\` — re-emits when the probe's rows change locally.`,
|
|
739
|
-
` Stream<List<${model}>> watch${cap(
|
|
740
|
-
|
|
741
|
-
|
|
1092
|
+
` Stream<List<${model}>> watch${cap(
|
|
1093
|
+
camel(sp.id)
|
|
1094
|
+
)}({required NomosBbox bbox}) =>`,
|
|
1095
|
+
` bridge.watchSpatial(${dartStr(
|
|
1096
|
+
sp.id
|
|
1097
|
+
)}, bbox).map((rows) => rows.map((r) => ${rowToModel(
|
|
1098
|
+
model,
|
|
1099
|
+
"r"
|
|
1100
|
+
)}).toList());`,
|
|
1101
|
+
``
|
|
742
1102
|
);
|
|
743
1103
|
}
|
|
744
1104
|
|
|
@@ -750,15 +1110,33 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
750
1110
|
const groupValue = c.by == null ? "" : dartKeyExpr(aggregate, c.by);
|
|
751
1111
|
methods.push(
|
|
752
1112
|
c.by != null
|
|
753
|
-
? ` /// Declared count \`${c.id}\` — the maintained O(1) tally per \`${
|
|
754
|
-
|
|
755
|
-
|
|
1113
|
+
? ` /// Declared count \`${c.id}\` — the maintained O(1) tally per \`${
|
|
1114
|
+
c.by
|
|
1115
|
+
}\`.\n Future<int> ${m}(${groupType} ${dartKeyParam(
|
|
1116
|
+
c.by
|
|
1117
|
+
)}) => bridge.count(${dartStr(c.id)}, ${groupValue});`
|
|
1118
|
+
: ` /// Declared count \`${
|
|
1119
|
+
c.id
|
|
1120
|
+
}\` — the maintained O(1) grand total.\n Future<int> ${m}() => bridge.count(${dartStr(
|
|
1121
|
+
c.id
|
|
1122
|
+
)});`,
|
|
1123
|
+
``
|
|
756
1124
|
);
|
|
757
1125
|
pastMethods.push(
|
|
758
1126
|
c.by != null
|
|
759
|
-
? ` /// Declared count \`${
|
|
760
|
-
|
|
761
|
-
|
|
1127
|
+
? ` /// Declared count \`${
|
|
1128
|
+
c.id
|
|
1129
|
+
}\` at [moment].\n Future<int> ${m}(${groupType} ${dartKeyParam(
|
|
1130
|
+
c.by
|
|
1131
|
+
)}) => _client.bridge.countAt(moment, ${dartStr(
|
|
1132
|
+
c.id
|
|
1133
|
+
)}, ${groupValue});`
|
|
1134
|
+
: ` /// Declared count \`${
|
|
1135
|
+
c.id
|
|
1136
|
+
}\` at [moment].\n Future<int> ${m}() => _client.bridge.countAt(moment, ${dartStr(
|
|
1137
|
+
c.id
|
|
1138
|
+
)});`,
|
|
1139
|
+
``
|
|
762
1140
|
);
|
|
763
1141
|
}
|
|
764
1142
|
for (const s of (mod.sums ?? []).map((raw) => finishSum(raw))) {
|
|
@@ -768,22 +1146,43 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
768
1146
|
const groupValue = s.by == null ? "" : dartKeyExpr(aggregate, s.by);
|
|
769
1147
|
methods.push(
|
|
770
1148
|
s.by != null
|
|
771
|
-
? ` /// Declared sum \`${s.id}\` — the maintained O(1) total per \`${
|
|
772
|
-
|
|
773
|
-
|
|
1149
|
+
? ` /// Declared sum \`${s.id}\` — the maintained O(1) total per \`${
|
|
1150
|
+
s.by
|
|
1151
|
+
}\`.\n Future<int> ${m}(${groupType} ${dartKeyParam(
|
|
1152
|
+
s.by
|
|
1153
|
+
)}) => bridge.sum(${dartStr(s.id)}, ${groupValue});`
|
|
1154
|
+
: ` /// Declared sum \`${
|
|
1155
|
+
s.id
|
|
1156
|
+
}\` — the maintained O(1) grand total.\n Future<int> ${m}() => bridge.sum(${dartStr(
|
|
1157
|
+
s.id
|
|
1158
|
+
)});`,
|
|
1159
|
+
``
|
|
774
1160
|
);
|
|
775
1161
|
pastMethods.push(
|
|
776
1162
|
s.by != null
|
|
777
|
-
? ` /// Declared sum \`${
|
|
778
|
-
|
|
779
|
-
|
|
1163
|
+
? ` /// Declared sum \`${
|
|
1164
|
+
s.id
|
|
1165
|
+
}\` at [moment].\n Future<int> ${m}(${groupType} ${dartKeyParam(
|
|
1166
|
+
s.by
|
|
1167
|
+
)}) => _client.bridge.sumAt(moment, ${dartStr(s.id)}, ${groupValue});`
|
|
1168
|
+
: ` /// Declared sum \`${
|
|
1169
|
+
s.id
|
|
1170
|
+
}\` at [moment].\n Future<int> ${m}() => _client.bridge.sumAt(moment, ${dartStr(
|
|
1171
|
+
s.id
|
|
1172
|
+
)});`,
|
|
1173
|
+
``
|
|
780
1174
|
);
|
|
781
1175
|
}
|
|
782
1176
|
|
|
783
1177
|
// ── BUSINESS MERGE CONFLICT DX: keep the heterogeneous Nomos index behind an aggregate-specific,
|
|
784
1178
|
// chained API. A pending conflict is a typed reference to its own `merge(...)` capability; callers
|
|
785
1179
|
// inspect all versions and produce a merge result rather than choosing a winner.
|
|
786
|
-
const resolverBindings =
|
|
1180
|
+
const resolverBindings =
|
|
1181
|
+
(
|
|
1182
|
+
mod as {
|
|
1183
|
+
surfacedResolvers?: import("./surfaced_lint.js").SurfacedResolverBinding[];
|
|
1184
|
+
}
|
|
1185
|
+
).surfacedResolvers ?? [];
|
|
787
1186
|
if (resolverBindings.length > 0) {
|
|
788
1187
|
const conflictsClass = `${cap(camel(domain))}Conflicts`;
|
|
789
1188
|
const summaryClass = `${cap(camel(domain))}ConflictSummary`;
|
|
@@ -796,7 +1195,10 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
796
1195
|
};
|
|
797
1196
|
const byAggregate = new Map<string, typeof resolverBindings>();
|
|
798
1197
|
for (const binding of resolverBindings) {
|
|
799
|
-
byAggregate.set(binding.aggregateId, [
|
|
1198
|
+
byAggregate.set(binding.aggregateId, [
|
|
1199
|
+
...(byAggregate.get(binding.aggregateId) ?? []),
|
|
1200
|
+
binding,
|
|
1201
|
+
]);
|
|
800
1202
|
}
|
|
801
1203
|
methods.push(
|
|
802
1204
|
` /// Pending business merges: summary → aggregate → typed conflict → merge().`,
|
|
@@ -805,39 +1207,53 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
805
1207
|
` /// Nomos-internal aggregate-filtered index used only by generated conflict references.`,
|
|
806
1208
|
` Future<List<${rowClass}>> _pendingConflictRows(String aggregateType) async =>`,
|
|
807
1209
|
` (await bridge.pendingConflictsFor(aggregateType)).map(${rowClass}.fromJson).toList();`,
|
|
808
|
-
|
|
1210
|
+
``
|
|
809
1211
|
);
|
|
810
1212
|
for (const b of resolverBindings) {
|
|
811
|
-
if (b.valuePayloadField === undefined || b.idPayloadField === undefined)
|
|
1213
|
+
if (b.valuePayloadField === undefined || b.idPayloadField === undefined)
|
|
1214
|
+
continue; // probe couldn't bind — dispatch the directive method directly.
|
|
812
1215
|
const agg = aggById.get(b.aggregateId);
|
|
813
1216
|
const fieldKind = (agg?.fields[b.field] as Field | undefined)?.kind;
|
|
814
|
-
const mergedType =
|
|
815
|
-
|
|
816
|
-
|
|
1217
|
+
const mergedType =
|
|
1218
|
+
fieldKind === "string" || fieldKind === "ref" || fieldKind === "enum"
|
|
1219
|
+
? "String"
|
|
1220
|
+
: fieldKind === "int"
|
|
817
1221
|
? "int"
|
|
818
1222
|
: fieldKind === "bool"
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
const helper = `_merge${cap(
|
|
1223
|
+
? "bool"
|
|
1224
|
+
: "Object?";
|
|
1225
|
+
const helper = `_merge${cap(
|
|
1226
|
+
camel(concreteClassName(b.aggregateId))
|
|
1227
|
+
)}${cap(camel(b.field))}`;
|
|
822
1228
|
const dir = allDirs.find((d) => d.id === b.directiveId);
|
|
823
|
-
const gate = (
|
|
1229
|
+
const gate = (
|
|
1230
|
+
dir as { requiresRelationGate?: { relation: string } } | undefined
|
|
1231
|
+
)?.requiresRelationGate;
|
|
824
1232
|
methods.push(
|
|
825
1233
|
` Future<NomosOfferOutcome> ${helper}(${rowClass} conflict, ${mergedType} merged,`,
|
|
826
1234
|
` {Map<String, Object?> rest = const {}}) {`,
|
|
827
1235
|
...(gate !== undefined
|
|
828
1236
|
? [
|
|
829
1237
|
` if (actor == null || actor!.trim().isEmpty) {`,
|
|
830
|
-
` throw MissingOfferAuthority(domain: ${dartStr(
|
|
1238
|
+
` throw MissingOfferAuthority(domain: ${dartStr(
|
|
1239
|
+
domain
|
|
1240
|
+
)}, directiveId: ${dartStr(b.directiveId)}, requires: ${dartStr(
|
|
1241
|
+
gate.relation
|
|
1242
|
+
)});`,
|
|
831
1243
|
` }`,
|
|
832
1244
|
]
|
|
833
1245
|
: []),
|
|
834
|
-
` final data = <String, Object?>{...rest, ${dartStr(
|
|
835
|
-
|
|
1246
|
+
` final data = <String, Object?>{...rest, ${dartStr(
|
|
1247
|
+
b.idPayloadField
|
|
1248
|
+
)}: conflict.aggregateId, ${dartStr(b.valuePayloadField)}: merged};`,
|
|
1249
|
+
` return _offer(${dartStr(
|
|
1250
|
+
b.directiveId
|
|
1251
|
+
)}, data, conflictSnapshots: [<String, Object?>{`,
|
|
836
1252
|
` 'aggregateType': conflict.aggregateType, 'aggregateId': conflict.aggregateId,`,
|
|
837
1253
|
` 'field': conflict.field, 'targetSchema': conflict.targetSchema, 'token': conflict.snapshot,`,
|
|
838
1254
|
` }]);`,
|
|
839
1255
|
` }`,
|
|
840
|
-
|
|
1256
|
+
``
|
|
841
1257
|
);
|
|
842
1258
|
}
|
|
843
1259
|
|
|
@@ -895,11 +1311,14 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
895
1311
|
` byAggregate: Map.unmodifiable(byAggregate),`,
|
|
896
1312
|
` );`,
|
|
897
1313
|
` }`,
|
|
898
|
-
...[...byAggregate.keys()].map(
|
|
899
|
-
|
|
1314
|
+
...[...byAggregate.keys()].map(
|
|
1315
|
+
(aggregateId) =>
|
|
1316
|
+
` ${aggregateConflictsClass(aggregateId)} get ${lcFirst(
|
|
1317
|
+
camel(concreteClassName(aggregateId))
|
|
1318
|
+
)} => ${aggregateConflictsClass(aggregateId)}(_client);`
|
|
900
1319
|
),
|
|
901
1320
|
`}`,
|
|
902
|
-
|
|
1321
|
+
``
|
|
903
1322
|
);
|
|
904
1323
|
|
|
905
1324
|
for (const [aggregateId, bindings] of byAggregate) {
|
|
@@ -913,21 +1332,25 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
913
1332
|
` String get aggregateId;`,
|
|
914
1333
|
` String get field;`,
|
|
915
1334
|
`}`,
|
|
916
|
-
|
|
1335
|
+
``
|
|
917
1336
|
);
|
|
918
1337
|
for (const b of bindings) {
|
|
919
|
-
if (b.valuePayloadField === undefined || b.idPayloadField === undefined)
|
|
1338
|
+
if (b.valuePayloadField === undefined || b.idPayloadField === undefined)
|
|
1339
|
+
continue;
|
|
920
1340
|
const agg = aggById.get(b.aggregateId);
|
|
921
1341
|
const fieldKind = (agg?.fields[b.field] as Field | undefined)?.kind;
|
|
922
|
-
const mergedType =
|
|
923
|
-
|
|
924
|
-
|
|
1342
|
+
const mergedType =
|
|
1343
|
+
fieldKind === "string" || fieldKind === "ref" || fieldKind === "enum"
|
|
1344
|
+
? "String"
|
|
1345
|
+
: fieldKind === "int"
|
|
925
1346
|
? "int"
|
|
926
1347
|
: fieldKind === "bool"
|
|
927
|
-
|
|
928
|
-
|
|
1348
|
+
? "bool"
|
|
1349
|
+
: "Object?";
|
|
929
1350
|
const typeName = `${aggregateName}${cap(camel(b.field))}Conflict`;
|
|
930
|
-
const evolutionTypeName = `${aggregateName}${cap(
|
|
1351
|
+
const evolutionTypeName = `${aggregateName}${cap(
|
|
1352
|
+
camel(b.field)
|
|
1353
|
+
)}ConflictEvolutionRequired`;
|
|
931
1354
|
const helper = `_merge${aggregateName}${cap(camel(b.field))}`;
|
|
932
1355
|
conflictDeclarations.push(
|
|
933
1356
|
`/// Pending versions of \`${aggregateId}.${b.field}\`. [merge] commits a result that may`,
|
|
@@ -962,7 +1385,7 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
962
1385
|
` String get field => _details.field;`,
|
|
963
1386
|
` Map<String, dynamic> get evolution => _details.evolution ?? const {};`,
|
|
964
1387
|
`}`,
|
|
965
|
-
|
|
1388
|
+
``
|
|
966
1389
|
);
|
|
967
1390
|
}
|
|
968
1391
|
conflictDeclarations.push(
|
|
@@ -972,7 +1395,9 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
972
1395
|
` const ${aggregateConflictsClass(aggregateId)}(this._client);`,
|
|
973
1396
|
``,
|
|
974
1397
|
` Future<List<${aggregateConflict}>> pending() async {`,
|
|
975
|
-
` final rows = await _client._pendingConflictRows(${dartStr(
|
|
1398
|
+
` final rows = await _client._pendingConflictRows(${dartStr(
|
|
1399
|
+
runtimeAggregateType
|
|
1400
|
+
)});`,
|
|
976
1401
|
` final out = <${aggregateConflict}>[];`,
|
|
977
1402
|
` for (final row in rows) {`,
|
|
978
1403
|
` if (row.aggregateType != ${dartStr(runtimeAggregateType)}) {`,
|
|
@@ -980,9 +1405,15 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
980
1405
|
` }`,
|
|
981
1406
|
` switch (row.field) {`,
|
|
982
1407
|
...bindings.flatMap((b) => {
|
|
983
|
-
if (
|
|
1408
|
+
if (
|
|
1409
|
+
b.valuePayloadField === undefined ||
|
|
1410
|
+
b.idPayloadField === undefined
|
|
1411
|
+
)
|
|
1412
|
+
return [];
|
|
984
1413
|
const typeName = `${aggregateName}${cap(camel(b.field))}Conflict`;
|
|
985
|
-
const evolutionTypeName = `${aggregateName}${cap(
|
|
1414
|
+
const evolutionTypeName = `${aggregateName}${cap(
|
|
1415
|
+
camel(b.field)
|
|
1416
|
+
)}ConflictEvolutionRequired`;
|
|
986
1417
|
return [
|
|
987
1418
|
` case ${dartStr(b.field)}:`,
|
|
988
1419
|
` if (row.status == 'ready') {`,
|
|
@@ -999,7 +1430,7 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
999
1430
|
` return List.unmodifiable(out);`,
|
|
1000
1431
|
` }`,
|
|
1001
1432
|
`}`,
|
|
1002
|
-
|
|
1433
|
+
``
|
|
1003
1434
|
);
|
|
1004
1435
|
}
|
|
1005
1436
|
}
|
|
@@ -1016,168 +1447,250 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1016
1447
|
const aOwned = aScopes.some((scope) => scope.owned === true);
|
|
1017
1448
|
const aPrivate = needsReadPrincipal(agg.id);
|
|
1018
1449
|
const aScopesExpr = scopesExpr(aScopes);
|
|
1019
|
-
const runtimeType = isNamespacedAggregate(agg)
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1450
|
+
const runtimeType = isNamespacedAggregate(agg)
|
|
1451
|
+
? qualifyReadProjectionType(domain, agg.id)
|
|
1452
|
+
: agg.id;
|
|
1453
|
+
const byIdKeys =
|
|
1454
|
+
aScopes.length === 0
|
|
1455
|
+
? aPrivate
|
|
1456
|
+
? ", null, actor"
|
|
1457
|
+
: ""
|
|
1458
|
+
: aOwned
|
|
1459
|
+
? `, await _readKeys(${aScopesExpr}, [{${dartStr("type")}: ${dartStr(
|
|
1460
|
+
runtimeType
|
|
1461
|
+
)}, ${dartStr("id")}: id}])${aPrivate ? ", actor" : ""}`
|
|
1024
1462
|
: keysArg(agg.id);
|
|
1025
|
-
const pastByIdKeys =
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1463
|
+
const pastByIdKeys =
|
|
1464
|
+
aScopes.length === 0
|
|
1465
|
+
? aPrivate
|
|
1466
|
+
? ", null, _client.actor"
|
|
1467
|
+
: ""
|
|
1468
|
+
: aOwned
|
|
1469
|
+
? `, await _client._readKeys(${aScopesExpr}, [{${dartStr(
|
|
1470
|
+
"type"
|
|
1471
|
+
)}: ${dartStr(runtimeType)}, ${dartStr("id")}: id}])${
|
|
1472
|
+
aPrivate ? ", _client.actor" : ""
|
|
1473
|
+
}`
|
|
1029
1474
|
: pastKeysArg(agg.id);
|
|
1030
1475
|
methods.push(
|
|
1031
|
-
` /// Read one ${agg.id} by aggregate id.${
|
|
1476
|
+
` /// Read one ${agg.id} by aggregate id.${
|
|
1477
|
+
readScopesFor(agg.id).length
|
|
1478
|
+
? " Encrypted field(s) decrypt when the bound [keyStore] holds the scope key."
|
|
1479
|
+
: ""
|
|
1480
|
+
}`,
|
|
1032
1481
|
` Future<List<${model}>> ${safeMethod(`${lc}ById`)}(String id) async =>`,
|
|
1033
|
-
` (await bridge.queryById(id${byIdKeys})).map((r) => ${rowToModel(
|
|
1482
|
+
` (await bridge.queryById(id${byIdKeys})).map((r) => ${rowToModel(
|
|
1483
|
+
model,
|
|
1484
|
+
"r"
|
|
1485
|
+
)}).toList();`,
|
|
1034
1486
|
``,
|
|
1035
1487
|
` /// Watch one ${agg.id} by aggregate id (local reactive read).`,
|
|
1036
1488
|
` Stream<List<${model}>> watch${Cap}ById(String id) =>`,
|
|
1037
1489
|
aOwned
|
|
1038
|
-
? ` bridge.watchById(id, null${
|
|
1039
|
-
|
|
1490
|
+
? ` bridge.watchById(id, null${
|
|
1491
|
+
aPrivate ? ", actor" : ""
|
|
1492
|
+
}).asyncMap((_) async => bridge.queryById(id, await _readKeys(${aScopesExpr}, [{${dartStr(
|
|
1493
|
+
"type"
|
|
1494
|
+
)}: ${dartStr(runtimeType)}, ${dartStr("id")}: id}])${
|
|
1495
|
+
aPrivate ? ", actor" : ""
|
|
1496
|
+
})).map((rows) => rows.map((r) => ${rowToModel(
|
|
1497
|
+
model,
|
|
1498
|
+
"r"
|
|
1499
|
+
)}).toList());`
|
|
1500
|
+
: ` bridge.watchById(id${
|
|
1501
|
+
aPrivate ? ", null, actor" : ""
|
|
1502
|
+
}).map((rows) => rows.map((r) => ${rowToModel(
|
|
1503
|
+
model,
|
|
1504
|
+
"r"
|
|
1505
|
+
)}).toList());`,
|
|
1040
1506
|
``,
|
|
1041
1507
|
` /// Every ${agg.id} (the type-index probe — O(result), not a scan).`,
|
|
1042
1508
|
` Future<List<${model}>> ${safeMethod(`list${Cap}s`)}() async =>`,
|
|
1043
1509
|
aOwned
|
|
1044
|
-
? ` (await _readWithOwnedKeys(${aScopesExpr}, (keys) => bridge.query('aggregatesByType', {'__type': ${dartStr(
|
|
1045
|
-
|
|
1510
|
+
? ` (await _readWithOwnedKeys(${aScopesExpr}, (keys) => bridge.query('aggregatesByType', {'__type': ${dartStr(
|
|
1511
|
+
runtimeType
|
|
1512
|
+
)}}, keys${aPrivate ? ", actor" : ""}))).map((r) => ${rowToModel(
|
|
1513
|
+
model,
|
|
1514
|
+
"r"
|
|
1515
|
+
)}).toList();`
|
|
1516
|
+
: ` (await bridge.query('aggregatesByType', {'__type': ${dartStr(
|
|
1517
|
+
runtimeType
|
|
1518
|
+
)}}${keysArg(agg.id)})).map((r) => ${rowToModel(
|
|
1519
|
+
model,
|
|
1520
|
+
"r"
|
|
1521
|
+
)}).toList();`,
|
|
1046
1522
|
``,
|
|
1047
1523
|
` /// Watch every ${agg.id} (the same indexed local read, re-emitted after each change).`,
|
|
1048
1524
|
` Stream<List<${model}>> ${safeMethod(`watchAll${Cap}s`)}() =>`,
|
|
1049
1525
|
aOwned
|
|
1050
|
-
? ` bridge.watch('aggregatesByType', {'__type': ${dartStr(
|
|
1051
|
-
|
|
1052
|
-
|
|
1526
|
+
? ` bridge.watch('aggregatesByType', {'__type': ${dartStr(
|
|
1527
|
+
runtimeType
|
|
1528
|
+
)}}, null${
|
|
1529
|
+
aPrivate ? ", actor" : ""
|
|
1530
|
+
}).asyncMap((_) => _readWithOwnedKeys(${aScopesExpr}, (keys) => bridge.query('aggregatesByType', {'__type': ${dartStr(
|
|
1531
|
+
runtimeType
|
|
1532
|
+
)}}, keys${
|
|
1533
|
+
aPrivate ? ", actor" : ""
|
|
1534
|
+
}))).map((rows) => rows.map((r) => ${rowToModel(
|
|
1535
|
+
model,
|
|
1536
|
+
"r"
|
|
1537
|
+
)}).toList());`
|
|
1538
|
+
: ` bridge.watch('aggregatesByType', {'__type': ${dartStr(
|
|
1539
|
+
runtimeType
|
|
1540
|
+
)}}${
|
|
1541
|
+
aPrivate ? ", null, actor" : ""
|
|
1542
|
+
}).map((rows) => rows.map((r) => ${rowToModel(
|
|
1543
|
+
model,
|
|
1544
|
+
"r"
|
|
1545
|
+
)}).toList());`,
|
|
1546
|
+
``
|
|
1053
1547
|
);
|
|
1054
1548
|
pastMethods.push(
|
|
1055
1549
|
` /// Read one ${agg.id} by aggregate id in the verified world at [moment].`,
|
|
1056
1550
|
` Future<List<${model}>> ${safeMethod(`${lc}ById`)}(String id) async =>`,
|
|
1057
|
-
` (await _client.bridge.queryByIdAt(moment, id${pastByIdKeys})).map((r) => ${rowToModel(
|
|
1551
|
+
` (await _client.bridge.queryByIdAt(moment, id${pastByIdKeys})).map((r) => ${rowToModel(
|
|
1552
|
+
model,
|
|
1553
|
+
"r",
|
|
1554
|
+
"_client._ws"
|
|
1555
|
+
)}).toList();`,
|
|
1058
1556
|
``,
|
|
1059
1557
|
` /// Every ${agg.id} in the verified world at [moment].`,
|
|
1060
1558
|
` Future<List<${model}>> ${safeMethod(`list${Cap}s`)}() async =>`,
|
|
1061
1559
|
aOwned
|
|
1062
|
-
? ` (await _client._readWithOwnedKeys(${aScopesExpr}, (keys) => _client.bridge.queryAt(moment, 'aggregatesByType', {'__type': ${dartStr(
|
|
1063
|
-
|
|
1064
|
-
|
|
1560
|
+
? ` (await _client._readWithOwnedKeys(${aScopesExpr}, (keys) => _client.bridge.queryAt(moment, 'aggregatesByType', {'__type': ${dartStr(
|
|
1561
|
+
runtimeType
|
|
1562
|
+
)}}, keys${
|
|
1563
|
+
aPrivate ? ", _client.actor" : ""
|
|
1564
|
+
}))).map((r) => ${rowToModel(model, "r", "_client._ws")}).toList();`
|
|
1565
|
+
: ` (await _client.bridge.queryAt(moment, 'aggregatesByType', {'__type': ${dartStr(
|
|
1566
|
+
runtimeType
|
|
1567
|
+
)}}${pastKeysArg(agg.id)})).map((r) => ${rowToModel(
|
|
1568
|
+
model,
|
|
1569
|
+
"r",
|
|
1570
|
+
"_client._ws"
|
|
1571
|
+
)}).toList();`,
|
|
1572
|
+
``
|
|
1065
1573
|
);
|
|
1066
1574
|
}
|
|
1067
1575
|
|
|
1068
|
-
if (opts.emitActivity === true)
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1576
|
+
if (opts.emitActivity === true)
|
|
1577
|
+
methods.push(
|
|
1578
|
+
` /// Adopt THIS generated model. Nomos proves it against current history; a deliberate`,
|
|
1579
|
+
` /// difference returns a decision whose accept(reason) re-proves and records the reason.`,
|
|
1580
|
+
` NomosModel get model => NomosModel(_adoptGeneratedModel, _restorePreviousGeneratedModel);`,
|
|
1581
|
+
``,
|
|
1582
|
+
` NomosModelReview _nomosModelReview(Map<String, Object?>? report) {`,
|
|
1583
|
+
` final differences = <NomosModelDifference>[];`,
|
|
1584
|
+
` for (final raw in ((report?['divergences'] as List?) ?? const [])) {`,
|
|
1585
|
+
` final difference = (raw as Map).cast<String, Object?>();`,
|
|
1586
|
+
` final rawArea = difference['domain'];`,
|
|
1587
|
+
` final area = rawArea is String && !rawArea.contains('nomos_internal') ? rawArea : null;`,
|
|
1588
|
+
` final rawAction = difference['directiveId'];`,
|
|
1589
|
+
` final action = rawAction is String && !rawAction.contains('nomos_internal') ? rawAction : null;`,
|
|
1590
|
+
` final rawKind = difference['kind']?.toString();`,
|
|
1591
|
+
` final kind = rawKind == 'plan-divergence'`,
|
|
1592
|
+
` ? NomosModelDifferenceKind.outcome`,
|
|
1593
|
+
` : rawKind == 'unsupported-premise' || rawKind == 'read-conflict'`,
|
|
1594
|
+
` ? NomosModelDifferenceKind.evidence`,
|
|
1595
|
+
` : NomosModelDifferenceKind.rules;`,
|
|
1596
|
+
` final subject = action ?? (kind == NomosModelDifferenceKind.outcome ? 'A business action' : 'A historical action');`,
|
|
1597
|
+
` final summary = kind == NomosModelDifferenceKind.outcome`,
|
|
1598
|
+
` ? '\$subject would produce a different result under this model.'`,
|
|
1599
|
+
` : kind == NomosModelDifferenceKind.evidence`,
|
|
1600
|
+
` ? 'Nomos could not reproduce all evidence needed by \$subject.'`,
|
|
1601
|
+
` : "\$subject is not accepted by this model's rules.";`,
|
|
1602
|
+
` differences.add(NomosModelDifference(kind: kind, area: area, action: action, summary: summary));`,
|
|
1603
|
+
` }`,
|
|
1604
|
+
` final evolve = (report?['evolve'] as Map?)?.cast<String, Object?>();`,
|
|
1605
|
+
` if (report != null && evolve?['ok'] != true && !differences.any((d) => d.kind == NomosModelDifferenceKind.rules)) {`,
|
|
1606
|
+
` differences.add(const NomosModelDifference(kind: NomosModelDifferenceKind.rules, summary: 'This model does not yet explain how to interpret every existing value.'));`,
|
|
1607
|
+
` }`,
|
|
1608
|
+
` if (report != null && (report['sourceVerified'] != true || report['complete'] != true)`,
|
|
1609
|
+
` && !differences.any((d) => d.kind == NomosModelDifferenceKind.evidence)) {`,
|
|
1610
|
+
` differences.add(const NomosModelDifference(kind: NomosModelDifferenceKind.evidence, summary: 'Nomos could not re-prove every premise in the current business history.'));`,
|
|
1611
|
+
` }`,
|
|
1612
|
+
` final projection = (report?['projection'] as Map?)?.cast<String, Object?>();`,
|
|
1613
|
+
` int number(String name) => (report?[name] as num?)?.toInt() ?? 0;`,
|
|
1614
|
+
` return NomosModelReview(`,
|
|
1615
|
+
` checkedActions: number('replayedIntents'), candidateActions: number('candidateIntents'),`,
|
|
1616
|
+
` sameOutcome: projection?['equal'] as bool?, differences: differences);`,
|
|
1617
|
+
` }`,
|
|
1618
|
+
``,
|
|
1619
|
+
` Future<NomosModelAdoption> _adoptGeneratedModel([String? because]) async {`,
|
|
1620
|
+
` final raw = await bridge.nomosFrameworkAdoptGeneratedModel(`,
|
|
1621
|
+
` domain: ${dartStr(
|
|
1622
|
+
domain
|
|
1623
|
+
)}, candidateHash: domainHash, because: because);`,
|
|
1624
|
+
` final reportRaw = raw['report'];`,
|
|
1625
|
+
` final report = reportRaw is Map ? reportRaw.cast<String, Object?>() : null;`,
|
|
1626
|
+
` final review = _nomosModelReview(report);`,
|
|
1627
|
+
` final stage = raw['stage']?.toString();`,
|
|
1628
|
+
` if (stage == 'promoted' && raw['ok'] == true) {`,
|
|
1629
|
+
` return NomosModelAdoption.adopted(`,
|
|
1630
|
+
` summary: 'Nomos proved this model against current history and made it active.', review: review);`,
|
|
1631
|
+
` }`,
|
|
1632
|
+
` if (stage == 'acceptance') {`,
|
|
1633
|
+
` return NomosModelAdoption.needsDecision(`,
|
|
1634
|
+
` summary: 'This model would change prior business outcomes. Review the differences and record why that is intended.',`,
|
|
1635
|
+
` review: review, accept: (reason) => _adoptGeneratedModel(reason));`,
|
|
1636
|
+
` }`,
|
|
1637
|
+
` final evolve = (report?['evolve'] as Map?)?.cast<String, Object?>();`,
|
|
1638
|
+
` final summary = stage == 'identity'`,
|
|
1639
|
+
` ? 'Sign in before adopting this model so the decision is accountable.'`,
|
|
1640
|
+
` : report != null && report['sourceVerified'] != true`,
|
|
1641
|
+
` ? 'The current history could not be verified. Nothing changed and the current model remains active.'`,
|
|
1642
|
+
` : report != null && report['complete'] != true`,
|
|
1643
|
+
` ? 'Nomos could not re-prove every historical dependency. Nothing changed and the current model remains active.'`,
|
|
1644
|
+
` : report != null && evolve?['ok'] != true`,
|
|
1645
|
+
` ? 'This model cannot yet interpret every existing value. Nothing changed and the current model remains active.'`,
|
|
1646
|
+
` : 'The workspace changed or refused this adoption. Nothing changed; ask Nomos to try again.';`,
|
|
1647
|
+
` return NomosModelAdoption.blocked(summary: summary, review: review);`,
|
|
1648
|
+
` }`,
|
|
1649
|
+
``,
|
|
1650
|
+
` Future<NomosModelRestoration> _restorePreviousGeneratedModel([String? because]) async {`,
|
|
1651
|
+
` final raw = await bridge.nomosFrameworkRestorePreviousGeneratedModel(`,
|
|
1652
|
+
` domain: ${dartStr(
|
|
1653
|
+
domain
|
|
1654
|
+
)}, currentHash: domainHash, because: because);`,
|
|
1655
|
+
` final reportRaw = raw['report'];`,
|
|
1656
|
+
` final report = reportRaw is Map ? reportRaw.cast<String, Object?>() : null;`,
|
|
1657
|
+
` final review = _nomosModelReview(report);`,
|
|
1658
|
+
` final stage = raw['stage']?.toString();`,
|
|
1659
|
+
` if (stage == 'restored' && raw['ok'] == true) {`,
|
|
1660
|
+
` return NomosModelRestoration.restored(`,
|
|
1661
|
+
` summary: 'Nomos re-proved current history and restored the previous model.', review: review);`,
|
|
1662
|
+
` }`,
|
|
1663
|
+
` if (stage == 'acceptance') {`,
|
|
1664
|
+
` return NomosModelRestoration.needsDecision(`,
|
|
1665
|
+
` summary: 'Restoring the previous model changes which rules govern future work. Review the effects and record why restoration is intended.',`,
|
|
1666
|
+
` review: review, accept: (reason) => _restorePreviousGeneratedModel(reason));`,
|
|
1667
|
+
` }`,
|
|
1668
|
+
` final evolve = (report?['evolve'] as Map?)?.cast<String, Object?>();`,
|
|
1669
|
+
` final error = raw['error']?.toString();`,
|
|
1670
|
+
` final summary = stage == 'identity'`,
|
|
1671
|
+
` ? 'Sign in before restoring a model so the decision is accountable.'`,
|
|
1672
|
+
` : error?.contains('restore-no-previous') == true`,
|
|
1673
|
+
` ? 'This is the first model for this business; there is no previous model to restore.'`,
|
|
1674
|
+
` : error?.contains('restore-current-changed') == true`,
|
|
1675
|
+
` ? 'The current model changed before restoration could finish. Nothing changed; ask Nomos to review again.'`,
|
|
1676
|
+
` : report != null && report['sourceVerified'] != true`,
|
|
1677
|
+
` ? 'The current history could not be verified. Nothing changed and the current model remains active.'`,
|
|
1678
|
+
` : report != null && report['complete'] != true`,
|
|
1679
|
+
` ? 'Nomos could not re-prove every historical dependency. Nothing changed and the current model remains active.'`,
|
|
1680
|
+
` : report != null && evolve?['ok'] != true`,
|
|
1681
|
+
` ? 'The previous model cannot safely interpret every existing value. Nothing changed and the current model remains active.'`,
|
|
1682
|
+
` : 'The workspace refused restoration. Nothing changed and the current model remains active.';`,
|
|
1683
|
+
` return NomosModelRestoration.blocked(summary: summary, review: review);`,
|
|
1684
|
+
` }`,
|
|
1685
|
+
``
|
|
1686
|
+
);
|
|
1174
1687
|
|
|
1175
1688
|
const pastClass = `${className}Past`;
|
|
1176
1689
|
const atClass = `${className}At`;
|
|
1177
1690
|
methods.push(
|
|
1178
1691
|
` /// Read the same typed business world at a verified point in its history.`,
|
|
1179
1692
|
` ${pastClass} get past => ${pastClass}(this);`,
|
|
1180
|
-
|
|
1693
|
+
``
|
|
1181
1694
|
);
|
|
1182
1695
|
pastDeclarations.push(
|
|
1183
1696
|
`/// The accountable timeline for a [${className}]. Commit identities remain opaque inside [NomosMoment].`,
|
|
@@ -1187,10 +1700,19 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1187
1700
|
``,
|
|
1188
1701
|
` Future<List<NomosMoment>> moments({int limit = 100}) async {`,
|
|
1189
1702
|
` final moments = await _client.bridge.moments(limit: limit);`,
|
|
1190
|
-
` const actions = <String>{${[
|
|
1703
|
+
` const actions = <String>{${[
|
|
1704
|
+
...allDirs.map((directive) => directive.id),
|
|
1705
|
+
...(correctionActions.length > 0 ? [BUSINESS_CORRECTION_DIRECTIVE] : []),
|
|
1706
|
+
]
|
|
1707
|
+
.map(dartStr)
|
|
1708
|
+
.join(", ")}};`,
|
|
1191
1709
|
` return moments.where((moment) =>`,
|
|
1192
|
-
` moment.domain == ${dartStr(
|
|
1193
|
-
|
|
1710
|
+
` moment.domain == ${dartStr(
|
|
1711
|
+
domain
|
|
1712
|
+
)} && moment.action != null && actions.contains(moment.action)).map((moment) =>`,
|
|
1713
|
+
` moment.action == ${dartStr(
|
|
1714
|
+
BUSINESS_CORRECTION_DIRECTIVE
|
|
1715
|
+
)} ? NomosMoment(`,
|
|
1194
1716
|
` id: moment.id, occurredAt: moment.occurredAt, performedBy: moment.performedBy, domain: moment.domain, action: 'correction') : moment).toList();`,
|
|
1195
1717
|
` }`,
|
|
1196
1718
|
` ${atClass} at(NomosMoment moment) => ${atClass}(_client, moment);`,
|
|
@@ -1204,7 +1726,7 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1204
1726
|
``,
|
|
1205
1727
|
...pastMethods,
|
|
1206
1728
|
`}`,
|
|
1207
|
-
|
|
1729
|
+
``
|
|
1208
1730
|
);
|
|
1209
1731
|
|
|
1210
1732
|
// ── REF-TRAVERSAL ACCESSORS (dart_codegen_parity.md P2 + §2a follow-on — ref ergonomics) ──
|
|
@@ -1239,16 +1761,34 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1239
1761
|
const model = readModelClassName(agg.id);
|
|
1240
1762
|
const entries = Object.entries(agg.fields) as [string, Field][];
|
|
1241
1763
|
const sameDomainRefs = entries.filter(
|
|
1242
|
-
([, f]) =>
|
|
1764
|
+
([, f]) =>
|
|
1765
|
+
f.kind === "ref" &&
|
|
1766
|
+
f.foreignRef === undefined &&
|
|
1767
|
+
f.refAggregateId !== undefined &&
|
|
1768
|
+
aggById.has(f.refAggregateId!)
|
|
1243
1769
|
);
|
|
1244
1770
|
const crossDomainRefs = entries.filter(([, f]) => {
|
|
1245
|
-
if (
|
|
1771
|
+
if (
|
|
1772
|
+
!(
|
|
1773
|
+
f.kind === "ref" &&
|
|
1774
|
+
f.foreignRef === undefined &&
|
|
1775
|
+
f.refAggregateId !== undefined
|
|
1776
|
+
)
|
|
1777
|
+
)
|
|
1778
|
+
return false;
|
|
1246
1779
|
if (aggById.has(f.refAggregateId!)) return false; // same-domain — handled above
|
|
1247
1780
|
const owner = pkgDomains[f.refAggregateId!];
|
|
1248
1781
|
return owner !== undefined && owner.domain !== domain; // resolvable on a SIBLING domain's client
|
|
1249
1782
|
});
|
|
1250
|
-
const foreignRefs = entries.filter(
|
|
1251
|
-
|
|
1783
|
+
const foreignRefs = entries.filter(
|
|
1784
|
+
([, f]) => f.kind === "ref" && f.foreignRef !== undefined
|
|
1785
|
+
);
|
|
1786
|
+
if (
|
|
1787
|
+
sameDomainRefs.length === 0 &&
|
|
1788
|
+
crossDomainRefs.length === 0 &&
|
|
1789
|
+
foreignRefs.length === 0
|
|
1790
|
+
)
|
|
1791
|
+
continue;
|
|
1252
1792
|
const lines: string[] = [
|
|
1253
1793
|
`/// Ref-traversal accessors for [${model}] (dart_codegen_parity.md P2) — resolve a stored ref id`,
|
|
1254
1794
|
`/// to the actual target row via the SAME by-id read lane already exposed above.`,
|
|
@@ -1263,7 +1803,10 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1263
1803
|
const member = dartMemberName(fieldName);
|
|
1264
1804
|
// `siteId` → `site` (the common `<name>Id` ref-field convention); anything else gets a `Ref`
|
|
1265
1805
|
// suffix so the resolver method never collides with the stored field's own getter.
|
|
1266
|
-
const methodStem =
|
|
1806
|
+
const methodStem =
|
|
1807
|
+
member.length > 2 && member.endsWith("Id")
|
|
1808
|
+
? member.slice(0, -2)
|
|
1809
|
+
: `${member}Ref`;
|
|
1267
1810
|
const method = safeMethod(methodStem);
|
|
1268
1811
|
// NULLABILITY: the read-model member's Dart type follows the PROJECTION's own nullability, not
|
|
1269
1812
|
// the DSL schema's `.optional()` — a partial fold decodes every scalar `null` when absent
|
|
@@ -1281,10 +1824,12 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1281
1824
|
` if (id == null) return null;`,
|
|
1282
1825
|
` final rows = await client.${byIdMethod}(NmsInternalNomosRefs.id(id));`,
|
|
1283
1826
|
]
|
|
1284
|
-
: [
|
|
1827
|
+
: [
|
|
1828
|
+
` final rows = await client.${byIdMethod}(NmsInternalNomosRefs.id(${member}));`,
|
|
1829
|
+
]),
|
|
1285
1830
|
` return rows.isEmpty ? null : rows.first;`,
|
|
1286
1831
|
` }`,
|
|
1287
|
-
|
|
1832
|
+
``
|
|
1288
1833
|
);
|
|
1289
1834
|
}
|
|
1290
1835
|
for (const [fieldName, f] of crossDomainRefs) {
|
|
@@ -1301,7 +1846,10 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1301
1846
|
addImport(importUri, targetClient);
|
|
1302
1847
|
addImport(importUri, targetModel);
|
|
1303
1848
|
const member = dartMemberName(fieldName);
|
|
1304
|
-
const methodStem =
|
|
1849
|
+
const methodStem =
|
|
1850
|
+
member.length > 2 && member.endsWith("Id")
|
|
1851
|
+
? member.slice(0, -2)
|
|
1852
|
+
: `${member}Ref`;
|
|
1305
1853
|
const method = safeMethod(methodStem);
|
|
1306
1854
|
const memberNullable = admitsNull(f) || f.isFromBirth !== true;
|
|
1307
1855
|
const memberValue = admitsNull(f) ? `${member}.valueOrNull` : member;
|
|
@@ -1317,10 +1865,12 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1317
1865
|
` if (id == null) return null;`,
|
|
1318
1866
|
` final rows = await client.${byIdMethod}(NmsInternalNomosRefs.id(id));`,
|
|
1319
1867
|
]
|
|
1320
|
-
: [
|
|
1868
|
+
: [
|
|
1869
|
+
` final rows = await client.${byIdMethod}(NmsInternalNomosRefs.id(${member}));`,
|
|
1870
|
+
]),
|
|
1321
1871
|
` return rows.isEmpty ? null : rows.first;`,
|
|
1322
1872
|
` }`,
|
|
1323
|
-
|
|
1873
|
+
``
|
|
1324
1874
|
);
|
|
1325
1875
|
}
|
|
1326
1876
|
for (const [fieldName, f] of foreignRefs) {
|
|
@@ -1333,14 +1883,16 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1333
1883
|
// raw `ForeignRef<T>` field — no resolver, no dangling type reference.
|
|
1334
1884
|
if (fr.targetIsContract === true) continue;
|
|
1335
1885
|
const owner = pkgDomains[targetType];
|
|
1336
|
-
const targetInThisDomain =
|
|
1886
|
+
const targetInThisDomain =
|
|
1887
|
+
aggById.has(targetType) || owner?.name === thisDomainName;
|
|
1337
1888
|
if (!targetInThisDomain) {
|
|
1338
1889
|
if (owner === undefined) continue; // target not in this package — unreachable, skip the resolver
|
|
1339
1890
|
// LAYERED-packages mode: the sibling domain's TYPES file lives in the TYPES package, not
|
|
1340
1891
|
// beside this client file — a relative `<name>.dart` import points at a nonexistent path in
|
|
1341
1892
|
// the client package (the co2 0.75.0 regression). The types-barrel import above already
|
|
1342
1893
|
// re-exports every generated concrete class, so the extra import is only needed FLAT.
|
|
1343
|
-
if (opts.typesImportUri === undefined)
|
|
1894
|
+
if (opts.typesImportUri === undefined)
|
|
1895
|
+
addImport(`${owner.name}.dart`, concreteClassName(targetType));
|
|
1344
1896
|
}
|
|
1345
1897
|
const targetConcrete = concreteClassName(targetType);
|
|
1346
1898
|
const member = dartMemberName(fieldName);
|
|
@@ -1358,7 +1910,7 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1358
1910
|
` if (ref == null) return Future<${targetConcrete}?>.value(null);`,
|
|
1359
1911
|
` return NmsInternalForeignRefs.resolve(ref, engine.foreignReader(cloud: cloud));`,
|
|
1360
1912
|
` }`,
|
|
1361
|
-
|
|
1913
|
+
``
|
|
1362
1914
|
);
|
|
1363
1915
|
}
|
|
1364
1916
|
lines.push(`}`);
|
|
@@ -1372,19 +1924,28 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1372
1924
|
` /// [ActivityEvent]s (automatic baseline; \`.activity(...)\` enriches it). Re-emits on every new`,
|
|
1373
1925
|
` /// committed intent; a directive`,
|
|
1374
1926
|
` /// outside this generated business package is dropped. Cancel the subscription to stop.`,
|
|
1375
|
-
`
|
|
1376
|
-
`
|
|
1927
|
+
` /// A bounded recent activity feed. [limit] is required and kernel-capped.`,
|
|
1928
|
+
` Stream<List<ActivityEvent>> watchRecentActivity({required int limit}) =>`,
|
|
1929
|
+
` bridge.watchActivity(limit: limit).map((rows) => rows`,
|
|
1377
1930
|
` .map((r) => renderActivity((r as Map).cast<String, dynamic>()))`,
|
|
1378
1931
|
` .whereType<ActivityEvent>()`,
|
|
1379
1932
|
` .toList());`,
|
|
1380
1933
|
``,
|
|
1381
|
-
` ///
|
|
1382
|
-
` Future<List<ActivityEvent>>
|
|
1934
|
+
` /// A bounded one-shot recent activity feed. [limit] is required and kernel-capped.`,
|
|
1935
|
+
` Future<List<ActivityEvent>> recentActivity({String? base, required int limit}) async =>`,
|
|
1383
1936
|
` (await bridge.activityLog(base: base, limit: limit))`,
|
|
1384
1937
|
` .map((r) => renderActivity(r.cast<String, dynamic>()))`,
|
|
1385
1938
|
` .whereType<ActivityEvent>()`,
|
|
1386
1939
|
` .toList();`,
|
|
1387
1940
|
``,
|
|
1941
|
+
` @Deprecated('Reconstructs ledger history. Use recentActivity with an explicit bounded limit, or a DSL managed count for quantities.')`,
|
|
1942
|
+
` Future<List<ActivityEvent>> activityLog({String? base, int? limit}) =>`,
|
|
1943
|
+
` recentActivity(base: base, limit: limit ?? 50);`,
|
|
1944
|
+
``,
|
|
1945
|
+
` @Deprecated('Reconstructs ledger history. Use watchRecentActivity with an explicit bounded limit.')`,
|
|
1946
|
+
` Stream<List<ActivityEvent>> watchActivity() =>`,
|
|
1947
|
+
` watchRecentActivity(limit: 50);`,
|
|
1948
|
+
``,
|
|
1388
1949
|
` /// THE SELF-DESCRIPTION: root links to exactly six grounded pages — model, current state, rules,`,
|
|
1389
1950
|
` /// history, decisions and evidence. The old events/transcript fields remain on [NomosDescription].`,
|
|
1390
1951
|
` Future<NomosDescription> describe({`,
|
|
@@ -1576,7 +2137,7 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1576
2137
|
` .toList();`,
|
|
1577
2138
|
` return activityNarrative(events);`,
|
|
1578
2139
|
` }`,
|
|
1579
|
-
|
|
2140
|
+
``
|
|
1580
2141
|
);
|
|
1581
2142
|
}
|
|
1582
2143
|
|
|
@@ -1592,21 +2153,32 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1592
2153
|
``,
|
|
1593
2154
|
// `dart:convert` is needed ONLY for the interface block's `jsonDecode` — the offer lane no longer encodes
|
|
1594
2155
|
// any envelope (it routes through `bridge.offerDirective` → THE ONE ENCODER). Omit it when no interface.
|
|
1595
|
-
...(clientInterface !== undefined ||
|
|
2156
|
+
...(clientInterface !== undefined ||
|
|
2157
|
+
roleHelpers !== "" ||
|
|
2158
|
+
opts.emitActivity === true
|
|
2159
|
+
? [`import 'dart:convert';`, ``]
|
|
2160
|
+
: []),
|
|
1596
2161
|
`import '${supportImport}';`,
|
|
1597
2162
|
// Layered-packages mode: the sibling types live in their OWN package — one barrel import
|
|
1598
2163
|
// replaces both the relative types import and the relative framework import.
|
|
1599
|
-
...(opts.typesImportUri !== undefined
|
|
2164
|
+
...(opts.typesImportUri !== undefined
|
|
2165
|
+
? [`import '${opts.typesImportUri}';`]
|
|
2166
|
+
: [`import '${mod.name}.dart';`]),
|
|
1600
2167
|
// EXPLICIT-BARREL layered mode: the types barrel withholds per-domain classes, so import the defining
|
|
1601
2168
|
// per-domain TYPES files directly (this domain's own + any sibling whose classes this client names).
|
|
1602
2169
|
// The client header already `ignore_for_file: unused_import`, so an over-broad set is harmless.
|
|
1603
2170
|
...(opts.explicitTypeImports ?? []).map((uri) => `import '${uri}';`),
|
|
1604
2171
|
// ACTIVITY FEED (flat mode): the shared pure `renderActivity` lives in the sibling `activity_registry.dart`
|
|
1605
2172
|
// (in layered mode it resolves through the types-package barrel above). Only when the package opts in.
|
|
1606
|
-
...(opts.emitActivity === true && opts.activityImportUri !== undefined
|
|
2173
|
+
...(opts.emitActivity === true && opts.activityImportUri !== undefined
|
|
2174
|
+
? [`import '${opts.activityImportUri}';`]
|
|
2175
|
+
: []),
|
|
1607
2176
|
// Framework-shared aggregate types (RelationTupleReadModel, …) live in the ONE shared file — import
|
|
1608
2177
|
// them (the by-id accessors reference them) and re-export them (once per package, Dart de-dups).
|
|
1609
|
-
...(opts.typesImportUri === undefined &&
|
|
2178
|
+
...(opts.typesImportUri === undefined &&
|
|
2179
|
+
opts.frameworkImportUri !== undefined
|
|
2180
|
+
? [`import '${opts.frameworkImportUri}';`]
|
|
2181
|
+
: []),
|
|
1610
2182
|
// CROSS-DOMAIN REF TRAVERSAL (§2a follow-on): a cross-domain resolver references a SIBLING domain's
|
|
1611
2183
|
// generated client class + read model (from `<name>.client.dart`, co-located with this file in BOTH
|
|
1612
2184
|
// modes) or — FLAT MODE ONLY — a foreignRef target's concrete class from the sibling TYPES
|
|
@@ -1615,19 +2187,29 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1615
2187
|
// Empty for a domain with no cross-domain ref (byte-identical to before this existed).
|
|
1616
2188
|
...[...crossDomainImports.entries()]
|
|
1617
2189
|
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
1618
|
-
.map(
|
|
2190
|
+
.map(
|
|
2191
|
+
([uri, names]) =>
|
|
2192
|
+
`import '${uri}' show ${[...names].sort().join(", ")};`
|
|
2193
|
+
),
|
|
1619
2194
|
`// Re-export business types only. The runtime import above is an implementation dependency, never`,
|
|
1620
2195
|
`// a tenant API: exporting it would create a second raw bridge/session/sync application lane.`,
|
|
1621
|
-
...(opts.typesImportUri !== undefined
|
|
1622
|
-
|
|
2196
|
+
...(opts.typesImportUri !== undefined
|
|
2197
|
+
? [`export '${opts.typesImportUri}';`]
|
|
2198
|
+
: [`export '${mod.name}.dart';`]),
|
|
2199
|
+
...(opts.typesImportUri === undefined &&
|
|
2200
|
+
opts.frameworkImportUri !== undefined
|
|
2201
|
+
? [`export '${opts.frameworkImportUri}';`]
|
|
2202
|
+
: []),
|
|
1623
2203
|
``,
|
|
1624
|
-
`///
|
|
2204
|
+
`/// Content identity of the installed typed USDA closure (transported as \`.law.usdz\`).`,
|
|
1625
2205
|
`const String ${hashConst} = ${dartStr(opts.domainHash)};`,
|
|
1626
2206
|
``,
|
|
1627
2207
|
...(opts.emitActivity === true
|
|
1628
2208
|
? [
|
|
1629
2209
|
`/// Compiler-known business language and policy; verified runtime facts join it behind describe().`,
|
|
1630
|
-
`const String _nomosDescribeModelJson = ${dartRawJson(
|
|
2210
|
+
`const String _nomosDescribeModelJson = ${dartRawJson(
|
|
2211
|
+
describeModel
|
|
2212
|
+
)};`,
|
|
1631
2213
|
``,
|
|
1632
2214
|
]
|
|
1633
2215
|
: []),
|
|
@@ -1637,7 +2219,9 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1637
2219
|
`/// shapes) + aggregates (read shapes, sid-keyed) it depends on. \`connect()\` negotiates this`,
|
|
1638
2220
|
`/// against the workspace's installed offer by SHAPE (stable-id subtyping), not by law hash,`,
|
|
1639
2221
|
`/// so the domain can evolve without republishing this client.`,
|
|
1640
|
-
`const String ${interfaceConstNameDart(mod.name)} = ${dartRawJson(
|
|
2222
|
+
`const String ${interfaceConstNameDart(mod.name)} = ${dartRawJson(
|
|
2223
|
+
clientInterface
|
|
2224
|
+
)};`,
|
|
1641
2225
|
``,
|
|
1642
2226
|
]
|
|
1643
2227
|
: []),
|
|
@@ -1646,7 +2230,9 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1646
2230
|
`/// The CLIENT/LAW COMPAT MANIFEST (§5) as JSON — the law hash, structural interface hash, and`,
|
|
1647
2231
|
`/// generator provenance this client was generated against. A DIAGNOSIS surface for connect-time`,
|
|
1648
2232
|
`/// classification (silent / shape-compatible / incompatible); it NEVER gates.`,
|
|
1649
|
-
`const String ${compatConstNameDart(mod.name)} = ${dartRawJson(
|
|
2233
|
+
`const String ${compatConstNameDart(mod.name)} = ${dartRawJson(
|
|
2234
|
+
opts.compat
|
|
2235
|
+
)};`,
|
|
1650
2236
|
``,
|
|
1651
2237
|
]
|
|
1652
2238
|
: []),
|
|
@@ -1676,21 +2262,31 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1676
2262
|
` final NomosKeyStore? keyStore;`,
|
|
1677
2263
|
]
|
|
1678
2264
|
: []),
|
|
1679
|
-
` ${className}(this.bridge, {this.domainHash = ${hashConst}, this.actor${
|
|
2265
|
+
` ${className}(this.bridge, {this.domainHash = ${hashConst}, this.actor${
|
|
2266
|
+
manualEncryptionPresent ? ", this.keyStore" : ""
|
|
2267
|
+
}});`,
|
|
1680
2268
|
``,
|
|
1681
2269
|
` /// THE STATIC INTERFACE DESCRIPTOR (session_bound_clients.md asks 5/9): what this client EXPECTS`,
|
|
1682
2270
|
` /// of the workspace it binds to — the domain key, the law hash it was generated against, and the`,
|
|
1683
2271
|
` /// structural interface (#72). Consumed by [bind] and by \`session.assertCompatible(${className}.interface)\`.`,
|
|
1684
2272
|
` /// The kernel capabilities this law REQUIRES (release coherence): a compound-key query needs`,
|
|
1685
2273
|
` /// 'compoundKeys', etc. [bind] asserts the connected cloud's kernel advertises these.`,
|
|
1686
|
-
` static const List<String> requiresKernel = [${requiresKernelFeats
|
|
2274
|
+
` static const List<String> requiresKernel = [${requiresKernelFeats
|
|
2275
|
+
.map((f) => dartStr(f))
|
|
2276
|
+
.join(", ")}];`,
|
|
1687
2277
|
``,
|
|
1688
2278
|
` static const NomosClientInterface interface = NomosClientInterface(`,
|
|
1689
2279
|
` domain: ${dartStr(domain)},`,
|
|
1690
2280
|
` expectedDomainHash: ${hashConst},`,
|
|
1691
|
-
...(compatInterfaceHash !== undefined
|
|
1692
|
-
|
|
1693
|
-
|
|
2281
|
+
...(compatInterfaceHash !== undefined
|
|
2282
|
+
? [` expectedInterfaceHash: ${dartStr(compatInterfaceHash)},`]
|
|
2283
|
+
: []),
|
|
2284
|
+
...(clientInterface !== undefined
|
|
2285
|
+
? [` interfaceJson: ${interfaceConstNameDart(mod.name)},`]
|
|
2286
|
+
: []),
|
|
2287
|
+
...(opts.compat !== undefined
|
|
2288
|
+
? [` compatJson: ${compatConstNameDart(mod.name)},`]
|
|
2289
|
+
: []),
|
|
1694
2290
|
` );`,
|
|
1695
2291
|
``,
|
|
1696
2292
|
` /// THE SESSION-BOUND CONSTRUCTION PATH (session_bound_clients.md asks 2/5): bind this client to`,
|
|
@@ -1700,7 +2296,9 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1700
2296
|
` /// IMMEDIATELY — at provider construction, not two clicks into the app. The synchronous`,
|
|
1701
2297
|
` /// constructor remains for codepaths that can't await; it lazily runs the SAME preflight on the`,
|
|
1702
2298
|
` /// first dispatch.`,
|
|
1703
|
-
` static Future<${className}> bind(NomosSession session, {String? actor${
|
|
2299
|
+
` static Future<${className}> bind(NomosSession session, {String? actor${
|
|
2300
|
+
manualEncryptionPresent ? ", NomosKeyStore? keyStore" : ""
|
|
2301
|
+
}}) async {`,
|
|
1704
2302
|
` await session.assertCompatible(interface);`,
|
|
1705
2303
|
` // RELEASE COHERENCE: the cloud's kernel must support what this law needs (e.g. a compound-key query`,
|
|
1706
2304
|
` // needs 'compoundKeys'). Throws a typed KernelCoherenceError on a stale/mismatched cloud kernel,`,
|
|
@@ -1708,8 +2306,16 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1708
2306
|
` await session.assertKernelCoherence(requiresKernel, lawHash: ${hashConst});`,
|
|
1709
2307
|
...(ownedScope === undefined
|
|
1710
2308
|
? []
|
|
1711
|
-
: [
|
|
1712
|
-
|
|
2309
|
+
: [
|
|
2310
|
+
` await session.nomosEnsureOwnedIdentity(scope: ${dartStr(
|
|
2311
|
+
ownedScope
|
|
2312
|
+
)}, domain: ${dartStr(domain)}, domainHash: ${hashConst});`,
|
|
2313
|
+
]),
|
|
2314
|
+
` return ${className}(session, actor: actor${
|
|
2315
|
+
manualEncryptionPresent ? ", keyStore: keyStore" : ""
|
|
2316
|
+
}).._preflighted = true${
|
|
2317
|
+
ownedScope === undefined ? "" : ".._ownedIdentityReady = true"
|
|
2318
|
+
};`,
|
|
1713
2319
|
` }`,
|
|
1714
2320
|
``,
|
|
1715
2321
|
` /// OPEN-THEN-BIND (session_bound_clients.md ask 3) — the ONE-CALL path for a born/resident workspace:`,
|
|
@@ -1718,13 +2324,17 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1718
2324
|
` /// resident/parked/just-born one opens local-only in place) and THEN binds — so a not-open workspace`,
|
|
1719
2325
|
` /// fails HERE, at open, never later on the client's first query. The generated stateful application`,
|
|
1720
2326
|
` /// privately retains refreshable cloud access for a genuinely-absent authorized workspace.`,
|
|
1721
|
-
` static Future<${className}> open<TAggregate>(NomosBridge bridge, {NomosRef<TAggregate>? ref, String? workspace, String? actor${
|
|
2327
|
+
` static Future<${className}> open<TAggregate>(NomosBridge bridge, {NomosRef<TAggregate>? ref, String? workspace, String? actor${
|
|
2328
|
+
manualEncryptionPresent ? ", NomosKeyStore? keyStore" : ""
|
|
2329
|
+
}}) async {`,
|
|
1722
2330
|
` final ws = ref == null ? workspace : NmsInternalNomosRefs.workspace(ref);`,
|
|
1723
2331
|
` if (ws == null || ws.isEmpty) {`,
|
|
1724
2332
|
` throw ArgumentError('${className}.open: pass a workspace: or ref: — the workspace to open and bind');`,
|
|
1725
2333
|
` }`,
|
|
1726
2334
|
` final session = await bridge.acquireSession(ws);`,
|
|
1727
|
-
` return bind(session, actor: actor${
|
|
2335
|
+
` return bind(session, actor: actor${
|
|
2336
|
+
manualEncryptionPresent ? ", keyStore: keyStore" : ""
|
|
2337
|
+
});`,
|
|
1728
2338
|
` }`,
|
|
1729
2339
|
``,
|
|
1730
2340
|
` /// The session workspace this client is bound to (from the [NomosSession] handle, when constructed with`,
|
|
@@ -1749,7 +2359,22 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1749
2359
|
` if (_preflighted) return;`,
|
|
1750
2360
|
` _preflighted = true;`,
|
|
1751
2361
|
` final b = bridge;`,
|
|
1752
|
-
` if (b is NomosSession)
|
|
2362
|
+
` if (b is NomosSession) {`,
|
|
2363
|
+
` await b.assertCompatible(interface);`,
|
|
2364
|
+
` }`,
|
|
2365
|
+
` }`,
|
|
2366
|
+
``,
|
|
2367
|
+
` // Command readiness is deliberately separate from client/read compatibility. Binding a`,
|
|
2368
|
+
` // generated application may construct clients for every installed domain; compiling every`,
|
|
2369
|
+
` // offer plan at that point recreates whole-law runtime work. The first actual offer prepares`,
|
|
2370
|
+
` // exactly this authenticated package/domain slice, once, before dispatch.`,
|
|
2371
|
+
` final Set<String> _preparedCommands = <String>{};`,
|
|
2372
|
+
` Future<void> _prepareCommand(String directiveId) async {`,
|
|
2373
|
+
` if (_preparedCommands.contains(directiveId)) return;`,
|
|
2374
|
+
` await bridge.prepareDomain(domain: ${dartStr(
|
|
2375
|
+
domain
|
|
2376
|
+
)}, directiveId: directiveId, packageHash: ${hashConst});`,
|
|
2377
|
+
` _preparedCommands.add(directiveId);`,
|
|
1753
2378
|
` }`,
|
|
1754
2379
|
``,
|
|
1755
2380
|
...(ownedScope === undefined || Object.keys(ownedActions).length === 0
|
|
@@ -1759,7 +2384,9 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1759
2384
|
` bool _ownedIdentityReady = false;`,
|
|
1760
2385
|
` Future<void> _ensureOwnedIdentity() async {`,
|
|
1761
2386
|
` if (_ownedIdentityReady) return;`,
|
|
1762
|
-
` await bridge.nomosEnsureOwnedIdentity(scope: ${dartStr(
|
|
2387
|
+
` await bridge.nomosEnsureOwnedIdentity(scope: ${dartStr(
|
|
2388
|
+
ownedScope
|
|
2389
|
+
)}, domain: ${dartStr(domain)}, domainHash: domainHash);`,
|
|
1763
2390
|
` _ownedIdentityReady = true;`,
|
|
1764
2391
|
` }`,
|
|
1765
2392
|
``,
|
|
@@ -1769,34 +2396,53 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1769
2396
|
` if (NmsInternalNomosRefs.workspace(record) != _ws) throw ArgumentError('$action() cannot use a record from another workspace');`,
|
|
1770
2397
|
` final directiveId = _ownedRecordActions[NmsInternalNomosRefs.type(record)]?[action];`,
|
|
1771
2398
|
` if (directiveId == null) throw ArgumentError('$action() needs a generated owned record reference');`,
|
|
2399
|
+
` await _prepareCommand(directiveId);`,
|
|
1772
2400
|
` if (peer.trim().isEmpty) throw ArgumentError('$action() needs the person to act with');`,
|
|
1773
|
-
` final payload = await bridge.nomosPrepareOwnedRecordAction(action: action, scope: ${dartStr(
|
|
1774
|
-
|
|
2401
|
+
` final payload = await bridge.nomosPrepareOwnedRecordAction(action: action, scope: ${dartStr(
|
|
2402
|
+
ownedScope
|
|
2403
|
+
)}, record: record, peer: peer);`,
|
|
2404
|
+
` return bridge.offerOutcome(domain: ${dartStr(
|
|
2405
|
+
domain
|
|
2406
|
+
)}, directiveId: directiveId, payload: payload, actor: actor, domainHash: domainHash);`,
|
|
1775
2407
|
` }`,
|
|
1776
2408
|
``,
|
|
1777
2409
|
]),
|
|
1778
|
-
...(dirs.length > 0 ||
|
|
2410
|
+
...(dirs.length > 0 ||
|
|
2411
|
+
birthDirs.some(
|
|
2412
|
+
(d) => (d as { businessBirth?: unknown }).businessBirth !== undefined
|
|
2413
|
+
)
|
|
1779
2414
|
? [
|
|
1780
2415
|
` /// THE ONE DISPATCH LANE every generated directive method routes through: lazy preflight, then`,
|
|
1781
2416
|
` /// the session's offer (the generated-internal use of the engine-level lane).`,
|
|
1782
|
-
` Future<NomosOfferOutcome> _offer(String directiveId, Object? payload, {List<Map<String, Object?>>?
|
|
2417
|
+
` Future<NomosOfferOutcome> _offer(String directiveId, Object? payload, {List<Map<String, Object?>>? conflictSnapshots}) async {`,
|
|
1783
2418
|
` await _preflightOnce();`,
|
|
1784
|
-
`
|
|
2419
|
+
` await _prepareCommand(directiveId);`,
|
|
2420
|
+
` return bridge.offerOutcome(domain: ${dartStr(
|
|
2421
|
+
domain
|
|
2422
|
+
)}, directiveId: directiveId, payload: payload, actor: actor, domainHash: domainHash, nomosConflictSnapshots: conflictSnapshots);`,
|
|
1785
2423
|
` }`,
|
|
1786
2424
|
``,
|
|
1787
2425
|
]
|
|
1788
2426
|
: []),
|
|
1789
|
-
...(encPresent
|
|
2427
|
+
...(encPresent
|
|
2428
|
+
? dartEncClassMembers(encMap!, manualEncryptionPresent)
|
|
2429
|
+
: []),
|
|
1790
2430
|
...(ownedScope === undefined
|
|
1791
2431
|
? []
|
|
1792
2432
|
: [
|
|
1793
2433
|
` /// Opt into all-device-loss recovery with one explicitly designated person.`,
|
|
1794
2434
|
` NomosRecovery get recovery => NomosRecovery(`,
|
|
1795
|
-
` bridge: bridge, domain: ${dartStr(
|
|
2435
|
+
` bridge: bridge, domain: ${dartStr(
|
|
2436
|
+
domain
|
|
2437
|
+
)}, domainHash: domainHash, scope: ${dartStr(ownedScope)});`,
|
|
1796
2438
|
``,
|
|
1797
2439
|
` /// Add another of this person's devices. Requests are opaque; Nomos owns the handoff.`,
|
|
1798
2440
|
` NomosDevices get devices => NomosDevices(`,
|
|
1799
|
-
` bridge: bridge, domain: ${dartStr(
|
|
2441
|
+
` bridge: bridge, domain: ${dartStr(
|
|
2442
|
+
domain
|
|
2443
|
+
)}, domainHash: domainHash, actor: actor, scope: ${dartStr(
|
|
2444
|
+
ownedScope
|
|
2445
|
+
)});`,
|
|
1800
2446
|
``,
|
|
1801
2447
|
]),
|
|
1802
2448
|
// THE OWNER CEREMONY (slice 2): grantAndWrap / revokeAndRotate / rotate — the operator-blind key-epoch
|
|
@@ -1806,8 +2452,12 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1806
2452
|
` /// OWNER CEREMONY (slice 2): \`grantAndWrap\` / \`revokeAndRotate\` / \`rotate\` — the operator-blind`,
|
|
1807
2453
|
` /// key-epoch handshake as one call. Bind \`actor\` (the owner) to author the fan-out.`,
|
|
1808
2454
|
` NomosEncryption get encryption => NomosEncryption(`,
|
|
1809
|
-
` bridge: bridge, domain: ${dartStr(
|
|
1810
|
-
|
|
2455
|
+
` bridge: bridge, domain: ${dartStr(
|
|
2456
|
+
domain
|
|
2457
|
+
)}, domainHash: domainHash, actor: actor,`,
|
|
2458
|
+
` scopes: const [${domScopes
|
|
2459
|
+
.map((s) => dartStr(s))
|
|
2460
|
+
.join(", ")}]);`,
|
|
1811
2461
|
``,
|
|
1812
2462
|
]
|
|
1813
2463
|
: []),
|
|
@@ -1818,7 +2468,9 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1818
2468
|
` String get interfaceJson => ${interfaceConstNameDart(mod.name)};`,
|
|
1819
2469
|
``,
|
|
1820
2470
|
` Map<String, Object?> get _requirement =>`,
|
|
1821
|
-
` {${dartStr(
|
|
2471
|
+
` {${dartStr(
|
|
2472
|
+
domain
|
|
2473
|
+
)}: (jsonDecode(interfaceJson) as Map).cast<String, Object?>()};`,
|
|
1822
2474
|
``,
|
|
1823
2475
|
` /// SHAPE COMPATIBILITY (#72): one-shot verdict for THIS client against the workspace's`,
|
|
1824
2476
|
` /// ON-LEDGER offer — \`{ ok, missing[], incompatible[] }\`, by shape not law hash.`,
|
|
@@ -1834,7 +2486,9 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1834
2486
|
` /// workspace's on-ledger offer + arm the pre-call refusal. Await it after connect so a KNOWN-`,
|
|
1835
2487
|
` /// incompatible directive throws [LawMismatchError] CLIENT-SIDE at write time (never a server 422).`,
|
|
1836
2488
|
` Future<NomosCompat?> checkCompat() => bridge.checkCompat(`,
|
|
1837
|
-
` (jsonDecode(${compatConstNameDart(
|
|
2489
|
+
` (jsonDecode(${compatConstNameDart(
|
|
2490
|
+
mod.name
|
|
2491
|
+
)}) as Map).cast<String, Object?>(), _requirement);`,
|
|
1838
2492
|
``,
|
|
1839
2493
|
` /// THE FEATURE FLAG (dark-launch): true iff [member] exists on the offer AND is compatible — ship`,
|
|
1840
2494
|
` /// UI with a member dark, deploy the law, it lights up. Await [checkCompat] once first to arm it.`,
|
|
@@ -1851,7 +2505,11 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1851
2505
|
`}`,
|
|
1852
2506
|
``,
|
|
1853
2507
|
...(refExtensions.length > 0
|
|
1854
|
-
? [
|
|
2508
|
+
? [
|
|
2509
|
+
`// ---- ref-traversal accessors (dart_codegen_parity.md P2) ----`,
|
|
2510
|
+
refExtensions.join("\n\n"),
|
|
2511
|
+
``,
|
|
2512
|
+
]
|
|
1855
2513
|
: []),
|
|
1856
2514
|
...(roleHelpers !== ""
|
|
1857
2515
|
? [
|
|
@@ -1863,11 +2521,16 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1863
2521
|
}
|
|
1864
2522
|
|
|
1865
2523
|
/** The in-class E2E-sealing members: the baked map + the seal/read private helpers. */
|
|
1866
|
-
function dartEncClassMembers(
|
|
2524
|
+
function dartEncClassMembers(
|
|
2525
|
+
encMap: EncDomainMap,
|
|
2526
|
+
manualEncryptionPresent: boolean
|
|
2527
|
+
): string[] {
|
|
1867
2528
|
return [
|
|
1868
2529
|
` /// The baked E2E-sealing map (compile-time, from the SAME sids the law was compiled with): which`,
|
|
1869
2530
|
` /// directive payload fields seal → { scope, aggSid, fieldSid }, and which read-model fields decrypt.`,
|
|
1870
|
-
` static final Map<String, dynamic> encryptionMap = (jsonDecode(${dartRawJson(
|
|
2531
|
+
` static final Map<String, dynamic> encryptionMap = (jsonDecode(${dartRawJson(
|
|
2532
|
+
encMap
|
|
2533
|
+
)}) as Map).cast<String, dynamic>();`,
|
|
1871
2534
|
``,
|
|
1872
2535
|
` /// Seal the mapped encrypted fields of \`data\` in place before dispatch. Ordinary encryption may`,
|
|
1873
2536
|
` /// use the bound keyStore; .owned() keys are held internally by the connected Nomos client.`,
|
|
@@ -1897,10 +2560,14 @@ function dartEncClassMembers(encMap: EncDomainMap, manualEncryptionPresent: bool
|
|
|
1897
2560
|
` }`,
|
|
1898
2561
|
` data[${JSON.stringify(OWNED_KEY_PAYLOAD_FIELD)}] = subject;`,
|
|
1899
2562
|
` if (ownedKey.ownedCustody != null && ownedKey.ownedCustody!.isNotEmpty) {`,
|
|
1900
|
-
` data[${JSON.stringify(
|
|
2563
|
+
` data[${JSON.stringify(
|
|
2564
|
+
OWNED_CUSTODY_PAYLOAD_FIELD
|
|
2565
|
+
)}] = ownedKey.ownedCustody;`,
|
|
1901
2566
|
` }`,
|
|
1902
2567
|
` if (ownedRecordPreMinted) {`,
|
|
1903
|
-
` data[${JSON.stringify(
|
|
2568
|
+
` data[${JSON.stringify(
|
|
2569
|
+
OWNED_PREMINTED_RECORDS_PAYLOAD_FIELD
|
|
2570
|
+
)}] = [{'type': ownedRecordType, 'id': ownedRecordId}];`,
|
|
1904
2571
|
` }`,
|
|
1905
2572
|
` }`,
|
|
1906
2573
|
` for (final entry in fields.entries) {`,
|
|
@@ -1908,7 +2575,11 @@ function dartEncClassMembers(encMap: EncDomainMap, manualEncryptionPresent: bool
|
|
|
1908
2575
|
` final m = entry.value as Map;`,
|
|
1909
2576
|
` if (data[field] == null) continue;`,
|
|
1910
2577
|
` final scope = m['scope'] as String;`,
|
|
1911
|
-
` NomosScopeKey? sk = m['owned'] == true ? ownedKey : ${
|
|
2578
|
+
` NomosScopeKey? sk = m['owned'] == true ? ownedKey : ${
|
|
2579
|
+
manualEncryptionPresent
|
|
2580
|
+
? "keyStore == null ? null : await keyStore!.scopeKey(scope)"
|
|
2581
|
+
: "null"
|
|
2582
|
+
};`,
|
|
1912
2583
|
` if (sk == null) throw EncryptionKeyUnavailable(scope, field);`,
|
|
1913
2584
|
` data[field] = await bridge.cryptoSealField(`,
|
|
1914
2585
|
` scopeKey: sk.key, scope: m['owned'] == true ? sk.scope : scope, epoch: sk.epoch,`,
|
|
@@ -1922,9 +2593,15 @@ function dartEncClassMembers(encMap: EncDomainMap, manualEncryptionPresent: bool
|
|
|
1922
2593
|
` Future<List<NomosScopeKey>?> _readKeys(List<Map<String, Object?>> scopes, [List<Map<String, String>> records = const []]) async {`,
|
|
1923
2594
|
` if (scopes.isEmpty) return null;`,
|
|
1924
2595
|
...(manualEncryptionPresent
|
|
1925
|
-
? [
|
|
2596
|
+
? [
|
|
2597
|
+
` final ordinary = scopes.where((s) => s['owned'] != true).map((s) => s['scope']! as String).toSet().toList();`,
|
|
2598
|
+
]
|
|
1926
2599
|
: []),
|
|
1927
|
-
` final keys = ${
|
|
2600
|
+
` final keys = ${
|
|
2601
|
+
manualEncryptionPresent
|
|
2602
|
+
? "keyStore == null || ordinary.isEmpty ? <NomosScopeKey>[] : await keyStore!.readKeys(ordinary)"
|
|
2603
|
+
: "<NomosScopeKey>[]"
|
|
2604
|
+
};`,
|
|
1928
2605
|
` for (final descriptor in scopes) {`,
|
|
1929
2606
|
` if (descriptor['owned'] != true) continue;`,
|
|
1930
2607
|
` final scope = descriptor['scope']! as String;`,
|