@githolon/dsl 0.101.5 → 0.102.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/dart/CHANGELOG.md +13 -0
- package/dart/lib/nomos_client.dart +3 -52
- package/dart/lib/src/dev_cloud.dart +5 -0
- package/dart/lib/src/home.dart +10 -15
- package/dart/lib/src/nms_conformance_runtime.dart +12 -0
- package/dart/lib/src/nms_generated_runtime.dart +29 -0
- package/dart/lib/src/nms_test_environment.dart +8 -0
- package/dart/lib/src/node_runner_transport.dart +56 -18
- package/dart/lib/src/provider.dart +5 -1
- package/dart/lib/src/record_origin.dart +55 -0
- package/dart/lib/src/runner/node-entry.mjs +9105 -0
- package/dart/lib/src/runner/offline-install.mjs +4729 -0
- package/dart/lib/src/session_bound.dart +23 -10
- package/dart/lib/src/source_trust.dart +51 -0
- package/dart/lib/src/test_harness.dart +490 -58
- package/dart/lib/src/webview_bridge.dart +167 -58
- package/dart/lib/src/workspace_name.dart +7 -1
- package/dart/pubspec.lock +3 -3
- package/dart/pubspec.yaml +4 -2
- package/dart/test/acquire_session_credentials_test.dart +34 -17
- package/dart/test/acquire_session_test.dart +109 -24
- package/dart/test/adv_realm_interference_test.dart +227 -99
- package/dart/test/bundle_version_pairing_test.dart +1 -1
- package/dart/test/cloud_bound_topology_test.dart +1 -1
- package/dart/test/cluster_attested_test.dart +52 -35
- package/dart/test/cluster_births_test.dart +5 -2
- package/dart/test/cluster_device_keyed_acceptance_test.dart +5 -2
- package/dart/test/cluster_local_autosync_test.dart +5 -2
- package/dart/test/cluster_scale_test.dart +5 -2
- package/dart/test/compat_test.dart +1 -1
- package/dart/test/dlq_session_surface_test.dart +2 -1
- package/dart/test/errors_test.dart +1 -1
- package/dart/test/first_app_gauntlet_test.dart +78 -51
- package/dart/test/four_layer_nested_topology_test.dart +1 -1
- package/dart/test/harness_phase_timeout_test.dart +1 -1
- package/dart/test/home_parity_test.dart +9 -5
- package/dart/test/local_sync_inert_test.dart +22 -14
- package/dart/test/node_runner_transport_test.dart +9 -5
- package/dart/test/null_slot_test.dart +1 -1
- package/dart/test/parent_signer_recovery_test.dart +5 -2
- package/dart/test/schema_validation_test.dart +1 -1
- package/dart/test/sealing_client_test.dart +2 -1
- package/dart/test/session_bound_test.dart +92 -119
- package/dart/test/stale_bundle_test.dart +1 -1
- package/dart/test/test_harness_test.dart +77 -22
- package/dart/test/vo_read_typing_test.dart +32 -14
- package/dart/test/watch_after_offer_stall_test.dart +2 -1
- package/dart/test/webview_bridge_test.dart +17 -4
- package/dart/test/wire_test.dart +1 -1
- package/dart_types/CHANGELOG.md +8 -0
- package/dart_types/lib/nomos_types.dart +7 -6
- package/dart_types/lib/src/activity.dart +23 -13
- package/dart_types/lib/src/foreign_ref.dart +61 -112
- package/dart_types/lib/src/invitation.dart +96 -0
- package/dart_types/lib/src/nomos_ref.dart +97 -59
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/surfaced_test.dart +125 -19
- package/package.json +7 -3
- package/runtime/generated-application-host.js +8851 -0
- package/src/activity.ts +2 -2
- package/src/aggregate.ts +24 -1
- package/src/application_composition.ts +282 -0
- package/src/application_topology.ts +151 -0
- package/src/attested_core.ts +2 -2
- package/src/authoring_context.ts +9 -0
- package/src/birth_meter_ensure.ts +1 -1
- package/src/births.ts +53 -5
- package/src/build_package.ts +79 -71
- package/src/business_birth.ts +93 -10
- package/src/clear_directive.ts +3 -1
- package/src/codegen_dart.ts +369 -420
- package/src/codegen_dart_application.ts +1508 -0
- package/src/codegen_dart_client.ts +156 -95
- package/src/codegen_dart_scenario.ts +299 -0
- package/src/codegen_openapi.ts +8 -3
- package/src/codegen_proof.ts +1113 -1137
- package/src/codegen_provider_dart.ts +12 -1
- package/src/codegen_ts.ts +1176 -496
- package/src/compat_package_main.ts +22 -10
- package/src/compile_package_main.ts +1536 -337
- package/src/contract.ts +10 -4
- package/src/dart_barrel_guard.ts +5 -1
- package/src/describe_model.ts +13 -5
- package/src/directive.ts +244 -5
- package/src/engine_entry.ts +56 -13
- package/src/engine_javascript.ts +31 -0
- package/src/evolve_preview.ts +3 -12
- package/src/framework/application_home.ts +24 -10
- package/src/framework/capability.ts +15 -1
- package/src/framework/domain_lifecycle.ts +8 -0
- package/src/framework/framework_aggregates.ts +2 -2
- package/src/framework/genesis.ts +11 -1
- package/src/framework/home.ts +178 -75
- package/src/framework/impure_capability.ts +24 -7
- package/src/framework/relations.ts +13 -5
- package/src/framework/workspaces.ts +13 -12
- package/src/generated_client_names.ts +42 -3
- package/src/idof_lint.ts +65 -13
- package/src/index.ts +46 -12
- package/src/intent.ts +587 -0
- package/src/law_check.ts +64 -16
- package/src/precondition.ts +16 -4
- package/src/read_projection_namespace.ts +2 -7
- package/src/recipient.ts +33 -0
- package/src/relation_schema.ts +93 -0
- package/src/secondary_ensure.ts +11 -2
- package/src/shape_compat.ts +11 -1
- package/src/usd.ts +30 -8
- package/src/usd_layers.ts +447 -246
- package/src/usd_state.ts +1 -1
- package/src/value_object.ts +49 -13
- package/src/wire_encode.ts +124 -26
- package/src/workspace_sharing.ts +269 -0
- package/src/zod_introspection.ts +84 -0
- package/dart/example/nomos_secure_key_store.dart +0 -82
- package/dart/example/nomos_webview_transport.dart +0 -63
- package/dart/lib/nomos_headless.dart +0 -50
- package/dart/lib/testing.dart +0 -14
- package/dart/test/co2_product_gauntlet_test.dart +0 -244
- package/src/framework/identity.ts +0 -537
|
@@ -7,21 +7,21 @@
|
|
|
7
7
|
* ASYNC DART BRIDGE-CLIENT CODEGEN (#61) — the Flutter sibling of `codegen_ts.ts`.
|
|
8
8
|
*
|
|
9
9
|
* `codegen_dart.ts` emits the per-domain TYPES: read-model classes (`…ReadModel.fromProjected`),
|
|
10
|
-
* typed
|
|
10
|
+
* typed action inputs (`…Input` publicly; private directives retain `…Payload`), typed ids and enums. Until this file a Flutter app
|
|
11
11
|
* still hand-typed directive ids + query ids against the raw `NomosBridge` (string + Map). This closes
|
|
12
12
|
* that: ONE generated `<name>.client.dart` per domain, emitted by `nomos-compile` AFTER the package is
|
|
13
13
|
* hashed (the deployed law's content hash baked in), carrying a `<Domain>Client` over a connected
|
|
14
14
|
* [NomosBridge] (the WebView runner) with:
|
|
15
15
|
*
|
|
16
|
-
* * a typed OFFER method per directive — takes the generated
|
|
17
|
-
* `
|
|
16
|
+
* * a typed OFFER method per directive — takes the generated input/implementation DTO, serialises via its
|
|
17
|
+
* `nmsInternalToPayloadJson()`, auto-stamps omitted `…At` timestamps (the same caller-clock sugar the TS
|
|
18
18
|
* client applies), returns the new local head;
|
|
19
19
|
* * a typed declared-QUERY accessor per `query(...)` — one-shot `Future<List<…ReadModel>>` + a
|
|
20
20
|
* reactive `watch…` `Stream<List<…ReadModel>>`, parsed off the bridge's `{type,id,data}` rows;
|
|
21
21
|
* * by-id read/watch + a type-index `list…` per aggregate;
|
|
22
22
|
* * typed O(1) count/sum accessors.
|
|
23
23
|
*
|
|
24
|
-
* It IMPORTS the sibling `<name>.dart` (for the read-model +
|
|
24
|
+
* It IMPORTS the sibling `<name>.dart` (for the read-model + input classes) and either the vendored
|
|
25
25
|
* `nomos_client.dart` barrel or the package `nomos_client` barrel (for `NomosBridge`). NO manifest/hash impact —
|
|
26
26
|
* pure frontend codegen, exactly like `codegen_dart.ts`. NO FALLBACK: an unsupported shape throws at compile
|
|
27
27
|
* time.
|
|
@@ -46,18 +46,22 @@ import {
|
|
|
46
46
|
dartRoleHelpers,
|
|
47
47
|
registerDartNames,
|
|
48
48
|
concreteClassName,
|
|
49
|
+
dartBusinessRecordInputs,
|
|
50
|
+
dartActionInputClassName,
|
|
49
51
|
dartMemberName,
|
|
50
52
|
} from "./codegen_dart.js";
|
|
51
53
|
import { finishCount } from "./count.js";
|
|
52
54
|
import { finishSum } from "./sum.js";
|
|
53
55
|
import { autoStampFields, domainEncryptionMap, type EncDomainMap, type InterfaceAggShape } from "./codegen_ts.js";
|
|
54
56
|
import { isNamespacedAggregate, qualifyReadProjectionType } from "./read_projection_namespace.js";
|
|
57
|
+
import { isBusinessIntent } from "./intent.js";
|
|
55
58
|
import { publicClientInterface, type DomainInterface } from "./shape_compat.js";
|
|
56
59
|
import { BUSINESS_CORRECTION_DIRECTIVE } from "./framework/business_corrections.js";
|
|
57
60
|
import { deriveDirectiveRecordTarget } from "./workspace_routing.js";
|
|
58
61
|
import { buildNomosDescribeModel, type NomosDescribeModel } from "./describe_model.js";
|
|
59
62
|
import { assertModelClientMemberAvailable } from "./generated_client_names.js";
|
|
60
63
|
import { ownedRecordActionDirectiveId } from "./framework/owned_custody.js";
|
|
64
|
+
import { contractTypeName } from "./contract.js";
|
|
61
65
|
|
|
62
66
|
const camel = (s: string) => s.replace(/[_\s-]+(\w)/g, (_m, c: string) => c.toUpperCase());
|
|
63
67
|
const lcFirst = (s: string) => (s.length ? s[0]!.toLowerCase() + s.slice(1) : s);
|
|
@@ -92,11 +96,31 @@ function dartRawJson(value: unknown): string {
|
|
|
92
96
|
|
|
93
97
|
const dartStr = (s: string) => `'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
94
98
|
|
|
95
|
-
/** The Dart param type for a query index key
|
|
99
|
+
/** The Dart param type for a query index key. Aggregate relations keep their canonical typed ref. */
|
|
96
100
|
function dartKeyType(agg: AggregateHandle | undefined, key: string): string {
|
|
97
101
|
const lead = key.includes(".") ? key.split(".")[0]! : key;
|
|
98
102
|
const f = agg?.fields[lead] as Field | undefined;
|
|
99
|
-
|
|
103
|
+
if (f && (f.kind === "int" || f.kind === "counter")) return "int";
|
|
104
|
+
if (f?.kind === "ref") {
|
|
105
|
+
if (f.foreignRef !== undefined) {
|
|
106
|
+
const target = f.foreignRef.targetIsContract === true
|
|
107
|
+
? contractTypeName(f.foreignRef.targetAggregateId)
|
|
108
|
+
: concreteClassName(f.foreignRef.targetAggregateId);
|
|
109
|
+
return `ForeignRef<${target}>`;
|
|
110
|
+
}
|
|
111
|
+
if (f.refAggregateId !== undefined) return `NomosRef<${concreteClassName(f.refAggregateId)}>`;
|
|
112
|
+
}
|
|
113
|
+
return "String";
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function dartKeyExpr(agg: AggregateHandle | undefined, key: string): string {
|
|
117
|
+
const lead = key.includes(".") ? key.split(".")[0]! : key;
|
|
118
|
+
const field = agg?.fields[lead] as Field | undefined;
|
|
119
|
+
const param = dartKeyParam(key);
|
|
120
|
+
if (field?.kind !== "ref") return param;
|
|
121
|
+
return field.foreignRef !== undefined
|
|
122
|
+
? `NmsInternalNomosRefs.id(${param}.ref)`
|
|
123
|
+
: `NmsInternalNomosRefs.id(${param})`;
|
|
100
124
|
}
|
|
101
125
|
|
|
102
126
|
/** A Dart-safe param identifier from a (possibly dotted) index key: `a.b.c` → `aBC`. */
|
|
@@ -112,7 +136,7 @@ function dartKeyParam(key: string): string {
|
|
|
112
136
|
* session workspace (`_ws`, a getter on the client) + the row's type/id. Works for a `Map` (one-shot
|
|
113
137
|
* `query`/`queryById` rows) or a `dynamic` (`watch` stream rows) — indexing resolves on both. */
|
|
114
138
|
function rowToModel(model: string, rowVar: string, workspace = "_ws"): string {
|
|
115
|
-
return
|
|
139
|
+
return `NmsInternalRecordOrigins.remember(${model}.fromProjected((${rowVar}['data'] as Map).cast<String, dynamic>(), (${rowVar}['id'] ?? '').toString(), ${workspace}, (${rowVar}['type'] ?? '').toString()), workspace: ${workspace}, type: (${rowVar}['type'] ?? '').toString(), id: (${rowVar}['id'] ?? '').toString())`;
|
|
116
140
|
}
|
|
117
141
|
|
|
118
142
|
export interface DartBridgeClientOptions {
|
|
@@ -186,10 +210,11 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
186
210
|
(a) => (a as { businessBirthInternal?: unknown }).businessBirthInternal !== true,
|
|
187
211
|
);
|
|
188
212
|
const publicAggregateIds = new Set(publicAggregates.map((a) => a.id));
|
|
213
|
+
const publicDirectives = mod.applicationIntents ?? mod.directives;
|
|
189
214
|
mod = {
|
|
190
215
|
...mod,
|
|
191
216
|
aggregates: publicAggregates,
|
|
192
|
-
directives:
|
|
217
|
+
directives: publicDirectives.filter(
|
|
193
218
|
(d) => (d as { businessBirthInternal?: unknown }).businessBirthInternal !== true,
|
|
194
219
|
),
|
|
195
220
|
...(mod.queries === undefined
|
|
@@ -214,7 +239,7 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
214
239
|
const requiresKernelFeats = requiredKernelFeatures([mod]);
|
|
215
240
|
const supportImport =
|
|
216
241
|
opts.supportImport === "package"
|
|
217
|
-
? "package:nomos_client/
|
|
242
|
+
? "package:nomos_client/src/nms_generated_runtime.dart"
|
|
218
243
|
: "nomos_client.dart";
|
|
219
244
|
const aggById = new Map<string, AggregateHandle>();
|
|
220
245
|
for (const a of mod.aggregates) aggById.set(a.id, a);
|
|
@@ -255,17 +280,23 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
255
280
|
};
|
|
256
281
|
const scopesExpr = (scopes: readonly { scope: string; owned?: true }[]): string =>
|
|
257
282
|
`[${scopes.map((s) => `{${dartStr("scope")}: ${dartStr(s.scope)}${s.owned === true ? `, ${dartStr("owned")}: true` : ""}}`).join(", ")}]`;
|
|
283
|
+
const needsReadPrincipal = (typeId: string): boolean => {
|
|
284
|
+
const audience = aggById.get(typeId)?.audience;
|
|
285
|
+
return audience?.kind === "readableBy" || audience?.kind === "owned";
|
|
286
|
+
};
|
|
258
287
|
const keysArg = (typeId: string): string => {
|
|
259
288
|
const scopes = readScopesFor(typeId);
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
289
|
+
if (scopes.length) {
|
|
290
|
+
return `, await _readKeys(${scopesExpr(scopes)})${needsReadPrincipal(typeId) ? ", actor" : ""}`;
|
|
291
|
+
}
|
|
292
|
+
return needsReadPrincipal(typeId) ? ", null, actor" : "";
|
|
263
293
|
};
|
|
264
294
|
const pastKeysArg = (typeId: string): string => {
|
|
265
295
|
const scopes = readScopesFor(typeId);
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
296
|
+
if (scopes.length) {
|
|
297
|
+
return `, await _client._readKeys(${scopesExpr(scopes)})${needsReadPrincipal(typeId) ? ", _client.actor" : ""}`;
|
|
298
|
+
}
|
|
299
|
+
return needsReadPrincipal(typeId) ? ", null, _client.actor" : "";
|
|
269
300
|
};
|
|
270
301
|
// The DISTINCT encryption scopes this domain defines (across encrypted reads + sealing directives) — the
|
|
271
302
|
// set the owner ceremony rotates. Empty ⇒ no `encryption` getter is emitted for this domain.
|
|
@@ -310,13 +341,13 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
310
341
|
if (ownedScope !== undefined && Object.keys(ownedActions).length > 0) {
|
|
311
342
|
methods.push(
|
|
312
343
|
` /// Share one record. The peer can read its history and future changes; Nomos owns the keys and policy.`,
|
|
313
|
-
` Future<NomosOfferOutcome> share(NomosRef record, String peer) => _ownedRecordAction('share', record, peer);`,
|
|
344
|
+
` Future<NomosOfferOutcome> share<TAggregate>(NomosRef<TAggregate> record, String peer) => _ownedRecordAction('share', record, peer);`,
|
|
314
345
|
``,
|
|
315
346
|
` /// Stop sharing one record. Earlier knowledge cannot be erased; future changes use a fresh private key.`,
|
|
316
|
-
` Future<NomosOfferOutcome> unshare(NomosRef record, String peer) => _ownedRecordAction('unshare', record, peer);`,
|
|
347
|
+
` Future<NomosOfferOutcome> unshare<TAggregate>(NomosRef<TAggregate> record, String peer) => _ownedRecordAction('unshare', record, peer);`,
|
|
317
348
|
``,
|
|
318
349
|
` /// Hand ownership, readable history and future authority for one record to another person.`,
|
|
319
|
-
` Future<NomosOfferOutcome> transfer(NomosRef record, String peer) => _ownedRecordAction('transfer', record, peer);`,
|
|
350
|
+
` Future<NomosOfferOutcome> transfer<TAggregate>(NomosRef<TAggregate> record, String peer) => _ownedRecordAction('transfer', record, peer);`,
|
|
320
351
|
``,
|
|
321
352
|
);
|
|
322
353
|
}
|
|
@@ -381,7 +412,7 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
381
412
|
);
|
|
382
413
|
}
|
|
383
414
|
for (const d of dirs) {
|
|
384
|
-
const payloadClass =
|
|
415
|
+
const payloadClass = dartActionInputClassName(d);
|
|
385
416
|
const stamps = autoStampFields((d as unknown as { payloadSchema: z.ZodTypeAny }).payloadSchema);
|
|
386
417
|
const method = safeMethod(d.id);
|
|
387
418
|
// THE AUTHORITY GATE (codegen-time): a directive carrying `.requires(relation)` is GATED — its method
|
|
@@ -411,6 +442,28 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
411
442
|
const createsDoc = (d as { marker?: string }).marker === "creates"
|
|
412
443
|
? ` The LAW mints the ${(d as { aggregateId?: string }).aggregateId ?? "aggregate"} id inside the sealed intent — it rides back on \`outcome.created\` (no id in the payload, no query-after).`
|
|
413
444
|
: "";
|
|
445
|
+
const recordInputs = dartBusinessRecordInputs(d, mod.aggregates);
|
|
446
|
+
const principalField = d.businessPrincipalField;
|
|
447
|
+
const principalPrelude = principalField === undefined
|
|
448
|
+
? []
|
|
449
|
+
: [
|
|
450
|
+
` final nmsInternalPrincipal = actor ?? bridge.principal;`,
|
|
451
|
+
` if (nmsInternalPrincipal == null || nmsInternalPrincipal.trim().isEmpty) {`,
|
|
452
|
+
` throw MissingOfferAuthority(domain: ${dartStr(domain)}, directiveId: ${dartStr(d.id)}, requires: 'the signed-in Nomos principal');`,
|
|
453
|
+
` }`,
|
|
454
|
+
];
|
|
455
|
+
const payloadJsonArgs = [
|
|
456
|
+
...(principalField === undefined ? [] : ["nmsInternalPrincipal: nmsInternalPrincipal"]),
|
|
457
|
+
...(recordInputs.length === 0
|
|
458
|
+
? []
|
|
459
|
+
: [
|
|
460
|
+
"nmsInternalLocationOf: (record) => NmsInternalRecordOrigins.require(record).workspace",
|
|
461
|
+
"nmsInternalIdOf: (record) => NmsInternalRecordOrigins.require(record).id",
|
|
462
|
+
]),
|
|
463
|
+
];
|
|
464
|
+
const payloadJson = payloadJsonArgs.length === 0
|
|
465
|
+
? "payload.nmsInternalToPayloadJson()"
|
|
466
|
+
: `payload.nmsInternalToPayloadJson(${payloadJsonArgs.join(", ")})`;
|
|
414
467
|
const stampsDoc = stamps.length > 0 ? `\n /// Omitted ${stamps.map((s) => `\`${s}\``).join(" / ")} auto-stamp(s) ISO now() at offer.` : "";
|
|
415
468
|
const directiveEncryption = encMap?.directives?.[d.id];
|
|
416
469
|
if (directiveEncryption !== undefined) {
|
|
@@ -453,7 +506,8 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
453
506
|
...(gateDoc ? [gateDoc] : []),
|
|
454
507
|
` Future<NomosOfferOutcome> ${method}(${payloadClass} payload${ownedSeal && !wantAttest ? "" : `, {${ownedSeal ? "" : "bool seal = true"}${wantAttest ? `${ownedSeal ? "" : ", "}${attNamed}` : ""}}`}) async {`,
|
|
455
508
|
...(gateGuard ? [gateGuard] : []),
|
|
456
|
-
|
|
509
|
+
...principalPrelude,
|
|
510
|
+
` final data = ${payloadJson};`,
|
|
457
511
|
...(stamps.length > 0
|
|
458
512
|
? [
|
|
459
513
|
` final now = DateTime.now().toUtc().toIso8601String();`,
|
|
@@ -474,21 +528,24 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
474
528
|
...(gateDoc ? [gateDoc] : []),
|
|
475
529
|
` Future<NomosOfferOutcome> ${method}(${payloadClass} payload${wantAttest ? ", {" + attNamed + "}" : ""}) {`,
|
|
476
530
|
...(gateGuard ? [gateGuard] : []),
|
|
477
|
-
|
|
531
|
+
...principalPrelude,
|
|
532
|
+
` final data = ${payloadJson};`,
|
|
478
533
|
` final now = DateTime.now().toUtc().toIso8601String();`,
|
|
479
534
|
...stamps.map((s) => ` data.putIfAbsent(${dartStr(s)}, () => now);`),
|
|
480
535
|
` return _offer(${dartStr(d.id)}, data${attArg});`,
|
|
481
536
|
` }`,
|
|
482
537
|
``,
|
|
483
538
|
);
|
|
484
|
-
} else if (gateGuard) {
|
|
485
|
-
// A gated directive with no
|
|
539
|
+
} else if (gateGuard || principalPrelude.length > 0) {
|
|
540
|
+
// A gated or principal-bound directive with no timestamp sugar still needs a block body
|
|
541
|
+
// for the framework-owned identity guard and payload binding.
|
|
486
542
|
methods.push(
|
|
487
543
|
` /// Author \`${domain}/${d.id}\` locally under the installed law; returns the typed [NomosOfferOutcome].${createsDoc}`,
|
|
488
544
|
gateDoc,
|
|
489
545
|
` Future<NomosOfferOutcome> ${method}(${payloadClass} payload${wantAttest ? ", {" + attNamed + "}" : ""}) {`,
|
|
490
|
-
gateGuard,
|
|
491
|
-
|
|
546
|
+
...(gateGuard ? [gateGuard] : []),
|
|
547
|
+
...principalPrelude,
|
|
548
|
+
` return _offer(${dartStr(d.id)}, ${payloadJson}${attArg});`,
|
|
492
549
|
` }`,
|
|
493
550
|
``,
|
|
494
551
|
);
|
|
@@ -497,7 +554,7 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
497
554
|
` /// Author \`${domain}/${d.id}\` locally under the installed law; returns the typed [NomosOfferOutcome].${createsDoc}`,
|
|
498
555
|
...(gateDoc ? [gateDoc] : []),
|
|
499
556
|
` Future<NomosOfferOutcome> ${method}(${payloadClass} payload${wantAttest ? ", {" + attNamed + "}" : ""}) =>`,
|
|
500
|
-
` _offer(${dartStr(d.id)},
|
|
557
|
+
` _offer(${dartStr(d.id)}, ${payloadJson}${attArg});`,
|
|
501
558
|
``,
|
|
502
559
|
);
|
|
503
560
|
}
|
|
@@ -509,12 +566,13 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
509
566
|
// domain's key + directiveId + baked domainHash are all bound at codegen time, so a frontend dev
|
|
510
567
|
// never hand-writes the four raw strings this replaces (the co2 production bug).
|
|
511
568
|
for (const d of birthDirs) {
|
|
512
|
-
const payloadClass = `${cap(d.id)}Payload`;
|
|
513
569
|
const method = safeMethod(d.id);
|
|
570
|
+
const publicMethod = isBusinessIntent(d) ? method : "birth";
|
|
514
571
|
const gate = (d as { requiresRelationGate?: { relation: string } }).requiresRelationGate;
|
|
515
572
|
const businessBirth = (d as unknown as {
|
|
516
573
|
businessBirth?: { aggregateId: string; rootId: string; namespace: string; keyField: string; publicPayloadSchema: z.ZodTypeAny };
|
|
517
574
|
}).businessBirth;
|
|
575
|
+
const payloadClass = dartActionInputClassName(d);
|
|
518
576
|
if (businessBirth !== undefined) {
|
|
519
577
|
const birthEncryption = encMap?.directives?.[d.id];
|
|
520
578
|
const ownedBirthDescriptor = birthEncryption === undefined
|
|
@@ -523,12 +581,12 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
523
581
|
methods.push(
|
|
524
582
|
` /// Give one ${businessBirth.aggregateId} its own durable life. Nomos owns identity, authorship,`,
|
|
525
583
|
` /// provenance, persistence and recovery; the app supplies only business facts.`,
|
|
526
|
-
` Future<NomosRef
|
|
584
|
+
` Future<NomosRef<${concreteClassName(businessBirth.aggregateId)}>> ${publicMethod}(${payloadClass} payload) async {`,
|
|
527
585
|
` final principal = actor ?? bridge.principal;`,
|
|
528
586
|
` if (principal == null || principal.trim().isEmpty) {`,
|
|
529
587
|
` throw MissingOfferAuthority(domain: ${dartStr(domain)}, directiveId: ${dartStr(d.id)}, requires: 'a signed-in Nomos session');`,
|
|
530
588
|
` }`,
|
|
531
|
-
` final data = payload.
|
|
589
|
+
` final data = payload.nmsInternalToPayloadJson();`,
|
|
532
590
|
...(ownedBirthDescriptor === undefined
|
|
533
591
|
? []
|
|
534
592
|
: [
|
|
@@ -558,8 +616,10 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
558
616
|
` throw StateError('birth() returned a workspace different from the deterministic owned-custody address');`,
|
|
559
617
|
` }`,
|
|
560
618
|
]),
|
|
561
|
-
|
|
562
|
-
|
|
619
|
+
// Birth is a local, durable business commit. The application host's continuous custody lifecycle
|
|
620
|
+
// pushes it immediately when online and retries when offline; awaiting a network round here makes
|
|
621
|
+
// the supposedly offline-first business action hang behind transport availability.
|
|
622
|
+
` return NmsInternalNomosRefs.create<${concreteClassName(businessBirth.aggregateId)}>(workspace: outcome.born.single, type: ${dartStr(businessBirth.aggregateId)}, id: ${dartStr(businessBirth.rootId)});`,
|
|
563
623
|
` }`,
|
|
564
624
|
``,
|
|
565
625
|
);
|
|
@@ -600,7 +660,7 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
600
660
|
` parent: explicitParent,`,
|
|
601
661
|
` actor: actor.wire,`,
|
|
602
662
|
` authorSecret: authorSecret?.wire,`,
|
|
603
|
-
` payload: payload.
|
|
663
|
+
` payload: payload.nmsInternalToPayloadJson(),`,
|
|
604
664
|
` authToken: authToken,`,
|
|
605
665
|
` );`,
|
|
606
666
|
` return NomosOfferOutcome(`,
|
|
@@ -623,7 +683,7 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
623
683
|
]
|
|
624
684
|
: []),
|
|
625
685
|
` await _preflightOnce();`,
|
|
626
|
-
` return bridge.offerOutcome(domain: ${dartStr(domain)}, directiveId: ${dartStr(d.id)}, payload: payload.
|
|
686
|
+
` return bridge.offerOutcome(domain: ${dartStr(domain)}, directiveId: ${dartStr(d.id)}, payload: payload.nmsInternalToPayloadJson(), actor: effActor, domainHash: domainHash);`,
|
|
627
687
|
` }`,
|
|
628
688
|
``,
|
|
629
689
|
);
|
|
@@ -634,30 +694,31 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
634
694
|
const model = readModelClassName(q.returns);
|
|
635
695
|
const agg = aggById.get(q.returns);
|
|
636
696
|
const params = q.key.map((k) => `required ${dartKeyType(agg, k)} ${dartKeyParam(k)}`).join(", ");
|
|
637
|
-
const argMap = `{${q.key.map((k) => `${dartStr(k)}: ${
|
|
697
|
+
const argMap = `{${q.key.map((k) => `${dartStr(k)}: ${dartKeyExpr(agg, k)}`).join(", ")}}`;
|
|
638
698
|
const m = safeMethod(lcFirst(camel(q.id)));
|
|
639
699
|
const qScopes = readScopesFor(q.returns);
|
|
640
700
|
const qOwned = qScopes.some((scope) => scope.owned === true);
|
|
701
|
+
const qPrivate = needsReadPrincipal(q.returns);
|
|
641
702
|
const qScopesExpr = scopesExpr(qScopes);
|
|
642
703
|
methods.push(
|
|
643
704
|
` /// Declared query \`${q.id}\` (indexed probe — routed by the read manifest).${readScopesFor(q.returns).length ? " Encrypted field(s) decrypt when the bound [keyStore] holds the scope key; else they stay opaque envelopes." : ""}`,
|
|
644
705
|
` Future<List<${model}>> ${m}({${params}}) async =>`,
|
|
645
706
|
qOwned
|
|
646
|
-
? ` (await _readWithOwnedKeys(${qScopesExpr}, (keys) => bridge.query(${dartStr(q.id)}, ${argMap}, keys))).map((r) => ${rowToModel(model, "r")}).toList();`
|
|
707
|
+
? ` (await _readWithOwnedKeys(${qScopesExpr}, (keys) => bridge.query(${dartStr(q.id)}, ${argMap}, keys${qPrivate ? ", actor" : ""}))).map((r) => ${rowToModel(model, "r")}).toList();`
|
|
647
708
|
: ` (await bridge.query(${dartStr(q.id)}, ${argMap}${keysArg(q.returns)})).map((r) => ${rowToModel(model, "r")}).toList();`,
|
|
648
709
|
``,
|
|
649
710
|
` /// Reactive \`${q.id}\` — re-emits on every local change (cancel the subscription to stop).`,
|
|
650
711
|
` Stream<List<${model}>> watch${cap(camel(q.id))}({${params}}) =>`,
|
|
651
712
|
qOwned
|
|
652
|
-
? ` bridge.watch(${dartStr(q.id)}, ${argMap}).asyncMap((_) => _readWithOwnedKeys(${qScopesExpr}, (keys) => bridge.query(${dartStr(q.id)}, ${argMap}, keys))).map((rows) => rows.map((r) => ${rowToModel(model, "r")}).toList());`
|
|
653
|
-
: ` bridge.watch(${dartStr(q.id)}, ${argMap}).map((rows) => rows.map((r) => ${rowToModel(model, "r")}).toList());`,
|
|
713
|
+
? ` bridge.watch(${dartStr(q.id)}, ${argMap}, null${qPrivate ? ", actor" : ""}).asyncMap((_) => _readWithOwnedKeys(${qScopesExpr}, (keys) => bridge.query(${dartStr(q.id)}, ${argMap}, keys${qPrivate ? ", actor" : ""}))).map((rows) => rows.map((r) => ${rowToModel(model, "r")}).toList());`
|
|
714
|
+
: ` bridge.watch(${dartStr(q.id)}, ${argMap}${qPrivate ? ", null, actor" : ""}).map((rows) => rows.map((r) => ${rowToModel(model, "r")}).toList());`,
|
|
654
715
|
``,
|
|
655
716
|
);
|
|
656
717
|
pastMethods.push(
|
|
657
718
|
` /// Declared query \`${q.id}\` in the verified world at [moment].`,
|
|
658
719
|
` Future<List<${model}>> ${m}({${params}}) async =>`,
|
|
659
720
|
qOwned
|
|
660
|
-
? ` (await _client._readWithOwnedKeys(${qScopesExpr}, (keys) => _client.bridge.queryAt(moment, ${dartStr(q.id)}, ${argMap}, keys))).map((r) => ${rowToModel(model, "r", "_client._ws")}).toList();`
|
|
721
|
+
? ` (await _client._readWithOwnedKeys(${qScopesExpr}, (keys) => _client.bridge.queryAt(moment, ${dartStr(q.id)}, ${argMap}, keys${qPrivate ? ", _client.actor" : ""}))).map((r) => ${rowToModel(model, "r", "_client._ws")}).toList();`
|
|
661
722
|
: ` (await _client.bridge.queryAt(moment, ${dartStr(q.id)}, ${argMap}${pastKeysArg(q.returns)})).map((r) => ${rowToModel(model, "r", "_client._ws")}).toList();`,
|
|
662
723
|
``,
|
|
663
724
|
);
|
|
@@ -683,30 +744,36 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
683
744
|
// ── counts / sums → O(1) typed reads ──
|
|
684
745
|
for (const c of (mod.counts ?? []).map((raw) => finishCount(raw))) {
|
|
685
746
|
const m = safeMethod(lcFirst(camel(c.id)));
|
|
747
|
+
const aggregate = aggById.get(c.of);
|
|
748
|
+
const groupType = c.by == null ? "" : dartKeyType(aggregate, c.by);
|
|
749
|
+
const groupValue = c.by == null ? "" : dartKeyExpr(aggregate, c.by);
|
|
686
750
|
methods.push(
|
|
687
751
|
c.by != null
|
|
688
|
-
? ` /// Declared count \`${c.id}\` — the maintained O(1) tally per \`${c.by}\`.\n Future<int> ${m}(
|
|
752
|
+
? ` /// Declared count \`${c.id}\` — the maintained O(1) tally per \`${c.by}\`.\n Future<int> ${m}(${groupType} ${dartKeyParam(c.by)}) => bridge.count(${dartStr(c.id)}, ${groupValue});`
|
|
689
753
|
: ` /// Declared count \`${c.id}\` — the maintained O(1) grand total.\n Future<int> ${m}() => bridge.count(${dartStr(c.id)});`,
|
|
690
754
|
``,
|
|
691
755
|
);
|
|
692
756
|
pastMethods.push(
|
|
693
757
|
c.by != null
|
|
694
|
-
? ` /// Declared count \`${c.id}\` at [moment].\n Future<int> ${m}(
|
|
758
|
+
? ` /// Declared count \`${c.id}\` at [moment].\n Future<int> ${m}(${groupType} ${dartKeyParam(c.by)}) => _client.bridge.countAt(moment, ${dartStr(c.id)}, ${groupValue});`
|
|
695
759
|
: ` /// Declared count \`${c.id}\` at [moment].\n Future<int> ${m}() => _client.bridge.countAt(moment, ${dartStr(c.id)});`,
|
|
696
760
|
``,
|
|
697
761
|
);
|
|
698
762
|
}
|
|
699
763
|
for (const s of (mod.sums ?? []).map((raw) => finishSum(raw))) {
|
|
700
764
|
const m = safeMethod(lcFirst(camel(s.id)));
|
|
765
|
+
const aggregate = aggById.get(s.of);
|
|
766
|
+
const groupType = s.by == null ? "" : dartKeyType(aggregate, s.by);
|
|
767
|
+
const groupValue = s.by == null ? "" : dartKeyExpr(aggregate, s.by);
|
|
701
768
|
methods.push(
|
|
702
769
|
s.by != null
|
|
703
|
-
? ` /// Declared sum \`${s.id}\` — the maintained O(1) total per \`${s.by}\`.\n Future<int> ${m}(
|
|
770
|
+
? ` /// Declared sum \`${s.id}\` — the maintained O(1) total per \`${s.by}\`.\n Future<int> ${m}(${groupType} ${dartKeyParam(s.by)}) => bridge.sum(${dartStr(s.id)}, ${groupValue});`
|
|
704
771
|
: ` /// Declared sum \`${s.id}\` — the maintained O(1) grand total.\n Future<int> ${m}() => bridge.sum(${dartStr(s.id)});`,
|
|
705
772
|
``,
|
|
706
773
|
);
|
|
707
774
|
pastMethods.push(
|
|
708
775
|
s.by != null
|
|
709
|
-
? ` /// Declared sum \`${s.id}\` at [moment].\n Future<int> ${m}(
|
|
776
|
+
? ` /// Declared sum \`${s.id}\` at [moment].\n Future<int> ${m}(${groupType} ${dartKeyParam(s.by)}) => _client.bridge.sumAt(moment, ${dartStr(s.id)}, ${groupValue});`
|
|
710
777
|
: ` /// Declared sum \`${s.id}\` at [moment].\n Future<int> ${m}() => _client.bridge.sumAt(moment, ${dartStr(s.id)});`,
|
|
711
778
|
``,
|
|
712
779
|
);
|
|
@@ -946,17 +1013,18 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
946
1013
|
const Cap = cap(camel(name));
|
|
947
1014
|
const aScopes = readScopesFor(agg.id);
|
|
948
1015
|
const aOwned = aScopes.some((scope) => scope.owned === true);
|
|
1016
|
+
const aPrivate = needsReadPrincipal(agg.id);
|
|
949
1017
|
const aScopesExpr = scopesExpr(aScopes);
|
|
950
1018
|
const runtimeType = isNamespacedAggregate(agg) ? qualifyReadProjectionType(domain, agg.id) : agg.id;
|
|
951
1019
|
const byIdKeys = aScopes.length === 0
|
|
952
|
-
? ""
|
|
1020
|
+
? (aPrivate ? ", null, actor" : "")
|
|
953
1021
|
: aOwned
|
|
954
|
-
? `, await _readKeys(${aScopesExpr}, [{${dartStr("type")}: ${dartStr(runtimeType)}, ${dartStr("id")}: id}])`
|
|
1022
|
+
? `, await _readKeys(${aScopesExpr}, [{${dartStr("type")}: ${dartStr(runtimeType)}, ${dartStr("id")}: id}])${aPrivate ? ", actor" : ""}`
|
|
955
1023
|
: keysArg(agg.id);
|
|
956
1024
|
const pastByIdKeys = aScopes.length === 0
|
|
957
|
-
? ""
|
|
1025
|
+
? (aPrivate ? ", null, _client.actor" : "")
|
|
958
1026
|
: aOwned
|
|
959
|
-
? `, await _client._readKeys(${aScopesExpr}, [{${dartStr("type")}: ${dartStr(runtimeType)}, ${dartStr("id")}: id}])`
|
|
1027
|
+
? `, await _client._readKeys(${aScopesExpr}, [{${dartStr("type")}: ${dartStr(runtimeType)}, ${dartStr("id")}: id}])${aPrivate ? ", _client.actor" : ""}`
|
|
960
1028
|
: pastKeysArg(agg.id);
|
|
961
1029
|
methods.push(
|
|
962
1030
|
` /// Read one ${agg.id} by aggregate id.${readScopesFor(agg.id).length ? " Encrypted field(s) decrypt when the bound [keyStore] holds the scope key." : ""}`,
|
|
@@ -966,14 +1034,20 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
966
1034
|
` /// Watch one ${agg.id} by aggregate id (local reactive read).`,
|
|
967
1035
|
` Stream<List<${model}>> watch${Cap}ById(String id) =>`,
|
|
968
1036
|
aOwned
|
|
969
|
-
? ` bridge.watchById(id).asyncMap((_) async => bridge.queryById(id, await _readKeys(${aScopesExpr}, [{${dartStr("type")}: ${dartStr(runtimeType)}, ${dartStr("id")}: id}]))).map((rows) => rows.map((r) => ${rowToModel(model, "r")}).toList());`
|
|
970
|
-
: ` bridge.watchById(id).map((rows) => rows.map((r) => ${rowToModel(model, "r")}).toList());`,
|
|
1037
|
+
? ` bridge.watchById(id, null${aPrivate ? ", actor" : ""}).asyncMap((_) async => bridge.queryById(id, await _readKeys(${aScopesExpr}, [{${dartStr("type")}: ${dartStr(runtimeType)}, ${dartStr("id")}: id}])${aPrivate ? ", actor" : ""})).map((rows) => rows.map((r) => ${rowToModel(model, "r")}).toList());`
|
|
1038
|
+
: ` bridge.watchById(id${aPrivate ? ", null, actor" : ""}).map((rows) => rows.map((r) => ${rowToModel(model, "r")}).toList());`,
|
|
971
1039
|
``,
|
|
972
1040
|
` /// Every ${agg.id} (the type-index probe — O(result), not a scan).`,
|
|
973
1041
|
` Future<List<${model}>> ${safeMethod(`list${Cap}s`)}() async =>`,
|
|
974
1042
|
aOwned
|
|
975
|
-
? ` (await _readWithOwnedKeys(${aScopesExpr}, (keys) => bridge.query('aggregatesByType', {'__type': ${dartStr(
|
|
976
|
-
: ` (await bridge.query('aggregatesByType', {'__type': ${dartStr(
|
|
1043
|
+
? ` (await _readWithOwnedKeys(${aScopesExpr}, (keys) => bridge.query('aggregatesByType', {'__type': ${dartStr(runtimeType)}}, keys${aPrivate ? ", actor" : ""}))).map((r) => ${rowToModel(model, "r")}).toList();`
|
|
1044
|
+
: ` (await bridge.query('aggregatesByType', {'__type': ${dartStr(runtimeType)}}${keysArg(agg.id)})).map((r) => ${rowToModel(model, "r")}).toList();`,
|
|
1045
|
+
``,
|
|
1046
|
+
` /// Watch every ${agg.id} (the same indexed local read, re-emitted after each change).`,
|
|
1047
|
+
` Stream<List<${model}>> ${safeMethod(`watchAll${Cap}s`)}() =>`,
|
|
1048
|
+
aOwned
|
|
1049
|
+
? ` bridge.watch('aggregatesByType', {'__type': ${dartStr(runtimeType)}}, null${aPrivate ? ", actor" : ""}).asyncMap((_) => _readWithOwnedKeys(${aScopesExpr}, (keys) => bridge.query('aggregatesByType', {'__type': ${dartStr(runtimeType)}}, keys${aPrivate ? ", actor" : ""}))).map((rows) => rows.map((r) => ${rowToModel(model, "r")}).toList());`
|
|
1050
|
+
: ` bridge.watch('aggregatesByType', {'__type': ${dartStr(runtimeType)}}${aPrivate ? ", null, actor" : ""}).map((rows) => rows.map((r) => ${rowToModel(model, "r")}).toList());`,
|
|
977
1051
|
``,
|
|
978
1052
|
);
|
|
979
1053
|
pastMethods.push(
|
|
@@ -984,8 +1058,8 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
984
1058
|
` /// Every ${agg.id} in the verified world at [moment].`,
|
|
985
1059
|
` Future<List<${model}>> ${safeMethod(`list${Cap}s`)}() async =>`,
|
|
986
1060
|
aOwned
|
|
987
|
-
? ` (await _client._readWithOwnedKeys(${aScopesExpr}, (keys) => _client.bridge.queryAt(moment, 'aggregatesByType', {'__type': ${dartStr(
|
|
988
|
-
: ` (await _client.bridge.queryAt(moment, 'aggregatesByType', {'__type': ${dartStr(
|
|
1061
|
+
? ` (await _client._readWithOwnedKeys(${aScopesExpr}, (keys) => _client.bridge.queryAt(moment, 'aggregatesByType', {'__type': ${dartStr(runtimeType)}}, keys${aPrivate ? ", _client.actor" : ""}))).map((r) => ${rowToModel(model, "r", "_client._ws")}).toList();`
|
|
1062
|
+
: ` (await _client.bridge.queryAt(moment, 'aggregatesByType', {'__type': ${dartStr(runtimeType)}}${pastKeysArg(agg.id)})).map((r) => ${rowToModel(model, "r", "_client._ws")}).toList();`,
|
|
989
1063
|
``,
|
|
990
1064
|
);
|
|
991
1065
|
}
|
|
@@ -1116,7 +1190,7 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1116
1190
|
` return moments.where((moment) =>`,
|
|
1117
1191
|
` moment.domain == ${dartStr(domain)} && moment.action != null && actions.contains(moment.action)).map((moment) =>`,
|
|
1118
1192
|
` moment.action == ${dartStr(BUSINESS_CORRECTION_DIRECTIVE)} ? NomosMoment(`,
|
|
1119
|
-
` id: moment.id, occurredAt: moment.occurredAt,
|
|
1193
|
+
` id: moment.id, occurredAt: moment.occurredAt, performedBy: moment.performedBy, domain: moment.domain, action: 'correction') : moment).toList();`,
|
|
1120
1194
|
` }`,
|
|
1121
1195
|
` ${atClass} at(NomosMoment moment) => ${atClass}(_client, moment);`,
|
|
1122
1196
|
`}`,
|
|
@@ -1203,9 +1277,9 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1203
1277
|
? [
|
|
1204
1278
|
` final id = ${member};`,
|
|
1205
1279
|
` if (id == null) return null;`,
|
|
1206
|
-
` final rows = await client.${byIdMethod}(id
|
|
1280
|
+
` final rows = await client.${byIdMethod}(NmsInternalNomosRefs.id(id));`,
|
|
1207
1281
|
]
|
|
1208
|
-
: [` final rows = await client.${byIdMethod}(${member}
|
|
1282
|
+
: [` final rows = await client.${byIdMethod}(NmsInternalNomosRefs.id(${member}));`]),
|
|
1209
1283
|
` return rows.isEmpty ? null : rows.first;`,
|
|
1210
1284
|
` }`,
|
|
1211
1285
|
``,
|
|
@@ -1238,9 +1312,9 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1238
1312
|
? [
|
|
1239
1313
|
` final id = ${member};`,
|
|
1240
1314
|
` if (id == null) return null;`,
|
|
1241
|
-
` final rows = await client.${byIdMethod}(id
|
|
1315
|
+
` final rows = await client.${byIdMethod}(NmsInternalNomosRefs.id(id));`,
|
|
1242
1316
|
]
|
|
1243
|
-
: [` final rows = await client.${byIdMethod}(${member}
|
|
1317
|
+
: [` final rows = await client.${byIdMethod}(NmsInternalNomosRefs.id(${member}));`]),
|
|
1244
1318
|
` return rows.isEmpty ? null : rows.first;`,
|
|
1245
1319
|
` }`,
|
|
1246
1320
|
``,
|
|
@@ -1249,7 +1323,7 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1249
1323
|
for (const [fieldName, f] of foreignRefs) {
|
|
1250
1324
|
const fr = f.foreignRef!;
|
|
1251
1325
|
const targetType = fr.targetAggregateId;
|
|
1252
|
-
// The resolver returns the target's CONCRETE class
|
|
1326
|
+
// The resolver returns the target's CONCRETE class. We can
|
|
1253
1327
|
// only reference it when it is reachable from this file: SAME-domain (already in the imported types),
|
|
1254
1328
|
// or a CROSS-DOMAIN concrete target in the package (import its sibling `<name>.dart` `show`-limited).
|
|
1255
1329
|
// A contract target (its DTO lives in the shared contracts file) or an unmapped target is left as the
|
|
@@ -1272,15 +1346,13 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1272
1346
|
const method = safeMethod(`remote${cap(camel(member))}`);
|
|
1273
1347
|
lines.push(
|
|
1274
1348
|
` /// Resolve \`${fieldName}\` (${agg.id}.${fieldName} -> ${targetType} @ another workspace, a`,
|
|
1275
|
-
` /// \`t.foreignRef\` locator) over the session-bound multi-workspace plane
|
|
1276
|
-
` ///
|
|
1277
|
-
` /// (no clone). This is the informational follow+read lane, NOT the law-grade attested-read one`,
|
|
1278
|
-
` /// (use \`${member}!.attest(engine.foreignReader())\` when your law depends on the foreign fact).`,
|
|
1349
|
+
` /// \`t.foreignRef\` locator) over the session-bound multi-workspace plane. The generated`,
|
|
1350
|
+
` /// implementation opens the foreign workspace on demand; tenant code sees only this business method.`,
|
|
1279
1351
|
` /// Null locator (absent / empty id or workspace) ⇒ null.`,
|
|
1280
1352
|
` Future<${targetConcrete}?> ${method}(NomosEngine engine, {String? cloud}) {`,
|
|
1281
1353
|
` final ref = ${member};`,
|
|
1282
1354
|
` if (ref == null) return Future<${targetConcrete}?>.value(null);`,
|
|
1283
|
-
` return
|
|
1355
|
+
` return NmsInternalForeignRefs.resolve(ref, engine.foreignReader(cloud: cloud));`,
|
|
1284
1356
|
` }`,
|
|
1285
1357
|
``,
|
|
1286
1358
|
);
|
|
@@ -1313,14 +1385,14 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1313
1385
|
` /// history, decisions and evidence. The old events/transcript fields remain on [NomosDescription].`,
|
|
1314
1386
|
` Future<NomosDescription> describe({`,
|
|
1315
1387
|
` NomosDescribeSection section = NomosDescribeSection.root,`,
|
|
1316
|
-
` String? since, String?
|
|
1388
|
+
` String? since, String? performedBy, String? category, int? limit,`,
|
|
1317
1389
|
` }) async {`,
|
|
1318
1390
|
` final raw = await bridge.activityLog(limit: limit);`,
|
|
1319
1391
|
` var events = raw`,
|
|
1320
1392
|
` .map((r) => renderActivity(r.cast<String, dynamic>()))`,
|
|
1321
1393
|
` .whereType<ActivityEvent>()`,
|
|
1322
1394
|
` .toList();`,
|
|
1323
|
-
` if (
|
|
1395
|
+
` if (performedBy != null) events = events.where((e) => e.performedBy == performedBy).toList();`,
|
|
1324
1396
|
` if (category != null) events = events.where((e) => e.category == category).toList();`,
|
|
1325
1397
|
` if (since != null) events = events.where((e) => e.occurredAt.compareTo(since) >= 0).toList();`,
|
|
1326
1398
|
` final narrative = activityNarrative(events);`,
|
|
@@ -1359,8 +1431,8 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1359
1431
|
` summary: item['summary'] as String,`,
|
|
1360
1432
|
` details: item['details'] == null ? null : (item['details'] as Map).cast<String, dynamic>());`,
|
|
1361
1433
|
` }`,
|
|
1362
|
-
` List<NomosRef
|
|
1363
|
-
` .map((ref) =>
|
|
1434
|
+
` List<NomosRef<Object?>> eventRefs(ActivityEvent event) => event.refs.values`,
|
|
1435
|
+
` .map((ref) => NmsInternalNomosRefs.create<Object?>(workspace: ref.workspace, type: ref.type, id: ref.id)).toList();`,
|
|
1364
1436
|
` String refusalReason(Object? raw) {`,
|
|
1365
1437
|
` final text = (raw ?? '').toString().toLowerCase();`,
|
|
1366
1438
|
` bool has(List<String> parts) => parts.any(text.contains);`,
|
|
@@ -1398,7 +1470,7 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1398
1470
|
` final label = aggregate['label'] as String;`,
|
|
1399
1471
|
` try {`,
|
|
1400
1472
|
` final rows = await bridge.query('aggregatesByType', {'__type': queryType});`,
|
|
1401
|
-
` final refs = rows.map((row) =>
|
|
1473
|
+
` final refs = rows.map((row) => NmsInternalNomosRefs.create<Object?>(`,
|
|
1402
1474
|
` workspace: _ws, type: (row['type'] ?? type).toString(), id: (row['id'] ?? '').toString())).toList();`,
|
|
1403
1475
|
` final exact = aggregate['audience'] == 'public' || rows.isNotEmpty;`,
|
|
1404
1476
|
` items.add(NomosDescribeItem(`,
|
|
@@ -1417,14 +1489,14 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1417
1489
|
` summary = 'What changed, who changed it and when, read from verified committed testimony.';`,
|
|
1418
1490
|
` items = events.map((event) => NomosDescribeItem(`,
|
|
1419
1491
|
` kind: 'change', id: event.eventId, label: event.title, summary: event.message,`,
|
|
1420
|
-
` details: {'
|
|
1492
|
+
` details: {'performedBy': event.performedBy, 'occurredAt': event.occurredAt, 'domain': event.domain, 'kind': event.kind},`,
|
|
1421
1493
|
` refs: eventRefs(event))).toList();`,
|
|
1422
1494
|
` } else if (section == NomosDescribeSection.decisions) {`,
|
|
1423
1495
|
` title = 'Decisions and work needing attention';`,
|
|
1424
1496
|
` summary = 'Recorded business reasons, pending merges and retained rejected work, plus declared conflict policy from the model.';`,
|
|
1425
1497
|
` items = events.where((event) => event.kind.endsWith('.correction')).map((event) => NomosDescribeItem(`,
|
|
1426
1498
|
` kind: 'decision', id: event.eventId, label: event.title, summary: event.message,`,
|
|
1427
|
-
` details: {'
|
|
1499
|
+
` details: {'performedBy': event.performedBy, 'occurredAt': event.occurredAt, 'domain': event.domain},`,
|
|
1428
1500
|
` refs: eventRefs(event))).toList();`,
|
|
1429
1501
|
` final aggregateTypes = ((model['aggregateTypes'] as List?) ?? const [])`,
|
|
1430
1502
|
` .map((raw) => (raw as Map).cast<String, dynamic>()).toList();`,
|
|
@@ -1454,7 +1526,7 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1454
1526
|
` id: 'decision:\${aggregate['domain']}.\${aggregate['type']}:\${conflict['aggregateId']}:$field',`,
|
|
1455
1527
|
` label: 'Review $subject', summary: conflictSummary,`,
|
|
1456
1528
|
` details: {'domain': aggregate['domain'], 'type': aggregate['type'], 'field': field, 'status': status, 'versions': versions, 'next': status == 'ready' ? 'merge' : 'update-model'},`,
|
|
1457
|
-
` refs: [
|
|
1529
|
+
` refs: [NmsInternalNomosRefs.create<Object?>(workspace: _ws, type: aggregate['type'].toString(), id: conflict['aggregateId'].toString())]));`,
|
|
1458
1530
|
` }`,
|
|
1459
1531
|
` final modelPageItems = ((((model['pages'] as Map)['model'] as Map)['items'] as List?) ?? const [])`,
|
|
1460
1532
|
` .map((raw) => (raw as Map).cast<String, dynamic>()).where((item) => item['kind'] == 'verb').toList();`,
|
|
@@ -1481,7 +1553,7 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1481
1553
|
` NomosDescribeItem(kind: 'evidence', id: 'law', label: 'Generated law', summary: domainHash, details: {'domainHash': domainHash}),`,
|
|
1482
1554
|
` NomosDescribeItem(kind: 'evidence', id: 'reader', label: 'Verified reader', summary: this.actor ?? bridge.principal ?? 'anonymous', details: {'actor': this.actor ?? bridge.principal}),`,
|
|
1483
1555
|
` if (latest != null) NomosDescribeItem(kind: 'evidence', id: latest.eventId, label: 'Latest verified moment', summary: latest.eventId,`,
|
|
1484
|
-
` details: {'occurredAt': latest.occurredAt, '
|
|
1556
|
+
` details: {'occurredAt': latest.occurredAt, 'performedBy': latest.performedBy, 'domain': latest.domain, 'kind': latest.kind}),`,
|
|
1485
1557
|
` ];`,
|
|
1486
1558
|
` }`,
|
|
1487
1559
|
` return NomosDescription(`,
|
|
@@ -1505,23 +1577,14 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1505
1577
|
}
|
|
1506
1578
|
|
|
1507
1579
|
return [
|
|
1508
|
-
`// AUTO-GENERATED by nomos-compile —
|
|
1509
|
-
`//
|
|
1510
|
-
`// not edit. SESSION-BOUND (architecture/session_bound_clients.md): construct with a NomosSession —`,
|
|
1511
|
-
`// one session, one workspace, one domain interface, one actor context:`,
|
|
1512
|
-
`//`,
|
|
1580
|
+
`// AUTO-GENERATED by nomos-compile — private runtime implementation for domain "${domain}".`,
|
|
1581
|
+
`// Regenerated every compile; do not edit or teach this file as an application entry point.`,
|
|
1513
1582
|
// GENERATED-CODE lints: the bridge read/watch surface returns dynamic (JSON rows), so typed accessors
|
|
1514
1583
|
// do dynamic member access + casts by construction. Silence the lints that only make sense for
|
|
1515
1584
|
// hand-written code, so this file never adds noise to a tenant's analyzer output (co2 feedback).
|
|
1516
1585
|
// (deprecated_member_use*: the compatibility shims intentionally delegate to the deprecated
|
|
1517
1586
|
// engine-level lanes for one minor version.)
|
|
1518
|
-
`// ignore_for_file: avoid_dynamic_calls, unnecessary_this, prefer_const_constructors, unused_import, type_annotate_public_apis, always_specify_types, deprecated_member_use, deprecated_member_use_from_same_package`,
|
|
1519
|
-
`//`,
|
|
1520
|
-
`// final engine = NomosBridge(transport); // the device's local plane (NomosEngine)`,
|
|
1521
|
-
`// await engine.ready;`,
|
|
1522
|
-
`// final session = await engine.open(cloud: cloud, workspace: ws, clientId: deviceId);`,
|
|
1523
|
-
`// final app = await ${className}.bind(session); // preflights law compatibility (ask 5)`,
|
|
1524
|
-
`// await app.${dirs[0] ? safeMethod(dirs[0].id) : "someDirective"}(/* typed payload */);`,
|
|
1587
|
+
`// ignore_for_file: avoid_dynamic_calls, unnecessary_this, prefer_const_constructors, unused_import, type_annotate_public_apis, always_specify_types, deprecated_member_use, deprecated_member_use_from_same_package, invalid_use_of_internal_member`,
|
|
1525
1588
|
``,
|
|
1526
1589
|
// `dart:convert` is needed ONLY for the interface block's `jsonDecode` — the offer lane no longer encodes
|
|
1527
1590
|
// any envelope (it routes through `bridge.offerDirective` → THE ONE ENCODER). Omit it when no interface.
|
|
@@ -1549,11 +1612,10 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1549
1612
|
...[...crossDomainImports.entries()]
|
|
1550
1613
|
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
1551
1614
|
.map(([uri, names]) => `import '${uri}' show ${[...names].sort().join(", ")};`),
|
|
1552
|
-
`// Re-export
|
|
1553
|
-
`//
|
|
1615
|
+
`// Re-export business types only. The runtime import above is an implementation dependency, never`,
|
|
1616
|
+
`// a tenant API: exporting it would create a second raw bridge/session/sync application lane.`,
|
|
1554
1617
|
...(opts.typesImportUri !== undefined ? [`export '${opts.typesImportUri}';`] : [`export '${mod.name}.dart';`]),
|
|
1555
1618
|
...(opts.typesImportUri === undefined && opts.frameworkImportUri !== undefined ? [`export '${opts.frameworkImportUri}';`] : []),
|
|
1556
|
-
`export '${supportImport}' show NomosBridge, NomosEngine, NomosSession, NomosSessionCompat, BridgeTransport, NomosBridgeError, MissingOfferAuthority, kWebviewBridgeVersion, NomosCompat, NomosCompatMember, NomosOfferOutcome, NomosCreated, NomosRef, NomosMoment, NomosClientInterface, DomainClientSessionMismatch, DomainNotInstalledForSession, SessionMismatchCause${manualEncryptionPresent ? ", NomosScopeKey, NomosKeyStore, EncryptionKeyUnavailable, WrappedKeyStore, NomosEncryption, NomosEpochResult, EncryptionMemberKeyMissing, EncryptionRosterMismatch" : ""};`,
|
|
1557
1619
|
``,
|
|
1558
1620
|
`/// sha256 of the deployed \`.package.usda\` — the installed law this client offers under.`,
|
|
1559
1621
|
`const String ${hashConst} = ${dartStr(opts.domainHash)};`,
|
|
@@ -1650,10 +1712,10 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1650
1712
|
` /// \`await ${className}.open(bridge, ref: outcome.bornRefs.single)\` (or \`workspace: 'estate-42'\`).`,
|
|
1651
1713
|
` /// It ACQUIRES the workspace's session on [bridge] first (an already-open one is reused; a device-`,
|
|
1652
1714
|
` /// resident/parked/just-born one opens local-only in place) and THEN binds — so a not-open workspace`,
|
|
1653
|
-
` /// fails HERE, at open, never later on the client's first query.
|
|
1654
|
-
` ///
|
|
1655
|
-
` static Future<${className}> open(NomosBridge bridge, {NomosRef
|
|
1656
|
-
` final ws = ref
|
|
1715
|
+
` /// fails HERE, at open, never later on the client's first query. The generated stateful application`,
|
|
1716
|
+
` /// privately retains refreshable cloud access for a genuinely-absent authorized workspace.`,
|
|
1717
|
+
` static Future<${className}> open<TAggregate>(NomosBridge bridge, {NomosRef<TAggregate>? ref, String? workspace, String? actor${manualEncryptionPresent ? ", NomosKeyStore? keyStore" : ""}}) async {`,
|
|
1718
|
+
` final ws = ref == null ? workspace : NmsInternalNomosRefs.workspace(ref);`,
|
|
1657
1719
|
` if (ws == null || ws.isEmpty) {`,
|
|
1658
1720
|
` throw ArgumentError('${className}.open: pass a workspace: or ref: — the workspace to open and bind');`,
|
|
1659
1721
|
` }`,
|
|
@@ -1697,11 +1759,11 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1697
1759
|
` _ownedIdentityReady = true;`,
|
|
1698
1760
|
` }`,
|
|
1699
1761
|
``,
|
|
1700
|
-
` Future<NomosOfferOutcome> _ownedRecordAction(String action, NomosRef record, String peer) async {`,
|
|
1762
|
+
` Future<NomosOfferOutcome> _ownedRecordAction<TAggregate>(String action, NomosRef<TAggregate> record, String peer) async {`,
|
|
1701
1763
|
` await _preflightOnce();`,
|
|
1702
1764
|
` await _ensureOwnedIdentity();`,
|
|
1703
|
-
` if (
|
|
1704
|
-
` final directiveId = _ownedRecordActions[
|
|
1765
|
+
` if (NmsInternalNomosRefs.workspace(record) != _ws) throw ArgumentError('$action() cannot use a record from another workspace');`,
|
|
1766
|
+
` final directiveId = _ownedRecordActions[NmsInternalNomosRefs.type(record)]?[action];`,
|
|
1705
1767
|
` if (directiveId == null) throw ArgumentError('$action() needs a generated owned record reference');`,
|
|
1706
1768
|
` if (peer.trim().isEmpty) throw ArgumentError('$action() needs the person to act with');`,
|
|
1707
1769
|
` final payload = await bridge.nomosPrepareOwnedRecordAction(action: action, scope: ${dartStr(ownedScope)}, record: record, peer: peer);`,
|
|
@@ -1791,7 +1853,6 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
1791
1853
|
? [
|
|
1792
1854
|
`// ---- role policy (UI hints — the KERNEL GATE is the authority) ----`,
|
|
1793
1855
|
roleHelpers,
|
|
1794
|
-
``,
|
|
1795
1856
|
]
|
|
1796
1857
|
: []),
|
|
1797
1858
|
].join("\n");
|