@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.
Files changed (44) hide show
  1. package/dart/CHANGELOG.md +10 -0
  2. package/dart/lib/src/application_framework_calls.dart +21 -0
  3. package/dart/lib/src/dev_cloud.dart +67 -0
  4. package/dart/lib/src/nms_test_environment.dart +1 -1
  5. package/dart/lib/src/runner/node-entry.mjs +372 -106
  6. package/dart/lib/src/runner/offline-install.mjs +171 -11
  7. package/dart/lib/src/session_bound.dart +58 -30
  8. package/dart/lib/src/test_harness.dart +60 -98
  9. package/dart/lib/src/webview_bridge.dart +212 -32
  10. package/dart/pubspec.lock +1 -1
  11. package/dart/pubspec.yaml +2 -2
  12. package/dart/test/node_runner_transport_test.dart +5 -5
  13. package/dart/test/webview_bridge_test.dart +6 -0
  14. package/dart_types/CHANGELOG.md +4 -0
  15. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +428 -2
  16. package/dart_types/pubspec.yaml +1 -1
  17. package/package.json +1 -1
  18. package/runtime/generated-application-host.js +314 -82
  19. package/src/application_topology.ts +120 -28
  20. package/src/births.ts +27 -0
  21. package/src/codegen_call_usda.ts +9 -4
  22. package/src/codegen_dart_application.ts +146 -37
  23. package/src/codegen_dart_client.ts +225 -160
  24. package/src/codegen_dart_framework_calls.ts +89 -4
  25. package/src/codegen_ts.ts +135 -60
  26. package/src/compile_package_main.ts +3544 -1460
  27. package/src/directive.ts +320 -118
  28. package/src/fields.ts +2 -3
  29. package/src/framework/application_home.ts +3 -0
  30. package/src/framework/auth_provider_law.ts +20 -16
  31. package/src/framework/genesis.ts +32 -1
  32. package/src/framework/home.ts +45 -8
  33. package/src/framework/signing_identity.ts +78 -0
  34. package/src/framework/workspaces.ts +9 -1
  35. package/src/framework_call_dart_enums.ts +33 -0
  36. package/src/framework_call_definitions.ts +11 -2
  37. package/src/index.ts +12 -0
  38. package/src/intent.ts +525 -194
  39. package/src/law_graph.ts +32 -4
  40. package/src/law_graph_usda.ts +3 -0
  41. package/src/package_config.ts +16 -1
  42. package/src/read.ts +33 -11
  43. package/src/wire.ts +1 -0
  44. 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
- .filter((directive) =>
26
- (directive as TopologyDirective).businessBirthInternal !== true,
27
- ) as readonly TopologyDirective[];
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 { rootDomains: [homeDomain], homeDomain, homeModule };
69
+ return {
70
+ rootDomains: [homeDomain],
71
+ remoteDomains: [],
72
+ homeDomain,
73
+ homeModule,
74
+ };
62
75
  }
63
76
 
64
77
  const declaredReceivingGroups = modules.filter((module) =>
65
- publicLanguageOf(module).some((directive) =>
66
- directive.id === "nomosInternalBirthReceivingGroup7c1f5e" &&
67
- directive.birthsChild === true &&
68
- directive.aggregateId === "NomosInternalReceivingGroup7c1f5e",
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(", ")}. An accountable participant gets one receiving group; keep one group birth language.`,
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
- targetedByAnotherDomain.add(target);
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: ${[...path, domain].join(" -> ")}`,
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) ?? []) walk(child, [...path, 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) => !targetedByAnotherDomain.has(domainOf(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
- const receivingGroupModule = declaredReceivingGroups[0];
141
- if (receivingGroupModule !== undefined && !rootModules.includes(receivingGroupModule)) {
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(receivingGroupModule)}' inside a child workspace`,
227
+ `generated application topology places its receiving-group domain '${domainOf(
228
+ receivingGroupModule
229
+ )}' inside a child workspace`
144
230
  );
145
231
  }
146
- const homeModule = receivingGroupModule
147
- ?? rootModules.find((module) => domainOf(module) === "home")
148
- ?? rootModules.find((module) => birthSources.has(domainOf(module)))
149
- ?? rootModules[0]!;
150
- return { rootDomains, homeDomain: domainOf(homeModule), homeModule };
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[] = [];
@@ -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
- begin("NomosStringValue");
401
- out.push(`_nmsUsdaString(${outName}, ${depth + 1}, "string", "nomos:value", ${JSON.stringify(literal)});`);
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
- begin("NomosStringValue");
810
- out.push(`_nmsUsdaString(${outName}, ${depth + 1}, 'string', 'nomos:value', ${dartString(literal)});`);
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 { isBusinessIntent } from "./intent.js";
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 = source.kind === "enum"
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
- .sort(([a], [b]) => a.localeCompare(b))
766
+ const copiedEntries = Object.entries(trust.copy).sort(([a], [b]) =>
767
+ a.localeCompare(b)
768
+ );
752
769
  const copied = copiedEntries.map(
753
- ([target, field]) => ` ${member(target)}: ${factValue(field)},`
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.map((field) => field.name).join(", ")}`
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
- ` final sourceGenesis = await _client.bridge.nomosWorkspaceGenesis(${factValue(
855
+ `${trustIndent}final sourceGenesis = await _client.bridge.nomosWorkspaceGenesis(${factValue(
791
856
  trust.fromPayload
792
857
  )});`,
793
- ` await _client.${dartInternalSourceTrustMethod(
858
+ `${trustIndent}await _client.${dartInternalSourceTrustMethod(
794
859
  trust.viaDirectiveId
795
860
  )}((`,
796
861
  ...copied,
797
862
  ...omittedOptionals,
798
- ` ${member(trust.genesisField)}: sourceGenesis,`,
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
- ` ${member(trust.keyHashesField)}: const <${keyElementType}>[],`,
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}(_client.bridge, reference, _client.actor);`,
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 = directive.targetBinding === undefined && payloadSpecs.length === 0;
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
- emptyPayload
1307
- ? ""
1308
- : directive.targetBinding === undefined
1309
- ? `${payload} facts`
1310
- : targetOnly
1311
- ? `NomosRef<${concreteClassName(
1312
- directive.targetBinding.aggregateId
1313
- )}> target`
1314
- : `NomosRef<${concreteClassName(
1315
- directive.targetBinding.aggregateId
1316
- )}> target, ${payload} facts`;
1317
- const factsPrelude = targetOnly || emptyPayload ? [` const facts = ${payload}();`] : [];
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) async {`,
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
- ` await ${client}.open(bridge, ref: reference, actor: signedIn),`,
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 application = ${application}._(await ${client}.open(bridge, ref: reference, actor: actor));`,
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();`]