@githolon/dsl 0.102.4 → 0.103.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/compile_package.mjs +43 -2
  2. package/compile_proof.d.mts +47 -0
  3. package/compile_proof.mjs +343 -0
  4. package/dart/CHANGELOG.md +7 -0
  5. package/dart/lib/src/dev_cloud.dart +8 -8
  6. package/dart/lib/src/nms_generated_runtime.dart +2 -0
  7. package/dart/lib/src/runner/node-entry.mjs +5504 -1236
  8. package/dart/lib/src/runner/offline-install.mjs +4578 -528
  9. package/dart/lib/src/test_harness.dart +206 -152
  10. package/dart/lib/src/webview_bridge.dart +196 -89
  11. package/dart/pubspec.lock +1 -1
  12. package/dart/pubspec.yaml +2 -2
  13. package/dart/test/acquire_session_credentials_test.dart +4 -3
  14. package/dart/test/adv_realm_interference_test.dart +7 -6
  15. package/dart/test/cloud_bound_topology_test.dart +3 -3
  16. package/dart/test/cluster_births_test.dart +4 -3
  17. package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
  18. package/dart/test/cluster_local_autosync_test.dart +3 -2
  19. package/dart/test/cluster_scale_test.dart +3 -2
  20. package/dart/test/dlq_session_surface_test.dart +7 -6
  21. package/dart/test/first_app_gauntlet_test.dart +14 -21
  22. package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
  23. package/dart/test/four_layer_nested_topology_test.dart +4 -4
  24. package/dart/test/harness_phase_timeout_test.dart +2 -2
  25. package/dart/test/local_sync_inert_test.dart +1 -1
  26. package/dart/test/parent_signer_recovery_test.dart +2 -2
  27. package/dart/test/sealing_client_test.dart +4 -3
  28. package/dart/test/session_bound_test.dart +8 -7
  29. package/dart/test/test_harness_test.dart +13 -12
  30. package/dart/test/vo_read_typing_test.dart +4 -3
  31. package/dart/test/watch_after_offer_stall_test.dart +4 -3
  32. package/dart/test/webview_bridge_test.dart +102 -5
  33. package/dart_types/CHANGELOG.md +5 -0
  34. package/dart_types/lib/nomos_types.dart +9 -0
  35. package/dart_types/lib/src/nomos_ref.dart +20 -0
  36. package/dart_types/lib/src/optional_ref.dart +85 -0
  37. package/dart_types/lib/src/sha256_digest.dart +41 -0
  38. package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
  39. package/dart_types/pubspec.yaml +1 -1
  40. package/dart_types/test/optional_ref_test.dart +44 -0
  41. package/dart_types/test/sha256_digest_test.dart +18 -0
  42. package/package.json +9 -5
  43. package/runtime/generated-application-host.js +5365 -1105
  44. package/src/aggregate.ts +18 -0
  45. package/src/application_composition.ts +6 -32
  46. package/src/attested_core.ts +6 -3
  47. package/src/birth_meter.ts +1 -0
  48. package/src/birth_meter_ensure.ts +28 -2
  49. package/src/births.ts +1 -1
  50. package/src/build_package.ts +162 -538
  51. package/src/business_birth.ts +19 -3
  52. package/src/canonical_json.ts +21 -0
  53. package/src/check_package_main.ts +76 -35
  54. package/src/clear_directive.ts +19 -8
  55. package/src/codegen_dart.ts +305 -22
  56. package/src/codegen_dart_application.ts +844 -292
  57. package/src/codegen_dart_client.ts +1084 -407
  58. package/src/codegen_dart_scenario.ts +12 -12
  59. package/src/codegen_proof.ts +169 -39
  60. package/src/codegen_provider_dart.ts +17 -1
  61. package/src/codegen_ts.ts +298 -106
  62. package/src/compat_manifest.ts +2 -2
  63. package/src/compat_package_main.ts +8 -12
  64. package/src/compile_package_main.ts +1866 -597
  65. package/src/compose.ts +1 -1
  66. package/src/directive.ts +208 -15
  67. package/src/engine_entry.ts +165 -111
  68. package/src/evolve_preview.ts +43 -81
  69. package/src/extremum.ts +1 -1
  70. package/src/fields.ts +183 -4
  71. package/src/flow_gate.ts +75 -2
  72. package/src/framework/application_home.ts +2 -0
  73. package/src/framework/auth_provider_law.ts +2 -2
  74. package/src/framework/birth_meter.ts +7 -19
  75. package/src/framework/bootstrap.ts +0 -2
  76. package/src/framework/business_corrections.ts +9 -5
  77. package/src/framework/domain_lifecycle.ts +15 -29
  78. package/src/framework/encryption_scope.ts +2 -1
  79. package/src/framework/framework_aggregates.ts +2 -0
  80. package/src/framework/genesis.ts +16 -14
  81. package/src/framework/home.ts +20 -14
  82. package/src/framework/owned_custody.ts +9 -3
  83. package/src/framework/platform_admin.ts +3 -3
  84. package/src/framework/relations.ts +21 -13
  85. package/src/framework/signing_identity.ts +45 -40
  86. package/src/framework/workspace_invariant.ts +245 -0
  87. package/src/framework/workspaces.ts +23 -29
  88. package/src/index.ts +36 -16
  89. package/src/intent.ts +178 -6
  90. package/src/law_check.ts +32 -11
  91. package/src/law_graph.ts +2902 -0
  92. package/src/law_graph_usda.ts +1799 -0
  93. package/src/law_identity.ts +435 -0
  94. package/src/law_value_type.ts +230 -0
  95. package/src/lsp_server.ts +61 -31
  96. package/src/ops.ts +7 -0
  97. package/src/package_config.ts +141 -0
  98. package/src/package_dependencies.ts +108 -0
  99. package/src/package_dependency_usage.ts +150 -0
  100. package/src/package_source_imports.ts +71 -0
  101. package/src/plan_source_slice.ts +335 -0
  102. package/src/point_guard.ts +192 -0
  103. package/src/precondition.ts +44 -9
  104. package/src/read_projection_namespace.ts +1 -1
  105. package/src/runtime_zod.ts +44 -0
  106. package/src/secondary_ensure.ts +67 -14
  107. package/src/shape_compat.ts +1 -1
  108. package/src/stable_id_identity_bridge.ts +149 -0
  109. package/src/stable_id_lockfile.ts +278 -0
  110. package/src/stable_ids.ts +7 -2
  111. package/src/stable_ids_for_module.ts +348 -0
  112. package/src/stable_ids_types.ts +1 -1
  113. package/src/sum.ts +1 -1
  114. package/src/usd_state.ts +1 -1
  115. package/src/usda_relationship_integrity.ts +66 -0
  116. package/src/usda_text.ts +18 -0
  117. package/src/usdz.ts +78 -0
  118. package/src/value_object.ts +95 -0
  119. package/src/wire.ts +19 -0
  120. package/src/wire_encode.ts +22 -0
  121. package/src/workspace_sharding.ts +52 -36
  122. package/src/workspace_sharing.ts +28 -4
  123. package/src/zod_introspection.ts +1 -1
  124. package/dart/test/cluster_attested_test.dart +0 -201
  125. package/src/compile_engine.ts +0 -595
  126. package/src/manifest.ts +0 -1476
  127. package/src/read_projection_composition.ts +0 -117
  128. package/src/read_projection_manifest.ts +0 -115
  129. package/src/relation_composition.ts +0 -286
  130. package/src/usd.ts +0 -1967
  131. package/src/usd_layers.ts +0 -1776
@@ -0,0 +1,2902 @@
1
+ // NOMOS v3 transient compiler graph. This is deliberately process-local: it gives
2
+ // the compiler one typed structure to validate and traverse, but it is never serialized
3
+ // as a second law representation. USDA remains canonical.
4
+ import type { DomainModule } from "./codegen_dart.js";
5
+ import type { z } from "zod";
6
+ import { finishCount } from "./count.js";
7
+ import { finishExists } from "./exists.js";
8
+ import { finishExtremum } from "./extremum.js";
9
+ import { finishSum } from "./sum.js";
10
+ import {
11
+ createLawIdentityReconciler,
12
+ lawEntityKey,
13
+ mintOwnedLawSid,
14
+ mintStructuralLawSid,
15
+ type LawEntityKind,
16
+ type LawIdentityCarry,
17
+ type LawIdentityRegistry,
18
+ } from "./law_identity.js";
19
+ import type { ReferentialMarker } from "./directive.js";
20
+ import type { CanonicalPred } from "./predicate.js";
21
+ import type { PointGuardPredicate } from "./point_guard.js";
22
+ import {
23
+ DEFAULT_RELATION_CAPS,
24
+ qualifyRelationType,
25
+ type AllowedSubject,
26
+ type Rewrite,
27
+ } from "./relation_schema.js";
28
+ import type { StableFieldEvolution, StableIds } from "./stable_ids_types.js";
29
+ import { isBusinessIntent } from "./intent.js";
30
+ import {
31
+ lowerLawValueType,
32
+ type LawConstant,
33
+ type LawLiteral,
34
+ type LawValueConstraint,
35
+ type LawValueType,
36
+ } from "./law_value_type.js";
37
+ import { harvestPrincipalFlows } from "./wire_encode.js";
38
+ import { admitsNull, type Field, type FieldKind, type ValueObjectFieldDescriptor } from "./fields.js";
39
+ import type { GeometryKind } from "./value_object.js";
40
+ import type { Driver } from "./drivers.js";
41
+ import {
42
+ BUSINESS_BIRTH_SEED_PAYLOAD_FIELD,
43
+ OWNED_CUSTODY_PAYLOAD_FIELD,
44
+ OWNED_KEY_PAYLOAD_FIELD,
45
+ OWNED_PREMINTED_RECORDS_PAYLOAD_FIELD,
46
+ OWNED_READ_RELATION,
47
+ } from "./aggregate.js";
48
+ import { isNamespacedAggregate, qualifyReadProjectionType } from "./read_projection_namespace.js";
49
+ import { assertFlowGateModule } from "./flow_gate.js";
50
+ import type { InvariantReadBinding } from "./framework/workspace_invariant.js";
51
+
52
+ declare const LAW_SID: unique symbol;
53
+ export type LawSid<K extends LawEntityKind> = string & { readonly [LAW_SID]: K };
54
+
55
+ export interface LawRef<K extends LawEntityKind> {
56
+ readonly id: LawSid<K>;
57
+ }
58
+
59
+ export interface LawSourceSpan {
60
+ readonly file: string;
61
+ readonly line: number;
62
+ readonly column: number;
63
+ readonly endLine?: number;
64
+ readonly endColumn?: number;
65
+ }
66
+
67
+ export interface LawEntity<K extends LawEntityKind> {
68
+ readonly id: LawSid<K>;
69
+ readonly sid: string;
70
+ readonly label: string;
71
+ readonly priorLabels: readonly string[];
72
+ readonly identityClass: "declared" | "owned" | "structural";
73
+ readonly source?: LawSourceSpan;
74
+ }
75
+
76
+ export interface LawFieldPathRef {
77
+ readonly field: LawRef<"field">;
78
+ /** Exact authored path retained because the terminal relationship alone cannot
79
+ * distinguish a nested projection path such as location.point. */
80
+ readonly authoredPath: string;
81
+ /** Stable nested field identities from the aggregate field towards the terminal value. */
82
+ readonly path: readonly (LawRef<"field"> | LawStructuralRef<"value-field">)[];
83
+ }
84
+
85
+ export interface LawDomainNode {
86
+ readonly kind: "domain";
87
+ readonly entity: LawEntity<"domain">;
88
+ readonly aggregates: readonly LawRef<"aggregate">[];
89
+ readonly intents: readonly LawRef<"intent">[];
90
+ readonly reads: readonly LawRef<"read">[];
91
+ readonly contracts: readonly LawRef<"contract">[];
92
+ readonly types: readonly LawRef<"type">[];
93
+ readonly objectTypes: readonly LawRef<"object-type">[];
94
+ readonly relations: readonly LawRef<"relation">[];
95
+ readonly policies: readonly LawRef<"policy">[];
96
+ readonly invariants: readonly LawRef<"invariant">[];
97
+ readonly capabilities: readonly LawRef<"capability">[];
98
+ readonly workspaceTypes: readonly LawRef<"workspace-type">[];
99
+ readonly flows: readonly LawStructuralRef<"flow">[];
100
+ readonly parentAttestedBirth?: {
101
+ readonly ownerObject: string;
102
+ readonly ownerField: string;
103
+ readonly certObject: string;
104
+ readonly certField: string;
105
+ };
106
+ }
107
+
108
+ export interface LawAggregateNode {
109
+ readonly kind: "aggregate";
110
+ readonly entity: LawEntity<"aggregate">;
111
+ readonly domain: LawRef<"domain">;
112
+ readonly fields: readonly LawRef<"field">[];
113
+ readonly public: boolean;
114
+ /** Exact `__type` stamped by the compiled runtime and consumed by admission/projection. */
115
+ readonly wireType: string;
116
+ readonly cap?: number;
117
+ readonly implements: readonly LawTargetRef<"contract">[];
118
+ readonly readPolicy?: LawTargetRef<"relation" | "policy">;
119
+ readonly ownerField?: LawRef<"field">;
120
+ }
121
+
122
+ export interface LawFieldNode {
123
+ readonly kind: "field";
124
+ readonly entity: LawEntity<"field">;
125
+ readonly owner: LawRef<"aggregate"> | LawRef<"contract"> | LawRef<"type">;
126
+ readonly value: LawValueTypeRef;
127
+ /** Exact kernel/projection storage semantics. Value type + merge driver is not
128
+ * sufficient to distinguish e.g. string from JSON, or map<string> from map<json>. */
129
+ readonly storageKind?: FieldKind;
130
+ readonly mapValueKind?: FieldKind;
131
+ /** Content identity of the complete business-value contract used to interpret
132
+ * retained conflict observations. */
133
+ readonly schemaIdentity?: string;
134
+ /** Compiler-proven routes from still-supported historical field schemas into the current one. */
135
+ readonly evolutions: readonly StableFieldEvolution[];
136
+ readonly mergeDrivers: readonly LawMergeDriver[];
137
+ readonly required: boolean;
138
+ readonly nullable: boolean;
139
+ readonly encrypted: boolean;
140
+ readonly blindIndexed: boolean;
141
+ readonly refTarget?: LawTargetRef<"aggregate" | "contract">;
142
+ readonly referenceMode?: "current" | "versioned";
143
+ readonly workspaceField?: LawRef<"field">;
144
+ readonly seriesTarget?: LawTargetRef<"aggregate" | "contract">;
145
+ readonly currentField?: LawTargetRef<"field">;
146
+ }
147
+
148
+ export interface LawDependencyRef<K extends LawEntityKind> {
149
+ readonly kind: "dependency-export";
150
+ readonly entityKind: K;
151
+ readonly label: string;
152
+ readonly domain?: string;
153
+ }
154
+
155
+ export type LawTargetRef<K extends LawEntityKind> = LawRef<K> | LawDependencyRef<K>;
156
+ export type LawValueTypeRef = LawStructuralRef<"value-type"> | LawRef<"type">;
157
+
158
+ export interface LawMergeDriver {
159
+ readonly kind: Driver["kind"];
160
+ readonly principal?: string;
161
+ }
162
+
163
+ export interface LawContractNode {
164
+ readonly kind: "contract";
165
+ readonly entity: LawEntity<"contract">;
166
+ readonly domain: LawRef<"domain">;
167
+ readonly public: boolean;
168
+ readonly fields: readonly LawRef<"field">[];
169
+ readonly implementedBy: readonly LawRef<"aggregate">[];
170
+ }
171
+
172
+ export interface LawPayloadContractNode {
173
+ readonly kind: "payload-contract";
174
+ readonly entity: LawEntity<"contract">;
175
+ readonly owner: LawRef<"intent">;
176
+ readonly fields: readonly LawStructuralRef<"value-field">[];
177
+ }
178
+
179
+ export interface LawReadParameter {
180
+ readonly name: string;
181
+ readonly field?: LawFieldPathRef;
182
+ readonly value?: LawStructuralRef<"value-type">;
183
+ }
184
+
185
+ export type LawReadResult =
186
+ | {
187
+ readonly kind: "rows";
188
+ readonly aggregate: LawRef<"aggregate">;
189
+ readonly cardinality: "many" | "optional";
190
+ }
191
+ | { readonly kind: "scalar"; readonly value: LawStructuralRef<"value-type"> };
192
+
193
+ export interface LawReadContractNode {
194
+ readonly kind: "read-contract";
195
+ readonly entity: LawEntity<"contract">;
196
+ readonly owner: LawRef<"read">;
197
+ readonly parameters: readonly LawReadParameter[];
198
+ readonly returns: LawReadResult;
199
+ }
200
+
201
+ export interface LawTypeNode {
202
+ readonly kind: "type";
203
+ readonly entity: LawEntity<"type">;
204
+ readonly domain: LawRef<"domain">;
205
+ readonly form: "record" | "sum" | "geometry";
206
+ readonly geometry?: GeometryKind;
207
+ readonly fields: readonly LawRef<"field">[];
208
+ readonly variants: readonly LawStructuralRef<"value-variant">[];
209
+ }
210
+
211
+ export interface LawRelationNode {
212
+ readonly kind: "relation";
213
+ readonly entity: LawEntity<"relation">;
214
+ readonly domain: LawRef<"domain">;
215
+ readonly objectType: string;
216
+ readonly object: LawRef<"aggregate"> | LawRef<"object-type">;
217
+ readonly relation: string;
218
+ readonly maxDepth: number;
219
+ readonly maxFanout: number;
220
+ readonly allowed: readonly LawAllowedSubject[];
221
+ readonly rewrite: LawStructuralRef<"relation-predicate">;
222
+ }
223
+
224
+ /** A Zanzibar object class which is not itself a stored aggregate schema. */
225
+ export interface LawRelationObjectTypeNode {
226
+ readonly kind: "object-type";
227
+ readonly entity: LawEntity<"object-type">;
228
+ readonly domain: LawRef<"domain">;
229
+ /** Canonical Zanzibar object namespace. The human label may stay stable across a namespace
230
+ * migration; checks and tuple identities use this qualified value. */
231
+ readonly namespace: string;
232
+ }
233
+
234
+ declare const LAW_STRUCTURAL_SID: unique symbol;
235
+ export type LawStructuralSid<K extends LawStructuralKind> = string & {
236
+ readonly [LAW_STRUCTURAL_SID]: K;
237
+ };
238
+ export type LawStructuralKind =
239
+ | "effect"
240
+ | "foreign-read"
241
+ | "flow"
242
+ | "predicate"
243
+ | "relation-predicate"
244
+ | "workspace-relation"
245
+ | "value-field"
246
+ | "value-type"
247
+ | "value-variant";
248
+
249
+ export interface LawStructuralEntity<K extends LawStructuralKind> {
250
+ readonly id: LawStructuralSid<K>;
251
+ readonly sid: string;
252
+ readonly label: string;
253
+ readonly identityClass: "structural";
254
+ }
255
+
256
+ export interface LawStructuralRef<K extends LawStructuralKind> {
257
+ readonly id: LawStructuralSid<K>;
258
+ }
259
+
260
+ export type LawPredicateExpression =
261
+ | { readonly kind: "eq" | "ne"; readonly field: LawFieldPathRef; readonly value: string }
262
+ | { readonly kind: "and" | "or"; readonly terms: readonly LawStructuralRef<"predicate">[] };
263
+
264
+ export interface LawPredicateNode {
265
+ readonly kind: "structural";
266
+ readonly structuralKind: "predicate";
267
+ readonly entity: LawStructuralEntity<"predicate">;
268
+ readonly owner: LawRef<"read">;
269
+ readonly expression: LawPredicateExpression;
270
+ }
271
+
272
+ export interface LawIntentEffectNode {
273
+ readonly kind: "structural";
274
+ readonly structuralKind: "effect";
275
+ readonly entity: LawStructuralEntity<"effect">;
276
+ readonly owner: LawRef<"intent">;
277
+ readonly marker: ReferentialMarker;
278
+ readonly aggregate: LawTargetRef<"aggregate">;
279
+ readonly cardinality: "one" | "optional" | "many";
280
+ }
281
+
282
+ export interface LawFlowNode {
283
+ readonly kind: "structural";
284
+ readonly structuralKind: "flow";
285
+ readonly entity: LawStructuralEntity<"flow">;
286
+ readonly owner: LawRef<"invariant" | "capability" | "intent">;
287
+ readonly flowKind: "declassify" | "endorse";
288
+ readonly site: string;
289
+ readonly reason: string | null;
290
+ readonly sink?: LawRef<"field">;
291
+ readonly facet?: string;
292
+ }
293
+
294
+ export type LawRelationPredicateExpression =
295
+ | { readonly kind: "this" }
296
+ | { readonly kind: "computed"; readonly relation: LawRef<"relation"> }
297
+ | {
298
+ readonly kind: "ttu";
299
+ readonly tupleset: LawRef<"relation">;
300
+ /** Relation(s) on the object types that the tupleset is allowed to reach. */
301
+ readonly computed: readonly LawRef<"relation">[];
302
+ readonly all: boolean;
303
+ }
304
+ | {
305
+ readonly kind: "union" | "intersection";
306
+ readonly terms: readonly LawStructuralRef<"relation-predicate">[];
307
+ }
308
+ | {
309
+ readonly kind: "exclusion";
310
+ readonly base: LawStructuralRef<"relation-predicate">;
311
+ readonly subtract: LawStructuralRef<"relation-predicate">;
312
+ };
313
+
314
+ export type LawAllowedSubject =
315
+ | { readonly kind: "direct"; readonly type: string }
316
+ | { readonly kind: "wildcard" }
317
+ | {
318
+ readonly kind: "userset";
319
+ readonly type: string;
320
+ readonly relation: LawRef<"relation">;
321
+ readonly relationName: string;
322
+ };
323
+
324
+ export interface LawRelationPredicateNode {
325
+ readonly kind: "structural";
326
+ readonly structuralKind: "relation-predicate";
327
+ readonly entity: LawStructuralEntity<"relation-predicate">;
328
+ readonly owner: LawRef<"relation">;
329
+ readonly expression: LawRelationPredicateExpression;
330
+ }
331
+
332
+ export type LawAnonymousValueTypeExpression =
333
+ | {
334
+ readonly kind: "string" | "number" | "integer";
335
+ readonly constraints: readonly LawValueConstraint[];
336
+ }
337
+ | { readonly kind: "boolean" | "null" | "unknown" }
338
+ | { readonly kind: "enum"; readonly values: readonly string[] }
339
+ | { readonly kind: "literal"; readonly value: LawLiteral }
340
+ | { readonly kind: "list"; readonly element: LawStructuralRef<"value-type"> }
341
+ | { readonly kind: "map"; readonly value: LawStructuralRef<"value-type"> }
342
+ | { readonly kind: "record"; readonly fields: readonly LawStructuralRef<"value-field">[] }
343
+ | { readonly kind: "sum"; readonly variants: readonly LawStructuralRef<"value-variant">[] }
344
+ | { readonly kind: "nullable"; readonly value: LawStructuralRef<"value-type"> }
345
+ | {
346
+ readonly kind: "default";
347
+ readonly value: LawStructuralRef<"value-type">;
348
+ readonly defaultValue: LawConstant;
349
+ }
350
+ | { readonly kind: "recursive"; readonly value: LawStructuralRef<"value-type"> };
351
+
352
+ export interface LawAnonymousValueTypeNode {
353
+ readonly kind: "structural";
354
+ readonly structuralKind: "value-type";
355
+ readonly entity: LawStructuralEntity<"value-type">;
356
+ readonly owner: LawRef<LawEntityKind>;
357
+ readonly expression: LawAnonymousValueTypeExpression;
358
+ }
359
+
360
+ export interface LawAnonymousValueFieldNode {
361
+ readonly kind: "structural";
362
+ readonly structuralKind: "value-field";
363
+ readonly entity: LawStructuralEntity<"value-field">;
364
+ readonly owner: LawStructuralRef<"value-type"> | LawRef<"contract">;
365
+ readonly name: string;
366
+ readonly required: boolean;
367
+ readonly value: LawValueTypeRef;
368
+ }
369
+
370
+ export interface LawAnonymousValueVariantNode {
371
+ readonly kind: "structural";
372
+ readonly structuralKind: "value-variant";
373
+ readonly entity: LawStructuralEntity<"value-variant">;
374
+ readonly owner: LawStructuralRef<"value-type"> | LawRef<"type">;
375
+ readonly label?: string;
376
+ readonly value: LawValueTypeRef;
377
+ }
378
+
379
+ export type LawAnonymousValueNode =
380
+ | LawAnonymousValueTypeNode
381
+ | LawAnonymousValueFieldNode
382
+ | LawAnonymousValueVariantNode;
383
+
384
+ export type LawStructuralNode =
385
+ | LawFlowNode
386
+ | LawIntentEffectNode
387
+ | LawForeignReadNode
388
+ | LawWorkspaceRelationNode
389
+ | LawPredicateNode
390
+ | LawRelationPredicateNode
391
+ | LawAnonymousValueNode;
392
+
393
+ interface LawPolicyBase<K extends string> {
394
+ readonly kind: "policy";
395
+ readonly entity: LawEntity<"policy">;
396
+ readonly domain: LawRef<"domain">;
397
+ readonly policyKind: K;
398
+ readonly maxAgeMs?: number;
399
+ readonly minSourceEpoch?: "birth" | number;
400
+ readonly monotone: boolean;
401
+ readonly onDishonor: "refuse" | "record";
402
+ }
403
+
404
+ export interface LawPinnedTrustSourceNode extends LawPolicyBase<"pinned-source"> {
405
+ readonly workspace: string;
406
+ readonly genesis: string;
407
+ readonly keyHashes: readonly string[];
408
+ readonly roster: "pinned" | "derived" | "tracked";
409
+ }
410
+
411
+ export interface LawTrustSourceSetNode extends LawPolicyBase<"source-set"> {
412
+ readonly allowList: LawRef<"aggregate">;
413
+ readonly genesisField: LawRef<"field">;
414
+ readonly keysField: LawRef<"field">;
415
+ }
416
+
417
+ export type LawPolicyNode = LawPinnedTrustSourceNode | LawTrustSourceSetNode;
418
+
419
+ export interface LawForeignReadNode {
420
+ readonly kind: "structural";
421
+ readonly structuralKind: "foreign-read";
422
+ readonly entity: LawStructuralEntity<"foreign-read">;
423
+ readonly owner: LawRef<"intent">;
424
+ readonly query: string;
425
+ readonly source: LawRef<"policy">;
426
+ readonly fromField?: LawStructuralRef<"value-field">;
427
+ }
428
+
429
+ /** A bounded, attested cross-workspace contribution contract declared by its target intent.
430
+ * This is intentionally distinct from Zanzibar's object#relation algebra. The authoring DSL's
431
+ * `where` shorthand is compiled into explicit bindings here, before the graph reaches custody,
432
+ * so the kernel never interprets a policy expression string. */
433
+ export interface LawWorkspaceRelationNode {
434
+ readonly kind: "structural";
435
+ readonly structuralKind: "workspace-relation";
436
+ readonly entity: LawStructuralEntity<"workspace-relation">;
437
+ readonly owner: LawRef<"intent">;
438
+ readonly relation: string;
439
+ readonly sourceType: string;
440
+ readonly targetType: string;
441
+ readonly evidenceAggregate: string;
442
+ readonly evidenceSelect: readonly string[];
443
+ readonly evidenceKind: "immutable" | "current-state";
444
+ readonly bindings: readonly {
445
+ readonly evidenceField: string;
446
+ readonly directiveParameter: string;
447
+ }[];
448
+ readonly hasInvariant: boolean;
449
+ }
450
+
451
+ export interface LawWorkspaceInvariantNode {
452
+ readonly kind: "invariant";
453
+ readonly entity: LawEntity<"invariant">;
454
+ readonly domain: LawRef<"domain">;
455
+ readonly invariantKind: "workspace";
456
+ readonly on: LawRef<"intent">;
457
+ readonly reads?: readonly {
458
+ readonly binding: string;
459
+ readonly aggregate: LawRef<"aggregate">;
460
+ readonly state: "pre" | "post";
461
+ readonly eachPayloadPath?: readonly string[];
462
+ readonly id: InvariantReadBinding["id"];
463
+ }[];
464
+ readonly evaluator?: "birth-quota-v1" | "founder-signer-write-once-v1" | "point-guard-v1";
465
+ readonly pointGuard?: {
466
+ readonly aggregate: LawRef<"aggregate">;
467
+ readonly binding: string;
468
+ readonly state: "pre" | "post";
469
+ readonly idPayloadPath: readonly string[];
470
+ readonly predicate: PointGuardPredicate;
471
+ readonly rejectCode: string;
472
+ };
473
+ }
474
+
475
+ export interface LawAggregateInvariantNode {
476
+ readonly kind: "invariant";
477
+ readonly entity: LawEntity<"invariant">;
478
+ readonly domain: LawRef<"domain">;
479
+ readonly invariantKind: "aggregate";
480
+ readonly onAggregate: LawRef<"aggregate">;
481
+ }
482
+
483
+ export type LawInvariantNode = LawWorkspaceInvariantNode | LawAggregateInvariantNode;
484
+
485
+ export interface LawCapabilityNode {
486
+ readonly kind: "capability";
487
+ readonly entity: LawEntity<"capability">;
488
+ readonly domain: LawRef<"domain">;
489
+ readonly task: LawRef<"aggregate">;
490
+ readonly obligation: LawRef<"intent">;
491
+ readonly complete: LawRef<"intent">;
492
+ readonly fail: LawRef<"intent">;
493
+ readonly block: LawRef<"intent">;
494
+ readonly deadLetter: LawRef<"intent">;
495
+ readonly capability: string;
496
+ readonly endpoint?: string;
497
+ readonly tasks?: LawRef<"read">;
498
+ }
499
+
500
+ export interface LawWorkspaceTypeNode {
501
+ readonly kind: "workspace-type";
502
+ readonly entity: LawEntity<"workspace-type">;
503
+ readonly domain: LawRef<"domain">;
504
+ readonly root?: LawRef<"aggregate">;
505
+ readonly children: readonly LawRef<"workspace-type">[];
506
+ readonly globals: readonly LawRef<"aggregate">[];
507
+ readonly coordinatorLocal: readonly LawRef<"aggregate">[];
508
+ readonly mode: "dedicated" | "packed";
509
+ }
510
+
511
+ export interface LawIntentAuthorizationObjectFrom {
512
+ readonly recordType: string;
513
+ readonly idField: LawStructuralRef<"value-field">;
514
+ }
515
+
516
+ export interface LawIntentAuthorization {
517
+ readonly relation: LawTargetRef<"relation">;
518
+ readonly object: string;
519
+ readonly objectFrom?: LawIntentAuthorizationObjectFrom;
520
+ }
521
+
522
+ export interface LawIntentNode {
523
+ readonly kind: "intent";
524
+ readonly entity: LawEntity<"intent">;
525
+ readonly domain: LawRef<"domain">;
526
+ readonly marker: ReferentialMarker;
527
+ readonly target: LawTargetRef<"aggregate">;
528
+ readonly public: boolean;
529
+ /** Developer-facing route name. Usually the intent label; a framework-lowered business birth
530
+ * keeps its private directive identity while publishing the stable `birth` route. */
531
+ readonly publicRoute: string;
532
+ readonly accepts: LawRef<"contract">;
533
+ /** Optional public contract when the executable intent accepts a richer, kernel-supplied
534
+ * payload than tenant developers provide (notably workspace birth). */
535
+ readonly publicAccepts?: LawRef<"contract">;
536
+ /** Aggregate-type and indexed-query boundaries the executable plan may read. */
537
+ readonly reads: readonly LawTargetRef<"aggregate" | "read">[];
538
+ readonly foreignReads: readonly LawStructuralRef<"foreign-read">[];
539
+ /** Attested cross-workspace contribution contracts which may propose this intent. */
540
+ readonly workspaceRelations: readonly LawStructuralRef<"workspace-relation">[];
541
+ /** Zanzibar relation required to author this intent. */
542
+ readonly authorization?: LawIntentAuthorization;
543
+ readonly requiresHostCapabilities: readonly string[];
544
+ readonly resolves?: LawRef<"field">;
545
+ readonly births: boolean;
546
+ readonly birthRecord?: LawRef<"aggregate">;
547
+ readonly birthPrefix?: string;
548
+ readonly attestation?: {
549
+ readonly ownerObject: string;
550
+ readonly ownerField: string;
551
+ readonly asserterWorkspace: LawStructuralRef<"value-field">;
552
+ readonly object: LawStructuralRef<"value-field">;
553
+ readonly relation: LawStructuralRef<"value-field">;
554
+ readonly attestedAt: LawStructuralRef<"value-field">;
555
+ readonly publicKey: LawStructuralRef<"value-field">;
556
+ readonly signature: LawStructuralRef<"value-field">;
557
+ };
558
+ readonly delegation?: {
559
+ readonly relation: string;
560
+ readonly subjectField: LawStructuralRef<"value-field">;
561
+ readonly certField: LawStructuralRef<"value-field">;
562
+ readonly parentKeyObject: string;
563
+ readonly parentKeyField: string;
564
+ };
565
+ readonly verifiedFirstEnroll?: {
566
+ readonly tokenField: LawStructuralRef<"value-field">;
567
+ readonly keyHashField: LawStructuralRef<"value-field">;
568
+ readonly verifiedViaField: LawStructuralRef<"value-field">;
569
+ };
570
+ readonly eraCertification?: {
571
+ readonly certField: LawStructuralRef<"value-field">;
572
+ };
573
+ readonly effects: readonly LawStructuralRef<"effect">[];
574
+ readonly steps: readonly LawRef<"intent">[];
575
+ readonly refuses: readonly string[];
576
+ }
577
+
578
+ /** The public directive vocabulary carried into typed USDA and generated-client compatibility. */
579
+ export function publicIntentRoute(intent: DomainModule["directives"][number]): string {
580
+ const businessBirth = (intent as { businessBirth?: unknown }).businessBirth;
581
+ return businessBirth === undefined || isBusinessIntent(intent) ? intent.id : "birth";
582
+ }
583
+
584
+ /**
585
+ * Whether an executable directive belongs to the public offer surface.
586
+ *
587
+ * `applicationIntents` is the authored business vocabulary, not an exhaustive list of
588
+ * compiler-expanded executable commands. Roles, framework ceremonies and other lawful
589
+ * expansions are added to `directives` after the language is closed. Conversely, a raw
590
+ * workspace-birth directive is presentation-private when a business intent wraps it.
591
+ * Keep this rule aligned with the generated-client/interface projection; the compiler
592
+ * also compares that projection with the resulting graph before it emits custody.
593
+ */
594
+ function isPublicIntent(
595
+ module: DomainModule,
596
+ intent: DomainModule["directives"][number],
597
+ ): boolean {
598
+ if ((intent as { businessBirthInternal?: unknown }).businessBirthInternal === true) return false;
599
+ if (module.applicationIntents === undefined) return true;
600
+ if (module.applicationIntents.some((candidate) => candidate.id === intent.id)) return true;
601
+ return (intent as { businessBirth?: unknown }).businessBirth === undefined;
602
+ }
603
+
604
+ interface LawReadBase<K extends string> {
605
+ readonly kind: "read";
606
+ readonly readKind: K;
607
+ readonly entity: LawEntity<"read">;
608
+ readonly domain: LawRef<"domain">;
609
+ readonly of: LawRef<"aggregate">;
610
+ readonly contract: LawRef<"contract">;
611
+ readonly public: boolean;
612
+ readonly predicate?: LawStructuralRef<"predicate">;
613
+ }
614
+
615
+ export interface LawQueryReadNode extends LawReadBase<"query"> {
616
+ readonly key: readonly LawFieldPathRef[];
617
+ }
618
+
619
+ export interface LawCountReadNode extends LawReadBase<"count" | "exists"> {
620
+ readonly groupBy?: LawFieldPathRef;
621
+ readonly filtered: boolean;
622
+ }
623
+
624
+ export interface LawSumReadNode extends LawReadBase<"sum"> {
625
+ readonly value: LawFieldPathRef;
626
+ readonly groupBy?: LawFieldPathRef;
627
+ readonly filtered: boolean;
628
+ }
629
+
630
+ export interface LawExtremumReadNode extends LawReadBase<"min" | "max"> {
631
+ readonly value: LawFieldPathRef;
632
+ readonly groupBy?: LawFieldPathRef;
633
+ readonly filtered: boolean;
634
+ }
635
+
636
+ export interface LawOrderedReadNode extends LawReadBase<"ordered"> {
637
+ readonly orderBy: LawFieldPathRef;
638
+ readonly descending: boolean;
639
+ readonly limit: number;
640
+ readonly filtered: boolean;
641
+ }
642
+
643
+ export interface LawSpatialReadNode extends LawReadBase<"spatial"> {
644
+ readonly geometry: LawFieldPathRef;
645
+ }
646
+
647
+ export interface LawDerivedReadNode extends LawReadBase<"derived"> {}
648
+
649
+ export interface LawCombinedReadNode extends LawReadBase<"combined"> {
650
+ readonly via: LawFieldPathRef;
651
+ readonly reads: LawRef<"aggregate">;
652
+ }
653
+
654
+ export type LawReadNode =
655
+ | LawQueryReadNode
656
+ | LawCountReadNode
657
+ | LawSumReadNode
658
+ | LawExtremumReadNode
659
+ | LawOrderedReadNode
660
+ | LawSpatialReadNode
661
+ | LawDerivedReadNode
662
+ | LawCombinedReadNode;
663
+
664
+ export type DeclaredLawNode =
665
+ | LawDomainNode
666
+ | LawAggregateNode
667
+ | LawFieldNode
668
+ | LawContractNode
669
+ | LawPayloadContractNode
670
+ | LawReadContractNode
671
+ | LawTypeNode
672
+ | LawRelationObjectTypeNode
673
+ | LawRelationNode
674
+ | LawPolicyNode
675
+ | LawInvariantNode
676
+ | LawCapabilityNode
677
+ | LawWorkspaceTypeNode
678
+ | LawIntentNode
679
+ | LawReadNode
680
+ | LawStructuralNode;
681
+
682
+ export interface LawGraph {
683
+ readonly v: 1;
684
+ readonly domain: LawDomainNode;
685
+ /** Canonically SID-ordered, so module/export discovery order cannot affect traversal. */
686
+ readonly nodes: readonly DeclaredLawNode[];
687
+ }
688
+
689
+ export interface DiscoverLawGraphOptions {
690
+ readonly intentCarries?: readonly LawIdentityCarry[];
691
+ readonly readCarries?: readonly LawIdentityCarry[];
692
+ readonly source?: (
693
+ kind:
694
+ | "domain"
695
+ | "aggregate"
696
+ | "field"
697
+ | "intent"
698
+ | "read"
699
+ | "contract"
700
+ | "type"
701
+ | "object-type"
702
+ | "relation"
703
+ | "policy"
704
+ | "invariant"
705
+ | "capability"
706
+ | "workspace-type",
707
+ label: string,
708
+ parentLabel?: string,
709
+ ) => LawSourceSpan | undefined;
710
+ }
711
+
712
+ export function discoverLawGraph(
713
+ module: DomainModule,
714
+ stableIds: StableIds,
715
+ sourceRegistry: LawIdentityRegistry,
716
+ options: DiscoverLawGraphOptions = {},
717
+ ): { registry: LawIdentityRegistry; graph: LawGraph } {
718
+ // Security analysis consumes the same live typed declarations this graph lowers. Do not reconstruct
719
+ // the retired canonical JSON manifest merely to run information-flow checks.
720
+ assertFlowGateModule(module);
721
+ const domainKey = lawEntityKey("domain", stableIds.domain);
722
+ const domainEntity = entity(sourceRegistry, domainKey, "domain", options.source?.("domain", module.name));
723
+ const domainRef = ref(domainEntity);
724
+ const aggregateByLabel = new Map<string, { node: LawAggregateNode; fields: Map<string, LawFieldNode> }>();
725
+ const aggregateNodes: LawAggregateNode[] = [];
726
+ const fieldNodes: LawFieldNode[] = [];
727
+ const anonymousValueNodes = new Map<string, LawAnonymousValueNode>();
728
+ const identities = createLawIdentityReconciler(sourceRegistry);
729
+ let registry = sourceRegistry;
730
+
731
+ for (const aggregate of [...module.aggregates].sort((a, b) => compareText(a.id, b.id))) {
732
+ if (aggregate.audience === undefined) {
733
+ fail(
734
+ `aggregate '${aggregate.id}' has no audience. Nomos will not guess who may read business state: ` +
735
+ `finish the declaration with .owned(), .readableBy("<business relation>"), or the deliberate ` +
736
+ `.public() exception.`,
737
+ );
738
+ }
739
+ const ids = stableIds.aggregates[aggregate.id];
740
+ if (ids === undefined) fail(`aggregate '${aggregate.id}' has no stable identity`);
741
+ const aggregateKey = lawEntityKey("aggregate", ids.sid);
742
+ const aggregateEntity = entity(
743
+ sourceRegistry,
744
+ aggregateKey,
745
+ "aggregate",
746
+ options.source?.("aggregate", aggregate.id, module.name),
747
+ );
748
+ const aggregateRef = ref(aggregateEntity);
749
+ const fields = new Map<string, LawFieldNode>();
750
+ for (const [fieldName, field] of Object.entries(aggregate.fields).sort(([a], [b]) =>
751
+ compareText(a, b),
752
+ )) {
753
+ const fieldIds = ids.fields[fieldName];
754
+ if (fieldIds === undefined) fail(`field '${aggregate.id}.${fieldName}' has no stable identity`);
755
+ const fieldEntity = entity(
756
+ sourceRegistry,
757
+ lawEntityKey("field", fieldIds.sid),
758
+ "field",
759
+ options.source?.("field", fieldName, aggregate.id),
760
+ );
761
+ const value = lowerAnonymousValueGraph(storedFieldValue(field), fieldEntity);
762
+ for (const structural of value.nodes) anonymousValueNodes.set(structural.entity.id, structural);
763
+ const node: LawFieldNode = {
764
+ kind: "field",
765
+ entity: fieldEntity,
766
+ owner: aggregateRef,
767
+ value: value.root,
768
+ storageKind: field.kind,
769
+ ...(field.mapValueKind === undefined ? {} : { mapValueKind: field.mapValueKind }),
770
+ ...(fieldIds.schema === undefined ? {} : { schemaIdentity: fieldIds.schema }),
771
+ evolutions: [...(fieldIds.evolutions ?? [])].sort((left, right) => compareText(left.from, right.from)),
772
+ mergeDrivers: mergeDrivers(field.driver),
773
+ required: !field.isOptional,
774
+ nullable: admitsNull(field),
775
+ encrypted: field.isEncrypted === true,
776
+ blindIndexed: field.isBlindIndexed === true,
777
+ };
778
+ fields.set(fieldName, node);
779
+ fieldNodes.push(node);
780
+ }
781
+ let ownerFieldRef: LawRef<"field"> | undefined;
782
+ if (aggregate.audience?.kind === "owned") {
783
+ const ownerField = fields.get(aggregate.audience.ownerField);
784
+ if (ownerField === undefined) {
785
+ fail(`aggregate '${aggregate.id}' owns through unknown field '${aggregate.audience.ownerField}'`);
786
+ }
787
+ ownerFieldRef = ref(ownerField.entity);
788
+ }
789
+ const node: LawAggregateNode = {
790
+ kind: "aggregate",
791
+ entity: aggregateEntity,
792
+ domain: domainRef,
793
+ fields: sortedRefs(fields.values()),
794
+ public: aggregate.audience?.kind === "public",
795
+ wireType: isNamespacedAggregate(aggregate)
796
+ // Runtime type names are qualified by the authored domain name. `domainKey` is the
797
+ // stable USDA identity path (`d_<sid>`), which must never leak into event `__type`.
798
+ ? qualifyReadProjectionType(module.name, aggregate.id)
799
+ : aggregate.id,
800
+ ...(aggregate.cap === undefined ? {} : { cap: aggregate.cap }),
801
+ implements: [],
802
+ ...(ownerFieldRef === undefined ? {} : { ownerField: ownerFieldRef }),
803
+ };
804
+ aggregateByLabel.set(aggregate.id, { node, fields });
805
+ aggregateNodes.push(node);
806
+ }
807
+
808
+ const contracts = [...(module.contracts ?? [])].sort((a, b) => compareText(a.id, b.id));
809
+ let reconciled = identities.reconcile({
810
+ kind: "contract",
811
+ parent: domainKey,
812
+ labels: contracts.map((contract) => contract.id),
813
+ });
814
+ registry = reconciled.registry;
815
+ const contractNodes: LawContractNode[] = [];
816
+ const contractEntitiesByLabel = new Map<string, LawEntity<"contract">>();
817
+ for (const contract of contracts) {
818
+ const contractEntity = entity(
819
+ registry,
820
+ reconciled.resolved[contract.id]!,
821
+ "contract",
822
+ options.source?.("contract", contract.id, module.name),
823
+ );
824
+ const contractRef = ref(contractEntity);
825
+ contractEntitiesByLabel.set(contract.id, contractEntity);
826
+ const fields = Object.entries(contract.fields).sort(([a], [b]) => compareText(a, b));
827
+ const fieldResult = identities.reconcile({
828
+ kind: "field",
829
+ parent: contractEntity.id,
830
+ labels: fields.map(([fieldName]) => fieldName),
831
+ });
832
+ registry = fieldResult.registry;
833
+ const ownedFields: LawFieldNode[] = fields.map(([fieldName, field]) => {
834
+ const fieldEntity = entity(
835
+ registry,
836
+ fieldResult.resolved[fieldName]!,
837
+ "field",
838
+ options.source?.("field", fieldName, contract.id),
839
+ );
840
+ const value = lowerAnonymousValueGraph(storedFieldValue(field), fieldEntity);
841
+ for (const structural of value.nodes) anonymousValueNodes.set(structural.entity.id, structural);
842
+ return {
843
+ kind: "field",
844
+ entity: fieldEntity,
845
+ owner: contractRef,
846
+ value: value.root,
847
+ storageKind: field.kind,
848
+ ...(field.mapValueKind === undefined ? {} : { mapValueKind: field.mapValueKind }),
849
+ evolutions: [],
850
+ mergeDrivers: mergeDrivers(field.driver),
851
+ required: !field.isOptional,
852
+ nullable: admitsNull(field),
853
+ encrypted: field.isEncrypted === true,
854
+ blindIndexed: field.isBlindIndexed === true,
855
+ };
856
+ });
857
+ fieldNodes.push(...ownedFields);
858
+ contractNodes.push({
859
+ kind: "contract",
860
+ entity: contractEntity,
861
+ domain: domainRef,
862
+ public: true,
863
+ fields: sortedRefs(ownedFields),
864
+ implementedBy: sortLawRefs(module.aggregates
865
+ .filter((aggregate) => aggregate.implementsContracts?.includes(contract.id) === true)
866
+ .map((aggregate) => aggregateRef(aggregateByLabel, aggregate.id, `contract '${contract.id}'`))),
867
+ });
868
+ }
869
+
870
+ for (const aggregate of module.aggregates) {
871
+ const aggregateEntry = aggregateByLabel.get(aggregate.id)!;
872
+ for (const [fieldName, field] of Object.entries(aggregate.fields)) {
873
+ const prior = aggregateEntry.fields.get(fieldName);
874
+ if (prior === undefined) continue;
875
+ const linked = linkStoredField(
876
+ prior,
877
+ field,
878
+ aggregate.id,
879
+ aggregateEntry.fields,
880
+ aggregateByLabel,
881
+ contractEntitiesByLabel,
882
+ );
883
+ aggregateEntry.fields.set(fieldName, linked);
884
+ const index = fieldNodes.findIndex((candidate) => candidate.entity.id === prior.entity.id);
885
+ if (index >= 0) fieldNodes[index] = linked;
886
+ }
887
+ const contractRefs = [...(aggregate.implementsContracts ?? [])]
888
+ .sort(compareText)
889
+ .map((label): LawTargetRef<"contract"> => {
890
+ const local = contractEntitiesByLabel.get(label);
891
+ return local === undefined
892
+ ? { kind: "dependency-export", entityKind: "contract", label }
893
+ : ref(local);
894
+ });
895
+ const updatedAggregate = { ...aggregateEntry.node, implements: contractRefs };
896
+ aggregateEntry.node = updatedAggregate;
897
+ const aggregateIndex = aggregateNodes.findIndex(
898
+ (candidate) => candidate.entity.id === updatedAggregate.entity.id,
899
+ );
900
+ if (aggregateIndex >= 0) aggregateNodes[aggregateIndex] = updatedAggregate;
901
+ }
902
+
903
+ const types = [...(module.valueObjects ?? [])].sort((a, b) => compareText(a.name, b.name));
904
+ reconciled = identities.reconcile({
905
+ kind: "type",
906
+ parent: domainKey,
907
+ labels: types.map((type) => type.name),
908
+ });
909
+ registry = reconciled.registry;
910
+ const typeEntitiesByLabel = new Map(types.map((type) => [
911
+ type.name,
912
+ entity(
913
+ registry,
914
+ reconciled.resolved[type.name]!,
915
+ "type",
916
+ options.source?.("type", type.name, module.name),
917
+ ),
918
+ ] as const));
919
+ const linkValueObjectField = (node: LawFieldNode, field: Field, owner: string): LawFieldNode => {
920
+ const valueObject = field.valueObject;
921
+ if (valueObject === undefined) return node;
922
+ const target = typeEntitiesByLabel.get(valueObject.name);
923
+ if (target === undefined) fail(`field '${owner}.${node.entity.label}' targets unknown type '${valueObject.name}'`);
924
+ for (const [id, structural] of anonymousValueNodes) {
925
+ if (structural.structuralKind === "value-type" && structural.owner.id === node.entity.id) {
926
+ anonymousValueNodes.delete(id);
927
+ }
928
+ }
929
+ return { ...node, value: ref(target) };
930
+ };
931
+ for (const aggregate of module.aggregates) {
932
+ const entry = aggregateByLabel.get(aggregate.id)!;
933
+ for (const [fieldName, field] of Object.entries(aggregate.fields)) {
934
+ const prior = entry.fields.get(fieldName);
935
+ if (prior === undefined) continue;
936
+ const linked = linkValueObjectField(prior, field, aggregate.id);
937
+ if (linked === prior) continue;
938
+ entry.fields.set(fieldName, linked);
939
+ const index = fieldNodes.findIndex((candidate) => candidate.entity.id === prior.entity.id);
940
+ if (index >= 0) fieldNodes[index] = linked;
941
+ }
942
+ }
943
+ for (const contract of contracts) {
944
+ const owner = contractEntitiesByLabel.get(contract.id)!;
945
+ for (const [fieldName, field] of Object.entries(contract.fields)) {
946
+ const index = fieldNodes.findIndex((candidate) =>
947
+ candidate.owner.id === owner.id && candidate.entity.label === fieldName);
948
+ if (index < 0) continue;
949
+ fieldNodes[index] = linkValueObjectField(fieldNodes[index]!, field, contract.id);
950
+ }
951
+ }
952
+ const typeNodes: LawTypeNode[] = [];
953
+ for (const type of types) {
954
+ const typeEntity = typeEntitiesByLabel.get(type.name)!;
955
+ const typeRef = ref(typeEntity);
956
+ const fields = Object.entries(type.fields ?? {}).sort(([a], [b]) => compareText(a, b));
957
+ const fieldResult = identities.reconcile({
958
+ kind: "field",
959
+ parent: typeEntity.id,
960
+ labels: fields.map(([fieldName]) => fieldName),
961
+ });
962
+ registry = fieldResult.registry;
963
+ const ownedFields: LawFieldNode[] = fields.map(([fieldName, field]) => {
964
+ const fieldEntity = entity(
965
+ registry,
966
+ fieldResult.resolved[fieldName]!,
967
+ "field",
968
+ options.source?.("field", fieldName, type.name),
969
+ );
970
+ let value: LawValueTypeRef;
971
+ if (field.type === "object" || field.type === "geometry") {
972
+ const target = typeEntitiesByLabel.get(field.ref);
973
+ if (target === undefined) fail(`type '${type.name}' field '${fieldName}' targets unknown type '${field.ref}'`);
974
+ value = ref(target);
975
+ } else {
976
+ const lowered = lowerAnonymousValueGraph(descriptorValue(field), fieldEntity);
977
+ for (const structural of lowered.nodes) anonymousValueNodes.set(structural.entity.id, structural);
978
+ value = lowered.root;
979
+ }
980
+ return {
981
+ kind: "field",
982
+ entity: fieldEntity,
983
+ owner: typeRef,
984
+ value,
985
+ evolutions: [],
986
+ mergeDrivers: [],
987
+ required: field.optional !== true,
988
+ nullable: false,
989
+ encrypted: false,
990
+ blindIndexed: false,
991
+ };
992
+ });
993
+ fieldNodes.push(...ownedFields);
994
+ const variants: LawStructuralRef<"value-variant">[] = [];
995
+ for (const [variantName, variant] of Object.entries(type.union ?? {}).sort(([a], [b]) => compareText(a, b))) {
996
+ let target: LawStructuralRef<"value-type"> | LawRef<"type">;
997
+ if ("ref" in variant) {
998
+ const referenced = typeEntitiesByLabel.get(variant.ref);
999
+ if (referenced === undefined) fail(`type '${type.name}' variant '${variantName}' targets unknown type '${variant.ref}'`);
1000
+ target = ref(referenced);
1001
+ } else {
1002
+ const value: LawValueType = {
1003
+ kind: "record",
1004
+ fields: Object.entries(variant.fields).sort(([a], [b]) => compareText(a, b)).map(([name, field]) => ({
1005
+ name,
1006
+ required: field.optional !== true,
1007
+ value: descriptorValue(field),
1008
+ })),
1009
+ };
1010
+ const lowered = lowerAnonymousValueGraph(value, typeEntity);
1011
+ for (const structural of lowered.nodes) anonymousValueNodes.set(structural.entity.id, structural);
1012
+ target = lowered.root;
1013
+ }
1014
+ const variantEntity = structuralEntity(
1015
+ typeEntity,
1016
+ "value-variant",
1017
+ { label: variantName, value: target.id },
1018
+ `${type.name}#${variantName}`,
1019
+ );
1020
+ anonymousValueNodes.set(variantEntity.id, {
1021
+ kind: "structural",
1022
+ structuralKind: "value-variant",
1023
+ entity: variantEntity,
1024
+ owner: typeRef,
1025
+ label: variantName,
1026
+ value: target,
1027
+ });
1028
+ variants.push(structuralRef(variantEntity));
1029
+ }
1030
+ typeNodes.push({
1031
+ kind: "type",
1032
+ entity: typeEntity,
1033
+ domain: domainRef,
1034
+ form: type.union !== undefined ? "sum" : type.geometry !== undefined ? "geometry" : "record",
1035
+ ...(type.geometry === undefined ? {} : { geometry: type.geometry }),
1036
+ fields: sortedRefs(ownedFields),
1037
+ variants: variants.sort((a, b) => compareText(a.id, b.id)),
1038
+ });
1039
+ }
1040
+
1041
+ // Public routes come from the package-closed module surface. The graph also carries private directives
1042
+ // reached only through a business intent's step closure; those must remain executable without becoming
1043
+ // independently callable client routes.
1044
+ const publicIntentIds = new Set(
1045
+ module.directives.filter((intent) => isPublicIntent(module, intent)).map((intent) => intent.id),
1046
+ );
1047
+ const publishedIntents = new Set(module.applicationIntents ?? []);
1048
+ const intentClosure = [...module.directives, ...(module.applicationIntents ?? [])];
1049
+ for (let index = 0; index < intentClosure.length; index += 1) {
1050
+ const intent = intentClosure[index]!;
1051
+ if (!isBusinessIntent(intent)) continue;
1052
+ for (const step of intent.steps) intentClosure.push(step.directive);
1053
+ }
1054
+ const intentsByLabel = new Map<string, typeof intentClosure[number]>();
1055
+ for (const intent of intentClosure) {
1056
+ const prior = intentsByLabel.get(intent.id);
1057
+ if (prior !== undefined && prior !== intent) {
1058
+ const priorPublished = publishedIntents.has(prior);
1059
+ const currentPublished = publishedIntents.has(intent);
1060
+ // Package composition may retain the executable lowering in `directives` and publish a
1061
+ // distinct business-intent object with the same route id in `applicationIntents`. They are
1062
+ // one authored intent, not two law identities; the published declaration carries the full
1063
+ // public contract and its private consequence graph, so it is the canonical graph node.
1064
+ if (priorPublished !== currentPublished) {
1065
+ if (currentPublished) intentsByLabel.set(intent.id, intent);
1066
+ continue;
1067
+ }
1068
+ fail(`intent label '${intent.id}' resolves to more than one declaration`);
1069
+ }
1070
+ intentsByLabel.set(intent.id, intent);
1071
+ }
1072
+ const intents = [...intentsByLabel.values()].sort((a, b) => compareText(a.id, b.id));
1073
+ reconciled = identities.reconcile({
1074
+ kind: "intent",
1075
+ parent: domainKey,
1076
+ labels: intents.map((intent) => intent.id),
1077
+ ...(options.intentCarries === undefined ? {} : { carries: options.intentCarries }),
1078
+ });
1079
+ registry = reconciled.registry;
1080
+ const intentEntities = new Map(intents.map((intent) => [
1081
+ intent.id,
1082
+ entity(
1083
+ registry,
1084
+ reconciled.resolved[intent.id]!,
1085
+ "intent",
1086
+ options.source?.("intent", intent.id, module.name),
1087
+ ),
1088
+ ] as const));
1089
+ const payloadContractNodes: LawPayloadContractNode[] = [];
1090
+ const payloadFieldsByIntent = new Map<string, Map<string, LawStructuralRef<"value-field">>>();
1091
+ const effectNodes: LawIntentEffectNode[] = [];
1092
+ const workspaceRelationNodes: LawWorkspaceRelationNode[] = [];
1093
+ const intentAggregateTarget = (
1094
+ intent: typeof intents[number],
1095
+ aggregateId: string,
1096
+ ): LawTargetRef<"aggregate"> => {
1097
+ const local = aggregateByLabel.get(aggregateId);
1098
+ if (local !== undefined) return ref(local.node.entity);
1099
+ const birth = (intent as { businessBirth?: { aggregateId?: unknown; domain?: unknown } }).businessBirth;
1100
+ if (birth?.aggregateId === aggregateId && typeof birth.domain === "string") {
1101
+ return { kind: "dependency-export", entityKind: "aggregate", label: aggregateId, domain: birth.domain };
1102
+ }
1103
+ fail(`intent '${intent.id}' in domain '${module.domain ?? module.name}' targets unknown aggregate '${aggregateId}'`);
1104
+ };
1105
+ const intentNodes: LawIntentNode[] = intents.map((intent) => {
1106
+ const intentEntity = intentEntities.get(intent.id)!;
1107
+ const businessBirth = (intent as {
1108
+ businessBirth?: { publicPayloadSchema?: z.ZodTypeAny };
1109
+ }).businessBirth;
1110
+ const payloadContractEntity = ownedEntity(
1111
+ "contract",
1112
+ intentEntity,
1113
+ "contract",
1114
+ "accepts",
1115
+ `${intent.id}#accepts`,
1116
+ options.source?.("intent", intent.id, module.name),
1117
+ );
1118
+ const payloadShape = lowerAnonymousContractFields(
1119
+ lowerLawValueType(isBusinessIntent(intent) ? intent.contract.accepts : intent.payloadSchema),
1120
+ payloadContractEntity,
1121
+ );
1122
+ for (const node of payloadShape.nodes) anonymousValueNodes.set(node.entity.id, node);
1123
+ for (const fieldRef of payloadShape.fields) {
1124
+ const field = anonymousValueNodes.get(fieldRef.id);
1125
+ if (field?.structuralKind !== "value-field") continue;
1126
+ const reserved = [
1127
+ OWNED_KEY_PAYLOAD_FIELD,
1128
+ OWNED_CUSTODY_PAYLOAD_FIELD,
1129
+ OWNED_PREMINTED_RECORDS_PAYLOAD_FIELD,
1130
+ BUSINESS_BIRTH_SEED_PAYLOAD_FIELD,
1131
+ ];
1132
+ if (!reserved.includes(field.name)) continue;
1133
+ const frameworkBirthCarrier = field.name === BUSINESS_BIRTH_SEED_PAYLOAD_FIELD && businessBirth !== undefined;
1134
+ if (frameworkBirthCarrier) continue;
1135
+ fail(
1136
+ `directive '${intent.id}': payload field '${field.name}' is reserved by Nomos ownership. ` +
1137
+ `Sorry mate, that concept is already taken by the framework; give the business payload field ` +
1138
+ `a different name.`,
1139
+ );
1140
+ }
1141
+ payloadContractNodes.push({
1142
+ kind: "payload-contract",
1143
+ entity: payloadContractEntity,
1144
+ owner: ref(intentEntity),
1145
+ fields: payloadShape.fields,
1146
+ });
1147
+ let publicPayloadContractEntity: LawEntity<"contract"> | undefined;
1148
+ if (businessBirth?.publicPayloadSchema !== undefined) {
1149
+ publicPayloadContractEntity = ownedEntity(
1150
+ "contract",
1151
+ intentEntity,
1152
+ "contract",
1153
+ "public-accepts",
1154
+ `${intent.id}#publicAccepts`,
1155
+ options.source?.("intent", intent.id, module.name),
1156
+ );
1157
+ const publicPayloadShape = lowerAnonymousContractFields(
1158
+ lowerLawValueType(businessBirth.publicPayloadSchema),
1159
+ publicPayloadContractEntity,
1160
+ );
1161
+ for (const node of publicPayloadShape.nodes) anonymousValueNodes.set(node.entity.id, node);
1162
+ payloadContractNodes.push({
1163
+ kind: "payload-contract",
1164
+ entity: publicPayloadContractEntity,
1165
+ owner: ref(intentEntity),
1166
+ fields: publicPayloadShape.fields,
1167
+ });
1168
+ }
1169
+ const payloadFields = new Map<string, LawStructuralRef<"value-field">>();
1170
+ const indexPayloadValue = (
1171
+ valueRef: LawValueTypeRef,
1172
+ prefix: string,
1173
+ seen: ReadonlySet<string>,
1174
+ ): void => {
1175
+ if (seen.has(valueRef.id)) return;
1176
+ const value = anonymousValueNodes.get(valueRef.id);
1177
+ if (value?.structuralKind !== "value-type") return;
1178
+ const nextSeen = new Set(seen).add(valueRef.id);
1179
+ switch (value.expression.kind) {
1180
+ case "record":
1181
+ for (const child of value.expression.fields) indexPayloadField(child, prefix, nextSeen);
1182
+ break;
1183
+ case "nullable":
1184
+ case "default":
1185
+ case "recursive":
1186
+ indexPayloadValue(value.expression.value, prefix, nextSeen);
1187
+ break;
1188
+ case "sum":
1189
+ for (const variantRef of value.expression.variants) {
1190
+ const variant = anonymousValueNodes.get(variantRef.id);
1191
+ if (variant?.structuralKind === "value-variant") {
1192
+ indexPayloadValue(variant.value, prefix, nextSeen);
1193
+ }
1194
+ }
1195
+ break;
1196
+ default:
1197
+ break;
1198
+ }
1199
+ };
1200
+ const indexPayloadField = (
1201
+ fieldRef: LawStructuralRef<"value-field">,
1202
+ prefix: string,
1203
+ seen: ReadonlySet<string>,
1204
+ ): void => {
1205
+ const field = anonymousValueNodes.get(fieldRef.id);
1206
+ if (field?.structuralKind !== "value-field") return;
1207
+ const path = prefix === "" ? field.name : `${prefix}.${field.name}`;
1208
+ payloadFields.set(path, fieldRef);
1209
+ indexPayloadValue(field.value, path, seen);
1210
+ };
1211
+ for (const fieldRef of payloadShape.fields) {
1212
+ indexPayloadField(fieldRef, "", new Set());
1213
+ }
1214
+ payloadFieldsByIntent.set(intent.id, payloadFields);
1215
+ const outputs: Array<Pick<LawIntentEffectNode, "marker" | "aggregate" | "cardinality">> = [
1216
+ {
1217
+ marker: intent.marker,
1218
+ aggregate: intentAggregateTarget(intent, intent.aggregateId),
1219
+ cardinality: intent.outputCardinality ?? "one",
1220
+ },
1221
+ ...(intent.secondaryOutputs ?? []).map((output) => ({
1222
+ marker: output.marker,
1223
+ aggregate: aggregateRef(aggregateByLabel, output.aggregateId, `intent '${intent.id}'`),
1224
+ cardinality: output.many === true ? "many" as const : output.optional === true ? "optional" as const : "one" as const,
1225
+ })),
1226
+ ];
1227
+ const effectsById = new Map<string, LawStructuralRef<"effect">>();
1228
+ for (const output of outputs) {
1229
+ const aggregateIdentity = "kind" in output.aggregate
1230
+ ? `${output.aggregate.domain ?? "?"}:${output.aggregate.entityKind}:${output.aggregate.label}`
1231
+ : output.aggregate.id;
1232
+ const effectEntity = structuralEntity(
1233
+ intentEntity,
1234
+ "effect",
1235
+ output,
1236
+ `${intent.id}#${output.marker}:${aggregateIdentity}:${output.cardinality}`,
1237
+ );
1238
+ if (!effectsById.has(effectEntity.id)) {
1239
+ effectNodes.push({
1240
+ kind: "structural",
1241
+ structuralKind: "effect",
1242
+ entity: effectEntity,
1243
+ owner: ref(intentEntity),
1244
+ ...output,
1245
+ });
1246
+ effectsById.set(effectEntity.id, structuralRef(effectEntity));
1247
+ }
1248
+ }
1249
+ const effects = [...effectsById.values()].sort((a, b) => compareText(a.id, b.id));
1250
+ const payloadField = (name: string, purpose: string): LawStructuralRef<"value-field"> => {
1251
+ const field = payloadFields.get(name);
1252
+ if (field === undefined) fail(`intent '${intent.id}' ${purpose} names missing payload field '${name}'`);
1253
+ return field;
1254
+ };
1255
+ const relationDeclarations = [...(intent.declaredRelations ?? [])];
1256
+ if (relationDeclarations.length > 1) {
1257
+ fail(
1258
+ `intent '${intent.id}' declares ${relationDeclarations.length} cross-workspace relations, but ` +
1259
+ "one proposed intent has exactly one attested contribution contract",
1260
+ );
1261
+ }
1262
+ const workspaceRelations = relationDeclarations.map((relation) => {
1263
+ if (relation.proposes !== intent.id) {
1264
+ fail(
1265
+ `intent '${intent.id}' declares relation '${relation.id}' for proposed intent ` +
1266
+ `'${relation.proposes}'; declare it on the target intent itself`,
1267
+ );
1268
+ }
1269
+ if (relation.target !== intent.aggregateId) {
1270
+ fail(
1271
+ `intent '${intent.id}' relation '${relation.id}' targets '${relation.target}', but the ` +
1272
+ `intent targets '${intent.aggregateId}'`,
1273
+ );
1274
+ }
1275
+ if (relation.evidence.aggregate !== relation.source) {
1276
+ fail(
1277
+ `intent '${intent.id}' relation '${relation.id}' reads '${relation.evidence.aggregate}', ` +
1278
+ `but its source endpoint is '${relation.source}'`,
1279
+ );
1280
+ }
1281
+ const evidenceSelect = [...new Set(relation.evidence.select)].sort(compareText);
1282
+ const bindings = relation.evidence.where.trim() === ""
1283
+ ? []
1284
+ : relation.evidence.where.split("&&").map((authoredClause) => {
1285
+ const clause = authoredClause.trim();
1286
+ const match = /^([A-Za-z_][A-Za-z0-9_.]*)\s*==\s*\$([A-Za-z_][A-Za-z0-9_]*)$/.exec(clause);
1287
+ if (match === null) {
1288
+ fail(
1289
+ `intent '${intent.id}' relation '${relation.id}' has an unenforceable evidence scope ` +
1290
+ `${JSON.stringify(clause)}; use '<evidenceField> == $<directiveParameter>' clauses joined by &&`,
1291
+ );
1292
+ }
1293
+ const evidenceField = match[1]!;
1294
+ const directiveParameter = match[2]!;
1295
+ if (!evidenceSelect.includes(evidenceField)) {
1296
+ fail(
1297
+ `intent '${intent.id}' relation '${relation.id}' binds evidence field '${evidenceField}', ` +
1298
+ "but that field is absent from its disclosed select set",
1299
+ );
1300
+ }
1301
+ payloadField(directiveParameter, `relation '${relation.id}'`);
1302
+ return { evidenceField, directiveParameter };
1303
+ }).sort((left, right) =>
1304
+ compareText(left.evidenceField, right.evidenceField) ||
1305
+ compareText(left.directiveParameter, right.directiveParameter));
1306
+ const canonical = {
1307
+ relation: relation.id,
1308
+ sourceType: relation.source,
1309
+ targetType: relation.target,
1310
+ evidenceAggregate: relation.evidence.aggregate,
1311
+ evidenceSelect,
1312
+ evidenceKind: relation.evidence.kind,
1313
+ bindings,
1314
+ hasInvariant: relation.hasInvariant,
1315
+ } as const;
1316
+ const relationEntity = structuralEntity(
1317
+ intentEntity,
1318
+ "workspace-relation",
1319
+ canonical,
1320
+ `${intent.id}#${relation.id}`,
1321
+ );
1322
+ workspaceRelationNodes.push({
1323
+ kind: "structural",
1324
+ structuralKind: "workspace-relation",
1325
+ entity: relationEntity,
1326
+ owner: ref(intentEntity),
1327
+ ...canonical,
1328
+ });
1329
+ return structuralRef(relationEntity);
1330
+ });
1331
+ const targetEntry = aggregateByLabel.get(intent.aggregateId);
1332
+ const resolvedField = intent.resolvesField === undefined
1333
+ ? undefined
1334
+ : targetEntry?.fields.get(intent.resolvesField);
1335
+ if (intent.resolvesField !== undefined && resolvedField === undefined) {
1336
+ fail(`intent '${intent.id}' resolves unknown field '${intent.resolvesField}' on '${intent.aggregateId}'`);
1337
+ }
1338
+ return {
1339
+ kind: "intent",
1340
+ entity: intentEntity,
1341
+ domain: domainRef,
1342
+ marker: intent.marker,
1343
+ target: intentAggregateTarget(intent, intent.aggregateId),
1344
+ public: publicIntentIds.has(intent.id),
1345
+ // An authored business intent may compose a child birth while retaining its own public
1346
+ // vocabulary (`birthEstateWorkspace`, `birthCatalogueWorkspace`, ...). Only the private
1347
+ // compiler-lowered business-birth directive publishes the generic framework `birth` route.
1348
+ // Keep this identical to domainInterface/codegen so the typed USDA is sufficient to
1349
+ // reconstruct the generated-client compatibility surface.
1350
+ publicRoute: publicIntentRoute(intent),
1351
+ accepts: ref(payloadContractEntity),
1352
+ ...(publicPayloadContractEntity === undefined ? {} : {
1353
+ publicAccepts: ref(publicPayloadContractEntity),
1354
+ }),
1355
+ reads: [],
1356
+ foreignReads: [],
1357
+ workspaceRelations,
1358
+ requiresHostCapabilities: [...new Set(intent.declaredHostCapabilities ?? [])].sort(compareText),
1359
+ ...(resolvedField === undefined ? {} : { resolves: ref(resolvedField.entity) }),
1360
+ births: intent.birthsChild === true,
1361
+ ...(intent.birthsChild !== true || aggregateByLabel.get("NomosBirthRecord") === undefined ? {} : {
1362
+ birthRecord: ref(aggregateByLabel.get("NomosBirthRecord")!.node.entity),
1363
+ birthPrefix: "nomos-birth-record:",
1364
+ }),
1365
+ ...(intent.attestationAuthorizedGate === undefined ? {} : {
1366
+ attestation: {
1367
+ ownerObject: intent.attestationAuthorizedGate.ownerObject,
1368
+ ownerField: intent.attestationAuthorizedGate.ownerField,
1369
+ asserterWorkspace: payloadField(intent.attestationAuthorizedGate.fields.asserterWorkspace, "attestation"),
1370
+ object: payloadField(intent.attestationAuthorizedGate.fields.object, "attestation"),
1371
+ relation: payloadField(intent.attestationAuthorizedGate.fields.relation, "attestation"),
1372
+ attestedAt: payloadField(intent.attestationAuthorizedGate.fields.attestedAt, "attestation"),
1373
+ publicKey: payloadField(intent.attestationAuthorizedGate.fields.publicKey, "attestation"),
1374
+ signature: payloadField(intent.attestationAuthorizedGate.fields.signature, "attestation"),
1375
+ },
1376
+ }),
1377
+ ...(intent.delegatedRoleGate === undefined ? {} : {
1378
+ delegation: {
1379
+ relation: intent.delegatedRoleGate.relation,
1380
+ subjectField: payloadField(intent.delegatedRoleGate.subjectField, "delegation"),
1381
+ certField: payloadField(intent.delegatedRoleGate.certField, "delegation"),
1382
+ parentKeyObject: intent.delegatedRoleGate.parentKeyObject,
1383
+ parentKeyField: intent.delegatedRoleGate.parentKeyField,
1384
+ },
1385
+ }),
1386
+ ...(intent.firstDeviceEnrollGate === undefined ? {} : {
1387
+ verifiedFirstEnroll: {
1388
+ tokenField: payloadField(intent.firstDeviceEnrollGate.tokenField, "verified-first enrollment"),
1389
+ keyHashField: payloadField(intent.firstDeviceEnrollGate.keyHashField, "verified-first enrollment"),
1390
+ verifiedViaField: payloadField(intent.firstDeviceEnrollGate.verifiedViaField, "verified-first enrollment"),
1391
+ },
1392
+ }),
1393
+ ...(intent.certifiesEraGate === undefined ? {} : {
1394
+ eraCertification: {
1395
+ certField: payloadField(intent.certifiesEraGate.certField, "era certification"),
1396
+ },
1397
+ }),
1398
+ effects,
1399
+ steps: isBusinessIntent(intent)
1400
+ ? sortLawRefs(intent.steps.map((step) => {
1401
+ const target = intentEntities.get(step.directive.id);
1402
+ if (target === undefined) fail(`intent '${intent.id}' targets unknown effect '${step.directive.id}'`);
1403
+ return ref(target);
1404
+ }))
1405
+ : [],
1406
+ refuses: isBusinessIntent(intent) ? [...intent.contract.refuses].sort(compareText) : [],
1407
+ };
1408
+ });
1409
+ const intentByLabel = new Map(intentNodes.map((node) => [node.entity.label, node] as const));
1410
+
1411
+ const namespacedRelationTypes = new Set(
1412
+ (module.relationTypes ?? [])
1413
+ .filter((type) => type.global !== true)
1414
+ .map((type) => type.type),
1415
+ );
1416
+ const authoredRelationDecls = (module.relationTypes ?? []).flatMap((type) =>
1417
+ [...type.relations.entries()].map(([relation, definition]) => ({
1418
+ label: `${type.type}#${relation}`,
1419
+ authoredObjectType: type.type,
1420
+ objectType: type.global === true ? type.type : qualifyRelationType(domainKey, type.type),
1421
+ global: type.global === true,
1422
+ caps: type.caps ?? DEFAULT_RELATION_CAPS,
1423
+ relation,
1424
+ definition,
1425
+ })),
1426
+ );
1427
+ const authoredRelationLabels = new Set(authoredRelationDecls.map((relation) => relation.label));
1428
+ const synthesizedReadRelations = new Map<string, (typeof authoredRelationDecls)[number]>();
1429
+ for (const aggregate of module.aggregates) {
1430
+ if (aggregate.audience === undefined || aggregate.audience.kind === "public") continue;
1431
+ const relation = aggregate.audience.kind === "owned"
1432
+ ? OWNED_READ_RELATION
1433
+ : aggregate.audience.cap;
1434
+ const label = `workspace#${relation}`;
1435
+ if (authoredRelationLabels.has(label) || synthesizedReadRelations.has(label)) continue;
1436
+ synthesizedReadRelations.set(label, {
1437
+ label,
1438
+ authoredObjectType: "workspace",
1439
+ objectType: "workspace",
1440
+ global: true,
1441
+ caps: DEFAULT_RELATION_CAPS,
1442
+ relation,
1443
+ definition: {
1444
+ rewrite: null,
1445
+ allowed: [
1446
+ { kind: "direct", type: "key" },
1447
+ { kind: "direct", type: "user" },
1448
+ { kind: "wildcard" },
1449
+ ],
1450
+ },
1451
+ });
1452
+ }
1453
+ const relationDecls = [...authoredRelationDecls, ...synthesizedReadRelations.values()]
1454
+ .sort((a, b) => compareText(a.label, b.label));
1455
+ const relationObjectLabels = [...new Set(relationDecls
1456
+ .filter((relation) =>
1457
+ relationObjectAggregate(relation.authoredObjectType, aggregateByLabel, module) === undefined)
1458
+ .map((relation) => relation.authoredObjectType))]
1459
+ .sort(compareText);
1460
+ const objectTypeIdentity = identities.reconcile({
1461
+ kind: "object-type",
1462
+ parent: domainKey,
1463
+ labels: relationObjectLabels,
1464
+ });
1465
+ registry = objectTypeIdentity.registry;
1466
+ const relationObjectTypeNodes: LawRelationObjectTypeNode[] = relationObjectLabels.map((label) => ({
1467
+ kind: "object-type",
1468
+ entity: entity(
1469
+ registry,
1470
+ objectTypeIdentity.resolved[label]!,
1471
+ "object-type",
1472
+ options.source?.("object-type", label, module.name),
1473
+ ),
1474
+ domain: domainRef,
1475
+ namespace: namespacedRelationTypes.has(label) ? qualifyRelationType(domainKey, label) : label,
1476
+ }));
1477
+ const relationObjectTypes = new Map(
1478
+ relationObjectTypeNodes.map((node) => [node.entity.label, node] as const),
1479
+ );
1480
+ reconciled = identities.reconcile({
1481
+ kind: "relation",
1482
+ parent: domainKey,
1483
+ labels: relationDecls.map((relation) => relation.label),
1484
+ });
1485
+ registry = reconciled.registry;
1486
+ const relationEntities = new Map(relationDecls.map((relation) => [
1487
+ relation.label,
1488
+ entity(
1489
+ registry,
1490
+ reconciled.resolved[relation.label]!,
1491
+ "relation",
1492
+ options.source?.("relation", relation.label, module.name),
1493
+ ),
1494
+ ] as const));
1495
+ const relationEntitiesByAddress = new Map(relationEntities);
1496
+ for (const relation of relationDecls) {
1497
+ relationEntitiesByAddress.set(
1498
+ `${relation.objectType}#${relation.relation}`,
1499
+ relationEntities.get(relation.label)!,
1500
+ );
1501
+ }
1502
+ const relationAllowed = new Map(relationDecls.map((relation) => [
1503
+ relation.label,
1504
+ linkRelationAllowed(
1505
+ relation.definition.allowed,
1506
+ relationEntitiesByAddress,
1507
+ relation.label,
1508
+ namespacedRelationTypes,
1509
+ domainKey,
1510
+ ),
1511
+ ] as const));
1512
+ const relationPredicateNodes = new Map<LawStructuralSid<"relation-predicate">, LawRelationPredicateNode>();
1513
+ const relationNodes: LawRelationNode[] = relationDecls.map((relation) => {
1514
+ const relationEntity = relationEntities.get(relation.label)!;
1515
+ const object = relationObjectAggregate(relation.authoredObjectType, aggregateByLabel, module)
1516
+ ?? ref(relationObjectTypes.get(relation.authoredObjectType)!.entity);
1517
+ const allowed = relationAllowed.get(relation.label)!;
1518
+ const rewrite = lowerRelationPredicate(
1519
+ relation.definition.rewrite ?? { kind: "this" },
1520
+ relationEntity,
1521
+ relation.authoredObjectType,
1522
+ relationEntitiesByAddress,
1523
+ relationAllowed,
1524
+ relationPredicateNodes,
1525
+ );
1526
+ return {
1527
+ kind: "relation",
1528
+ entity: relationEntity,
1529
+ domain: domainRef,
1530
+ objectType: relation.objectType,
1531
+ object,
1532
+ relation: relation.relation,
1533
+ maxDepth: relation.caps.maxDepth,
1534
+ maxFanout: relation.caps.maxFanout,
1535
+ allowed,
1536
+ rewrite,
1537
+ };
1538
+ });
1539
+ for (const [index, intentNode] of intentNodes.entries()) {
1540
+ const declaration = intents.find((intent) => intent.id === intentNode.entity.label)!;
1541
+ const gate = declaration.requiresRelationGate;
1542
+ if (gate === undefined) continue;
1543
+ const objectType = gate.objectFrom?.recordType ?? gate.object.split(":", 1)[0]!;
1544
+ const exactLabel = gate.relation.includes("#") ? gate.relation : `${objectType}#${gate.relation}`;
1545
+ const candidates = relationNodes.filter((relation) =>
1546
+ relation.entity.label === exactLabel ||
1547
+ (relation.relation === gate.relation &&
1548
+ relation.entity.label.split("#", 1)[0]!.toLocaleLowerCase("en-US") ===
1549
+ objectType.toLocaleLowerCase("en-US"))
1550
+ );
1551
+ if (candidates.length > 1) {
1552
+ fail(
1553
+ `intent '${declaration.id}' authorization relation '${gate.relation}' on '${objectType}' ` +
1554
+ `resolved to ${candidates.length} declarations`,
1555
+ );
1556
+ }
1557
+ let objectFrom: LawIntentAuthorizationObjectFrom | undefined;
1558
+ if (gate.objectFrom !== undefined) {
1559
+ const idField = payloadFieldsByIntent.get(declaration.id)?.get(gate.objectFrom.idField);
1560
+ if (idField === undefined) {
1561
+ fail(
1562
+ `intent '${declaration.id}' authorization object id field '${gate.objectFrom.idField}' ` +
1563
+ `is not present in its payload contract`,
1564
+ );
1565
+ }
1566
+ objectFrom = {
1567
+ recordType: candidates[0]?.objectType ?? gate.objectFrom.recordType,
1568
+ idField,
1569
+ };
1570
+ }
1571
+ const authorizationObject = (() => {
1572
+ const canonicalType = candidates[0]?.objectType;
1573
+ const colon = gate.object.indexOf(":");
1574
+ return canonicalType === undefined || colon < 0
1575
+ ? gate.object
1576
+ : `${canonicalType}${gate.object.slice(colon)}`;
1577
+ })();
1578
+ intentNodes[index] = {
1579
+ ...intentNode,
1580
+ authorization: {
1581
+ relation: candidates[0] === undefined
1582
+ ? { kind: "dependency-export", entityKind: "relation", label: exactLabel }
1583
+ : ref(candidates[0].entity),
1584
+ object: authorizationObject,
1585
+ ...(objectFrom === undefined ? {} : { objectFrom }),
1586
+ },
1587
+ };
1588
+ }
1589
+ for (const aggregate of module.aggregates) {
1590
+ if (aggregate.audience === undefined || aggregate.audience.kind === "public") continue;
1591
+ const relationName = aggregate.audience.kind === "owned"
1592
+ ? OWNED_READ_RELATION
1593
+ : aggregate.audience.cap;
1594
+ const candidates = relationNodes.filter((candidate) =>
1595
+ candidate.entity.label === `workspace#${relationName}`);
1596
+ if (candidates.length > 1) {
1597
+ fail(`aggregate '${aggregate.id}' read relation '${relationName}' is ambiguous across relation types`);
1598
+ }
1599
+ const readPolicy: LawTargetRef<"relation" | "policy"> = candidates[0] === undefined
1600
+ ? { kind: "dependency-export", entityKind: "relation", label: `workspace#${relationName}` }
1601
+ : ref(candidates[0].entity);
1602
+ const entry = aggregateByLabel.get(aggregate.id)!;
1603
+ const updated = { ...entry.node, readPolicy };
1604
+ entry.node = updated;
1605
+ const index = aggregateNodes.findIndex((candidate) => candidate.entity.id === updated.entity.id);
1606
+ if (index >= 0) aggregateNodes[index] = updated;
1607
+ }
1608
+
1609
+ const pinnedSources = [...(module.trustSources ?? [])].sort((a, b) => compareText(a.workspace, b.workspace));
1610
+ const sourceSets = [...(module.trustPolicies ?? [])].sort((a, b) => compareText(a.policyId, b.policyId));
1611
+ const policyLabels = [
1612
+ ...pinnedSources.map((source) => `source:${source.workspace}`),
1613
+ ...sourceSets.map((policy) => policy.policyId),
1614
+ ];
1615
+ reconciled = identities.reconcile({
1616
+ kind: "policy",
1617
+ parent: domainKey,
1618
+ labels: policyLabels,
1619
+ });
1620
+ registry = reconciled.registry;
1621
+ const policyNodes: LawPolicyNode[] = [
1622
+ ...pinnedSources.map((source): LawPinnedTrustSourceNode => {
1623
+ const label = `source:${source.workspace}`;
1624
+ return {
1625
+ kind: "policy",
1626
+ entity: entity(registry, reconciled.resolved[label]!, "policy", options.source?.("policy", label, module.name)),
1627
+ domain: domainRef,
1628
+ policyKind: "pinned-source",
1629
+ workspace: source.workspace,
1630
+ genesis: source.genesis,
1631
+ keyHashes: [...new Set(source.keyHashes ?? [])].sort(compareText),
1632
+ roster: source.roster ?? "pinned",
1633
+ ...(source.maxAgeMs === undefined ? {} : { maxAgeMs: source.maxAgeMs }),
1634
+ ...(source.minSourceEpoch === undefined ? {} : { minSourceEpoch: source.minSourceEpoch }),
1635
+ monotone: source.monotone === true,
1636
+ onDishonor: source.onDishonor ?? "refuse",
1637
+ };
1638
+ }),
1639
+ ...sourceSets.map((policy): LawTrustSourceSetNode => {
1640
+ const allowList = aggregateByLabel.get(policy.allowListType);
1641
+ if (allowList === undefined) fail(`policy '${policy.policyId}' targets unknown aggregate '${policy.allowListType}'`);
1642
+ const genesisField = allowList.fields.get(policy.genesisField);
1643
+ const keysField = allowList.fields.get(policy.keysField);
1644
+ if (genesisField === undefined || keysField === undefined) {
1645
+ fail(`policy '${policy.policyId}' targets missing allow-list fields '${policy.genesisField}'/'${policy.keysField}'`);
1646
+ }
1647
+ return {
1648
+ kind: "policy",
1649
+ entity: entity(registry, reconciled.resolved[policy.policyId]!, "policy", options.source?.("policy", policy.policyId, module.name)),
1650
+ domain: domainRef,
1651
+ policyKind: "source-set",
1652
+ allowList: ref(allowList.node.entity),
1653
+ genesisField: ref(genesisField.entity),
1654
+ keysField: ref(keysField.entity),
1655
+ ...(policy.maxAgeMs === undefined ? {} : { maxAgeMs: policy.maxAgeMs }),
1656
+ ...(policy.minSourceEpoch === undefined ? {} : { minSourceEpoch: policy.minSourceEpoch }),
1657
+ monotone: policy.monotone === true,
1658
+ onDishonor: policy.onDishonor ?? "refuse",
1659
+ };
1660
+ }),
1661
+ ];
1662
+ const pinnedPolicyByWorkspace = new Map(
1663
+ policyNodes.filter((node): node is LawPinnedTrustSourceNode => node.policyKind === "pinned-source")
1664
+ .map((node) => [node.workspace, node] as const),
1665
+ );
1666
+ const sourceSetByLabel = new Map(
1667
+ policyNodes.filter((node): node is LawTrustSourceSetNode => node.policyKind === "source-set")
1668
+ .map((node) => [node.entity.label, node] as const),
1669
+ );
1670
+ const foreignReadNodes: LawForeignReadNode[] = [];
1671
+ for (const [index, intentNode] of intentNodes.entries()) {
1672
+ const declaration = intents.find((intent) => intent.id === intentNode.entity.label)!;
1673
+ const refs: LawStructuralRef<"foreign-read">[] = [];
1674
+ for (const edge of declaration.declaredForeignReads ?? []) {
1675
+ const source = "from" in edge
1676
+ ? pinnedPolicyByWorkspace.get(edge.from)
1677
+ : sourceSetByLabel.get(edge.policy);
1678
+ if (source === undefined) {
1679
+ fail(
1680
+ `intent '${declaration.id}' foreign read '${edge.queryId}' targets undeclared ` +
1681
+ `${"from" in edge ? `source '${edge.from}'` : `policy '${edge.policy}'`}`,
1682
+ );
1683
+ }
1684
+ const fromField = "fromPayload" in edge
1685
+ ? payloadFieldsByIntent.get(declaration.id)?.get(edge.fromPayload)
1686
+ : undefined;
1687
+ if ("fromPayload" in edge && fromField === undefined) {
1688
+ fail(`intent '${declaration.id}' foreign source field '${edge.fromPayload}' is absent from its payload`);
1689
+ }
1690
+ const foreignEntity = structuralEntity(
1691
+ intentNode.entity,
1692
+ "foreign-read",
1693
+ {
1694
+ query: edge.queryId,
1695
+ source: source.entity.id,
1696
+ ...(fromField === undefined ? {} : { fromField: fromField.id }),
1697
+ },
1698
+ `${declaration.id}#foreign-read:${edge.queryId}`,
1699
+ );
1700
+ foreignReadNodes.push({
1701
+ kind: "structural",
1702
+ structuralKind: "foreign-read",
1703
+ entity: foreignEntity,
1704
+ owner: ref(intentNode.entity),
1705
+ query: edge.queryId,
1706
+ source: ref(source.entity),
1707
+ ...(fromField === undefined ? {} : { fromField }),
1708
+ });
1709
+ refs.push(structuralRef(foreignEntity));
1710
+ }
1711
+ intentNodes[index] = { ...intentNode, foreignReads: refs.sort((a, b) => compareText(a.id, b.id)) };
1712
+ }
1713
+
1714
+ const invariants = [...(module.workspaceInvariants ?? [])].sort((a, b) => compareText(a.id, b.id));
1715
+ reconciled = identities.reconcile({
1716
+ kind: "invariant",
1717
+ parent: domainKey,
1718
+ labels: invariants.map((invariant) => invariant.id),
1719
+ });
1720
+ registry = reconciled.registry;
1721
+ const invariantNodes: LawInvariantNode[] = invariants.map((invariant) => {
1722
+ const point = invariant.__nomosPointGuard;
1723
+ const pointAggregate = point === undefined ? undefined : aggregateByLabel.get(point.aggregateId)?.node;
1724
+ if (point !== undefined && pointAggregate === undefined) {
1725
+ fail(`invariant '${invariant.id}' point guard targets unknown aggregate '${point.aggregateId}'`);
1726
+ }
1727
+ const nativeReads = invariant.__nomosReadBindings?.map((read) => {
1728
+ const aggregate = aggregateByLabel.get(read.aggregate)?.node;
1729
+ if (aggregate === undefined) {
1730
+ fail(`invariant '${invariant.id}' native read targets unknown aggregate '${read.aggregate}'`);
1731
+ }
1732
+ return {
1733
+ binding: read.binding,
1734
+ aggregate: ref(aggregate.entity),
1735
+ state: read.state,
1736
+ ...(read.eachPayloadPath === undefined ? {} : { eachPayloadPath: read.eachPayloadPath }),
1737
+ id: read.id,
1738
+ };
1739
+ });
1740
+ return {
1741
+ kind: "invariant",
1742
+ entity: entity(
1743
+ registry,
1744
+ reconciled.resolved[invariant.id]!,
1745
+ "invariant",
1746
+ options.source?.("invariant", invariant.id, module.name),
1747
+ ),
1748
+ domain: domainRef,
1749
+ invariantKind: "workspace",
1750
+ on: intentRef(intentByLabel, invariant.on, `invariant '${invariant.id}'`),
1751
+ ...(nativeReads === undefined ? {} : { reads: nativeReads }),
1752
+ ...(invariant.__nomosEvaluator === undefined
1753
+ ? {}
1754
+ : { evaluator: invariant.__nomosEvaluator }),
1755
+ ...(point === undefined || pointAggregate === undefined
1756
+ ? {}
1757
+ : {
1758
+ pointGuard: {
1759
+ aggregate: ref(pointAggregate.entity),
1760
+ binding: point.binding,
1761
+ state: point.state,
1762
+ idPayloadPath: point.idPayloadPath,
1763
+ predicate: point.predicate,
1764
+ rejectCode: point.rejectCode,
1765
+ },
1766
+ }),
1767
+ };
1768
+ });
1769
+ for (const aggregate of module.aggregates.filter((candidate) => candidate.hasInvariant === true)) {
1770
+ const aggregateNode = aggregateByLabel.get(aggregate.id)?.node;
1771
+ if (aggregateNode === undefined) fail(`aggregate invariant targets unknown aggregate '${aggregate.id}'`);
1772
+ invariantNodes.push({
1773
+ kind: "invariant",
1774
+ entity: ownedEntity(
1775
+ "invariant",
1776
+ aggregateNode.entity,
1777
+ "invariant",
1778
+ "aggregate",
1779
+ `${aggregate.id}#invariant`,
1780
+ options.source?.("aggregate", aggregate.id, module.name),
1781
+ ),
1782
+ domain: domainRef,
1783
+ invariantKind: "aggregate",
1784
+ onAggregate: ref(aggregateNode.entity),
1785
+ });
1786
+ }
1787
+
1788
+ const capabilities = [...(module.impureCapabilities ?? [])].sort((a, b) =>
1789
+ compareText(a.aggregate.id, b.aggregate.id),
1790
+ );
1791
+ reconciled = identities.reconcile({
1792
+ kind: "capability",
1793
+ parent: domainKey,
1794
+ labels: capabilities.map((capability) => capability.aggregate.id),
1795
+ });
1796
+ registry = reconciled.registry;
1797
+ let capabilityNodes: LawCapabilityNode[] = capabilities.map((capability) => {
1798
+ const metadata = capability as typeof capability & {
1799
+ readonly name?: unknown;
1800
+ readonly capability?: unknown;
1801
+ readonly endpoint?: unknown;
1802
+ };
1803
+ return {
1804
+ kind: "capability",
1805
+ entity: entity(
1806
+ registry,
1807
+ reconciled.resolved[capability.aggregate.id]!,
1808
+ "capability",
1809
+ options.source?.("capability", capability.aggregate.id, module.name),
1810
+ ),
1811
+ domain: domainRef,
1812
+ task: aggregateRef(aggregateByLabel, capability.aggregate.id, `capability '${capability.aggregate.id}'`),
1813
+ obligation: intentRef(intentByLabel, capability.obligation.id, `capability '${capability.aggregate.id}'`),
1814
+ complete: intentRef(intentByLabel, capability.complete.id, `capability '${capability.aggregate.id}'`),
1815
+ fail: intentRef(intentByLabel, capability.fail.id, `capability '${capability.aggregate.id}'`),
1816
+ block: intentRef(intentByLabel, capability.block.id, `capability '${capability.aggregate.id}'`),
1817
+ deadLetter: intentRef(intentByLabel, capability.deadLetter.id, `capability '${capability.aggregate.id}'`),
1818
+ capability: typeof metadata.name === "string"
1819
+ ? metadata.name
1820
+ : typeof metadata.capability === "string"
1821
+ ? metadata.capability
1822
+ : capability.aggregate.id,
1823
+ ...(typeof metadata.endpoint === "string" ? { endpoint: metadata.endpoint } : {}),
1824
+ };
1825
+ });
1826
+
1827
+ const flowNodes: LawFlowNode[] = [];
1828
+ const addDeclassify = (
1829
+ owner: LawEntity<"invariant"> | LawEntity<"capability">,
1830
+ reason: string | undefined,
1831
+ ): void => {
1832
+ if (reason === undefined) return;
1833
+ const semantic = { flowKind: "declassify" as const, site: `${owner.label}:refusal`, reason };
1834
+ const entity = structuralEntity(owner, "flow", semantic, `${owner.label}#declassify`);
1835
+ flowNodes.push({
1836
+ kind: "structural",
1837
+ structuralKind: "flow",
1838
+ entity,
1839
+ owner: ref(owner),
1840
+ ...semantic,
1841
+ });
1842
+ };
1843
+ for (const invariant of invariants) {
1844
+ const node = invariantNodes.find(
1845
+ (candidate) => candidate.invariantKind === "workspace" && candidate.entity.label === invariant.id,
1846
+ );
1847
+ if (node !== undefined) addDeclassify(node.entity, invariant.declassify);
1848
+ }
1849
+ for (const aggregate of module.aggregates) {
1850
+ const node = invariantNodes.find(
1851
+ (candidate) => candidate.invariantKind === "aggregate" && candidate.onAggregate.id ===
1852
+ aggregateByLabel.get(aggregate.id)?.node.entity.id,
1853
+ );
1854
+ if (node !== undefined) addDeclassify(node.entity, aggregate.invariantDeclassify);
1855
+ }
1856
+ for (const capability of capabilities) {
1857
+ const node = capabilityNodes.find((candidate) => candidate.entity.label === capability.aggregate.id);
1858
+ if (node !== undefined) {
1859
+ addDeclassify(node.entity, (capability as { readonly declassify?: string }).declassify);
1860
+ }
1861
+ }
1862
+ for (const intent of intents) {
1863
+ const owner = intentEntities.get(intent.id)!;
1864
+ const seen = new Set<string>();
1865
+ for (const flow of harvestPrincipalFlows(intent, module.domain ?? module.name)) {
1866
+ const sinkParts = flow.sink.split(".");
1867
+ const sinkFieldName = sinkParts.pop();
1868
+ const sinkAggregateName = sinkParts.pop();
1869
+ const sinkField = sinkAggregateName === undefined || sinkFieldName === undefined
1870
+ ? undefined
1871
+ : aggregateByLabel.get(sinkAggregateName)?.fields.get(sinkFieldName);
1872
+ if (sinkField === undefined) fail(`intent '${intent.id}' endorsement targets unknown field '${flow.sink}'`);
1873
+ const semantic = {
1874
+ flowKind: "endorse" as const,
1875
+ site: `${intent.id}:${flow.sink}:${flow.facet}`,
1876
+ sink: ref(sinkField.entity),
1877
+ facet: flow.facet,
1878
+ reason: flow.reason,
1879
+ };
1880
+ const entity = structuralEntity(
1881
+ owner,
1882
+ "flow",
1883
+ semantic,
1884
+ `${intent.id}#endorse:${flow.sink}:${flow.facet}`,
1885
+ );
1886
+ if (seen.has(entity.id)) continue;
1887
+ seen.add(entity.id);
1888
+ flowNodes.push({
1889
+ kind: "structural",
1890
+ structuralKind: "flow",
1891
+ entity,
1892
+ owner: ref(owner),
1893
+ ...semantic,
1894
+ });
1895
+ }
1896
+ }
1897
+
1898
+ const workspaceTypes = [...(module.workspaceTypes ?? [])].sort((a, b) => compareText(a.id, b.id));
1899
+ reconciled = identities.reconcile({
1900
+ kind: "workspace-type",
1901
+ parent: domainKey,
1902
+ labels: workspaceTypes.map((workspaceType) => workspaceType.id),
1903
+ });
1904
+ registry = reconciled.registry;
1905
+ const workspaceTypeEntities = new Map(
1906
+ workspaceTypes.map((workspaceType) => [
1907
+ workspaceType.id,
1908
+ entity(
1909
+ registry,
1910
+ reconciled.resolved[workspaceType.id]!,
1911
+ "workspace-type",
1912
+ options.source?.("workspace-type", workspaceType.id, module.name),
1913
+ ),
1914
+ ] as const),
1915
+ );
1916
+ const workspaceTypeNodes: LawWorkspaceTypeNode[] = workspaceTypes.map((workspaceType) => {
1917
+ const workspaceTypeEntity = workspaceTypeEntities.get(workspaceType.id)!;
1918
+ return {
1919
+ kind: "workspace-type",
1920
+ entity: workspaceTypeEntity,
1921
+ domain: domainRef,
1922
+ ...(workspaceType.rootAggregate === undefined
1923
+ ? {}
1924
+ : { root: aggregateRef(aggregateByLabel, workspaceType.rootAggregate.id, `workspace type '${workspaceType.id}'`) }),
1925
+ children: sortLawRefs(workspaceType.childRefs.map((childRef) => {
1926
+ const child = typeof childRef === "function" ? childRef() : childRef;
1927
+ const childEntity = workspaceTypeEntities.get(child.id);
1928
+ if (childEntity === undefined) fail(`workspace type '${workspaceType.id}' targets unknown child '${child.id}'`);
1929
+ return ref(childEntity);
1930
+ })),
1931
+ globals: sortLawRefs(workspaceType.globalAggregates.map((aggregate) =>
1932
+ aggregateRef(aggregateByLabel, aggregate.id, `workspace type '${workspaceType.id}'`))),
1933
+ coordinatorLocal: sortLawRefs(workspaceType.coordinatorLocalAggregates.map((aggregate) =>
1934
+ aggregateRef(aggregateByLabel, aggregate.id, `workspace type '${workspaceType.id}'`))),
1935
+ mode: workspaceType.mode,
1936
+ };
1937
+ });
1938
+
1939
+ const reads = readInputs(module);
1940
+ reconciled = identities.reconcile({
1941
+ kind: "read",
1942
+ parent: domainKey,
1943
+ labels: reads.map((read) => read.id),
1944
+ ...(options.readCarries === undefined ? {} : { carries: options.readCarries }),
1945
+ });
1946
+ registry = reconciled.registry;
1947
+ const predicateNodes = new Map<LawStructuralSid<"predicate">, LawPredicateNode>();
1948
+ const readContractNodes: LawReadContractNode[] = [];
1949
+ const resolveReadFieldPath = (
1950
+ fields: ReadonlyMap<string, LawFieldNode>,
1951
+ aggregate: string,
1952
+ path: string,
1953
+ ): LawFieldPathRef => fieldPath(fields, aggregate, path, fieldNodes, anonymousValueNodes);
1954
+ const readNodes = reads.map((read): LawReadNode => {
1955
+ const readEntity = entity(
1956
+ registry,
1957
+ reconciled.resolved[read.id]!,
1958
+ "read",
1959
+ options.source?.("read", read.id, module.name),
1960
+ );
1961
+ const fields = aggregateByLabel.get(read.of)?.fields;
1962
+ if (fields === undefined) fail(`read '${read.id}' targets unknown aggregate '${read.of}'`);
1963
+ const readAggregate = aggregateRef(aggregateByLabel, read.of, `read '${read.id}'`);
1964
+ const readContractEntity = ownedEntity(
1965
+ "contract",
1966
+ readEntity,
1967
+ "contract",
1968
+ "returns",
1969
+ `${read.id}#contract`,
1970
+ options.source?.("read", read.id, module.name),
1971
+ );
1972
+ const lowerReadValue = (value: LawValueType): LawStructuralRef<"value-type"> => {
1973
+ const lowered = lowerAnonymousValueGraph(value, readContractEntity);
1974
+ for (const node of lowered.nodes) anonymousValueNodes.set(node.entity.id, node);
1975
+ return lowered.root;
1976
+ };
1977
+ const rawParameters = readParameters(read, fields, read.of, resolveReadFieldPath);
1978
+ const rawResult = readResult(read, readAggregate);
1979
+ readContractNodes.push({
1980
+ kind: "read-contract",
1981
+ entity: readContractEntity,
1982
+ owner: ref(readEntity),
1983
+ parameters: rawParameters.map((parameter) => ({
1984
+ name: parameter.name,
1985
+ ...(parameter.field === undefined ? {} : { field: parameter.field }),
1986
+ ...(parameter.value === undefined ? {} : { value: lowerReadValue(parameter.value) }),
1987
+ })),
1988
+ returns: rawResult.kind === "rows"
1989
+ ? rawResult
1990
+ : { kind: "scalar", value: lowerReadValue(rawResult.value) },
1991
+ });
1992
+ let predicate: LawStructuralRef<"predicate"> | undefined;
1993
+ if ("where" in read && read.where !== undefined) {
1994
+ predicate = lowerReadPredicate(
1995
+ read.where,
1996
+ readEntity,
1997
+ fields,
1998
+ read.of,
1999
+ predicateNodes,
2000
+ resolveReadFieldPath,
2001
+ );
2002
+ }
2003
+ const base = {
2004
+ kind: "read" as const,
2005
+ entity: readEntity,
2006
+ domain: domainRef,
2007
+ of: readAggregate,
2008
+ contract: ref(readContractEntity),
2009
+ public: read.public,
2010
+ ...(predicate === undefined ? {} : { predicate }),
2011
+ };
2012
+ switch (read.readKind) {
2013
+ case "query":
2014
+ return { ...base, readKind: "query", key: read.key.map((path) => resolveReadFieldPath(fields, read.of, path)) };
2015
+ case "count":
2016
+ case "exists":
2017
+ return {
2018
+ ...base,
2019
+ readKind: read.readKind,
2020
+ ...(read.by === undefined ? {} : { groupBy: resolveReadFieldPath(fields, read.of, read.by) }),
2021
+ filtered: read.filtered,
2022
+ };
2023
+ case "sum":
2024
+ return {
2025
+ ...base,
2026
+ readKind: "sum",
2027
+ value: resolveReadFieldPath(fields, read.of, read.value),
2028
+ ...(read.by === undefined ? {} : { groupBy: resolveReadFieldPath(fields, read.of, read.by) }),
2029
+ filtered: read.filtered,
2030
+ };
2031
+ case "min":
2032
+ case "max":
2033
+ return {
2034
+ ...base,
2035
+ readKind: read.readKind,
2036
+ value: resolveReadFieldPath(fields, read.of, read.value),
2037
+ ...(read.by === undefined ? {} : { groupBy: resolveReadFieldPath(fields, read.of, read.by) }),
2038
+ filtered: read.filtered,
2039
+ };
2040
+ case "ordered":
2041
+ return {
2042
+ ...base,
2043
+ readKind: "ordered",
2044
+ orderBy: resolveReadFieldPath(fields, read.of, read.orderBy),
2045
+ descending: read.descending,
2046
+ limit: read.limit,
2047
+ filtered: read.filtered,
2048
+ };
2049
+ case "spatial":
2050
+ return { ...base, readKind: "spatial", geometry: resolveReadFieldPath(fields, read.of, read.geometry) };
2051
+ case "derived":
2052
+ return { ...base, readKind: "derived" };
2053
+ case "combined":
2054
+ return {
2055
+ ...base,
2056
+ readKind: "combined",
2057
+ via: resolveReadFieldPath(fields, read.of, read.via),
2058
+ reads: aggregateRef(aggregateByLabel, read.reads, `combined read '${read.id}'`),
2059
+ };
2060
+ }
2061
+ });
2062
+ const readByLabel = new Map(readNodes.map((read) => [read.entity.label, read] as const));
2063
+ capabilityNodes = capabilityNodes.map((node, index) => {
2064
+ const tasksLabel = (capabilities[index] as { readonly tasks?: { readonly id?: unknown } } | undefined)
2065
+ ?.tasks?.id;
2066
+ if (typeof tasksLabel !== "string") return node;
2067
+ const tasks = readByLabel.get(tasksLabel);
2068
+ if (tasks === undefined) {
2069
+ fail(`capability '${node.entity.label}' targets unknown tasks query '${tasksLabel}'`);
2070
+ }
2071
+ return { ...node, tasks: ref(tasks.entity) };
2072
+ });
2073
+ for (const [index, intentNode] of intentNodes.entries()) {
2074
+ const declaration = intents.find((intent) => intent.id === intentNode.entity.label)!;
2075
+ const aggregateReads = [...new Set(declaration.declaredReads ?? [])].map((label) =>
2076
+ aggregateRef(aggregateByLabel, label, `intent '${declaration.id}' read boundary`));
2077
+ const queryReads = [...new Set((declaration.declaredQueryReads ?? []).map((read) => read.id))].map((label) => {
2078
+ const read = readByLabel.get(label);
2079
+ if (read === undefined) fail(`intent '${declaration.id}' reads unknown query '${label}'`);
2080
+ return ref(read.entity);
2081
+ });
2082
+ const intentReads: LawTargetRef<"aggregate" | "read">[] = [...aggregateReads, ...queryReads];
2083
+ intentNodes[index] = {
2084
+ ...intentNode,
2085
+ reads: intentReads.sort((a, b) => compareText(lawTargetKey(a), lawTargetKey(b))),
2086
+ };
2087
+ }
2088
+
2089
+ const domainNode: LawDomainNode = {
2090
+ kind: "domain",
2091
+ entity: domainEntity,
2092
+ aggregates: sortedRefs(aggregateNodes),
2093
+ intents: sortedRefs(intentNodes),
2094
+ reads: sortedRefs(readNodes),
2095
+ contracts: sortedRefs([...contractNodes, ...payloadContractNodes, ...readContractNodes]),
2096
+ types: sortedRefs(typeNodes),
2097
+ objectTypes: sortedRefs(relationObjectTypeNodes),
2098
+ relations: sortedRefs(relationNodes),
2099
+ policies: sortedRefs(policyNodes),
2100
+ invariants: sortedRefs(invariantNodes),
2101
+ capabilities: sortedRefs(capabilityNodes),
2102
+ workspaceTypes: sortedRefs(workspaceTypeNodes),
2103
+ flows: flowNodes.map((node) => structuralRef(node.entity)).sort((a, b) => compareText(a.id, b.id)),
2104
+ ...(module.parentAttestedBirth === undefined || module.parentAttestedBirth === false ? {} : {
2105
+ parentAttestedBirth: {
2106
+ ownerObject: typeof module.parentAttestedBirth === "object" && module.parentAttestedBirth.ownerObject !== undefined
2107
+ ? module.parentAttestedBirth.ownerObject : "home-owner:self",
2108
+ ownerField: typeof module.parentAttestedBirth === "object" && module.parentAttestedBirth.ownerField !== undefined
2109
+ ? module.parentAttestedBirth.ownerField : "principal",
2110
+ certObject: typeof module.parentAttestedBirth === "object" && module.parentAttestedBirth.certObject !== undefined
2111
+ ? module.parentAttestedBirth.certObject : "birth-attestation:self",
2112
+ certField: typeof module.parentAttestedBirth === "object" && module.parentAttestedBirth.certField !== undefined
2113
+ ? module.parentAttestedBirth.certField : "certChain",
2114
+ },
2115
+ }),
2116
+ };
2117
+ const nodes: DeclaredLawNode[] = [
2118
+ domainNode,
2119
+ ...aggregateNodes,
2120
+ ...fieldNodes,
2121
+ ...contractNodes,
2122
+ ...payloadContractNodes,
2123
+ ...anonymousValueNodes.values(),
2124
+ ...readContractNodes,
2125
+ ...typeNodes,
2126
+ ...relationObjectTypeNodes,
2127
+ ...intentNodes,
2128
+ ...effectNodes,
2129
+ ...foreignReadNodes,
2130
+ ...workspaceRelationNodes,
2131
+ ...relationNodes,
2132
+ ...relationPredicateNodes.values(),
2133
+ ...policyNodes,
2134
+ ...invariantNodes,
2135
+ ...capabilityNodes,
2136
+ ...flowNodes,
2137
+ ...workspaceTypeNodes,
2138
+ ...readNodes,
2139
+ ...predicateNodes.values(),
2140
+ ].sort((a, b) => compareText(a.entity.id, b.entity.id));
2141
+ registry = identities.finish();
2142
+ return { registry, graph: { v: 1, domain: domainNode, nodes } };
2143
+ }
2144
+
2145
+ type ReadInputBase = { id: string; public: boolean };
2146
+ type ReadInput =
2147
+ | (ReadInputBase & { readKind: "query"; of: string; key: readonly string[] })
2148
+ | (ReadInputBase & { readKind: "count" | "exists"; of: string; by?: string; where?: CanonicalPred; filtered: boolean })
2149
+ | (ReadInputBase & { readKind: "sum" | "min" | "max"; of: string; value: string; by?: string; where?: CanonicalPred; filtered: boolean })
2150
+ | (ReadInputBase & { readKind: "ordered"; of: string; orderBy: string; descending: boolean; limit: number; where?: CanonicalPred; filtered: boolean })
2151
+ | (ReadInputBase & { readKind: "spatial"; of: string; geometry: string })
2152
+ | (ReadInputBase & { readKind: "derived"; of: string; returns: z.ZodTypeAny })
2153
+ | (ReadInputBase & { readKind: "combined"; of: string; via: string; reads: string; returns: z.ZodTypeAny });
2154
+
2155
+ interface RawLawReadParameter {
2156
+ readonly name: string;
2157
+ readonly field?: LawFieldPathRef;
2158
+ readonly value?: LawValueType;
2159
+ }
2160
+
2161
+ type ResolveFieldPath = (
2162
+ fields: ReadonlyMap<string, LawFieldNode>,
2163
+ aggregate: string,
2164
+ path: string,
2165
+ ) => LawFieldPathRef;
2166
+
2167
+ type RawLawReadResult =
2168
+ | { readonly kind: "rows"; readonly aggregate: LawRef<"aggregate">; readonly cardinality: "many" | "optional" }
2169
+ | { readonly kind: "scalar"; readonly value: LawValueType };
2170
+
2171
+ function readInputs(module: DomainModule): ReadInput[] {
2172
+ const isPublic = (value: unknown): boolean =>
2173
+ !(value && typeof value === "object" &&
2174
+ (value as { businessBirthInternal?: unknown }).businessBirthInternal === true);
2175
+ const reads: ReadInput[] = [
2176
+ ...(module.queries ?? []).map((read) => ({ id: read.id, public: isPublic(read), readKind: "query" as const, of: read.returns, key: read.key })),
2177
+ ...(module.counts ?? []).map((value) => {
2178
+ const read = finishCount(value);
2179
+ return { id: read.id, public: isPublic(value), readKind: "count" as const, of: read.of, ...(read.by === undefined ? {} : { by: read.by }), ...(read.where === undefined ? {} : { where: read.where }), filtered: read.where !== undefined };
2180
+ }),
2181
+ ...(module.exists_ ?? []).map((value) => {
2182
+ const read = finishExists(value);
2183
+ return { id: read.id, public: isPublic(value), readKind: "exists" as const, of: read.of, ...(read.by === undefined ? {} : { by: read.by }), ...(read.where === undefined ? {} : { where: read.where }), filtered: read.where !== undefined };
2184
+ }),
2185
+ ...(module.sums ?? []).map((value) => {
2186
+ const read = finishSum(value);
2187
+ return { id: read.id, public: isPublic(value), readKind: "sum" as const, of: read.of, value: read.sumField, ...(read.by === undefined ? {} : { by: read.by }), ...(read.where === undefined ? {} : { where: read.where }), filtered: read.where !== undefined };
2188
+ }),
2189
+ ...(module.mins ?? []).map((value) => {
2190
+ const read = finishExtremum(value);
2191
+ return { id: read.id, public: isPublic(value), readKind: "min" as const, of: read.of, value: read.valueField, ...(read.by === undefined ? {} : { by: read.by }), ...(read.where === undefined ? {} : { where: read.where }), filtered: read.where !== undefined };
2192
+ }),
2193
+ ...(module.maxes ?? []).map((value) => {
2194
+ const read = finishExtremum(value);
2195
+ return { id: read.id, public: isPublic(value), readKind: "max" as const, of: read.of, value: read.valueField, ...(read.by === undefined ? {} : { by: read.by }), ...(read.where === undefined ? {} : { where: read.where }), filtered: read.where !== undefined };
2196
+ }),
2197
+ ...(module.orderedReads ?? []).map((read) => ({ id: read.id, public: isPublic(read), readKind: "ordered" as const, of: read.of, orderBy: read.orderKey, descending: read.orderDesc, limit: read.limit, ...(read.where === undefined ? {} : { where: read.where }), filtered: read.where !== undefined })),
2198
+ ...(module.spatials ?? []).map((read) => ({ id: read.id, public: isPublic(read), readKind: "spatial" as const, of: read.of, geometry: read.on })),
2199
+ ...(module.deriveds ?? []).map((read) => ({ id: read.id, public: isPublic(read), readKind: "derived" as const, of: read.of, returns: read.returns })),
2200
+ ...(module.combineds ?? []).map((read) => ({ id: read.id, public: isPublic(read), readKind: "combined" as const, of: read.of, via: read.refField, reads: read.reads, returns: read.returns })),
2201
+ ];
2202
+ return reads.sort((a, b) => compareText(a.id, b.id));
2203
+ }
2204
+
2205
+ function readParameters(
2206
+ read: ReadInput,
2207
+ fields: ReadonlyMap<string, LawFieldNode>,
2208
+ aggregate: string,
2209
+ resolveFieldPath: ResolveFieldPath,
2210
+ ): RawLawReadParameter[] {
2211
+ if (read.readKind === "query") {
2212
+ return read.key.map((name) => ({ name, field: resolveFieldPath(fields, aggregate, name) }));
2213
+ }
2214
+ if (
2215
+ (read.readKind === "count" || read.readKind === "exists" || read.readKind === "sum" ||
2216
+ read.readKind === "min" || read.readKind === "max") && read.by !== undefined
2217
+ ) {
2218
+ return [{ name: read.by, field: resolveFieldPath(fields, aggregate, read.by) }];
2219
+ }
2220
+ if (read.readKind === "spatial") {
2221
+ const coordinate = { kind: "number" as const, constraints: [] };
2222
+ return [{
2223
+ name: "bounds",
2224
+ value: {
2225
+ kind: "sum",
2226
+ options: [
2227
+ {
2228
+ kind: "record",
2229
+ fields: ["maxX", "maxY", "minX", "minY"].map((name) => ({
2230
+ name,
2231
+ required: true,
2232
+ value: coordinate,
2233
+ })),
2234
+ },
2235
+ {
2236
+ kind: "record",
2237
+ fields: ["maxLat", "maxLng", "minLat", "minLng"].map((name) => ({
2238
+ name,
2239
+ required: true,
2240
+ value: coordinate,
2241
+ })),
2242
+ },
2243
+ ],
2244
+ },
2245
+ }];
2246
+ }
2247
+ return [];
2248
+ }
2249
+
2250
+ function readResult(read: ReadInput, aggregate: LawRef<"aggregate">): RawLawReadResult {
2251
+ switch (read.readKind) {
2252
+ case "query":
2253
+ case "spatial":
2254
+ return { kind: "rows", aggregate, cardinality: "many" };
2255
+ case "ordered":
2256
+ return { kind: "rows", aggregate, cardinality: read.limit === 1 ? "optional" : "many" };
2257
+ case "count":
2258
+ case "sum":
2259
+ return { kind: "scalar", value: { kind: "integer", constraints: [] } };
2260
+ case "exists":
2261
+ return { kind: "scalar", value: { kind: "boolean" } };
2262
+ case "min":
2263
+ case "max":
2264
+ return {
2265
+ kind: "scalar",
2266
+ value: { kind: "nullable", value: { kind: "integer", constraints: [] } },
2267
+ };
2268
+ case "derived":
2269
+ case "combined":
2270
+ return { kind: "scalar", value: lowerLawValueType(read.returns) };
2271
+ }
2272
+ }
2273
+
2274
+ function storedFieldValue(field: Field): LawValueType {
2275
+ if (field.kind === "json") return { kind: "unknown" };
2276
+ if (field.kind === "ref" || field.kind === "position") {
2277
+ return { kind: "string", constraints: [] };
2278
+ }
2279
+ if (field.kind === "counter") return { kind: "integer", constraints: [] };
2280
+ if (field.kind === "hasMany") return { kind: "unknown" };
2281
+ return lowerLawValueType(field.zod);
2282
+ }
2283
+
2284
+ function linkStoredField(
2285
+ node: LawFieldNode,
2286
+ field: Field,
2287
+ owner: string,
2288
+ siblings: ReadonlyMap<string, LawFieldNode>,
2289
+ aggregates: ReadonlyMap<string, { node: LawAggregateNode; fields: Map<string, LawFieldNode> }>,
2290
+ contracts: ReadonlyMap<string, LawEntity<"contract">>,
2291
+ ): LawFieldNode {
2292
+ const target = (
2293
+ label: string,
2294
+ targetIsContract: boolean | undefined,
2295
+ domain?: string,
2296
+ ): LawTargetRef<"aggregate" | "contract"> => {
2297
+ if (targetIsContract === true) {
2298
+ const local = contracts.get(label);
2299
+ return local === undefined
2300
+ ? { kind: "dependency-export", entityKind: "contract", label, ...(domain === undefined ? {} : { domain }) }
2301
+ : ref(local);
2302
+ }
2303
+ const local = aggregates.get(label);
2304
+ return local === undefined
2305
+ ? { kind: "dependency-export", entityKind: "aggregate", label, ...(domain === undefined ? {} : { domain }) }
2306
+ : ref(local.node.entity);
2307
+ };
2308
+ if (field.versionedForeignRef !== undefined) {
2309
+ const versioned = field.versionedForeignRef;
2310
+ const seriesAggregate = aggregates.get(versioned.seriesAggregateId);
2311
+ const current = seriesAggregate?.fields.get(versioned.currentField);
2312
+ return {
2313
+ ...node,
2314
+ refTarget: target(
2315
+ versioned.targetAggregateId,
2316
+ versioned.targetIsContract,
2317
+ versioned.targetDomain,
2318
+ ),
2319
+ referenceMode: "versioned",
2320
+ seriesTarget: target(
2321
+ versioned.seriesAggregateId,
2322
+ versioned.seriesIsContract,
2323
+ versioned.targetDomain,
2324
+ ),
2325
+ currentField: current === undefined
2326
+ ? {
2327
+ kind: "dependency-export",
2328
+ entityKind: "field",
2329
+ label: `${versioned.seriesAggregateId}.${versioned.currentField}`,
2330
+ ...(versioned.targetDomain === undefined ? {} : { domain: versioned.targetDomain }),
2331
+ }
2332
+ : ref(current.entity),
2333
+ };
2334
+ }
2335
+ const declared = field.nomosRef ?? field.foreignRef;
2336
+ const targetLabel = declared?.targetAggregateId ?? field.refAggregateId;
2337
+ if (targetLabel === undefined) return node;
2338
+ let workspaceField: LawRef<"field"> | undefined;
2339
+ if (field.foreignRef !== undefined) {
2340
+ const workspace = siblings.get(field.foreignRef.workspaceField);
2341
+ if (workspace === undefined) {
2342
+ fail(`field '${owner}.${node.entity.label}' targets unknown workspace field '${field.foreignRef.workspaceField}'`);
2343
+ }
2344
+ workspaceField = ref(workspace.entity);
2345
+ }
2346
+ return {
2347
+ ...node,
2348
+ refTarget: target(
2349
+ targetLabel,
2350
+ declared?.targetIsContract,
2351
+ field.foreignRef?.targetDomain,
2352
+ ),
2353
+ referenceMode: "current",
2354
+ ...(workspaceField === undefined ? {} : { workspaceField }),
2355
+ };
2356
+ }
2357
+
2358
+ function descriptorValue(field: ValueObjectFieldDescriptor): LawValueType {
2359
+ switch (field.type) {
2360
+ case "string":
2361
+ return { kind: "string", constraints: [] };
2362
+ case "number":
2363
+ return { kind: field.int === true ? "integer" : "number", constraints: [] };
2364
+ case "boolean":
2365
+ return { kind: "boolean" };
2366
+ case "enum":
2367
+ return { kind: "enum", values: [...field.values].sort(compareText) };
2368
+ case "literal":
2369
+ return { kind: "literal", value: field.value };
2370
+ case "array":
2371
+ return { kind: "list", element: descriptorValue(field.items) };
2372
+ case "map":
2373
+ return { kind: "map", value: descriptorValue(field.values) };
2374
+ case "object":
2375
+ case "geometry":
2376
+ return { kind: "unknown" };
2377
+ }
2378
+ }
2379
+
2380
+ function mergeDrivers(driver: Driver): LawMergeDriver[] {
2381
+ const drivers: LawMergeDriver[] = [];
2382
+ let current: Driver | undefined = driver;
2383
+ while (current !== undefined) {
2384
+ drivers.push({
2385
+ kind: current.kind,
2386
+ ...(current.principal === undefined ? {} : { principal: current.principal }),
2387
+ });
2388
+ current = current.inner;
2389
+ }
2390
+ return drivers;
2391
+ }
2392
+
2393
+ function entity<K extends LawEntityKind>(
2394
+ registry: LawIdentityRegistry,
2395
+ key: string,
2396
+ expected: K,
2397
+ source: LawSourceSpan | undefined,
2398
+ ): LawEntity<K> {
2399
+ const stored = registry.entities[key];
2400
+ if (stored === undefined) fail(`${expected} identity '${key}' is missing`);
2401
+ if (stored.kind !== expected) fail(`identity '${key}' is ${stored.kind}, expected ${expected}`);
2402
+ if (stored.status !== "active") fail(`${expected} identity '${key}' is retired`);
2403
+ return {
2404
+ id: key as LawSid<K>,
2405
+ sid: stored.sid,
2406
+ label: stored.label,
2407
+ priorLabels: [...(stored.priorLabels ?? [])],
2408
+ identityClass: "declared",
2409
+ ...(source === undefined ? {} : { source }),
2410
+ };
2411
+ }
2412
+
2413
+ function ownedEntity<K extends LawEntityKind, O extends LawEntityKind>(
2414
+ kind: K,
2415
+ owner: LawEntity<O>,
2416
+ slotKind: string,
2417
+ slot: string,
2418
+ label: string,
2419
+ source: LawSourceSpan | undefined,
2420
+ ): LawEntity<K> {
2421
+ const sid = mintOwnedLawSid(owner.sid, slotKind, slot);
2422
+ return {
2423
+ id: lawEntityKey(kind, sid) as LawSid<K>,
2424
+ sid,
2425
+ label,
2426
+ priorLabels: [],
2427
+ identityClass: "owned",
2428
+ ...(source === undefined ? {} : { source }),
2429
+ };
2430
+ }
2431
+
2432
+ function structuralEntity<K extends LawStructuralKind, O extends LawEntityKind>(
2433
+ owner: LawEntity<O>,
2434
+ structuralKind: K,
2435
+ canonicalValue: unknown,
2436
+ label: string,
2437
+ ): LawStructuralEntity<K> {
2438
+ const sid = mintStructuralLawSid(owner.sid, structuralKind, canonicalValue);
2439
+ return {
2440
+ id: `s_${sid}` as LawStructuralSid<K>,
2441
+ sid,
2442
+ label,
2443
+ identityClass: "structural",
2444
+ };
2445
+ }
2446
+
2447
+ function structuralRef<K extends LawStructuralKind>(
2448
+ value: LawStructuralEntity<K>,
2449
+ ): LawStructuralRef<K> {
2450
+ return { id: value.id };
2451
+ }
2452
+
2453
+ /** Lower one anonymous contract/value shape into content-addressed graph nodes under its stable owner. */
2454
+ export function lowerAnonymousValueGraph<K extends LawEntityKind>(
2455
+ source: LawValueType,
2456
+ owner: LawEntity<K>,
2457
+ ): { readonly root: LawStructuralRef<"value-type">; readonly nodes: readonly LawAnonymousValueNode[] } {
2458
+ const nodes = new Map<string, LawAnonymousValueNode>();
2459
+ const anchors = new Map<string, LawStructuralRef<"value-type">>();
2460
+
2461
+ const lower = (input: LawValueType): LawStructuralRef<"value-type"> => {
2462
+ if (input.kind === "recursive-ref") {
2463
+ const target = anchors.get(input.anchor);
2464
+ if (target === undefined) fail(`anonymous value type references unknown recursion anchor '${input.anchor}'`);
2465
+ return target;
2466
+ }
2467
+ const value = canonicalValueType(input);
2468
+ const entity = structuralEntity(owner, "value-type", value, `${owner.label}#value-type`);
2469
+ const existing = nodes.get(entity.id);
2470
+ if (existing !== undefined) return structuralRef(entity);
2471
+ // Reserve recursive roots before walking their body so a back-edge resolves to this exact node.
2472
+ if (value.kind === "recursive") anchors.set(value.anchor, structuralRef(entity));
2473
+
2474
+ let expression: LawAnonymousValueTypeExpression;
2475
+ switch (value.kind) {
2476
+ case "string":
2477
+ case "number":
2478
+ case "integer":
2479
+ expression = { kind: value.kind, constraints: value.constraints };
2480
+ break;
2481
+ case "boolean":
2482
+ case "null":
2483
+ case "unknown":
2484
+ expression = { kind: value.kind };
2485
+ break;
2486
+ case "enum":
2487
+ expression = { kind: "enum", values: value.values };
2488
+ break;
2489
+ case "literal":
2490
+ expression = { kind: "literal", value: value.value };
2491
+ break;
2492
+ case "list":
2493
+ expression = { kind: "list", element: lower(value.element) };
2494
+ break;
2495
+ case "map":
2496
+ expression = { kind: "map", value: lower(value.value) };
2497
+ break;
2498
+ case "nullable":
2499
+ expression = { kind: "nullable", value: lower(value.value) };
2500
+ break;
2501
+ case "default":
2502
+ expression = { kind: "default", value: lower(value.value), defaultValue: value.defaultValue };
2503
+ break;
2504
+ case "recursive":
2505
+ expression = { kind: "recursive", value: lower(value.value) };
2506
+ anchors.delete(value.anchor);
2507
+ break;
2508
+ case "record": {
2509
+ const fields = value.fields.map((member) => {
2510
+ const child = lower(member.value);
2511
+ const fieldEntity = structuralEntity(
2512
+ owner,
2513
+ "value-field",
2514
+ { parent: entity.id, name: member.name, required: member.required, value: child.id },
2515
+ `${owner.label}#${member.name}`,
2516
+ );
2517
+ if (!nodes.has(fieldEntity.id)) {
2518
+ nodes.set(fieldEntity.id, {
2519
+ kind: "structural",
2520
+ structuralKind: "value-field",
2521
+ entity: fieldEntity,
2522
+ owner: structuralRef(entity),
2523
+ name: member.name,
2524
+ required: member.required,
2525
+ value: child,
2526
+ });
2527
+ }
2528
+ return structuralRef(fieldEntity);
2529
+ }).sort((a, b) => compareText(a.id, b.id));
2530
+ expression = { kind: "record", fields };
2531
+ break;
2532
+ }
2533
+ case "sum": {
2534
+ const variants = value.options.map((option) => {
2535
+ const child = lower(option);
2536
+ const variantEntity = structuralEntity(
2537
+ owner,
2538
+ "value-variant",
2539
+ { parent: entity.id, value: child.id },
2540
+ `${owner.label}#${child.id}`,
2541
+ );
2542
+ if (!nodes.has(variantEntity.id)) {
2543
+ nodes.set(variantEntity.id, {
2544
+ kind: "structural",
2545
+ structuralKind: "value-variant",
2546
+ entity: variantEntity,
2547
+ owner: structuralRef(entity),
2548
+ value: child,
2549
+ });
2550
+ }
2551
+ return structuralRef(variantEntity);
2552
+ }).sort((a, b) => compareText(a.id, b.id));
2553
+ expression = { kind: "sum", variants };
2554
+ break;
2555
+ }
2556
+ case "recursive-ref":
2557
+ throw new Error("unreachable recursive-ref lowering");
2558
+ }
2559
+ nodes.set(entity.id, {
2560
+ kind: "structural",
2561
+ structuralKind: "value-type",
2562
+ entity: { ...entity, label: `${owner.label}#${entity.id}` },
2563
+ owner: ref(owner),
2564
+ expression,
2565
+ });
2566
+ return structuralRef(entity);
2567
+ };
2568
+
2569
+ const root = lower(source);
2570
+ return { root, nodes: [...nodes.values()].sort((a, b) => compareText(a.entity.id, b.entity.id)) };
2571
+ }
2572
+
2573
+ /** A generated payload contract is itself the record; only its member value types are anonymous. */
2574
+ export function lowerAnonymousContractFields(
2575
+ source: LawValueType,
2576
+ owner: LawEntity<"contract">,
2577
+ ): { readonly fields: readonly LawStructuralRef<"value-field">[]; readonly nodes: readonly LawAnonymousValueNode[] } {
2578
+ const value = canonicalValueType(source);
2579
+ if (value.kind !== "record") {
2580
+ fail(`payload contract '${owner.label}' must lower to a record, received '${value.kind}'`);
2581
+ }
2582
+ const nodes = new Map<string, LawAnonymousValueNode>();
2583
+ const fields = value.fields.map((member) => {
2584
+ const lowered = lowerAnonymousValueGraph(member.value, owner);
2585
+ for (const node of lowered.nodes) nodes.set(node.entity.id, node);
2586
+ const entity = structuralEntity(
2587
+ owner,
2588
+ "value-field",
2589
+ { name: member.name, required: member.required, value: lowered.root.id },
2590
+ `${owner.label}#${member.name}`,
2591
+ );
2592
+ nodes.set(entity.id, {
2593
+ kind: "structural",
2594
+ structuralKind: "value-field",
2595
+ entity,
2596
+ owner: ref(owner),
2597
+ name: member.name,
2598
+ required: member.required,
2599
+ value: lowered.root,
2600
+ });
2601
+ return structuralRef(entity);
2602
+ }).sort((a, b) => compareText(a.id, b.id));
2603
+ return { fields, nodes: [...nodes.values()].sort((a, b) => compareText(a.entity.id, b.entity.id)) };
2604
+ }
2605
+
2606
+ function canonicalValueType(value: LawValueType): LawValueType {
2607
+ switch (value.kind) {
2608
+ case "record":
2609
+ return {
2610
+ kind: "record",
2611
+ fields: value.fields.map((field) => ({ ...field, value: canonicalValueType(field.value) }))
2612
+ .sort((a, b) => compareText(a.name, b.name)),
2613
+ };
2614
+ case "sum":
2615
+ return {
2616
+ kind: "sum",
2617
+ options: value.options.map(canonicalValueType)
2618
+ .sort((a, b) => compareText(JSON.stringify(a), JSON.stringify(b))),
2619
+ };
2620
+ case "list":
2621
+ return { kind: "list", element: canonicalValueType(value.element) };
2622
+ case "map":
2623
+ return { kind: "map", value: canonicalValueType(value.value) };
2624
+ case "nullable":
2625
+ return { kind: "nullable", value: canonicalValueType(value.value) };
2626
+ case "default":
2627
+ return { ...value, value: canonicalValueType(value.value) };
2628
+ case "recursive":
2629
+ return { ...value, value: canonicalValueType(value.value) };
2630
+ default:
2631
+ return value;
2632
+ }
2633
+ }
2634
+
2635
+ function lowerReadPredicate(
2636
+ predicate: CanonicalPred,
2637
+ owner: LawEntity<"read">,
2638
+ fields: ReadonlyMap<string, LawFieldNode>,
2639
+ aggregate: string,
2640
+ nodes: Map<LawStructuralSid<"predicate">, LawPredicateNode>,
2641
+ resolveFieldPath: ResolveFieldPath,
2642
+ ): LawStructuralRef<"predicate"> {
2643
+ let expression: LawPredicateExpression;
2644
+ if (predicate.kind === "eq" || predicate.kind === "ne") {
2645
+ expression = {
2646
+ kind: predicate.kind,
2647
+ field: resolveFieldPath(fields, aggregate, predicate.field),
2648
+ value: predicate.value,
2649
+ };
2650
+ } else {
2651
+ expression = {
2652
+ kind: predicate.kind,
2653
+ terms: predicate.clauses.map((clause) =>
2654
+ lowerReadPredicate(clause, owner, fields, aggregate, nodes, resolveFieldPath))
2655
+ .sort((a, b) => compareText(a.id, b.id)),
2656
+ };
2657
+ }
2658
+ const entity = structuralEntity(owner, "predicate", expression, `${owner.label}#predicate`);
2659
+ if (!nodes.has(entity.id)) {
2660
+ nodes.set(entity.id, {
2661
+ kind: "structural",
2662
+ structuralKind: "predicate",
2663
+ entity: { ...entity, label: `${owner.label}#${entity.id}` },
2664
+ owner: ref(owner),
2665
+ expression,
2666
+ });
2667
+ }
2668
+ return structuralRef(entity);
2669
+ }
2670
+
2671
+ function lowerRelationPredicate(
2672
+ rewrite: Rewrite,
2673
+ owner: LawEntity<"relation">,
2674
+ objectType: string,
2675
+ relations: ReadonlyMap<string, LawEntity<"relation">>,
2676
+ allowedByRelation: ReadonlyMap<string, readonly LawAllowedSubject[]>,
2677
+ nodes: Map<LawStructuralSid<"relation-predicate">, LawRelationPredicateNode>,
2678
+ ): LawStructuralRef<"relation-predicate"> {
2679
+ const local = (relation: string): LawRef<"relation"> => {
2680
+ const target = relations.get(`${objectType}#${relation}`);
2681
+ if (target === undefined) {
2682
+ fail(`relation '${owner.label}' targets unknown sibling relation '${objectType}#${relation}'`);
2683
+ }
2684
+ return ref(target);
2685
+ };
2686
+ let expression: LawRelationPredicateExpression;
2687
+ switch (rewrite.kind) {
2688
+ case "this":
2689
+ expression = { kind: "this" };
2690
+ break;
2691
+ case "computed":
2692
+ expression = { kind: "computed", relation: local(rewrite.relation) };
2693
+ break;
2694
+ case "ttu":
2695
+ const tupleset = local(rewrite.tupleset);
2696
+ const reachedTypes = new Set((allowedByRelation.get(`${objectType}#${rewrite.tupleset}`) ?? [])
2697
+ .filter((subject) => subject.kind !== "wildcard")
2698
+ .map((subject) => subject.type));
2699
+ const computed = [...reachedTypes]
2700
+ .map((type) => relations.get(`${type}#${rewrite.computed}`))
2701
+ .filter((target): target is LawEntity<"relation"> => target !== undefined)
2702
+ .map(ref)
2703
+ .sort((a, b) => compareText(a.id, b.id));
2704
+ if (computed.length === 0) {
2705
+ fail(
2706
+ `relation '${owner.label}' tuple-to-userset '${rewrite.tupleset}' cannot resolve ` +
2707
+ `computed relation '${rewrite.computed}' on any allowed object type`,
2708
+ );
2709
+ }
2710
+ expression = {
2711
+ kind: "ttu",
2712
+ tupleset,
2713
+ computed,
2714
+ all: rewrite.all,
2715
+ };
2716
+ break;
2717
+ case "union":
2718
+ case "intersection":
2719
+ expression = {
2720
+ kind: rewrite.kind,
2721
+ terms: rewrite.children.map((child) =>
2722
+ lowerRelationPredicate(child, owner, objectType, relations, allowedByRelation, nodes))
2723
+ .sort((a, b) => compareText(a.id, b.id)),
2724
+ };
2725
+ break;
2726
+ case "exclusion":
2727
+ expression = {
2728
+ kind: "exclusion",
2729
+ base: lowerRelationPredicate(rewrite.base, owner, objectType, relations, allowedByRelation, nodes),
2730
+ subtract: lowerRelationPredicate(rewrite.subtract, owner, objectType, relations, allowedByRelation, nodes),
2731
+ };
2732
+ break;
2733
+ }
2734
+ const minted = structuralEntity(owner, "relation-predicate", expression, owner.label);
2735
+ const entity: LawStructuralEntity<"relation-predicate"> = {
2736
+ ...minted,
2737
+ label: `${owner.label}#${minted.id}`,
2738
+ };
2739
+ if (!nodes.has(entity.id)) {
2740
+ nodes.set(entity.id, {
2741
+ kind: "structural",
2742
+ structuralKind: "relation-predicate",
2743
+ entity,
2744
+ owner: ref(owner),
2745
+ expression,
2746
+ });
2747
+ }
2748
+ return structuralRef(entity);
2749
+ }
2750
+
2751
+ function linkRelationAllowed(
2752
+ allowed: readonly AllowedSubject[],
2753
+ relations: ReadonlyMap<string, LawEntity<"relation">>,
2754
+ owner: string,
2755
+ namespacedTypes: ReadonlySet<string>,
2756
+ domainKey: string,
2757
+ ): LawAllowedSubject[] {
2758
+ return sortStructuralValues(allowed.map((subject): LawAllowedSubject => {
2759
+ if (subject.kind === "wildcard") return { kind: "wildcard" };
2760
+ const subjectType = namespacedTypes.has(subject.type)
2761
+ ? qualifyRelationType(domainKey, subject.type)
2762
+ : subject.type;
2763
+ if (subject.kind === "direct") return { kind: "direct", type: subjectType };
2764
+ const target = relations.get(`${subject.type}#${subject.relation}`);
2765
+ if (target === undefined) {
2766
+ fail(`relation '${owner}' allows unknown userset relation '${subject.type}#${subject.relation}'`);
2767
+ }
2768
+ return {
2769
+ kind: "userset",
2770
+ type: subjectType,
2771
+ relation: ref(target),
2772
+ relationName: subject.relation,
2773
+ };
2774
+ }));
2775
+ }
2776
+
2777
+ function sortStructuralValues<T>(values: readonly T[]): T[] {
2778
+ return [...values].sort((a, b) => compareText(JSON.stringify(a), JSON.stringify(b)));
2779
+ }
2780
+
2781
+ function lawTargetKey(target: LawTargetRef<LawEntityKind>): string {
2782
+ return "kind" in target
2783
+ ? `${target.domain ?? ""}:${target.entityKind}:${target.label}`
2784
+ : target.id;
2785
+ }
2786
+
2787
+ function ref<K extends LawEntityKind>(value: LawEntity<K>): LawRef<K> {
2788
+ return { id: value.id };
2789
+ }
2790
+
2791
+ function sortedRefs<K extends LawEntityKind>(
2792
+ values: Iterable<{ readonly entity: LawEntity<K> }>,
2793
+ ): LawRef<K>[] {
2794
+ return [...values].map((node) => ref(node.entity)).sort((a, b) => compareText(a.id, b.id));
2795
+ }
2796
+
2797
+ function aggregateRef(
2798
+ aggregates: ReadonlyMap<string, { node: LawAggregateNode }>,
2799
+ label: string,
2800
+ owner: string,
2801
+ ): LawRef<"aggregate"> {
2802
+ const aggregate = aggregates.get(label);
2803
+ if (aggregate === undefined) fail(`${owner} targets unknown aggregate '${label}'`);
2804
+ return ref(aggregate.node.entity);
2805
+ }
2806
+
2807
+ function relationObjectAggregate(
2808
+ objectType: string,
2809
+ aggregates: ReadonlyMap<string, { node: LawAggregateNode }>,
2810
+ module: DomainModule,
2811
+ ): LawRef<"aggregate"> | undefined {
2812
+ const exact = aggregates.get(objectType);
2813
+ if (exact !== undefined) return ref(exact.node.entity);
2814
+ const folded = [...aggregates.entries()].filter(([label]) =>
2815
+ label.toLocaleLowerCase("en-US") === objectType.toLocaleLowerCase("en-US"));
2816
+ if (folded.length === 1) return ref(folded[0]![1].node.entity);
2817
+ if (folded.length > 1) fail(`relation object type '${objectType}' ambiguously matches several aggregates`);
2818
+ if (objectType !== "workspace") return undefined;
2819
+ const roots = new Map<string, LawRef<"aggregate">>();
2820
+ for (const workspaceType of module.workspaceTypes ?? []) {
2821
+ if (workspaceType.rootAggregate === undefined) continue;
2822
+ const root = aggregates.get(workspaceType.rootAggregate.id);
2823
+ if (root !== undefined) roots.set(root.node.entity.id, ref(root.node.entity));
2824
+ }
2825
+ if (roots.size === 1) return [...roots.values()][0];
2826
+ if (roots.size > 1) fail("relation object type 'workspace' has several possible workspace roots");
2827
+ return undefined;
2828
+ }
2829
+
2830
+ function intentRef(
2831
+ intents: ReadonlyMap<string, LawIntentNode>,
2832
+ label: string,
2833
+ owner: string,
2834
+ ): LawRef<"intent"> {
2835
+ const direct = intents.get(label);
2836
+ const short = label.includes(".") ? intents.get(label.slice(label.lastIndexOf(".") + 1)) : undefined;
2837
+ const intent = direct ?? short;
2838
+ if (intent === undefined) fail(`${owner} targets unknown intent '${label}'`);
2839
+ return ref(intent.entity);
2840
+ }
2841
+
2842
+ function sortLawRefs<K extends LawEntityKind>(values: readonly LawRef<K>[]): LawRef<K>[] {
2843
+ return [...values].sort((a, b) => compareText(a.id, b.id));
2844
+ }
2845
+
2846
+ function fieldPath(
2847
+ fields: ReadonlyMap<string, LawFieldNode>,
2848
+ aggregate: string,
2849
+ path: string,
2850
+ allFields: readonly LawFieldNode[],
2851
+ anonymousNodes: ReadonlyMap<string, LawAnonymousValueNode>,
2852
+ ): LawFieldPathRef {
2853
+ const [head, ...tail] = path.split(".");
2854
+ const field = head === undefined ? undefined : fields.get(head);
2855
+ if (field === undefined) fail(`read field path '${aggregate}.${path}' targets an unknown field`);
2856
+ const nested: Array<LawRef<"field"> | LawStructuralRef<"value-field">> = [];
2857
+ let value: LawValueTypeRef = field.value;
2858
+ const unwrap = (): void => {
2859
+ const seen = new Set<string>();
2860
+ while (true) {
2861
+ if (seen.has(value.id)) fail(`read field path '${aggregate}.${path}' crosses a wrapper cycle`);
2862
+ seen.add(value.id);
2863
+ const node = anonymousNodes.get(value.id);
2864
+ if (node?.structuralKind !== "value-type") return;
2865
+ if (
2866
+ node.expression.kind !== "nullable" &&
2867
+ node.expression.kind !== "default" &&
2868
+ node.expression.kind !== "recursive"
2869
+ ) return;
2870
+ value = node.expression.value;
2871
+ }
2872
+ };
2873
+ for (const segment of tail) {
2874
+ unwrap();
2875
+ const declared = allFields.find((candidate) =>
2876
+ candidate.owner.id === value.id && candidate.entity.label === segment);
2877
+ if (declared !== undefined) {
2878
+ nested.push(ref(declared.entity));
2879
+ value = declared.value;
2880
+ continue;
2881
+ }
2882
+ const structural = [...anonymousNodes.values()].find((candidate): candidate is LawAnonymousValueFieldNode =>
2883
+ candidate.structuralKind === "value-field" &&
2884
+ candidate.owner.id === value.id &&
2885
+ candidate.name === segment);
2886
+ if (structural !== undefined) {
2887
+ nested.push(structuralRef(structural.entity));
2888
+ value = structural.value;
2889
+ continue;
2890
+ }
2891
+ fail(`read field path '${aggregate}.${path}' targets unknown nested field '${segment}'`);
2892
+ }
2893
+ return { field: ref(field.entity), authoredPath: path, path: nested };
2894
+ }
2895
+
2896
+ function fail(message: string): never {
2897
+ throw new Error(`NMS-GRAPH-001 ${message}`);
2898
+ }
2899
+
2900
+ function compareText(a: string, b: string): number {
2901
+ return a < b ? -1 : a > b ? 1 : 0;
2902
+ }