@githolon/dsl 0.108.4 → 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 +5 -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 +354 -104
  6. package/dart/lib/src/runner/offline-install.mjs +64 -8
  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 +293 -79
  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 -28
  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
package/src/intent.ts CHANGED
@@ -40,6 +40,9 @@ import {
40
40
 
41
41
  const INTENT_BRAND = "__nomos_business_intent_v1" as const;
42
42
  const LANGUAGE_BRAND = "__nomos_application_language_v1" as const;
43
+ const CHILD_AUTHENTICATION_RECOVERY = Symbol(
44
+ "nomos.child-authentication-recovery"
45
+ );
43
46
 
44
47
  export interface IntentStep<I = unknown, P = unknown> {
45
48
  readonly directive: Directive<P>;
@@ -77,9 +80,15 @@ function payloadTrace(path: readonly string[] = []): PayloadTrace {
77
80
  get(_target, property) {
78
81
  if (property === PAYLOAD_TRACE_PATH) return path;
79
82
  if (property === "then") return undefined;
80
- if (property === Symbol.toPrimitive || property === "toString" || property === "valueOf") {
83
+ if (
84
+ property === Symbol.toPrimitive ||
85
+ property === "toString" ||
86
+ property === "valueOf"
87
+ ) {
81
88
  return () => {
82
- throw new Error("computed application-language payload field is not a direct path");
89
+ throw new Error(
90
+ "computed application-language payload field is not a direct path"
91
+ );
83
92
  };
84
93
  }
85
94
  return payloadTrace([...path, String(property)]);
@@ -96,7 +105,10 @@ function tracedPath(value: unknown): readonly string[] | undefined {
96
105
  function valueAtPath(value: unknown, path: readonly string[]): unknown {
97
106
  let current = value;
98
107
  for (const part of path) {
99
- if (current === null || (typeof current !== "object" && typeof current !== "function")) {
108
+ if (
109
+ current === null ||
110
+ (typeof current !== "object" && typeof current !== "function")
111
+ ) {
100
112
  return undefined;
101
113
  }
102
114
  current = (current as Record<string, unknown>)[part];
@@ -106,14 +118,14 @@ function valueAtPath(value: unknown, path: readonly string[]): unknown {
106
118
 
107
119
  function remapPayloadPath(
108
120
  mapped: unknown,
109
- privatePath: readonly string[],
121
+ privatePath: readonly string[]
110
122
  ): readonly string[] | undefined {
111
123
  return tracedPath(valueAtPath(mapped, privatePath));
112
124
  }
113
125
 
114
126
  function remapStepPayloadPath(
115
127
  step: IntentStep<any, any>,
116
- privatePath: readonly string[],
128
+ privatePath: readonly string[]
117
129
  ): readonly string[] | undefined {
118
130
  try {
119
131
  return remapPayloadPath(step.payload(payloadTrace()), privatePath);
@@ -124,14 +136,17 @@ function remapStepPayloadPath(
124
136
 
125
137
  function remapReadPart(
126
138
  part: InvariantReadIdPart,
127
- mapped: unknown,
139
+ mapped: unknown
128
140
  ): InvariantReadIdPart | undefined {
129
141
  if (part.source !== "payload") return part;
130
142
  const path = remapPayloadPath(mapped, part.path);
131
143
  return path === undefined ? undefined : { ...part, path };
132
144
  }
133
145
 
134
- function remapReadId(id: InvariantReadId, mapped: unknown): InvariantReadId | undefined {
146
+ function remapReadId(
147
+ id: InvariantReadId,
148
+ mapped: unknown
149
+ ): InvariantReadId | undefined {
135
150
  if (id.source === "literal") return id;
136
151
  if (id.source === "payload") {
137
152
  const path = remapPayloadPath(mapped, id.path);
@@ -150,12 +165,14 @@ function remapReadId(id: InvariantReadId, mapped: unknown): InvariantReadId | un
150
165
  * Returns undefined unless every payload dependency is structurally proven. */
151
166
  function remapNativeReads(
152
167
  reads: readonly InvariantReadBinding[],
153
- step: IntentStep<any, any>,
168
+ step: IntentStep<any, any>
154
169
  ): readonly InvariantReadBinding[] | undefined {
155
- const needsPayload = reads.some((read) =>
156
- read.eachPayloadPath !== undefined ||
157
- read.id.source === "payload" ||
158
- (read.id.source === "template" && read.id.parts.some((part) => part.source === "payload"))
170
+ const needsPayload = reads.some(
171
+ (read) =>
172
+ read.eachPayloadPath !== undefined ||
173
+ read.id.source === "payload" ||
174
+ (read.id.source === "template" &&
175
+ read.id.parts.some((part) => part.source === "payload"))
159
176
  );
160
177
  if (!needsPayload) return reads;
161
178
 
@@ -168,10 +185,12 @@ function remapNativeReads(
168
185
 
169
186
  const out: InvariantReadBinding[] = [];
170
187
  for (const read of reads) {
171
- const eachPayloadPath = read.eachPayloadPath === undefined
172
- ? undefined
173
- : remapPayloadPath(mapped, read.eachPayloadPath);
174
- if (read.eachPayloadPath !== undefined && eachPayloadPath === undefined) return undefined;
188
+ const eachPayloadPath =
189
+ read.eachPayloadPath === undefined
190
+ ? undefined
191
+ : remapPayloadPath(mapped, read.eachPayloadPath);
192
+ if (read.eachPayloadPath !== undefined && eachPayloadPath === undefined)
193
+ return undefined;
175
194
 
176
195
  const id = remapReadId(read.id, mapped);
177
196
  if (id === undefined) return undefined;
@@ -186,16 +205,81 @@ function remapNativeReads(
186
205
 
187
206
  export interface BusinessIntent<I = unknown> extends Directive<I> {
188
207
  readonly [INTENT_BRAND]: true;
189
- readonly [BUSINESS_BIRTH_POLICY_BINDING]?: (domains: readonly string[]) => void;
208
+ readonly [BUSINESS_BIRTH_POLICY_BINDING]?: (
209
+ domains: readonly string[]
210
+ ) => void;
190
211
  readonly steps: readonly IntentStep<I, any>[];
191
212
  readonly workspaceEffects: readonly WorkspaceEffectStep<I, any>[];
192
213
  /** Law-selected proof for this exact public offer. Omitted means the ordinary session proof
193
214
  * posture. Tenant callers never choose this at invocation time. */
194
215
  readonly offerAuthenticationProof?: "enrolledDevice";
216
+ /** Compiler-visible application lifecycle declaration. When opening a child of this aggregate
217
+ * type proves that its verifier/signer posture is stale, the generated parent application may
218
+ * invoke this exact nominal action on the already-authenticated parent. The action itself remains
219
+ * ordinary tenant law and the child independently authorizes the resulting workspace effect. */
220
+ readonly [CHILD_AUTHENTICATION_RECOVERY]?: string;
195
221
  /** The complete public business boundary. Runtime lowering still uses Directive.payloadSchema privately. */
196
222
  readonly contract: IntentContract<I>;
197
223
  }
198
224
 
225
+ /** Select the already-enrolled device proof for a finished one-effect intent.
226
+ *
227
+ * `intent(...).performs(...)` deliberately returns the finished nominal action
228
+ * so the effect's complete framework metadata is preserved. This companion is
229
+ * the finished-action equivalent of `IntentBuilder.authenticatesWithEnrolledDevice()`;
230
+ * the compiler records the same exact per-intent proof requirement either way.
231
+ */
232
+ export function authenticatesWithEnrolledDevice<I>(
233
+ action: BusinessIntent<I>
234
+ ): BusinessIntent<I> {
235
+ return Object.freeze({
236
+ ...action,
237
+ offerAuthenticationProof: "enrolledDevice" as const,
238
+ });
239
+ }
240
+
241
+ /** Mark one parent-law action as the typed recovery operation for children rooted at [child].
242
+ *
243
+ * This does not grant authority or create a generic host callback. It only lets generated application
244
+ * code connect a failed child-open posture to an exact, nominal parent action which already has its own
245
+ * enrolled-device proof and emits one law-authored effect to that child.
246
+ */
247
+ export function repairsChildAuthenticationFor<I>(
248
+ child: AggregateHandle,
249
+ action: BusinessIntent<I>
250
+ ): BusinessIntent<I> {
251
+ if (action.offerAuthenticationProof !== "enrolledDevice") {
252
+ throw new Error(
253
+ `repairsChildAuthenticationFor(${child.id}): '${action.id}' must authenticate with the parent's enrolled device`
254
+ );
255
+ }
256
+ if (action.steps.length !== 0 || action.workspaceEffects.length === 0) {
257
+ throw new Error(
258
+ `repairsChildAuthenticationFor(${child.id}): '${action.id}' must emit at least one child effect and make no parent-local write`
259
+ );
260
+ }
261
+ if (
262
+ action.workspaceEffects.some(
263
+ (effect) => effect.destination.kind !== "workspace"
264
+ )
265
+ ) {
266
+ throw new Error(
267
+ `repairsChildAuthenticationFor(${child.id}): '${action.id}' must address the exact child workspace from its typed input`
268
+ );
269
+ }
270
+ return Object.freeze({
271
+ ...action,
272
+ [CHILD_AUTHENTICATION_RECOVERY]: child.id,
273
+ });
274
+ }
275
+
276
+ /** Internal compiler projection of {@link repairsChildAuthenticationFor}. */
277
+ export function childAuthenticationRecoveryAggregateId(
278
+ action: BusinessIntent<unknown>
279
+ ): string | undefined {
280
+ return action[CHILD_AUTHENTICATION_RECOVERY];
281
+ }
282
+
199
283
  /** The public promise of one business action. Outputs are inferred from its composed consequences unless
200
284
  * the author names them explicitly, in which case the compiler verifies the promise exactly. */
201
285
  export interface IntentContract<I = unknown> {
@@ -231,43 +315,51 @@ const stable = (value: unknown): string => {
231
315
  .join(",")}}`;
232
316
  };
233
317
 
234
- function oneStaticRelationGate<I>(steps: readonly IntentStep<I, any>[]): Directive["requiresRelationGate"] {
318
+ function oneStaticRelationGate<I>(
319
+ steps: readonly IntentStep<I, any>[]
320
+ ): Directive["requiresRelationGate"] {
235
321
  const gates = steps
236
322
  .map((step) => step.directive.requiresRelationGate)
237
- .filter((gate): gate is NonNullable<Directive["requiresRelationGate"]> => gate !== undefined);
323
+ .filter(
324
+ (gate): gate is NonNullable<Directive["requiresRelationGate"]> =>
325
+ gate !== undefined
326
+ );
238
327
  if (gates.length === 0) return undefined;
239
328
  if (gates.some((gate) => gate.objectFrom !== undefined)) {
240
329
  throw new Error(
241
- "compound intent cannot yet remap a per-record access gate — bind that gate on the intent itself",
330
+ "compound intent cannot yet remap a per-record access gate — bind that gate on the intent itself"
242
331
  );
243
332
  }
244
333
  const first = stable(gates[0]);
245
334
  if (gates.some((gate) => stable(gate) !== first)) {
246
335
  throw new Error(
247
- "compound intent directives require different access gates — declare one jointly satisfiable intent gate",
336
+ "compound intent directives require different access gates — declare one jointly satisfiable intent gate"
248
337
  );
249
338
  }
250
339
  return gates[0];
251
340
  }
252
341
 
253
342
  function unsupportedKernelGate(directive: Directive<any>): string | undefined {
254
- if (directive.attestationAuthorizedGate !== undefined) return "attestation authorization";
343
+ if (directive.attestationAuthorizedGate !== undefined)
344
+ return "attestation authorization";
255
345
  if (directive.delegatedRoleGate !== undefined) return "delegated authority";
256
- if (directive.firstDeviceEnrollGate !== undefined) return "first-device enrollment";
346
+ if (directive.firstDeviceEnrollGate !== undefined)
347
+ return "first-device enrollment";
257
348
  if (directive.certifiesEraGate !== undefined) return "era certification";
258
- if ((directive.businessRecordInputs?.length ?? 0) > 0) return "record-input binding";
349
+ if ((directive.businessRecordInputs?.length ?? 0) > 0)
350
+ return "record-input binding";
259
351
  return undefined;
260
352
  }
261
353
 
262
354
  function remapBusinessSourceTrust<I>(
263
355
  step: IntentStep<I, any>,
264
- trust: BusinessSourceTrust,
356
+ trust: BusinessSourceTrust
265
357
  ): BusinessSourceTrust {
266
358
  const field = (privatePath: string): string => {
267
359
  const mapped = remapStepPayloadPath(step, privatePath.split("."));
268
360
  if (mapped === undefined || mapped.length === 0) {
269
361
  throw new Error(
270
- `compound intent cannot remap source-trust field '${privatePath}' from '${step.directive.id}'`,
362
+ `compound intent cannot remap source-trust field '${privatePath}' from '${step.directive.id}'`
271
363
  );
272
364
  }
273
365
  return mapped.join(".");
@@ -277,13 +369,29 @@ function remapBusinessSourceTrust<I>(
277
369
  fromPayload: field(trust.fromPayload),
278
370
  queryId: trust.queryId,
279
371
  queryArgs: Object.fromEntries(
280
- Object.entries(trust.queryArgs).map(([target, source]) => [target, field(source)]),
372
+ Object.entries(trust.queryArgs).map(([target, source]) => [
373
+ target,
374
+ field(source),
375
+ ])
281
376
  ),
282
377
  copy: Object.fromEntries(
283
- Object.entries(trust.copy).map(([target, source]) => [target, field(source)]),
378
+ Object.entries(trust.copy).map(([target, source]) => [
379
+ target,
380
+ field(source),
381
+ ])
284
382
  ),
285
383
  genesisField: trust.genesisField,
286
384
  keyHashesField: trust.keyHashesField,
385
+ ...(trust.satisfiedByQueryId === undefined
386
+ ? {}
387
+ : {
388
+ satisfiedByQueryId: trust.satisfiedByQueryId,
389
+ satisfiedByQueryArgs: Object.fromEntries(
390
+ Object.entries(trust.satisfiedByQueryArgs ?? {}).map(
391
+ ([target, source]) => [target, field(source)]
392
+ )
393
+ ),
394
+ }),
287
395
  };
288
396
  }
289
397
 
@@ -307,12 +415,13 @@ const dedupeBy = <T>(values: readonly T[], key: (value: T) => string): T[] => {
307
415
  */
308
416
  function composePrimaryConsequenceOp(
309
417
  directive: Directive<any>,
310
- op: PlannedOp,
418
+ op: PlannedOp
311
419
  ): PlannedOp {
312
420
  if (op.kind === "strike") return op;
313
- const targetsPrimary = op.aggregateType !== undefined
314
- ? op.aggregateType === directive.aggregateId
315
- : op.aggregateId === directive.aggregateId;
421
+ const targetsPrimary =
422
+ op.aggregateType !== undefined
423
+ ? op.aggregateType === directive.aggregateId
424
+ : op.aggregateId === directive.aggregateId;
316
425
  if (!targetsPrimary) return op;
317
426
  return {
318
427
  ...op,
@@ -328,25 +437,45 @@ class IntentBuilder<I> {
328
437
  private readonly id: string,
329
438
  private readonly contractDeclaration: IntentContractDeclaration<I>,
330
439
  private readonly steps: readonly IntentStep<I, any>[] = [],
331
- private readonly workspaceEffects: readonly WorkspaceEffectStep<I, any>[] = [],
440
+ private readonly workspaceEffects: readonly WorkspaceEffectStep<
441
+ I,
442
+ any
443
+ >[] = [],
332
444
  private readonly intentReads: readonly (string | QueryDecl)[] = [],
333
445
  private readonly acceptsWorkspaceEffects = false,
334
- private readonly intentRelationGate?: NonNullable<Directive["requiresRelationGate"]>,
446
+ private readonly intentRelationGate?: NonNullable<
447
+ Directive["requiresRelationGate"]
448
+ >,
335
449
  private readonly offerAuthenticationProof?: "enrolledDevice",
450
+ private readonly acceptsVerifiedBirthParentRecovery = false
336
451
  ) {}
337
452
 
338
453
  /** Add one private directive consequence to this business intent. */
339
- directs<P>(directive: Directive<P>, payload: (facts: I) => P): IntentBuilder<I> {
454
+ directs<P>(
455
+ directive: Directive<P>,
456
+ payload: (facts: I) => P
457
+ ): IntentBuilder<I> {
340
458
  if (isBusinessIntent(directive)) {
341
- throw new Error(`intent('${this.id}') cannot direct another intent — compose its private directives instead`);
459
+ throw new Error(
460
+ `intent('${this.id}') cannot direct another intent — compose its private directives instead`
461
+ );
342
462
  }
343
463
  if (directive.birthsChild === true) {
344
- throw new Error(`intent('${this.id}') composes a declared birth with .births(...), never a raw birth directive`);
464
+ throw new Error(
465
+ `intent('${this.id}') composes a declared birth with .births(...), never a raw birth directive`
466
+ );
345
467
  }
346
- return new IntentBuilder(this.id, this.contractDeclaration, [
347
- ...this.steps,
348
- { directive, payload },
349
- ], this.workspaceEffects, this.intentReads, this.acceptsWorkspaceEffects, this.intentRelationGate, this.offerAuthenticationProof);
468
+ return new IntentBuilder(
469
+ this.id,
470
+ this.contractDeclaration,
471
+ [...this.steps, { directive, payload }],
472
+ this.workspaceEffects,
473
+ this.intentReads,
474
+ this.acceptsWorkspaceEffects,
475
+ this.intentRelationGate,
476
+ this.offerAuthenticationProof,
477
+ this.acceptsVerifiedBirthParentRecovery
478
+ );
350
479
  }
351
480
 
352
481
  /** Author one exact nominal call into another workspace. The caller chooses only the target
@@ -354,21 +483,37 @@ class IntentBuilder<I> {
354
483
  * the compiler bind its stable USDA identity; no operation string or host-selected mapping exists. */
355
484
  directsWorkspace<P>(
356
485
  target: Directive<P>,
357
- effect: { readonly workspace: (facts: I) => string; readonly payload: (facts: I) => P },
486
+ effect: {
487
+ readonly workspace: (facts: I) => string;
488
+ readonly payload: (facts: I) => P;
489
+ }
358
490
  ): IntentBuilder<I> {
359
- if (isBusinessIntent(target) && target.acceptsWorkspaceEffectGate !== true) {
491
+ if (
492
+ isBusinessIntent(target) &&
493
+ target.acceptsWorkspaceEffectGate !== true
494
+ ) {
360
495
  throw new Error(
361
- `intent('${this.id}') workspace effect may target a public intent only when target law explicitly accepts workspace effects`,
496
+ `intent('${this.id}') workspace effect may target a public intent only when target law explicitly accepts workspace effects`
362
497
  );
363
498
  }
364
- return new IntentBuilder(this.id, this.contractDeclaration, this.steps, [
365
- ...this.workspaceEffects,
366
- {
367
- target,
368
- destination: { kind: "workspace", address: effect.workspace },
369
- payload: effect.payload,
370
- },
371
- ], this.intentReads, this.acceptsWorkspaceEffects, this.intentRelationGate, this.offerAuthenticationProof);
499
+ return new IntentBuilder(
500
+ this.id,
501
+ this.contractDeclaration,
502
+ this.steps,
503
+ [
504
+ ...this.workspaceEffects,
505
+ {
506
+ target,
507
+ destination: { kind: "workspace", address: effect.workspace },
508
+ payload: effect.payload,
509
+ },
510
+ ],
511
+ this.intentReads,
512
+ this.acceptsWorkspaceEffects,
513
+ this.intentRelationGate,
514
+ this.offerAuthenticationProof,
515
+ this.acceptsVerifiedBirthParentRecovery
516
+ );
372
517
  }
373
518
 
374
519
  /** Send an exact nominal request to this workspace's verified immediate parent. The route is
@@ -376,21 +521,34 @@ class IntentBuilder<I> {
376
521
  * enters the public payload or the JavaScript execution boundary. */
377
522
  directsParentWorkspace<P>(
378
523
  target: Directive<P>,
379
- effect: { readonly payload: (facts: I) => P },
524
+ effect: { readonly payload: (facts: I) => P }
380
525
  ): IntentBuilder<I> {
381
- if (isBusinessIntent(target) && target.acceptsWorkspaceEffectGate !== true) {
526
+ if (
527
+ isBusinessIntent(target) &&
528
+ target.acceptsWorkspaceEffectGate !== true
529
+ ) {
382
530
  throw new Error(
383
- `intent('${this.id}') parent request may target a public intent only when target law explicitly accepts workspace effects`,
531
+ `intent('${this.id}') parent request may target a public intent only when target law explicitly accepts workspace effects`
384
532
  );
385
533
  }
386
- return new IntentBuilder(this.id, this.contractDeclaration, this.steps, [
387
- ...this.workspaceEffects,
388
- {
389
- target,
390
- destination: { kind: "verifiedParent" },
391
- payload: effect.payload,
392
- },
393
- ], this.intentReads, this.acceptsWorkspaceEffects, this.intentRelationGate, this.offerAuthenticationProof);
534
+ return new IntentBuilder(
535
+ this.id,
536
+ this.contractDeclaration,
537
+ this.steps,
538
+ [
539
+ ...this.workspaceEffects,
540
+ {
541
+ target,
542
+ destination: { kind: "verifiedParent" },
543
+ payload: effect.payload,
544
+ },
545
+ ],
546
+ this.intentReads,
547
+ this.acceptsWorkspaceEffects,
548
+ this.intentRelationGate,
549
+ this.offerAuthenticationProof,
550
+ this.acceptsVerifiedBirthParentRecovery
551
+ );
394
552
  }
395
553
 
396
554
  /** Reply to the exact workspace whose carried effect invoked this intent. The destination is
@@ -399,21 +557,34 @@ class IntentBuilder<I> {
399
557
  * still authorizes the verified workspace actor through its ordinary relation gate. */
400
558
  repliesWorkspace<P>(
401
559
  target: Directive<P>,
402
- effect: { readonly payload: (facts: I) => P },
560
+ effect: { readonly payload: (facts: I) => P }
403
561
  ): IntentBuilder<I> {
404
- if (isBusinessIntent(target)) {
562
+ if (
563
+ isBusinessIntent(target) &&
564
+ target.acceptsWorkspaceEffectGate !== true
565
+ ) {
405
566
  throw new Error(
406
- `intent('${this.id}') workspace reply must target a private accepted directive, not another public intent`,
567
+ `intent('${this.id}') workspace reply may target a public intent only when target law explicitly accepts workspace effects`
407
568
  );
408
569
  }
409
- return new IntentBuilder(this.id, this.contractDeclaration, this.steps, [
410
- ...this.workspaceEffects,
411
- {
412
- target,
413
- destination: { kind: "verifiedSource" },
414
- payload: effect.payload,
415
- },
416
- ], this.intentReads, this.acceptsWorkspaceEffects, this.intentRelationGate, this.offerAuthenticationProof);
570
+ return new IntentBuilder(
571
+ this.id,
572
+ this.contractDeclaration,
573
+ this.steps,
574
+ [
575
+ ...this.workspaceEffects,
576
+ {
577
+ target,
578
+ destination: { kind: "verifiedSource" },
579
+ payload: effect.payload,
580
+ },
581
+ ],
582
+ this.intentReads,
583
+ this.acceptsWorkspaceEffects,
584
+ this.intentRelationGate,
585
+ this.offerAuthenticationProof,
586
+ this.acceptsVerifiedBirthParentRecovery
587
+ );
417
588
  }
418
589
 
419
590
  /** Admit this public request only through the cross-workspace effect door. Authority remains the
@@ -428,6 +599,24 @@ class IntentBuilder<I> {
428
599
  true,
429
600
  this.intentRelationGate,
430
601
  this.offerAuthenticationProof,
602
+ this.acceptsVerifiedBirthParentRecovery
603
+ );
604
+ }
605
+
606
+ /** Recovery-only cross-workspace door for an existing child whose ordinary parent relation may not
607
+ * be seated yet. The certified manifest selects this exact intent; admission then requires the
608
+ * carried K_root-rooted workspace actor to equal the target's persisted birth parent. */
609
+ acceptsVerifiedBirthParentRecoveryEffect(): IntentBuilder<I> {
610
+ return new IntentBuilder(
611
+ this.id,
612
+ this.contractDeclaration,
613
+ this.steps,
614
+ this.workspaceEffects,
615
+ this.intentReads,
616
+ true,
617
+ this.intentRelationGate,
618
+ this.offerAuthenticationProof,
619
+ true
431
620
  );
432
621
  }
433
622
 
@@ -444,6 +633,7 @@ class IntentBuilder<I> {
444
633
  this.acceptsWorkspaceEffects,
445
634
  this.intentRelationGate,
446
635
  "enrolledDevice",
636
+ this.acceptsVerifiedBirthParentRecovery
447
637
  );
448
638
  }
449
639
 
@@ -451,12 +641,20 @@ class IntentBuilder<I> {
451
641
  * a pure request/reply action that has no local storage consequence on which to hang a gate. */
452
642
  requires(
453
643
  relation: string,
454
- options: { readonly object: "workspace:self" } = { object: "workspace:self" },
644
+ options: { readonly object: "workspace:self" } = {
645
+ object: "workspace:self",
646
+ }
455
647
  ): IntentBuilder<I> {
456
- if (!relation.trim()) throw new Error(`intent('${this.id}') requires a non-empty relation`);
648
+ if (!relation.trim())
649
+ throw new Error(`intent('${this.id}') requires a non-empty relation`);
457
650
  const gate = { relation, object: options.object };
458
- if (this.intentRelationGate !== undefined && stable(this.intentRelationGate) !== stable(gate)) {
459
- throw new Error(`intent('${this.id}') already has a different authorization gate`);
651
+ if (
652
+ this.intentRelationGate !== undefined &&
653
+ stable(this.intentRelationGate) !== stable(gate)
654
+ ) {
655
+ throw new Error(
656
+ `intent('${this.id}') already has a different authorization gate`
657
+ );
460
658
  }
461
659
  return new IntentBuilder(
462
660
  this.id,
@@ -467,6 +665,7 @@ class IntentBuilder<I> {
467
665
  this.acceptsWorkspaceEffects,
468
666
  gate,
469
667
  this.offerAuthenticationProof,
668
+ this.acceptsVerifiedBirthParentRecovery
470
669
  );
471
670
  }
472
671
 
@@ -476,7 +675,10 @@ class IntentBuilder<I> {
476
675
  reads(...reads: readonly (string | QueryDecl)[]): IntentBuilder<I> {
477
676
  const byKey = new Map<string, string | QueryDecl>();
478
677
  for (const read of [...this.intentReads, ...reads]) {
479
- byKey.set(typeof read === "string" ? `ref:${read}` : `query:${read.id}`, read);
678
+ byKey.set(
679
+ typeof read === "string" ? `ref:${read}` : `query:${read.id}`,
680
+ read
681
+ );
480
682
  }
481
683
  return new IntentBuilder(
482
684
  this.id,
@@ -487,48 +689,69 @@ class IntentBuilder<I> {
487
689
  this.acceptsWorkspaceEffects,
488
690
  this.intentRelationGate,
489
691
  this.offerAuthenticationProof,
692
+ this.acceptsVerifiedBirthParentRecovery
490
693
  );
491
694
  }
492
695
 
493
696
  /** Add one declared child birth as a consequence; Nomos owns the private genesis recipe. */
494
697
  births<P extends Record<string, unknown>, F extends Record<string, Field>>(
495
698
  declaration: BusinessBirthDecl<P, F>,
496
- payload: (facts: I) => P,
699
+ payload: (facts: I) => P
497
700
  ): IntentBuilder<I> {
498
701
  const directive = businessBirthConsequence(declaration);
499
- return new IntentBuilder(this.id, this.contractDeclaration, [
500
- ...this.steps,
501
- { directive, payload },
502
- ], this.workspaceEffects, this.intentReads, this.acceptsWorkspaceEffects, this.intentRelationGate, this.offerAuthenticationProof);
702
+ return new IntentBuilder(
703
+ this.id,
704
+ this.contractDeclaration,
705
+ [...this.steps, { directive, payload }],
706
+ this.workspaceEffects,
707
+ this.intentReads,
708
+ this.acceptsWorkspaceEffects,
709
+ this.intentRelationGate,
710
+ this.offerAuthenticationProof,
711
+ this.acceptsVerifiedBirthParentRecovery
712
+ );
503
713
  }
504
714
 
505
715
  /** Finish the intent and lower its directive graph to one atomic offer. */
506
716
  atomically(): BusinessIntent<I> {
507
717
  if (this.steps.length === 0 && this.workspaceEffects.length === 0) {
508
- throw new Error(`intent('${this.id}') needs at least one directed consequence`);
718
+ throw new Error(
719
+ `intent('${this.id}') needs at least one directed consequence`
720
+ );
509
721
  }
510
722
  const duplicate = this.steps.find(
511
- (step, index) => this.steps.findIndex((candidate) => candidate.directive.id === step.directive.id) !== index,
723
+ (step, index) =>
724
+ this.steps.findIndex(
725
+ (candidate) => candidate.directive.id === step.directive.id
726
+ ) !== index
512
727
  );
513
728
  if (duplicate !== undefined) {
514
- throw new Error(`intent('${this.id}') directs '${duplicate.directive.id}' more than once`);
729
+ throw new Error(
730
+ `intent('${this.id}') directs '${duplicate.directive.id}' more than once`
731
+ );
515
732
  }
516
- const birthSteps = this.steps.filter((step) => step.directive.birthsChild === true);
733
+ const birthSteps = this.steps.filter(
734
+ (step) => step.directive.birthsChild === true
735
+ );
517
736
  if (birthSteps.length > 1) {
518
- throw new Error(`intent('${this.id}') cannot birth more than one durable life atomically`);
737
+ throw new Error(
738
+ `intent('${this.id}') cannot birth more than one durable life atomically`
739
+ );
519
740
  }
520
741
  for (const step of this.steps) {
521
742
  const unsupported = unsupportedKernelGate(step.directive);
522
743
  if (unsupported !== undefined) {
523
744
  throw new Error(
524
- `intent('${this.id}') cannot compose '${step.directive.id}' yet: ${unsupported} needs intent-level lowering`,
745
+ `intent('${this.id}') cannot compose '${step.directive.id}' yet: ${unsupported} needs intent-level lowering`
525
746
  );
526
747
  }
527
748
  }
528
749
 
529
- const primary = this.steps.find((step) => step.directive.birthsChild !== true)?.directive
530
- ?? this.steps[0]?.directive
531
- ?? this.workspaceEffects[0]!.target;
750
+ const primary =
751
+ this.steps.find((step) => step.directive.birthsChild !== true)
752
+ ?.directive ??
753
+ this.steps[0]?.directive ??
754
+ this.workspaceEffects[0]!.target;
532
755
  const allOutputs: SecondaryOutput[] = [];
533
756
  for (const step of this.steps) {
534
757
  const candidate = step.directive;
@@ -537,7 +760,9 @@ class IntentBuilder<I> {
537
760
  aggregateId: candidate.aggregateId,
538
761
  marker: candidate.marker,
539
762
  ...(candidate.outputCardinality === "many" ? { many: true } : {}),
540
- ...(candidate.outputCardinality === "optional" ? { optional: true } : {}),
763
+ ...(candidate.outputCardinality === "optional"
764
+ ? { optional: true }
765
+ : {}),
541
766
  });
542
767
  allOutputs.push(...(candidate.secondaryOutputs ?? []));
543
768
  }
@@ -547,7 +772,9 @@ class IntentBuilder<I> {
547
772
  const key = `${output.marker}\0${output.aggregateId}`;
548
773
  outputGroups.set(key, [...(outputGroups.get(key) ?? []), output]);
549
774
  }
550
- const composedOutput = (group: readonly SecondaryOutput[]): SecondaryOutput => ({
775
+ const composedOutput = (
776
+ group: readonly SecondaryOutput[]
777
+ ): SecondaryOutput => ({
551
778
  aggregateId: group[0]!.aggregateId,
552
779
  marker: group[0]!.marker,
553
780
  ...(group.length > 1 || group.some((output) => output.many === true)
@@ -556,35 +783,48 @@ class IntentBuilder<I> {
556
783
  ? { optional: true }
557
784
  : {}),
558
785
  });
559
- const primaryOutput = this.steps.length === 0
560
- ? undefined
561
- : composedOutput(
562
- outputGroups.get(`${primary.marker}\0${primary.aggregateId}`) ?? [{
563
- aggregateId: primary.aggregateId,
564
- marker: primary.marker,
565
- }],
566
- );
786
+ const primaryOutput =
787
+ this.steps.length === 0
788
+ ? undefined
789
+ : composedOutput(
790
+ outputGroups.get(`${primary.marker}\0${primary.aggregateId}`) ?? [
791
+ {
792
+ aggregateId: primary.aggregateId,
793
+ marker: primary.marker,
794
+ },
795
+ ]
796
+ );
567
797
  const secondaryOutputs = [...outputGroups.values()]
568
798
  .map(composedOutput)
569
799
  .filter(
570
- (output) => !(output.aggregateId === primary.aggregateId && output.marker === primary.marker),
800
+ (output) =>
801
+ !(
802
+ output.aggregateId === primary.aggregateId &&
803
+ output.marker === primary.marker
804
+ )
571
805
  );
572
806
  const inferredReturns = [
573
807
  ...(primaryOutput === undefined ? [] : [primary.aggregateId]),
574
808
  ...secondaryOutputs.map((output) => output.aggregateId),
575
809
  ...birthSteps.flatMap((step) => {
576
- const aggregateId = (step.directive as BusinessBirthDirective).businessBirth?.aggregateId;
810
+ const aggregateId = (step.directive as BusinessBirthDirective)
811
+ .businessBirth?.aggregateId;
577
812
  return aggregateId === undefined ? [] : [aggregateId];
578
813
  }),
579
814
  ].filter((value, index, values) => values.indexOf(value) === index);
580
- const declaredReturns = this.contractDeclaration.returns?.map((aggregate) => aggregate.id);
815
+ const declaredReturns = this.contractDeclaration.returns?.map(
816
+ (aggregate) => aggregate.id
817
+ );
581
818
  if (
582
819
  declaredReturns !== undefined &&
583
- stable([...new Set(declaredReturns)].sort()) !== stable([...inferredReturns].sort())
820
+ stable([...new Set(declaredReturns)].sort()) !==
821
+ stable([...inferredReturns].sort())
584
822
  ) {
585
823
  throw new Error(
586
- `intent('${this.id}') contract returns ${declaredReturns.join(", ") || "nothing"}, but its ` +
587
- `consequences affect ${inferredReturns.join(", ") || "nothing"}`,
824
+ `intent('${this.id}') contract returns ${
825
+ declaredReturns.join(", ") || "nothing"
826
+ }, but its ` +
827
+ `consequences affect ${inferredReturns.join(", ") || "nothing"}`
588
828
  );
589
829
  }
590
830
 
@@ -595,15 +835,16 @@ class IntentBuilder<I> {
595
835
  readId: privateReadId,
596
836
  ...rest
597
837
  } = condition;
598
- const publicIdPayloadPath = privateIdPayloadPath === undefined
599
- ? undefined
600
- : remapStepPayloadPath(step, privateIdPayloadPath);
838
+ const publicIdPayloadPath =
839
+ privateIdPayloadPath === undefined
840
+ ? undefined
841
+ : remapStepPayloadPath(step, privateIdPayloadPath);
601
842
  let publicReadId: InvariantReadId | undefined;
602
843
  if (privateReadId !== undefined) {
603
844
  try {
604
845
  publicReadId = remapReadId(
605
846
  privateReadId,
606
- step.payload(payloadTrace() as unknown as I),
847
+ step.payload(payloadTrace() as unknown as I)
607
848
  );
608
849
  } catch {
609
850
  publicReadId = undefined;
@@ -611,32 +852,43 @@ class IntentBuilder<I> {
611
852
  }
612
853
  return {
613
854
  ...rest,
614
- ...(publicIdPayloadPath === undefined ? {} : { idPayloadPath: publicIdPayloadPath }),
855
+ ...(publicIdPayloadPath === undefined
856
+ ? {}
857
+ : { idPayloadPath: publicIdPayloadPath }),
615
858
  ...(publicReadId === undefined ? {} : { readId: publicReadId }),
616
- idField: (facts: unknown) => condition.idField(step.payload(facts as I)),
859
+ idField: (facts: unknown) =>
860
+ condition.idField(step.payload(facts as I)),
617
861
  };
618
- }),
862
+ })
619
863
  );
620
864
  const intentQueryReads = this.intentReads.filter(
621
- (read): read is QueryDecl => typeof read !== "string",
865
+ (read): read is QueryDecl => typeof read !== "string"
622
866
  );
623
867
  const intentRefReads = this.intentReads.filter(
624
- (read): read is string => typeof read === "string",
868
+ (read): read is string => typeof read === "string"
625
869
  );
626
870
  const queryReads = dedupeBy(
627
- [...intentQueryReads, ...this.steps.flatMap((step) => step.directive.declaredQueryReads ?? [])],
628
- (query) => query.id,
871
+ [
872
+ ...intentQueryReads,
873
+ ...this.steps.flatMap(
874
+ (step) => step.directive.declaredQueryReads ?? []
875
+ ),
876
+ ],
877
+ (query) => query.id
629
878
  );
630
879
  const foreignReads = dedupeBy(
631
880
  this.steps.flatMap((step) => step.directive.declaredForeignReads ?? []),
632
- (read) => stable(read),
881
+ (read) => stable(read)
633
882
  );
634
883
  const emits: Record<string, { max?: number }> = {};
635
884
  for (const step of this.steps) {
636
- for (const [event, declaration] of Object.entries(step.directive.declaredEmits)) {
885
+ for (const [event, declaration] of Object.entries(
886
+ step.directive.declaredEmits
887
+ )) {
637
888
  const prior = emits[event];
638
889
  if (prior === undefined) {
639
- emits[event] = declaration.max === undefined ? {} : { max: declaration.max };
890
+ emits[event] =
891
+ declaration.max === undefined ? {} : { max: declaration.max };
640
892
  } else if (prior.max === undefined || declaration.max === undefined) {
641
893
  emits[event] = {};
642
894
  } else {
@@ -650,26 +902,31 @@ class IntentBuilder<I> {
650
902
  inheritedRelationGate !== undefined &&
651
903
  stable(this.intentRelationGate) !== stable(inheritedRelationGate)
652
904
  ) {
653
- throw new Error(`intent('${this.id}') has conflicting intent and consequence authorization gates`);
905
+ throw new Error(
906
+ `intent('${this.id}') has conflicting intent and consequence authorization gates`
907
+ );
654
908
  }
655
909
  const relationGate = this.intentRelationGate ?? inheritedRelationGate;
656
910
  if (
657
911
  this.acceptsWorkspaceEffects &&
658
- (relationGate === undefined || relationGate.object !== "workspace:self" || relationGate.objectFrom !== undefined)
912
+ !this.acceptsVerifiedBirthParentRecovery &&
913
+ (relationGate === undefined ||
914
+ relationGate.object !== "workspace:self" ||
915
+ relationGate.objectFrom !== undefined)
659
916
  ) {
660
917
  throw new Error(
661
918
  `intent('${this.id}') .acceptsWorkspaceEffect() requires one static ` +
662
- `.requires(relation, { object: "workspace:self" }) consequence gate`,
919
+ `.requires(relation, { object: "workspace:self" }) consequence gate`
663
920
  );
664
921
  }
665
922
  const sourceTrusts = this.steps.flatMap((step) =>
666
923
  step.directive.businessSourceTrust === undefined
667
924
  ? []
668
- : [remapBusinessSourceTrust(step, step.directive.businessSourceTrust)],
925
+ : [remapBusinessSourceTrust(step, step.directive.businessSourceTrust)]
669
926
  );
670
927
  if (sourceTrusts.length > 1) {
671
928
  throw new Error(
672
- `intent('${this.id}') composes more than one source-trust preflight — split the business action by source`,
929
+ `intent('${this.id}') composes more than one source-trust preflight — split the business action by source`
673
930
  );
674
931
  }
675
932
  const steps = [...this.steps];
@@ -680,19 +937,26 @@ class IntentBuilder<I> {
680
937
  const result = step.directive.plan(mapped, ctx);
681
938
  if (!Array.isArray(result)) {
682
939
  throw new Error(
683
- `intent '${this.id}' consequence '${step.directive.id}' did not return an operation array`,
940
+ `intent '${this.id}' consequence '${step.directive.id}' did not return an operation array`
684
941
  );
685
942
  }
686
- returned.push(...result.map((op) => composePrimaryConsequenceOp(step.directive, op)));
943
+ returned.push(
944
+ ...result.map((op) => composePrimaryConsequenceOp(step.directive, op))
945
+ );
687
946
  }
688
947
  return returned;
689
948
  };
690
949
 
691
- const businessBirth = (birthSteps[0]?.directive as BusinessBirthDirective | undefined)?.businessBirth;
692
- const bindBusinessBirthPolicy =
693
- (birthSteps[0]?.directive as BusinessBirthDirective | undefined)?.[BUSINESS_BIRTH_POLICY_BINDING];
950
+ const businessBirth = (
951
+ birthSteps[0]?.directive as BusinessBirthDirective | undefined
952
+ )?.businessBirth;
953
+ const bindBusinessBirthPolicy = (
954
+ birthSteps[0]?.directive as BusinessBirthDirective | undefined
955
+ )?.[BUSINESS_BIRTH_POLICY_BINDING];
694
956
  const carriedCapabilities = [
695
- ...new Set(this.steps.flatMap((step) => capabilityDeclarationsOf(step.directive))),
957
+ ...new Set(
958
+ this.steps.flatMap((step) => capabilityDeclarationsOf(step.directive))
959
+ ),
696
960
  ];
697
961
  return {
698
962
  [INTENT_BRAND]: true,
@@ -708,21 +972,29 @@ class IntentBuilder<I> {
708
972
  // runtime plan may retain only this intent export; without the handle the
709
973
  // engine cannot rebuild its aggregate registry entry and silently drops
710
974
  // compound intents that also birth a child.
711
- ...(primary.aggregate === undefined ? {} : { aggregate: primary.aggregate }),
975
+ ...(primary.aggregate === undefined
976
+ ? {}
977
+ : { aggregate: primary.aggregate }),
712
978
  ...(primaryOutput?.many === true
713
979
  ? { outputCardinality: "many" as const }
714
980
  : primaryOutput?.optional === true
715
981
  ? { outputCardinality: "optional" as const }
716
982
  : {}),
717
983
  ...(secondaryOutputs.length > 0 ? { secondaryOutputs } : {}),
718
- payloadSchema: (birthSteps[0]?.directive.payloadSchema ?? this.contractDeclaration.accepts) as unknown as z.ZodType<I>,
984
+ payloadSchema: (birthSteps[0]?.directive.payloadSchema ??
985
+ this.contractDeclaration.accepts) as unknown as z.ZodType<I>,
719
986
  plan,
720
987
  steps,
721
988
  workspaceEffects: this.workspaceEffects,
722
989
  ...(this.offerAuthenticationProof === undefined
723
990
  ? {}
724
991
  : { offerAuthenticationProof: this.offerAuthenticationProof }),
725
- ...(this.acceptsWorkspaceEffects ? { acceptsWorkspaceEffectGate: true as const } : {}),
992
+ ...(this.acceptsWorkspaceEffects
993
+ ? { acceptsWorkspaceEffectGate: true as const }
994
+ : {}),
995
+ ...(this.acceptsVerifiedBirthParentRecovery
996
+ ? { acceptsVerifiedBirthParentRecoveryGate: true as const }
997
+ : {}),
726
998
  declaredReads: [
727
999
  ...new Set([
728
1000
  ...intentRefReads,
@@ -730,17 +1002,23 @@ class IntentBuilder<I> {
730
1002
  ]),
731
1003
  ].sort(),
732
1004
  ...(queryReads.length > 0 ? { declaredQueryReads: queryReads } : {}),
733
- ...(foreignReads.length > 0 ? { declaredForeignReads: foreignReads } : {}),
1005
+ ...(foreignReads.length > 0
1006
+ ? { declaredForeignReads: foreignReads }
1007
+ : {}),
734
1008
  declaredEmits: emits,
735
1009
  declaredRelations: dedupeBy(
736
1010
  this.steps.flatMap((step) => step.directive.declaredRelations),
737
- (relation) => stable(relation),
1011
+ (relation) => stable(relation)
738
1012
  ),
739
1013
  declaredHostCapabilities: [
740
- ...new Set(this.steps.flatMap((step) => step.directive.declaredHostCapabilities)),
1014
+ ...new Set(
1015
+ this.steps.flatMap((step) => step.directive.declaredHostCapabilities)
1016
+ ),
741
1017
  ].sort(),
742
1018
  ...(preconditions.length > 0 ? { preconditions } : {}),
743
- ...(relationGate !== undefined ? { requiresRelationGate: relationGate } : {}),
1019
+ ...(relationGate !== undefined
1020
+ ? { requiresRelationGate: relationGate }
1021
+ : {}),
744
1022
  ...(sourceTrusts[0] === undefined
745
1023
  ? {}
746
1024
  : { businessSourceTrust: sourceTrusts[0] }),
@@ -775,9 +1053,14 @@ class IntentContractStep {
775
1053
  */
776
1054
  performs<P>(effect: Directive<P>): BusinessIntent<P> {
777
1055
  if (isBusinessIntent(effect)) {
778
- throw new Error(`intent('${this.id}').performs(...) expects a private directive effect, not an intent`);
1056
+ throw new Error(
1057
+ `intent('${this.id}').performs(...) expects a private directive effect, not an intent`
1058
+ );
779
1059
  }
780
- const step: IntentStep<P, P> = { directive: effect, payload: (facts) => facts };
1060
+ const step: IntentStep<P, P> = {
1061
+ directive: effect,
1062
+ payload: (facts) => facts,
1063
+ };
781
1064
  // A one-effect publication does not need to synthesize or approximate any
782
1065
  // kernel policy. Preserve the effect's complete metadata (birth, access,
783
1066
  // custody, recovery, preconditions, activity...) and change only the public
@@ -789,33 +1072,48 @@ class IntentContractStep {
789
1072
  id: this.id,
790
1073
  contract: {
791
1074
  accepts: effect.payloadSchema,
792
- returns: Object.freeze([
793
- effect.aggregateId,
794
- ...(effect.secondaryOutputs ?? []).map((output) => output.aggregateId),
795
- ].filter((value, index, values) => values.indexOf(value) === index)),
1075
+ returns: Object.freeze(
1076
+ [
1077
+ effect.aggregateId,
1078
+ ...(effect.secondaryOutputs ?? []).map(
1079
+ (output) => output.aggregateId
1080
+ ),
1081
+ ].filter((value, index, values) => values.indexOf(value) === index)
1082
+ ),
796
1083
  refuses: Object.freeze([]),
797
1084
  },
798
1085
  steps: [step],
799
1086
  workspaceEffects: [],
800
- plan: (facts, ctx) => effect
801
- .plan(effect.payloadSchema.parse(facts), ctx)
802
- .map((op) => composePrimaryConsequenceOp(effect, op)),
1087
+ plan: (facts, ctx) =>
1088
+ effect
1089
+ .plan(effect.payloadSchema.parse(facts), ctx)
1090
+ .map((op) => composePrimaryConsequenceOp(effect, op)),
803
1091
  };
804
1092
  }
805
1093
 
806
1094
  /** Begin an intent from one declared business birth without repeating its payload schema. */
807
1095
  births<P extends Record<string, unknown>, F extends Record<string, Field>>(
808
- declaration: BusinessBirthDecl<P, F>,
1096
+ declaration: BusinessBirthDecl<P, F>
809
1097
  ): IntentBuilder<P> {
810
- return new IntentBuilder<P>(this.id, { accepts: declaration.payloadSchema as z.ZodType<P> }, [
811
- { directive: businessBirthConsequence(declaration), payload: (facts) => facts },
812
- ]);
1098
+ return new IntentBuilder<P>(
1099
+ this.id,
1100
+ { accepts: declaration.payloadSchema as z.ZodType<P> },
1101
+ [
1102
+ {
1103
+ directive: businessBirthConsequence(declaration),
1104
+ payload: (facts) => facts,
1105
+ },
1106
+ ]
1107
+ );
813
1108
  }
814
1109
 
815
1110
  contract<S extends z.ZodTypeAny>(
816
- declaration: IntentContractDeclaration<z.infer<S>> & { readonly accepts: S },
1111
+ declaration: IntentContractDeclaration<z.infer<S>> & { readonly accepts: S }
817
1112
  ): IntentBuilder<z.infer<S>> {
818
- return new IntentBuilder(this.id, declaration as IntentContractDeclaration<z.infer<S>>);
1113
+ return new IntentBuilder(
1114
+ this.id,
1115
+ declaration as IntentContractDeclaration<z.infer<S>>
1116
+ );
819
1117
  }
820
1118
  }
821
1119
 
@@ -823,7 +1121,9 @@ class IntentContractStep {
823
1121
  export function intent<const Id extends string>(id: Id): IntentContractStep {
824
1122
  if (id.trim().length === 0) throw new Error("intent id must not be empty");
825
1123
  if (id.startsWith("_nms")) {
826
- throw new Error(`intent id '${id}' uses Nomos's reserved internal prefix '_nms'`);
1124
+ throw new Error(
1125
+ `intent id '${id}' uses Nomos's reserved internal prefix '_nms'`
1126
+ );
827
1127
  }
828
1128
  return new IntentContractStep(id);
829
1129
  }
@@ -840,16 +1140,21 @@ export function isBusinessIntent(value: unknown): value is BusinessIntent<any> {
840
1140
  export function language(
841
1141
  domain: string,
842
1142
  intents: readonly BusinessIntent<any>[],
843
- options: ApplicationLanguageOptions = {},
1143
+ options: ApplicationLanguageOptions = {}
844
1144
  ): ApplicationLanguage {
845
- if (domain.trim().length === 0) throw new Error("language domain must not be empty");
1145
+ if (domain.trim().length === 0)
1146
+ throw new Error("language domain must not be empty");
846
1147
  const seen = new Set<string>();
847
1148
  for (const candidate of intents) {
848
1149
  if (!isBusinessIntent(candidate)) {
849
- throw new Error(`language('${domain}') accepts finished intent(...).atomically() declarations only`);
1150
+ throw new Error(
1151
+ `language('${domain}') accepts finished intent(...).atomically() declarations only`
1152
+ );
850
1153
  }
851
1154
  if (seen.has(candidate.id)) {
852
- throw new Error(`language('${domain}') publishes intent '${candidate.id}' more than once`);
1155
+ throw new Error(
1156
+ `language('${domain}') publishes intent '${candidate.id}' more than once`
1157
+ );
853
1158
  }
854
1159
  seen.add(candidate.id);
855
1160
  }
@@ -858,7 +1163,7 @@ export function language(
858
1163
  for (const initiator of policy.initiatedBy) {
859
1164
  if (!intents.some((candidate) => candidate.id === initiator.id)) {
860
1165
  throw new Error(
861
- `language('${domain}') workspaceSharing(${policy.orders.id}) initiator '${initiator.id}' is not a public business action`,
1166
+ `language('${domain}') workspaceSharing(${policy.orders.id}) initiator '${initiator.id}' is not a public business action`
862
1167
  );
863
1168
  }
864
1169
  }
@@ -871,7 +1176,9 @@ export function language(
871
1176
  };
872
1177
  }
873
1178
 
874
- export function isApplicationLanguage(value: unknown): value is ApplicationLanguage {
1179
+ export function isApplicationLanguage(
1180
+ value: unknown
1181
+ ): value is ApplicationLanguage {
875
1182
  return (
876
1183
  value !== null &&
877
1184
  typeof value === "object" &&
@@ -881,7 +1188,7 @@ export function isApplicationLanguage(value: unknown): value is ApplicationLangu
881
1188
 
882
1189
  export function applicationLanguageOf(
883
1190
  modules: readonly Record<string, unknown>[],
884
- domain: string,
1191
+ domain: string
885
1192
  ): ApplicationLanguage | undefined {
886
1193
  const found: ApplicationLanguage[] = [];
887
1194
  for (const mod of modules) {
@@ -891,12 +1198,14 @@ export function applicationLanguageOf(
891
1198
  }
892
1199
  const unique = [...new Set(found)];
893
1200
  if (unique.length > 1) {
894
- throw new Error(`domain '${domain}' exports more than one application language`);
1201
+ throw new Error(
1202
+ `domain '${domain}' exports more than one application language`
1203
+ );
895
1204
  }
896
1205
  const language = unique[0];
897
1206
  if (language !== undefined && language.domain !== domain) {
898
1207
  throw new Error(
899
- `domain '${domain}' exports language('${language.domain}') — the language name must match its domain`,
1208
+ `domain '${domain}' exports language('${language.domain}') — the language name must match its domain`
900
1209
  );
901
1210
  }
902
1211
  return language;
@@ -910,17 +1219,21 @@ export function applicationLanguageOf(
910
1219
  export function executableDirectivesForLanguage(
911
1220
  language: ApplicationLanguage,
912
1221
  discovered: readonly Directive<any>[],
913
- domain: string,
1222
+ domain: string
914
1223
  ): Directive<any>[] {
915
- const publicById = new Map(language.intents.map((candidate) => [candidate.id, candidate]));
1224
+ const publicById = new Map(
1225
+ language.intents.map((candidate) => [candidate.id, candidate])
1226
+ );
916
1227
  const privateIds = new Set(
917
- language.intents.flatMap((candidate) => candidate.steps.map((step) => step.directive.id)),
1228
+ language.intents.flatMap((candidate) =>
1229
+ candidate.steps.map((step) => step.directive.id)
1230
+ )
918
1231
  );
919
1232
 
920
1233
  for (const candidate of language.intents) {
921
1234
  if (privateIds.has(candidate.id)) {
922
1235
  throw new Error(
923
- `domain '${domain}' publishes intent '${candidate.id}' with the same name as one of its private directives`,
1236
+ `domain '${domain}' publishes intent '${candidate.id}' with the same name as one of its private directives`
924
1237
  );
925
1238
  }
926
1239
  }
@@ -933,22 +1246,23 @@ export function executableDirectivesForLanguage(
933
1246
  candidate.marker === published.marker &&
934
1247
  candidate.steps.length === published.steps.length &&
935
1248
  candidate.steps.every(
936
- (step, index) => step.directive.id === published.steps[index]?.directive.id,
1249
+ (step, index) =>
1250
+ step.directive.id === published.steps[index]?.directive.id
937
1251
  );
938
1252
  if (published !== undefined && candidate !== published && !sameLowering) {
939
1253
  throw new Error(
940
- `domain '${domain}' intent '${candidate.id}' collides with a different directive — rename one of them`,
1254
+ `domain '${domain}' intent '${candidate.id}' collides with a different directive — rename one of them`
941
1255
  );
942
1256
  }
943
1257
  if (isBusinessIntent(candidate) && published === undefined) {
944
1258
  throw new Error(
945
- `domain '${domain}' exports intent '${candidate.id}' outside its declared language — add it to language('${domain}', [...]) or remove it`,
1259
+ `domain '${domain}' exports intent '${candidate.id}' outside its declared language — add it to language('${domain}', [...]) or remove it`
946
1260
  );
947
1261
  }
948
1262
  }
949
1263
 
950
1264
  const remaining = discovered.filter(
951
- (candidate) => !privateIds.has(candidate.id) && !isBusinessIntent(candidate),
1265
+ (candidate) => !privateIds.has(candidate.id) && !isBusinessIntent(candidate)
952
1266
  );
953
1267
  return [...remaining, ...language.intents];
954
1268
  }
@@ -962,16 +1276,20 @@ export function executableDirectivesForLanguage(
962
1276
  export function assertApplicationLanguageCoversAuthoredDirectives(
963
1277
  language: ApplicationLanguage,
964
1278
  authored: readonly Directive<any>[],
965
- domain: string,
1279
+ domain: string
966
1280
  ): void {
967
1281
  const publicIds = new Set(language.intents.map((candidate) => candidate.id));
968
1282
  const privateIds = new Set(
969
- language.intents.flatMap((candidate) => candidate.steps.map((step) => step.directive.id)),
1283
+ language.intents.flatMap((candidate) =>
1284
+ candidate.steps.map((step) => step.directive.id)
1285
+ )
970
1286
  );
971
1287
  const sharingReceiptIds = new Set(
972
1288
  language.workspaceSharing.flatMap((policy) =>
973
- [...policy.delivered, ...policy.refused].map((receipt) => receipt.records.id)
974
- ),
1289
+ [...policy.delivered, ...policy.refused].map(
1290
+ (receipt) => receipt.records.id
1291
+ )
1292
+ )
975
1293
  );
976
1294
  const uncovered = authored
977
1295
  .filter(
@@ -979,15 +1297,17 @@ export function assertApplicationLanguageCoversAuthoredDirectives(
979
1297
  directive.businessBirthInternal !== true &&
980
1298
  !publicIds.has(directive.id) &&
981
1299
  !privateIds.has(directive.id) &&
982
- !sharingReceiptIds.has(directive.id),
1300
+ !sharingReceiptIds.has(directive.id)
983
1301
  )
984
1302
  .map((directive) => directive.id)
985
1303
  .sort();
986
1304
  if (uncovered.length > 0) {
987
1305
  throw new Error(
988
1306
  `domain '${domain}' declares language('${domain}', [...]) but still exports routable directive ` +
989
- `${uncovered.map((id) => `'${id}'`).join(", ")} outside it — publish business intents for ` +
990
- `those actions or make their effects private`,
1307
+ `${uncovered
1308
+ .map((id) => `'${id}'`)
1309
+ .join(", ")} outside it — publish business intents for ` +
1310
+ `those actions or make their effects private`
991
1311
  );
992
1312
  }
993
1313
  }
@@ -995,36 +1315,43 @@ export function assertApplicationLanguageCoversAuthoredDirectives(
995
1315
  /** Carry a private directive's invariant onto every public intent that uses it. */
996
1316
  export function remapWorkspaceInvariantsForLanguage(
997
1317
  invariants: readonly WorkspaceInvariantDecl[],
998
- language: ApplicationLanguage,
1318
+ language: ApplicationLanguage
999
1319
  ): WorkspaceInvariantDecl[] {
1000
1320
  const out: WorkspaceInvariantDecl[] = [];
1001
1321
  for (const invariant of invariants) {
1002
1322
  const uses = language.intents.flatMap((candidate) =>
1003
1323
  candidate.steps
1004
1324
  .filter((step) => step.directive.id === invariant.on)
1005
- .map((step) => ({ intent: candidate, step })),
1325
+ .map((step) => ({ intent: candidate, step }))
1006
1326
  );
1007
1327
  if (uses.length === 0) {
1008
1328
  out.push(invariant);
1009
1329
  continue;
1010
1330
  }
1011
1331
  for (const use of uses) {
1012
- const mapFacts = (facts: Record<string, unknown>): Record<string, unknown> => {
1332
+ const mapFacts = (
1333
+ facts: Record<string, unknown>
1334
+ ): Record<string, unknown> => {
1013
1335
  const mapped = use.step.payload(facts);
1014
1336
  const parsed = use.step.directive.payloadSchema.safeParse(mapped);
1015
1337
  // The flow/security compiler probes invariant read-sets with empty
1016
1338
  // placeholder facts. That probe must remain total even when the real
1017
1339
  // business payload requires non-empty values. Runtime intent planning
1018
1340
  // still validates every mapped consequence before it can commit.
1019
- return (parsed.success ? parsed.data : mapped) as Record<string, unknown>;
1341
+ return (parsed.success ? parsed.data : mapped) as Record<
1342
+ string,
1343
+ unknown
1344
+ >;
1020
1345
  };
1021
1346
  // Never carry a private payload path blindly onto a public intent. Prove the
1022
1347
  // mapping structurally with inert path values; otherwise leave the selector
1023
1348
  // compiler-visible so the native-only gate can name the exact unmapped law.
1024
- const remappedNativeReads = invariant.__nomosReadBindings === undefined
1025
- ? undefined
1026
- : remapNativeReads(invariant.__nomosReadBindings, use.step);
1027
- const { __nomosReadBindings: _privateReads, ...withoutPrivateReads } = invariant;
1349
+ const remappedNativeReads =
1350
+ invariant.__nomosReadBindings === undefined
1351
+ ? undefined
1352
+ : remapNativeReads(invariant.__nomosReadBindings, use.step);
1353
+ const { __nomosReadBindings: _privateReads, ...withoutPrivateReads } =
1354
+ invariant;
1028
1355
  out.push({
1029
1356
  ...withoutPrivateReads,
1030
1357
  id: `${use.intent.id}__${invariant.id}`,
@@ -1032,11 +1359,15 @@ export function remapWorkspaceInvariantsForLanguage(
1032
1359
  ...(remappedNativeReads === undefined
1033
1360
  ? {}
1034
1361
  : { __nomosReadBindings: remappedNativeReads }),
1035
- reads: (ctx) => invariant.reads({ ...ctx, intent: mapFacts(ctx.intent) }),
1036
- assert: (snapshots, ctx) => invariant.assert(
1037
- snapshots,
1038
- ctx === undefined ? undefined : { ...ctx, intent: mapFacts(ctx.intent) },
1039
- ),
1362
+ reads: (ctx) =>
1363
+ invariant.reads({ ...ctx, intent: mapFacts(ctx.intent) }),
1364
+ assert: (snapshots, ctx) =>
1365
+ invariant.assert(
1366
+ snapshots,
1367
+ ctx === undefined
1368
+ ? undefined
1369
+ : { ...ctx, intent: mapFacts(ctx.intent) }
1370
+ ),
1040
1371
  });
1041
1372
  }
1042
1373
  }