@githolon/dsl 0.108.3 → 0.108.6
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 +10 -0
- package/dart/lib/src/application_framework_calls.dart +21 -0
- package/dart/lib/src/dev_cloud.dart +67 -0
- package/dart/lib/src/nms_test_environment.dart +1 -1
- package/dart/lib/src/runner/node-entry.mjs +372 -106
- package/dart/lib/src/runner/offline-install.mjs +171 -11
- package/dart/lib/src/session_bound.dart +58 -30
- package/dart/lib/src/test_harness.dart +60 -98
- package/dart/lib/src/webview_bridge.dart +212 -32
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/node_runner_transport_test.dart +5 -5
- package/dart/test/webview_bridge_test.dart +6 -0
- package/dart_types/CHANGELOG.md +4 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +428 -2
- package/dart_types/pubspec.yaml +1 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +314 -82
- package/src/application_topology.ts +120 -28
- package/src/births.ts +27 -0
- package/src/codegen_call_usda.ts +9 -4
- package/src/codegen_dart_application.ts +146 -37
- package/src/codegen_dart_client.ts +225 -160
- package/src/codegen_dart_framework_calls.ts +89 -4
- package/src/codegen_ts.ts +135 -60
- package/src/compile_package_main.ts +3544 -1460
- package/src/directive.ts +320 -118
- package/src/fields.ts +2 -3
- package/src/framework/application_home.ts +3 -0
- package/src/framework/auth_provider_law.ts +20 -16
- package/src/framework/genesis.ts +32 -1
- package/src/framework/home.ts +45 -8
- package/src/framework/signing_identity.ts +78 -0
- package/src/framework/workspaces.ts +9 -1
- package/src/framework_call_dart_enums.ts +33 -0
- package/src/framework_call_definitions.ts +11 -2
- package/src/index.ts +12 -0
- package/src/intent.ts +525 -194
- package/src/law_graph.ts +32 -4
- package/src/law_graph_usda.ts +3 -0
- package/src/package_config.ts +16 -1
- package/src/read.ts +33 -11
- package/src/wire.ts +1 -0
- package/src/wire_encode.ts +3 -3
|
@@ -8,11 +8,17 @@ type TopologyDirective = DomainModule["directives"][number] & {
|
|
|
8
8
|
readonly birthsChild?: unknown;
|
|
9
9
|
readonly businessBirth?: BusinessBirthMetadata;
|
|
10
10
|
readonly businessBirthInternal?: unknown;
|
|
11
|
+
readonly workspaceEffects?: readonly {
|
|
12
|
+
readonly target: unknown;
|
|
13
|
+
readonly destination: { readonly kind?: unknown };
|
|
14
|
+
}[];
|
|
11
15
|
};
|
|
12
16
|
|
|
13
17
|
export interface ApplicationTopology {
|
|
14
18
|
/** Co-resident business domains bound to the application workspace. */
|
|
15
19
|
readonly rootDomains: readonly string[];
|
|
20
|
+
/** Verified ancestor domains that are addressed remotely and never opened on the device plane. */
|
|
21
|
+
readonly remoteDomains: readonly string[];
|
|
16
22
|
/** Deterministic primary facet for generated narration/scenarios; not a storage-topology choice. */
|
|
17
23
|
readonly homeDomain: string;
|
|
18
24
|
readonly homeModule: DomainModule;
|
|
@@ -21,10 +27,10 @@ export interface ApplicationTopology {
|
|
|
21
27
|
const domainOf = (module: DomainModule): string => module.domain ?? module.name;
|
|
22
28
|
|
|
23
29
|
const publicLanguageOf = (module: DomainModule): readonly TopologyDirective[] =>
|
|
24
|
-
(module.applicationIntents ?? module.directives)
|
|
25
|
-
|
|
26
|
-
(directive as TopologyDirective).businessBirthInternal !== true
|
|
27
|
-
|
|
30
|
+
(module.applicationIntents ?? module.directives).filter(
|
|
31
|
+
(directive) =>
|
|
32
|
+
(directive as TopologyDirective).businessBirthInternal !== true
|
|
33
|
+
) as readonly TopologyDirective[];
|
|
28
34
|
|
|
29
35
|
/**
|
|
30
36
|
* Resolve which business domains run together in the application workspace.
|
|
@@ -32,13 +38,15 @@ const publicLanguageOf = (module: DomainModule): readonly TopologyDirective[] =>
|
|
|
32
38
|
* This is deliberately inferred from business law rather than configured as a framework topology knob:
|
|
33
39
|
*
|
|
34
40
|
* Domains are vocabulary, not storage partitions. They are co-resident by default. A declared
|
|
35
|
-
* cross-domain business birth is the evidence that its target belongs in a child workspace
|
|
41
|
+
* cross-domain business birth is the evidence that its target belongs in a child workspace; a
|
|
42
|
+
* verified-parent effect is the equally explicit evidence that its target belongs in an ancestor
|
|
43
|
+
* workspace and must not be bound into the participant's receiving group.
|
|
36
44
|
*
|
|
37
45
|
* The primary domain is only a deterministic generated-surface/narration choice: an explicit receiving-group
|
|
38
46
|
* language wins, then a literal `home`, then declaration order. It does not move the other root domains.
|
|
39
47
|
*/
|
|
40
48
|
export function resolveApplicationTopology(
|
|
41
|
-
modules: readonly DomainModule[]
|
|
49
|
+
modules: readonly DomainModule[]
|
|
42
50
|
): ApplicationTopology {
|
|
43
51
|
if (modules.length === 0) {
|
|
44
52
|
throw new Error("generated application topology has no business domains");
|
|
@@ -49,7 +57,7 @@ export function resolveApplicationTopology(
|
|
|
49
57
|
const domain = domainOf(module);
|
|
50
58
|
if (byDomain.has(domain)) {
|
|
51
59
|
throw new Error(
|
|
52
|
-
`generated application topology contains domain '${domain}' more than once
|
|
60
|
+
`generated application topology contains domain '${domain}' more than once`
|
|
53
61
|
);
|
|
54
62
|
}
|
|
55
63
|
byDomain.set(domain, module);
|
|
@@ -58,26 +66,35 @@ export function resolveApplicationTopology(
|
|
|
58
66
|
if (modules.length === 1) {
|
|
59
67
|
const homeModule = modules[0]!;
|
|
60
68
|
const homeDomain = domainOf(homeModule);
|
|
61
|
-
return {
|
|
69
|
+
return {
|
|
70
|
+
rootDomains: [homeDomain],
|
|
71
|
+
remoteDomains: [],
|
|
72
|
+
homeDomain,
|
|
73
|
+
homeModule,
|
|
74
|
+
};
|
|
62
75
|
}
|
|
63
76
|
|
|
64
77
|
const declaredReceivingGroups = modules.filter((module) =>
|
|
65
|
-
|
|
66
|
-
directive
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
78
|
+
module.directives.some(
|
|
79
|
+
(directive) =>
|
|
80
|
+
directive.id === "nomosInternalBirthReceivingGroup7c1f5e" &&
|
|
81
|
+
directive.birthsChild === true &&
|
|
82
|
+
directive.aggregateId === "NomosInternalReceivingGroup7c1f5e"
|
|
83
|
+
)
|
|
70
84
|
);
|
|
71
85
|
if (declaredReceivingGroups.length > 1) {
|
|
72
86
|
throw new Error(
|
|
73
87
|
`generated application topology has more than one receiving-group domain: ${declaredReceivingGroups
|
|
74
88
|
.map(domainOf)
|
|
75
89
|
.sort()
|
|
76
|
-
.join(
|
|
90
|
+
.join(
|
|
91
|
+
", "
|
|
92
|
+
)}. An accountable participant gets one receiving group; keep one group birth language.`
|
|
77
93
|
);
|
|
78
94
|
}
|
|
79
95
|
|
|
80
96
|
const aggregateOwners = new Map<string, Set<string>>();
|
|
97
|
+
const directiveOwners = new Map<object, string>();
|
|
81
98
|
for (const module of modules) {
|
|
82
99
|
const domain = domainOf(module);
|
|
83
100
|
for (const aggregate of module.aggregates) {
|
|
@@ -85,11 +102,65 @@ export function resolveApplicationTopology(
|
|
|
85
102
|
owners.add(domain);
|
|
86
103
|
aggregateOwners.set(aggregate.id, owners);
|
|
87
104
|
}
|
|
105
|
+
for (const directive of module.directives) {
|
|
106
|
+
directiveOwners.set(directive as object, domain);
|
|
107
|
+
}
|
|
88
108
|
}
|
|
89
109
|
|
|
90
110
|
const targetedByAnotherDomain = new Set<string>();
|
|
111
|
+
const remoteAncestorDomains = new Set<string>();
|
|
112
|
+
const parentsByDomain = new Map<string, Set<string>>();
|
|
91
113
|
const childrenByDomain = new Map<string, Set<string>>();
|
|
92
114
|
const birthSources = new Set<string>();
|
|
115
|
+
|
|
116
|
+
// Establish the participant's remote ancestors before interpreting births. An ancestor may
|
|
117
|
+
// lawfully commission the participant workspace (Platform -> Home), but that global lineage
|
|
118
|
+
// must not make the participant's own Home disappear from its receiving-group application.
|
|
119
|
+
// Descendant births authored by a co-resident domain (Home -> Estate) still place their targets
|
|
120
|
+
// outside the receiving group below.
|
|
121
|
+
for (const module of modules) {
|
|
122
|
+
const source = domainOf(module);
|
|
123
|
+
for (const directive of publicLanguageOf(module)) {
|
|
124
|
+
for (const effect of directive.workspaceEffects ?? []) {
|
|
125
|
+
if (effect.destination.kind !== "verifiedParent") continue;
|
|
126
|
+
const target = directiveOwners.get(effect.target as object);
|
|
127
|
+
if (target !== undefined && target !== source) {
|
|
128
|
+
const parents = parentsByDomain.get(source) ?? new Set<string>();
|
|
129
|
+
parents.add(target);
|
|
130
|
+
parentsByDomain.set(source, parents);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Follow the receiving group's own verified-parent chain. A descendant also points back to this
|
|
137
|
+
// group, but that makes the group its parent—not an ancestor that should be removed from the app.
|
|
138
|
+
const receivingGroupModule = declaredReceivingGroups[0];
|
|
139
|
+
const participantDomain = receivingGroupModule
|
|
140
|
+
? domainOf(receivingGroupModule)
|
|
141
|
+
: byDomain.has("home")
|
|
142
|
+
? "home"
|
|
143
|
+
: undefined;
|
|
144
|
+
if (participantDomain !== undefined) {
|
|
145
|
+
const pending = [participantDomain];
|
|
146
|
+
const seen = new Set(pending);
|
|
147
|
+
while (pending.length > 0) {
|
|
148
|
+
const source = pending.pop()!;
|
|
149
|
+
for (const parent of parentsByDomain.get(source) ?? []) {
|
|
150
|
+
if (seen.has(parent)) continue;
|
|
151
|
+
seen.add(parent);
|
|
152
|
+
remoteAncestorDomains.add(parent);
|
|
153
|
+
pending.push(parent);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
// Packages without an explicit receiving group retain the conservative interpretation: a
|
|
158
|
+
// verified-parent target cannot be silently opened in the same workspace as its requester.
|
|
159
|
+
for (const parents of parentsByDomain.values()) {
|
|
160
|
+
for (const parent of parents) remoteAncestorDomains.add(parent);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
93
164
|
for (const module of modules) {
|
|
94
165
|
const source = domainOf(module);
|
|
95
166
|
for (const directive of publicLanguageOf(module)) {
|
|
@@ -99,13 +170,15 @@ export function resolveApplicationTopology(
|
|
|
99
170
|
if (owners === undefined || owners.size !== 1) {
|
|
100
171
|
throw new Error(
|
|
101
172
|
`generated application topology cannot locate the one domain owning born aggregate ` +
|
|
102
|
-
`'${aggregateId}' from '${source}/${directive.id}'
|
|
173
|
+
`'${aggregateId}' from '${source}/${directive.id}'`
|
|
103
174
|
);
|
|
104
175
|
}
|
|
105
176
|
const target = [...owners][0]!;
|
|
106
177
|
birthSources.add(source);
|
|
107
178
|
if (target !== source) {
|
|
108
|
-
|
|
179
|
+
if (!remoteAncestorDomains.has(source)) {
|
|
180
|
+
targetedByAnotherDomain.add(target);
|
|
181
|
+
}
|
|
109
182
|
const children = childrenByDomain.get(source) ?? new Set<string>();
|
|
110
183
|
children.add(target);
|
|
111
184
|
childrenByDomain.set(source, children);
|
|
@@ -118,34 +191,53 @@ export function resolveApplicationTopology(
|
|
|
118
191
|
const walk = (domain: string, path: readonly string[]): void => {
|
|
119
192
|
if (visiting.has(domain)) {
|
|
120
193
|
throw new Error(
|
|
121
|
-
`generated application topology contains a cross-workspace birth cycle: ${[
|
|
194
|
+
`generated application topology contains a cross-workspace birth cycle: ${[
|
|
195
|
+
...path,
|
|
196
|
+
domain,
|
|
197
|
+
].join(" -> ")}`
|
|
122
198
|
);
|
|
123
199
|
}
|
|
124
200
|
if (visited.has(domain)) return;
|
|
125
201
|
visiting.add(domain);
|
|
126
|
-
for (const child of childrenByDomain.get(domain) ?? [])
|
|
202
|
+
for (const child of childrenByDomain.get(domain) ?? [])
|
|
203
|
+
walk(child, [...path, domain]);
|
|
127
204
|
visiting.delete(domain);
|
|
128
205
|
visited.add(domain);
|
|
129
206
|
};
|
|
130
207
|
for (const domain of byDomain.keys()) walk(domain, []);
|
|
131
208
|
|
|
132
|
-
const rootModules = modules.filter((module) =>
|
|
209
|
+
const rootModules = modules.filter((module) => {
|
|
210
|
+
const domain = domainOf(module);
|
|
211
|
+
return (
|
|
212
|
+
!targetedByAnotherDomain.has(domain) && !remoteAncestorDomains.has(domain)
|
|
213
|
+
);
|
|
214
|
+
});
|
|
133
215
|
const rootDomains = rootModules.map(domainOf);
|
|
134
216
|
if (rootDomains.length === 0) {
|
|
135
217
|
throw new Error(
|
|
136
|
-
"Nomos cannot determine an application workspace because every business domain is born by another domain."
|
|
218
|
+
"Nomos cannot determine an application workspace because every business domain is born by another domain."
|
|
137
219
|
);
|
|
138
220
|
}
|
|
139
221
|
|
|
140
|
-
|
|
141
|
-
|
|
222
|
+
if (
|
|
223
|
+
receivingGroupModule !== undefined &&
|
|
224
|
+
!rootModules.includes(receivingGroupModule)
|
|
225
|
+
) {
|
|
142
226
|
throw new Error(
|
|
143
|
-
`generated application topology places its receiving-group domain '${domainOf(
|
|
227
|
+
`generated application topology places its receiving-group domain '${domainOf(
|
|
228
|
+
receivingGroupModule
|
|
229
|
+
)}' inside a child workspace`
|
|
144
230
|
);
|
|
145
231
|
}
|
|
146
|
-
const homeModule =
|
|
147
|
-
??
|
|
148
|
-
|
|
149
|
-
??
|
|
150
|
-
|
|
232
|
+
const homeModule =
|
|
233
|
+
receivingGroupModule ??
|
|
234
|
+
rootModules.find((module) => domainOf(module) === "home") ??
|
|
235
|
+
rootModules.find((module) => birthSources.has(domainOf(module))) ??
|
|
236
|
+
rootModules[0]!;
|
|
237
|
+
return {
|
|
238
|
+
rootDomains,
|
|
239
|
+
remoteDomains: [...remoteAncestorDomains],
|
|
240
|
+
homeDomain: domainOf(homeModule),
|
|
241
|
+
homeModule,
|
|
242
|
+
};
|
|
151
243
|
}
|
package/src/births.ts
CHANGED
|
@@ -8,6 +8,12 @@ import type { AggregateHandle } from "./aggregate.js";
|
|
|
8
8
|
import type { AggregateRef } from "./authoring.js";
|
|
9
9
|
import type { Ports } from "./ctx.js";
|
|
10
10
|
import type { Field, FieldValue, NomosRefValue } from "./fields.js";
|
|
11
|
+
import {
|
|
12
|
+
object,
|
|
13
|
+
relationType,
|
|
14
|
+
self,
|
|
15
|
+
type RelationTypeBuilder,
|
|
16
|
+
} from "./relation_schema.js";
|
|
11
17
|
|
|
12
18
|
/**
|
|
13
19
|
* THE BIRTH EFFECT — birth is an EXPLICIT, DECLARED kernel primitive (G3, the unix model).
|
|
@@ -49,6 +55,27 @@ export function workspaceRole<const Name extends string>(name: Name): Name & Wor
|
|
|
49
55
|
return name as Name & WorkspaceRole;
|
|
50
56
|
}
|
|
51
57
|
|
|
58
|
+
/** Declare target-owned roles which a born workspace may grant to its verified immediate parent.
|
|
59
|
+
* The matching relation is necessarily on Nomos's canonical global `workspace` object and accepts
|
|
60
|
+
* only another nominal workspace subject. Keeping this shape behind the branded WorkspaceRole API
|
|
61
|
+
* prevents a local namespaced lookalike from making a correctly attested parent impossible to admit. */
|
|
62
|
+
export function parentWorkspaceRoleRelations(
|
|
63
|
+
...roles: readonly WorkspaceRole[]
|
|
64
|
+
): RelationTypeBuilder {
|
|
65
|
+
const unique = [...new Set(roles)];
|
|
66
|
+
if (unique.length !== roles.length) {
|
|
67
|
+
throw new Error("parentWorkspaceRoleRelations repeats a workspace role");
|
|
68
|
+
}
|
|
69
|
+
const relations = relationType("workspace").asGlobal();
|
|
70
|
+
for (const role of unique) {
|
|
71
|
+
relations.relation(role, {
|
|
72
|
+
rewrite: self(),
|
|
73
|
+
allow: [object("workspace")],
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return relations;
|
|
77
|
+
}
|
|
78
|
+
|
|
52
79
|
// ── the per-dispatch birth footprint — library state drained by `executeDirectiveToIntent` (mirrors the
|
|
53
80
|
// captured-read footprint in read.ts; internal to the directive runtime, never a sandbox side channel). ──
|
|
54
81
|
let footprint: BirthRecipe[] = [];
|
package/src/codegen_call_usda.ts
CHANGED
|
@@ -397,8 +397,11 @@ export function emitTsCallUsdaEncoder(
|
|
|
397
397
|
const literal = node.expression.value;
|
|
398
398
|
if (typeof literal === "string") {
|
|
399
399
|
out.push(`_nmsRoutingCandidates.push(${JSON.stringify(literal)});`);
|
|
400
|
-
|
|
401
|
-
|
|
400
|
+
// String literals are singleton enum members in the law graph. The kernel's
|
|
401
|
+
// canonical authoring path therefore lowers them as USDA tokens, exactly like
|
|
402
|
+
// ordinary enums, rather than as unconstrained strings.
|
|
403
|
+
begin("NomosTokenValue");
|
|
404
|
+
out.push(`_nmsUsdaString(${outName}, ${depth + 1}, "token", "nomos:value", ${JSON.stringify(literal)});`);
|
|
402
405
|
} else if (typeof literal === "boolean") {
|
|
403
406
|
begin("NomosBoolValue");
|
|
404
407
|
out.push(`_nmsUsdaScalar(${outName}, ${depth + 1}, "bool", "nomos:value", ${literal ? '"true"' : '"false"'});`);
|
|
@@ -806,8 +809,10 @@ export function emitDartCallUsdaEncoder(
|
|
|
806
809
|
case "literal": {
|
|
807
810
|
const literal = node.expression.value;
|
|
808
811
|
if (typeof literal === "string") {
|
|
809
|
-
|
|
810
|
-
|
|
812
|
+
// Keep Dart's authored graph identical to the kernel's canonical lowering:
|
|
813
|
+
// string literals are singleton enum members and therefore USDA tokens.
|
|
814
|
+
begin("NomosTokenValue");
|
|
815
|
+
out.push(`_nmsUsdaString(${outName}, ${depth + 1}, 'token', 'nomos:value', ${dartString(literal)});`);
|
|
811
816
|
} else if (typeof literal === "boolean") {
|
|
812
817
|
begin("NomosBoolValue");
|
|
813
818
|
out.push(`_nmsUsdaScalar(${outName}, ${depth + 1}, 'bool', 'nomos:value', '${literal ? "true" : "false"}');`);
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
} from "./codegen_dart.js";
|
|
29
29
|
import {
|
|
30
30
|
dartClientClassName,
|
|
31
|
+
dartInternalChildAuthenticationRecoveryMethod,
|
|
31
32
|
dartInternalSourceTrustMethod,
|
|
32
33
|
dartInternalWorkspaceSharingReceiptMethod,
|
|
33
34
|
} from "./codegen_dart_client.js";
|
|
@@ -43,7 +44,10 @@ import {
|
|
|
43
44
|
isFrameworkDirective,
|
|
44
45
|
isFrameworkQuery,
|
|
45
46
|
} from "./framework/framework_aggregates.js";
|
|
46
|
-
import {
|
|
47
|
+
import {
|
|
48
|
+
childAuthenticationRecoveryAggregateId,
|
|
49
|
+
isBusinessIntent,
|
|
50
|
+
} from "./intent.js";
|
|
47
51
|
import { contractTypeName } from "./contract.js";
|
|
48
52
|
import { admitsNull } from "./null_meta.js";
|
|
49
53
|
import type {
|
|
@@ -203,7 +207,7 @@ function sharingReceiptPayload(
|
|
|
203
207
|
policy: WorkspaceSharingPolicy,
|
|
204
208
|
receipt: WorkspaceSharingReceipt,
|
|
205
209
|
refused: boolean,
|
|
206
|
-
enumDartTypes: ReadonlyMap<string, string
|
|
210
|
+
enumDartTypes: ReadonlyMap<string, string>
|
|
207
211
|
): string[] {
|
|
208
212
|
const fields = introspectPayload(
|
|
209
213
|
receipt.records.payloadSchema as z.ZodTypeAny,
|
|
@@ -236,9 +240,8 @@ function sharingReceiptPayload(
|
|
|
236
240
|
);
|
|
237
241
|
}
|
|
238
242
|
const raw = sharingOrderValue(policy, sourceName);
|
|
239
|
-
const expression =
|
|
240
|
-
? `${target.dartType}.fromWire(${raw}.wire)`
|
|
241
|
-
: raw;
|
|
243
|
+
const expression =
|
|
244
|
+
source.kind === "enum" ? `${target.dartType}.fromWire(${raw}.wire)` : raw;
|
|
242
245
|
return [` ${member(target.name)}: ${expression},`];
|
|
243
246
|
});
|
|
244
247
|
}
|
|
@@ -335,9 +338,7 @@ function dartConflictPublicClassNames(mod: DomainModule): string[] {
|
|
|
335
338
|
) {
|
|
336
339
|
names.add(`${aggregateName}${cap(camel(binding.field))}Conflict`);
|
|
337
340
|
names.add(
|
|
338
|
-
`${aggregateName}${cap(
|
|
339
|
-
camel(binding.field)
|
|
340
|
-
)}ConflictEvolutionRequired`
|
|
341
|
+
`${aggregateName}${cap(camel(binding.field))}ConflictEvolutionRequired`
|
|
341
342
|
);
|
|
342
343
|
}
|
|
343
344
|
}
|
|
@@ -601,6 +602,21 @@ export function generateDartApplication(
|
|
|
601
602
|
.businessBirthInternal !== true &&
|
|
602
603
|
!isFrameworkDirective(directive)
|
|
603
604
|
);
|
|
605
|
+
const childAuthenticationRecoveryByAggregate = new Map<
|
|
606
|
+
string,
|
|
607
|
+
AnyDirective
|
|
608
|
+
>();
|
|
609
|
+
for (const directive of directives) {
|
|
610
|
+
if (!isBusinessIntent(directive)) continue;
|
|
611
|
+
const aggregateId = childAuthenticationRecoveryAggregateId(directive);
|
|
612
|
+
if (aggregateId === undefined) continue;
|
|
613
|
+
if (childAuthenticationRecoveryByAggregate.has(aggregateId)) {
|
|
614
|
+
throw new Error(
|
|
615
|
+
`generated application '${domain}' has more than one child authentication recovery action for '${aggregateId}'`
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
childAuthenticationRecoveryByAggregate.set(aggregateId, directive);
|
|
619
|
+
}
|
|
604
620
|
const actionReferenceChecks = (directive: AnyDirective): string[] => {
|
|
605
621
|
let specs: ReturnType<typeof introspectPayload>;
|
|
606
622
|
try {
|
|
@@ -747,11 +763,12 @@ export function generateDartApplication(
|
|
|
747
763
|
if (trust === undefined) {
|
|
748
764
|
return { prelude: [], call };
|
|
749
765
|
}
|
|
750
|
-
const copiedEntries = Object.entries(trust.copy)
|
|
751
|
-
|
|
766
|
+
const copiedEntries = Object.entries(trust.copy).sort(([a], [b]) =>
|
|
767
|
+
a.localeCompare(b)
|
|
768
|
+
);
|
|
752
769
|
const copied = copiedEntries.map(
|
|
753
|
-
|
|
754
|
-
|
|
770
|
+
([target, field]) => ` ${member(target)}: ${factValue(field)},`
|
|
771
|
+
);
|
|
755
772
|
const trustDirective = (mod.directives as AnyDirective[]).find(
|
|
756
773
|
(candidate) => candidate.id === trust.viaDirectiveId
|
|
757
774
|
);
|
|
@@ -775,7 +792,9 @@ export function generateDartApplication(
|
|
|
775
792
|
if (missingRequired.length > 0) {
|
|
776
793
|
throw new Error(
|
|
777
794
|
`Dart source-trust codegen: '${directive.id}' cannot construct '${trust.viaDirectiveId}'; ` +
|
|
778
|
-
`missing required typed field(s): ${missingRequired
|
|
795
|
+
`missing required typed field(s): ${missingRequired
|
|
796
|
+
.map((field) => field.name)
|
|
797
|
+
.join(", ")}`
|
|
779
798
|
);
|
|
780
799
|
}
|
|
781
800
|
const omittedOptionals = trustFields
|
|
@@ -785,22 +804,70 @@ export function generateDartApplication(
|
|
|
785
804
|
(field) => field.name === trust.keyHashesField
|
|
786
805
|
)?.dartType;
|
|
787
806
|
const keyElementType = keyHashesType?.match(/^List<(.+)>$/)?.[1] ?? "Never";
|
|
807
|
+
const satisfactionQuery =
|
|
808
|
+
trust.satisfiedByQueryId === undefined
|
|
809
|
+
? undefined
|
|
810
|
+
: (mod.queries as QueryDecl[]).find(
|
|
811
|
+
(query) => query.id === trust.satisfiedByQueryId
|
|
812
|
+
);
|
|
813
|
+
if (
|
|
814
|
+
trust.satisfiedByQueryId !== undefined &&
|
|
815
|
+
satisfactionQuery === undefined
|
|
816
|
+
) {
|
|
817
|
+
throw new Error(
|
|
818
|
+
`Dart source-trust codegen: '${directive.id}' names missing satisfaction query '${trust.satisfiedByQueryId}'`
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
if (
|
|
822
|
+
satisfactionQuery !== undefined &&
|
|
823
|
+
satisfactionQuery.returns !== trustDirective.aggregateId
|
|
824
|
+
) {
|
|
825
|
+
throw new Error(
|
|
826
|
+
`Dart source-trust codegen: '${directive.id}' satisfaction query '${satisfactionQuery.id}' returns '${satisfactionQuery.returns}', not trust aggregate '${trustDirective.aggregateId}'`
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
const satisfactionArgs = trust.satisfiedByQueryArgs ?? {};
|
|
830
|
+
if (
|
|
831
|
+
satisfactionQuery !== undefined &&
|
|
832
|
+
(satisfactionQuery.key.some(
|
|
833
|
+
(key) => satisfactionArgs[key] === undefined
|
|
834
|
+
) ||
|
|
835
|
+
Object.keys(satisfactionArgs).some(
|
|
836
|
+
(key) => !satisfactionQuery.key.includes(key)
|
|
837
|
+
))
|
|
838
|
+
) {
|
|
839
|
+
throw new Error(
|
|
840
|
+
`Dart source-trust codegen: '${directive.id}' satisfaction query '${
|
|
841
|
+
satisfactionQuery.id
|
|
842
|
+
}' needs exactly keys ${satisfactionQuery.key.join(", ")}`
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
// A declared satisfaction query means trust is already a typed law fact.
|
|
846
|
+
// The kernel's foreign-read policy evaluates that fact while admitting the
|
|
847
|
+
// business offer. Do not create a second, client-side read-then-auto-grant
|
|
848
|
+
// authority ceremony.
|
|
849
|
+
if (satisfactionQuery !== undefined) {
|
|
850
|
+
return { prelude: [], call };
|
|
851
|
+
}
|
|
852
|
+
const trustIndent = " ";
|
|
788
853
|
return {
|
|
789
854
|
prelude: [
|
|
790
|
-
|
|
855
|
+
`${trustIndent}final sourceGenesis = await _client.bridge.nomosWorkspaceGenesis(${factValue(
|
|
791
856
|
trust.fromPayload
|
|
792
857
|
)});`,
|
|
793
|
-
|
|
858
|
+
`${trustIndent}await _client.${dartInternalSourceTrustMethod(
|
|
794
859
|
trust.viaDirectiveId
|
|
795
860
|
)}((`,
|
|
796
861
|
...copied,
|
|
797
862
|
...omittedOptionals,
|
|
798
|
-
|
|
863
|
+
`${trustIndent} ${member(trust.genesisField)}: sourceGenesis,`,
|
|
799
864
|
// Custody-native v2 proves the sibling by genesis + authenticated Git position and has no
|
|
800
865
|
// signing key. Never fabricate a v1 attestor identity; an operator must explicitly enroll
|
|
801
866
|
// real keys before a signed v1 read can pass.
|
|
802
|
-
|
|
803
|
-
|
|
867
|
+
`${trustIndent} ${member(
|
|
868
|
+
trust.keyHashesField
|
|
869
|
+
)}: const <${keyElementType}>[],`,
|
|
870
|
+
`${trustIndent} ));`,
|
|
804
871
|
],
|
|
805
872
|
call,
|
|
806
873
|
};
|
|
@@ -1029,6 +1096,8 @@ export function generateDartApplication(
|
|
|
1029
1096
|
: concreteClassName(rootAggregate.id)
|
|
1030
1097
|
)
|
|
1031
1098
|
);
|
|
1099
|
+
const childAuthenticationRecovery =
|
|
1100
|
+
childAuthenticationRecoveryByAggregate.get(birth.aggregateId);
|
|
1032
1101
|
if (targetFile !== mod.name) {
|
|
1033
1102
|
addSibling(`${targetFile}.application.dart`, targetApplication);
|
|
1034
1103
|
addSibling(`${targetFile}.application.dart`, opener);
|
|
@@ -1087,7 +1156,16 @@ export function generateDartApplication(
|
|
|
1087
1156
|
` late final Future<${targetApplication}> opened;`,
|
|
1088
1157
|
` opened = (() async {`,
|
|
1089
1158
|
` try {`,
|
|
1090
|
-
` return await ${opener}(
|
|
1159
|
+
` return await ${opener}(`,
|
|
1160
|
+
` _client.bridge, reference, _client.actor,`,
|
|
1161
|
+
...(childAuthenticationRecovery === undefined
|
|
1162
|
+
? []
|
|
1163
|
+
: [
|
|
1164
|
+
` recoverAuthentication: () => _client.${dartInternalChildAuthenticationRecoveryMethod(
|
|
1165
|
+
birth.aggregateId
|
|
1166
|
+
)}(workspace),`,
|
|
1167
|
+
]),
|
|
1168
|
+
` );`,
|
|
1091
1169
|
` } catch (_) {`,
|
|
1092
1170
|
` if (identical(_nmsApplications[workspace], opened)) {`,
|
|
1093
1171
|
` _nmsApplications.remove(workspace);`,
|
|
@@ -1121,6 +1199,22 @@ export function generateDartApplication(
|
|
|
1121
1199
|
` ),`,
|
|
1122
1200
|
` );`,
|
|
1123
1201
|
``,
|
|
1202
|
+
` /// Current kernel-owned progress while [open] prepares this workspace.`,
|
|
1203
|
+
` ApplicationWorkspaceOpenProgress? openProgress(String key) =>`,
|
|
1204
|
+
` NomosApplicationObservability.workspaceOpenProgress(`,
|
|
1205
|
+
` deriveWorkspaceName(${JSON.stringify(
|
|
1206
|
+
birth.namespace
|
|
1207
|
+
)}, key),`,
|
|
1208
|
+
` );`,
|
|
1209
|
+
``,
|
|
1210
|
+
` /// Kernel-owned progress updates while [open] prepares this workspace.`,
|
|
1211
|
+
` Stream<ApplicationWorkspaceOpenProgress> watchOpenProgress(String key) =>`,
|
|
1212
|
+
` NomosApplicationObservability.watchWorkspaceOpenProgress(`,
|
|
1213
|
+
` deriveWorkspaceName(${JSON.stringify(
|
|
1214
|
+
birth.namespace
|
|
1215
|
+
)}, key),`,
|
|
1216
|
+
` );`,
|
|
1217
|
+
``,
|
|
1124
1218
|
]);
|
|
1125
1219
|
addCollectionMember(birth.aggregateId, "mine", [
|
|
1126
1220
|
` /// The ${birth.aggregateId} lives founded by the signed-in person.`,
|
|
@@ -1291,9 +1385,10 @@ export function generateDartApplication(
|
|
|
1291
1385
|
const payload = dartActionInputClassName(directive);
|
|
1292
1386
|
const payloadSpecs = introspectPayload(
|
|
1293
1387
|
directive.payloadSchema as z.ZodTypeAny,
|
|
1294
|
-
new Map()
|
|
1388
|
+
new Map()
|
|
1295
1389
|
);
|
|
1296
|
-
const emptyPayload =
|
|
1390
|
+
const emptyPayload =
|
|
1391
|
+
directive.targetBinding === undefined && payloadSpecs.length === 0;
|
|
1297
1392
|
const targetOnly =
|
|
1298
1393
|
directive.targetBinding !== undefined &&
|
|
1299
1394
|
payloadSpecs.every(
|
|
@@ -1302,19 +1397,19 @@ export function generateDartApplication(
|
|
|
1302
1397
|
spec.name === directive.businessPrincipalField
|
|
1303
1398
|
) &&
|
|
1304
1399
|
(directive.businessRecordInputs?.length ?? 0) === 0;
|
|
1305
|
-
const targetParameter =
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1400
|
+
const targetParameter = emptyPayload
|
|
1401
|
+
? ""
|
|
1402
|
+
: directive.targetBinding === undefined
|
|
1403
|
+
? `${payload} facts`
|
|
1404
|
+
: targetOnly
|
|
1405
|
+
? `NomosRef<${concreteClassName(
|
|
1406
|
+
directive.targetBinding.aggregateId
|
|
1407
|
+
)}> target`
|
|
1408
|
+
: `NomosRef<${concreteClassName(
|
|
1409
|
+
directive.targetBinding.aggregateId
|
|
1410
|
+
)}> target, ${payload} facts`;
|
|
1411
|
+
const factsPrelude =
|
|
1412
|
+
targetOnly || emptyPayload ? [` const facts = ${payload}();`] : [];
|
|
1318
1413
|
const referenceChecks = actionReferenceChecks(directive);
|
|
1319
1414
|
const outputs = dartActionOutputs(directive, mod.aggregates);
|
|
1320
1415
|
const resultClass = dartActionResultClassName(directive.id);
|
|
@@ -1995,6 +2090,10 @@ export function generateDartApplication(
|
|
|
1995
2090
|
tenantRoot
|
|
1996
2091
|
? "ApplicationModelChange, NomosApplicationStartupState, "
|
|
1997
2092
|
: ""
|
|
2093
|
+
}${
|
|
2094
|
+
usesWorkspaceName
|
|
2095
|
+
? "ApplicationWorkspaceOpenProgress, NomosApplicationObservability, "
|
|
2096
|
+
: ""
|
|
1998
2097
|
}NmsInternalApplicationUserBinding${
|
|
1999
2098
|
opts.flutterHost === true || sharingPolicies.length > 0
|
|
2000
2099
|
? ", NmsInternalApplicationHost"
|
|
@@ -2227,11 +2326,13 @@ export function generateDartApplication(
|
|
|
2227
2326
|
`}`,
|
|
2228
2327
|
``,
|
|
2229
2328
|
`Future<${application}> nomosInternalOpen${application}<TAggregate>(`,
|
|
2230
|
-
` NomosBridge bridge, NomosRef<TAggregate> reference, String? actor
|
|
2329
|
+
` NomosBridge bridge, NomosRef<TAggregate> reference, String? actor,`,
|
|
2330
|
+
` {Future<void> Function()? recoverAuthentication}) async {`,
|
|
2231
2331
|
...(opts.flutterHost === true
|
|
2232
2332
|
? [
|
|
2233
2333
|
` return NmsInternalApplicationHost.nmsInternalOpenReferencedApplication<${application}>(`,
|
|
2234
2334
|
` parent: bridge,`,
|
|
2335
|
+
` workspace: NmsInternalNomosRefs.workspace(reference),`,
|
|
2235
2336
|
` open: () async {`,
|
|
2236
2337
|
]
|
|
2237
2338
|
: []),
|
|
@@ -2239,8 +2340,12 @@ export function generateDartApplication(
|
|
|
2239
2340
|
? [
|
|
2240
2341
|
` final signedIn = actor?.trim() ?? '';`,
|
|
2241
2342
|
` if (signedIn.isEmpty) throw StateError('Nomos could not identify the signed-in application user.');`,
|
|
2343
|
+
` final client = await ${client}.open(`,
|
|
2344
|
+
` bridge, ref: reference, actor: signedIn,`,
|
|
2345
|
+
` recoverAuthentication: recoverAuthentication,`,
|
|
2346
|
+
` );`,
|
|
2242
2347
|
` final application = ${application}._(`,
|
|
2243
|
-
`
|
|
2348
|
+
` client,`,
|
|
2244
2349
|
` NmsInternalUsers.create(`,
|
|
2245
2350
|
` identityId: nmsInternalReceivingGroupNameOf(signedIn),`,
|
|
2246
2351
|
` identityAddress: 'nomos-identity:\${Uri.encodeComponent(nmsInternalReceivingGroupNameOf(signedIn))}',`,
|
|
@@ -2248,7 +2353,11 @@ export function generateDartApplication(
|
|
|
2248
2353
|
` );`,
|
|
2249
2354
|
]
|
|
2250
2355
|
: [
|
|
2251
|
-
` final
|
|
2356
|
+
` final client = await ${client}.open(`,
|
|
2357
|
+
` bridge, ref: reference, actor: actor,`,
|
|
2358
|
+
` recoverAuthentication: recoverAuthentication,`,
|
|
2359
|
+
` );`,
|
|
2360
|
+
` final application = ${application}._(client);`,
|
|
2252
2361
|
]),
|
|
2253
2362
|
...(sharingPolicies.length > 0
|
|
2254
2363
|
? [` await application._nmsStartWorkspaceSharing();`]
|