@githolon/dsl 0.108.7 → 0.110.1

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 (308) hide show
  1. package/assets/operation-contract.wasm +0 -0
  2. package/check_package.mjs +12 -15
  3. package/compile_package.mjs +17 -18
  4. package/compile_proof.mjs +16 -1
  5. package/dart/CHANGELOG.md +80 -1
  6. package/dart/README.md +25 -2
  7. package/dart/lib/src/admission_decision.dart +41 -0
  8. package/dart/lib/src/application_framework_calls.dart +52 -2
  9. package/dart/lib/src/application_model_adoption.dart +187 -0
  10. package/dart/lib/src/application_status.dart +73 -0
  11. package/dart/lib/src/console_enrolment.dart +271 -0
  12. package/dart/lib/src/console_http_io.dart +26 -0
  13. package/dart/lib/src/console_http_stub.dart +7 -0
  14. package/dart/lib/src/dev_cloud.dart +69 -22
  15. package/dart/lib/src/generated_call.dart +83 -23
  16. package/dart/lib/src/kernel_primitives.dart +119 -0
  17. package/dart/lib/src/nms_conformance_runtime.dart +1 -1
  18. package/dart/lib/src/nms_generated_runtime.dart +32 -10
  19. package/dart/lib/src/nms_test_environment.dart +1 -1
  20. package/dart/lib/src/node_runner_transport.dart +41 -46
  21. package/dart/lib/src/observed_session_directory.dart +70 -0
  22. package/dart/lib/src/operation_receipts.dart +112 -0
  23. package/dart/lib/src/peer_application_streams.dart +103 -0
  24. package/dart/lib/src/reception/dart_reception.dart +4280 -0
  25. package/dart/lib/src/reception/event_desk.dart +214 -0
  26. package/dart/lib/src/reception/extremum_read.dart +46 -0
  27. package/dart/lib/src/reception/local_copies.dart +371 -0
  28. package/dart/lib/src/reception/peer_office_connection.dart +11 -0
  29. package/dart/lib/src/reception/progress_desk.dart +268 -0
  30. package/dart/lib/src/reception/reception_contract.dart +11 -0
  31. package/dart/lib/src/reception/reception_session.dart +284 -0
  32. package/dart/lib/src/reception/sync_diagnostics.dart +26 -0
  33. package/dart/lib/src/reception/workspace_preparation_report.dart +126 -0
  34. package/dart/lib/src/replica_readiness.dart +123 -0
  35. package/dart/lib/src/runner/node-entry.mjs +23950 -15776
  36. package/dart/lib/src/runtime_v2_gateway.dart +56 -3
  37. package/dart/lib/src/runtime_v2_transport.dart +544 -3
  38. package/dart/lib/src/session_bound.dart +200 -311
  39. package/dart/lib/src/signing_identity_kernel.dart +84 -0
  40. package/dart/lib/src/sync_status_projection.dart +550 -0
  41. package/dart/lib/src/sync_status_reducer.dart +170 -0
  42. package/dart/lib/src/test_harness.dart +809 -1153
  43. package/dart/lib/src/testing/test_custody.dart +66 -0
  44. package/dart/lib/src/testing/test_peer.dart +328 -0
  45. package/dart/lib/src/typed_watch_registry.dart +169 -0
  46. package/dart/lib/src/workspace_open_progress.dart +27 -0
  47. package/dart/pubspec.lock +24 -1
  48. package/dart/pubspec.yaml +7 -2
  49. package/dart/pubspec_overrides.yaml +2 -0
  50. package/dart/test/admission_decision_test.dart +48 -0
  51. package/dart/test/background_model_adoption_test.dart +228 -0
  52. package/dart/test/bundle_version_pairing_test.dart +4 -4
  53. package/dart/test/child_authentication_repair_test.dart +359 -0
  54. package/dart/test/cloud_bound_local_shell_test.dart +119 -0
  55. package/dart/test/console_enrolment_refusal_test.dart +52 -0
  56. package/dart/test/console_enrolment_test.dart +227 -0
  57. package/dart/test/errors_test.dart +5 -5
  58. package/dart/test/event_plane_seam_test.dart +50 -164
  59. package/dart/test/extremum_read_test.dart +58 -0
  60. package/dart/test/fixtures/wedge/wedge-entry.mjs +3 -21
  61. package/dart/test/generated_query_test.dart +32 -0
  62. package/dart/test/harness_phase_timeout_test.dart +18 -23
  63. package/dart/test/kernel_author_after_read_test.dart +242 -0
  64. package/dart/test/kernel_fence_test.dart +137 -0
  65. package/dart/test/local_copies_test.dart +222 -0
  66. package/dart/test/model_adoption_test.dart +198 -0
  67. package/dart/test/node_peer_local_custody_test.dart +153 -0
  68. package/dart/test/offer_authentication_proof_test.dart +352 -52
  69. package/dart/test/operation_compatibility_test.dart +151 -0
  70. package/dart/test/platform_credential_effect_test.dart +386 -0
  71. package/dart/test/receiving_group_open_test.dart +213 -0
  72. package/dart/test/reception/workspace_preparation_report_test.dart +103 -0
  73. package/dart/test/reception_event_desk_close_test.dart +16 -0
  74. package/dart/test/reception_span_trace_test.dart +23 -0
  75. package/dart/test/replica_readiness_test.dart +156 -0
  76. package/dart/test/runtime_projections_test.dart +92 -0
  77. package/dart/test/runtime_v2_gateway_test.dart +131 -0
  78. package/dart/test/signing_identity_kernel_test.dart +75 -0
  79. package/dart/test/sync_status_preparation_report_test.dart +29 -0
  80. package/dart/test/sync_status_reducer_test.dart +419 -0
  81. package/dart/test/test_harness_test.dart +71 -95
  82. package/dart/test/typed_query_watch_test.dart +170 -0
  83. package/dart/test/typed_watch_registry_test.dart +249 -0
  84. package/dart_lints/README.md +18 -0
  85. package/dart_lints/lib/nomos_lints.dart +70 -0
  86. package/dart_lints/pubspec.lock +580 -0
  87. package/dart_lints/pubspec.yaml +12 -0
  88. package/dart_lints/test/reference_identity_test.dart +75 -0
  89. package/dart_types/CHANGELOG.md +18 -0
  90. package/dart_types/lib/nomos_kernel_protocol.dart +21 -1
  91. package/dart_types/lib/nomos_types.dart +9 -1
  92. package/dart_types/lib/src/admission_decision.dart +37 -0
  93. package/dart_types/lib/src/aggregate_meta.dart +1 -1
  94. package/dart_types/lib/src/approved_model.dart +65 -0
  95. package/dart_types/lib/src/custody.dart +10 -18
  96. package/dart_types/lib/src/error_info.dart +4 -4
  97. package/dart_types/lib/src/errors.dart +29 -29
  98. package/dart_types/lib/src/foreign_ref.dart +62 -33
  99. package/dart_types/lib/src/local_work_rejected.dart +3 -3
  100. package/dart_types/lib/src/nomos_ref.dart +3 -1
  101. package/dart_types/lib/src/principal.dart +2 -2
  102. package/dart_types/lib/src/proto/google/protobuf/any.pb.dart +163 -0
  103. package/dart_types/lib/src/proto/google/protobuf/any.pbenum.dart +11 -0
  104. package/dart_types/lib/src/proto/nomos/discovery/v1/workspace_placement.pb.dart +137 -0
  105. package/dart_types/lib/src/proto/nomos/discovery/v1/workspace_placement.pbenum.dart +11 -0
  106. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +20721 -10433
  107. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +555 -27
  108. package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pb.dart +387 -0
  109. package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pbenum.dart +46 -0
  110. package/dart_types/lib/src/proto/nomos/runtime/v2/application_readiness_desk.pb.dart +295 -0
  111. package/dart_types/lib/src/proto/nomos/runtime/v2/application_readiness_desk.pbenum.dart +139 -0
  112. package/dart_types/lib/src/proto/nomos/runtime/v2/authentication_desk.pb.dart +269 -0
  113. package/dart_types/lib/src/proto/nomos/runtime/v2/authentication_desk.pbenum.dart +103 -0
  114. package/dart_types/lib/src/proto/nomos/runtime/v2/compatibility_desk.pb.dart +417 -0
  115. package/dart_types/lib/src/proto/nomos/runtime/v2/compatibility_desk.pbenum.dart +62 -0
  116. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +9968 -0
  117. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pbenum.dart +133 -0
  118. package/dart_types/lib/src/proto/nomos/runtime/v2/credentials_desk.pb.dart +204 -0
  119. package/dart_types/lib/src/proto/nomos/runtime/v2/credentials_desk.pbenum.dart +54 -0
  120. package/dart_types/lib/src/proto/nomos/runtime/v2/custody_desk.pb.dart +1550 -0
  121. package/dart_types/lib/src/proto/nomos/runtime/v2/custody_desk.pbenum.dart +11 -0
  122. package/dart_types/lib/src/proto/nomos/runtime/v2/framework.pb.dart +710 -0
  123. package/dart_types/lib/src/proto/nomos/runtime/v2/framework.pbenum.dart +11 -0
  124. package/dart_types/lib/src/proto/nomos/runtime/v2/identity_desk.pb.dart +845 -0
  125. package/dart_types/lib/src/proto/nomos/runtime/v2/identity_desk.pbenum.dart +11 -0
  126. package/dart_types/lib/src/proto/nomos/runtime/v2/interaction_desk.pb.dart +198 -0
  127. package/dart_types/lib/src/proto/nomos/runtime/v2/interaction_desk.pbenum.dart +11 -0
  128. package/dart_types/lib/src/proto/nomos/runtime/v2/law_desk.pb.dart +369 -0
  129. package/dart_types/lib/src/proto/nomos/runtime/v2/law_desk.pbenum.dart +11 -0
  130. package/dart_types/lib/src/proto/nomos/runtime/v2/maintenance_desk.pb.dart +609 -0
  131. package/dart_types/lib/src/proto/nomos/runtime/v2/maintenance_desk.pbenum.dart +65 -0
  132. package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +358 -0
  133. package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pbenum.dart +100 -0
  134. package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pb.dart +2231 -0
  135. package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pbenum.dart +271 -0
  136. package/dart_types/lib/src/proto/nomos/runtime/v2/placement_desk.pb.dart +1286 -0
  137. package/dart_types/lib/src/proto/nomos/runtime/v2/placement_desk.pbenum.dart +11 -0
  138. package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pb.dart +636 -0
  139. package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pbenum.dart +116 -0
  140. package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pb.dart +1637 -0
  141. package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pbenum.dart +175 -0
  142. package/dart_types/lib/src/proto/nomos/runtime/v2/recovery_desk.pb.dart +1269 -0
  143. package/dart_types/lib/src/proto/nomos/runtime/v2/recovery_desk.pbenum.dart +129 -0
  144. package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pb.dart +2782 -0
  145. package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pbenum.dart +97 -0
  146. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +2232 -8627
  147. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pbenum.dart +0 -358
  148. package/dart_types/lib/src/proto/nomos/runtime/v2/sealing_desk.pb.dart +1639 -0
  149. package/dart_types/lib/src/proto/nomos/runtime/v2/sealing_desk.pbenum.dart +46 -0
  150. package/dart_types/lib/src/runtime_outcome.dart +133 -0
  151. package/dart_types/lib/src/subscriptions.dart +116 -23
  152. package/dart_types/pubspec.yaml +1 -1
  153. package/dart_types/test/extremum_subscriptions_test.dart +40 -0
  154. package/dart_types/test/foreign_ref_resolution_test.dart +75 -0
  155. package/dart_types/test/nomos_ref_address_test.dart +25 -0
  156. package/dart_types/test/ordered_subscriptions_test.dart +34 -0
  157. package/operation_contract_check.mjs +24 -0
  158. package/package.json +11 -6
  159. package/src/aggregate.ts +70 -10
  160. package/src/application_topology.ts +18 -11
  161. package/src/attested.ts +3 -0
  162. package/src/attested_core.ts +46 -3
  163. package/src/birth_meter_ensure.ts +19 -7
  164. package/src/births.ts +29 -4
  165. package/src/build_package.ts +140 -13
  166. package/src/business_birth.ts +181 -4
  167. package/src/codegen_call_proto.ts +1276 -0
  168. package/src/codegen_call_usda.ts +49 -6
  169. package/src/codegen_dart.ts +83 -44
  170. package/src/codegen_dart_client.ts +458 -800
  171. package/src/{codegen_dart_application.ts → codegen_dart_facade.ts} +331 -582
  172. package/src/codegen_dart_framework_calls.ts +320 -37
  173. package/src/codegen_dart_indexed_variant.ts +159 -0
  174. package/src/codegen_dart_scenario.ts +42 -13
  175. package/src/codegen_proof.ts +3 -3
  176. package/src/codegen_provider_dart.ts +5 -0
  177. package/src/codegen_query_proto.ts +158 -0
  178. package/src/codegen_refusal.ts +189 -0
  179. package/src/codegen_result_proto.ts +931 -0
  180. package/src/codegen_result_usda.ts +4 -0
  181. package/src/codegen_ts.ts +464 -616
  182. package/src/codegen_workspace_access.ts +105 -0
  183. package/src/compatibility_graph.ts +759 -0
  184. package/src/compatibility_usda.ts +264 -0
  185. package/src/compile_package_main.ts +2017 -1195
  186. package/src/custody_placement.ts +156 -0
  187. package/src/custody_placement_usda.ts +91 -0
  188. package/src/decl_shapes.ts +21 -2
  189. package/src/describe_model.ts +1 -0
  190. package/src/directive.ts +59 -2
  191. package/src/drivers.ts +51 -0
  192. package/src/engine_entry.ts +52 -8
  193. package/src/evolve_preview.ts +11 -4
  194. package/src/exists.ts +2 -0
  195. package/src/fields.ts +75 -11
  196. package/src/flow_gate.ts +2 -1
  197. package/src/framework/application_home.ts +170 -18
  198. package/src/framework/attested_receipts.ts +8 -3
  199. package/src/framework/auth_provider_law.ts +169 -9
  200. package/src/framework/birth_certificate_law.ts +4 -0
  201. package/src/framework/capability.ts +34 -3
  202. package/src/framework/child_authentication.ts +346 -0
  203. package/src/framework/console_governance.ts +295 -0
  204. package/src/framework/delegated_roles.ts +301 -0
  205. package/src/framework/domain_lifecycle.ts +26 -13
  206. package/src/framework/founding_genesis.ts +193 -0
  207. package/src/framework/framework_aggregates.ts +1 -1
  208. package/src/framework/genesis.ts +6 -1
  209. package/src/framework/governance_relations.ts +19 -0
  210. package/src/framework/home.ts +5 -734
  211. package/src/framework/legacy/child_auth_provider_epoch.ts +73 -0
  212. package/src/framework/model_authority_repair.ts +116 -0
  213. package/src/framework/model_authority_seed.ts +185 -0
  214. package/src/framework/model_transition.ts +211 -0
  215. package/src/framework/owned_custody.ts +1 -1
  216. package/src/framework/principal_enrollment.ts +302 -0
  217. package/src/framework/receiving_group.ts +857 -0
  218. package/src/framework/receiving_group_recovery.ts +314 -0
  219. package/src/framework/relations.ts +4 -8
  220. package/src/framework/signing_identity.ts +242 -95
  221. package/src/framework/source_roster.ts +11 -5
  222. package/src/framework/workspace_access.ts +149 -0
  223. package/src/framework/workspace_invariant.ts +73 -52
  224. package/src/framework/workspaces.ts +407 -40
  225. package/src/framework_call_definitions.ts +32 -3
  226. package/src/generated_client_bundle_boundary.ts +97 -0
  227. package/src/generated_client_names.ts +1 -0
  228. package/src/identity_usda.ts +27 -0
  229. package/src/index.ts +183 -12
  230. package/src/indexed_variant.ts +83 -0
  231. package/src/intent.ts +173 -45
  232. package/src/law_check.ts +8 -5
  233. package/src/law_dependency_index.ts +231 -0
  234. package/src/law_graph.ts +508 -71
  235. package/src/law_graph_usda.ts +170 -24
  236. package/src/law_identity.ts +119 -81
  237. package/src/law_source_map.ts +4 -1
  238. package/src/law_units.ts +498 -0
  239. package/src/law_value_type.ts +7 -3
  240. package/src/member_authentication.ts +302 -0
  241. package/src/null_meta.ts +1 -1
  242. package/src/openusd_compiled_layer.ts +132 -0
  243. package/src/operation_contract_check.ts +31 -0
  244. package/src/ordered_read.ts +2 -1
  245. package/src/package_config.ts +33 -12
  246. package/src/package_dependencies.ts +1 -0
  247. package/src/plan_source_slice.ts +30 -1
  248. package/src/protobuf_field_number.ts +36 -0
  249. package/src/provenance_wire.ts +6 -0
  250. package/src/query.ts +2 -1
  251. package/src/read.ts +50 -0
  252. package/src/relation.ts +4 -1
  253. package/src/relation_schema.ts +15 -2
  254. package/src/report.ts +2 -2
  255. package/src/sqlite_computation_bundle.ts +78 -0
  256. package/src/sqlite_computed.ts +102 -0
  257. package/src/sqlite_counts.ts +154 -0
  258. package/src/sqlite_layout.ts +823 -0
  259. package/src/sqlite_reads.ts +275 -0
  260. package/src/sqlite_samples.ts +185 -0
  261. package/src/stable_id_identity_bridge.ts +1 -1
  262. package/src/stable_ids_for_module.ts +16 -4
  263. package/src/test_call_usda.ts +6 -6
  264. package/src/usda_relationship_integrity.ts +20 -3
  265. package/src/usda_text.ts +21 -1
  266. package/src/value_object.ts +81 -11
  267. package/src/wire.ts +22 -0
  268. package/src/wire_encode.ts +227 -20
  269. package/src/workspace_sharding.ts +15 -6
  270. package/src/workspace_sharing.ts +51 -49
  271. package/src/zod_introspection.ts +2 -0
  272. package/compat_package.mjs +0 -46
  273. package/dart/lib/src/home.dart +0 -15
  274. package/dart/lib/src/runner/offline-install.mjs +0 -15367
  275. package/dart/lib/src/source_trust.dart +0 -51
  276. package/dart/lib/src/webview_bridge.dart +0 -6540
  277. package/dart/lib/src/workspace_name.dart +0 -30
  278. package/dart/test/acquire_session_credentials_test.dart +0 -150
  279. package/dart/test/acquire_session_test.dart +0 -534
  280. package/dart/test/adv_realm_interference_test.dart +0 -600
  281. package/dart/test/cloud_bound_topology_test.dart +0 -168
  282. package/dart/test/cluster_births_test.dart +0 -202
  283. package/dart/test/cluster_device_keyed_acceptance_test.dart +0 -105
  284. package/dart/test/cluster_local_autosync_test.dart +0 -123
  285. package/dart/test/cluster_scale_test.dart +0 -122
  286. package/dart/test/compat_test.dart +0 -61
  287. package/dart/test/dlq_session_surface_test.dart +0 -222
  288. package/dart/test/first_app_gauntlet_test.dart +0 -386
  289. package/dart/test/fixtures/wedge/fake-install.mjs +0 -11
  290. package/dart/test/four_layer_nested_topology_test.dart +0 -290
  291. package/dart/test/home_parity_test.dart +0 -13
  292. package/dart/test/local_sync_inert_test.dart +0 -107
  293. package/dart/test/node_runner_transport_test.dart +0 -78
  294. package/dart/test/parent_signer_recovery_test.dart +0 -155
  295. package/dart/test/residency_refusal_test.dart +0 -49
  296. package/dart/test/sealing_client_test.dart +0 -181
  297. package/dart/test/session_bound_test.dart +0 -285
  298. package/dart/test/stale_bundle_test.dart +0 -80
  299. package/dart/test/vo_read_typing_test.dart +0 -131
  300. package/dart/test/watch_after_offer_stall_test.dart +0 -100
  301. package/dart/test/webview_bridge_test.dart +0 -1930
  302. package/dart/test/workspace_name_test.dart +0 -24
  303. package/runtime/generated-application-host.js +0 -22860
  304. package/src/codegen_query_usda.ts +0 -214
  305. package/src/compat_manifest.ts +0 -26
  306. package/src/compat_package_main.ts +0 -224
  307. package/src/shape_compat.ts +0 -352
  308. package/src/usd_state.ts +0 -505
@@ -12,7 +12,7 @@
12
12
  * THE TENANT GAP THIS CLOSES: a structured value on an aggregate was a hand-rolled
13
13
  * `t.jsonObject()` plus a privately-kept zod schema per site — untyped in the generated
14
14
  * clients, invisible in the package IR. A value object is the NAMED, REUSABLE,
15
- * COMPOSABLE version of that same leaf:
15
+ * COMPOSABLE replacement for that opaque leaf:
16
16
  *
17
17
  * const PostalAddress = valueObject("PostalAddress", {
18
18
  * streetAddress: z.string(), city: z.string(), countryCode: z.string(),
@@ -28,13 +28,12 @@
28
28
  * ONE definition produces: the zod schema (the SAME zod the engine validates — embed
29
29
  * `SiteLocation.zod` in a directive payload), the inferred TS type (`SiteLocation.field()`
30
30
  * types `set()` and the generated `.client.ts` names the interface), the generated Dart
31
- * class (typed decode from the projection's json-object value), and a canonical
31
+ * class (typed decode from the projection's nominal value), and a canonical
32
32
  * descriptor that rides the package IR (the additive `nomosValueObjects` USD key) so
33
33
  * tooling/visualizers can SEE the structure.
34
34
  *
35
- * STORAGE / MERGE SEMANTICS (the v1 boundary — state it, don't blur it): a VO field IS
36
- * the existing `t.jsonObject()` value-leaf lane — ONE Lww-class leaf, replaced WHOLESALE
37
- * on write (exactly the encrypted-envelope rule). There is NO per-subfield merge: two
35
+ * STORAGE / MERGE SEMANTICS: a VO is encoded as named, recursively typed USDA prims. The field
36
+ * remains ONE Lww-class value, replaced WHOLESALE on write. There is NO per-subfield merge: two
38
37
  * concurrent writes to different subfields of one VO converge to ONE writer's whole
39
38
  * value. When subfields must merge independently, model them as aggregate fields or a
40
39
  * `t.map(...)` — that is what aggregates/maps are for.
@@ -79,6 +78,8 @@ type ZodInternalDef = {
79
78
  entries?: Record<string, string | number>;
80
79
  valueType?: z.ZodTypeAny;
81
80
  keyType?: z.ZodTypeAny;
81
+ shape?: Record<string, z.ZodTypeAny> | (() => Record<string, z.ZodTypeAny>);
82
+ options?: readonly z.ZodTypeAny[];
82
83
  };
83
84
  function zodDef(zt: z.ZodTypeAny): ZodInternalDef {
84
85
  const raw = zt as unknown as { _def?: ZodInternalDef; def?: ZodInternalDef };
@@ -88,6 +89,10 @@ function zodKind(zt: z.ZodTypeAny): string {
88
89
  const t = zodDef(zt).type;
89
90
  return typeof t === "string" ? t : "unknown";
90
91
  }
92
+ function zodObjectShape(zt: z.ZodTypeAny): Record<string, z.ZodTypeAny> {
93
+ const raw = zodDef(zt).shape;
94
+ return typeof raw === "function" ? raw() : raw ?? {};
95
+ }
91
96
  function zodEnumValues(zt: z.ZodTypeAny): string[] {
92
97
  const raw = zt as unknown as { options?: readonly unknown[] };
93
98
  if (raw.options !== undefined) return raw.options.map(String);
@@ -142,8 +147,8 @@ export interface ValueObject<T = unknown> {
142
147
  * for an optional aggregate FIELD use `.field().optional()`). */
143
148
  optional(): OptionalValueObject<T>;
144
149
  /**
145
- * Lower this VO to an aggregate FIELD: one `t.jsonObject()`-class value leaf
146
- * (kind `json`, `jsonShape:"object"`, Lww) whose whole value is this VO —
150
+ * Lower this VO to an aggregate FIELD: one nominal structural value (Lww) whose whole value is
151
+ * this VO —
147
152
  * WHOLESALE-REPLACE merge, no per-subfield merge (v1). Chain the usual field
148
153
  * modifiers: `.merge(Lww/LwwCausal/LwwBy)` (Lww-class only — the value is replaced
149
154
  * whole), `.optional()`, `.encrypted()` (under the existing `requires(cap)` rules).
@@ -152,7 +157,7 @@ export interface ValueObject<T = unknown> {
152
157
  /**
153
158
  * SUGAR for `.field().merge(driver)` — lower this VO DIRECTLY to an aggregate field with
154
159
  * the chosen merge driver: `location: SiteLocation.merge(Lww).optional()`. A VO field is
155
- * ONE Lww-class json leaf replaced WHOLESALE, so only the Lww-class family
160
+ * ONE Lww-class structural value replaced WHOLESALE, so only the Lww-class family
156
161
  * (`Lww` / `LwwCausal` / `LwwBy`) is accepted — any other driver throws (field-level merge
157
162
  * composition of a VO's subfields is a later slice; model those as aggregate fields / a
158
163
  * `t.map(...)`). The returned `Field` chains the usual modifiers (`.optional()`,
@@ -596,6 +601,10 @@ function describeZod(
596
601
  const inner = describeZod(voName, path, zodDef(schema).innerType!, deps);
597
602
  return { ...inner, optional: true };
598
603
  }
604
+ case "nullable": {
605
+ const inner = describeZod(voName, path, zodDef(schema).innerType!, deps);
606
+ return { ...inner, nullable: true };
607
+ }
599
608
  default:
600
609
  throw new Error(
601
610
  `valueObject '${voName}': field '${path}' uses zod shape '${zodKind(schema)}', which is outside ` +
@@ -651,6 +660,7 @@ function sampleOfDescriptorField(
651
660
  includeOptional = false,
652
661
  path = fieldName,
653
662
  ): unknown {
663
+ if (f.nullable === true) return null;
654
664
  switch (f.type) {
655
665
  case "string": return `sample-${fieldName}`;
656
666
  case "number": return 1;
@@ -769,7 +779,7 @@ function makeValueObject<T>(
769
779
  },
770
780
  merge(driver: Driver): Field<T, "json"> {
771
781
  const k = driver.kind;
772
- if (k !== "FirstWriterWins" && k !== "Lww" && k !== "LwwCausal" && k !== "LwwBy") {
782
+ if (k !== "FirstWriterWins" && k !== "FirstWriterCandidates" && k !== "Lww" && k !== "LwwCausal" && k !== "LwwBy") {
773
783
  throw new Error(
774
784
  `valueObject '${name}': .merge(${k}) is not a valid value-object field merge — a VO is ONE ` +
775
785
  `wholesale json leaf, so only FirstWriterWins / Lww / LwwCausal / LwwBy apply. Field-level ` +
@@ -867,6 +877,15 @@ type UnionValue<Arms extends Record<string, UnionArmShape>> = {
867
877
  [K in keyof Arms & string]: { type: K } & UnionArmValue<Arms[K]>;
868
878
  }[keyof Arms & string];
869
879
 
880
+ /** Construction provenance is compile-time only; the kernel validates the declared
881
+ * nominal type from authenticated law, never from a caller-supplied brand. */
882
+ declare const NOMINAL_CASE: unique symbol;
883
+ export type NominalCase<Name extends string, T> = T & { readonly [NOMINAL_CASE]: Name };
884
+ export type VariantCases<Name extends string, Arms extends Record<string, UnionArmShape>> = {
885
+ readonly [K in keyof Arms & string]: (value: UnionArmValue<Arms[K]>) =>
886
+ NominalCase<Name, { type: K } & UnionArmValue<Arms[K]>>;
887
+ };
888
+
870
889
  /**
871
890
  * Declare a NAMED SUM TYPE (`variant("Shape", { circle: {...}, rect: {...} })`). The blessed,
872
891
  * FIXED discriminator is `type` (the geo/RFC 7946 convention); its value is the arm key.
@@ -882,7 +901,7 @@ export function variant<const Name extends string, Arms extends Record<string, U
882
901
  name: Name,
883
902
  arms: Arms,
884
903
  opts?: { example?: UnionValue<Arms> },
885
- ): ValueObject<UnionValue<Arms>> {
904
+ ): ValueObject<UnionValue<Arms>> & { readonly name: Name; readonly cases: VariantCases<Name, Arms> } {
886
905
  const armKeys = Object.keys(arms).sort();
887
906
  if (armKeys.length === 0) throw new Error(`variant '${name}': at least one arm is required.`);
888
907
  const deps = new Map<string, ValueObjectDescriptor>();
@@ -935,7 +954,12 @@ export function variant<const Name extends string, Arms extends Record<string, U
935
954
  const zodSchema = (armZods.length === 1
936
955
  ? armZods[0]!
937
956
  : z.discriminatedUnion("type", armZods as [z.ZodObject<z.ZodRawShape>, ...z.ZodObject<z.ZodRawShape>[]])) as unknown as z.ZodType<UnionValue<Arms>>;
938
- return makeValueObject(name, zodSchema, descriptor, dependencies, undefined, opts?.example);
957
+ const cases = Object.fromEntries(armKeys.map((type) => [type,
958
+ (value: object) => ({ ...value, type }),
959
+ ])) as unknown as VariantCases<Name, Arms>;
960
+ return Object.assign(makeValueObject(name, zodSchema, descriptor, dependencies, undefined, opts?.example), {
961
+ name, cases: Object.freeze(cases),
962
+ });
939
963
  }
940
964
 
941
965
  // ── geo.* — the RFC 7946 geometry value objects ──────────────────────────────────────
@@ -1073,6 +1097,52 @@ export function collectPayloadValueObjectDescriptors(
1073
1097
  return [...byName.values()].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
1074
1098
  }
1075
1099
 
1100
+ /** Collect every named value object reachable from arbitrary public Zod
1101
+ * surfaces (notably derived/combined read results). This is the same nominal
1102
+ * vocabulary collector used for stored fields and directive payloads; a value
1103
+ * object does not stop being a public type merely because it is read-only. */
1104
+ export function collectSchemaValueObjectDescriptors(
1105
+ schemas: readonly unknown[],
1106
+ ): ValueObjectDescriptor[] {
1107
+ const byName = new Map<string, ValueObjectDescriptor>();
1108
+ const take = (meta: ValueObjectFieldMeta | undefined): void => {
1109
+ if (meta === undefined) return;
1110
+ for (const descriptor of [meta.descriptor, ...meta.dependencies]) {
1111
+ const existing = byName.get(descriptor.name);
1112
+ if (existing !== undefined && JSON.stringify(existing) !== JSON.stringify(descriptor)) {
1113
+ throw new Error(
1114
+ `value object '${descriptor.name}' has DIVERGENT shapes across public schemas — one name, one shape.`,
1115
+ );
1116
+ }
1117
+ byName.set(descriptor.name, descriptor);
1118
+ }
1119
+ };
1120
+ const visit = (candidate: unknown, active: Set<z.ZodTypeAny>): void => {
1121
+ if (typeof candidate !== "object" || candidate === null) return;
1122
+ const schema = candidate as z.ZodTypeAny;
1123
+ if (active.has(schema)) return;
1124
+ const next = new Set(active).add(schema);
1125
+ take(voMetaOf(schema));
1126
+ const kind = zodKind(schema);
1127
+ const def = zodDef(schema);
1128
+ if (kind === "optional" || kind === "default" || kind === "nullable") {
1129
+ visit(def.innerType, next);
1130
+ } else if (kind === "array") {
1131
+ visit(zodArrayElement(schema), next);
1132
+ } else if (kind === "record") {
1133
+ visit(def.valueType, next);
1134
+ } else if (kind === "object") {
1135
+ for (const value of Object.values(zodObjectShape(schema))) visit(value, next);
1136
+ } else if (kind === "union") {
1137
+ for (const value of def.options ?? []) visit(value, next);
1138
+ }
1139
+ };
1140
+ for (const schema of schemas) visit(schema, new Set());
1141
+ return [...byName.values()].sort((a, b) =>
1142
+ a.name < b.name ? -1 : a.name > b.name ? 1 : 0,
1143
+ );
1144
+ }
1145
+
1076
1146
  export function collectValueObjectDescriptors(
1077
1147
  aggregates: readonly { id: string; fields: Record<string, Field> }[],
1078
1148
  ): ValueObjectDescriptor[] {
package/src/wire.ts CHANGED
@@ -45,6 +45,7 @@ export type WireValue =
45
45
  };
46
46
  }
47
47
  | { Int: number }
48
+ | { ValueObject: WireValueObject }
48
49
  | { Set: string[] }
49
50
  | { Map: Record<string, WireField> }
50
51
  | {
@@ -72,6 +73,24 @@ export type WireValue =
72
73
  | { Position: string }
73
74
  | { TwoPhaseSet: { adds: string[]; removes: string[] } };
74
75
 
76
+ /** Recursive, nominal application value carried by a value-object field. This is the plan ABI's
77
+ * typed value algebra, not a serialized document: the kernel converts it directly from QuickJS
78
+ * objects into nested USDA prims without JSON stringify/parse or a generic map at the public API. */
79
+ export interface WireValueObject {
80
+ readonly typeName: string;
81
+ readonly fields: Readonly<Record<string, WireNominalValue>>;
82
+ }
83
+
84
+ export type WireNominalValue =
85
+ | { Null: true }
86
+ | { String: string }
87
+ | { Bool: boolean }
88
+ | { Int: number }
89
+ | { Double: number }
90
+ | { List: readonly WireNominalValue[] }
91
+ | { Map: Readonly<Record<string, WireNominalValue>> }
92
+ | { Object: WireValueObject };
93
+
75
94
  export interface WireOwnedDeviceRequest {
76
95
  v: number;
77
96
  workspace: string;
@@ -201,6 +220,8 @@ export interface WireIntent {
201
220
  | { readonly verifiedParent: true }
202
221
  | { readonly verifiedSource: true };
203
222
  targetCallUsda: Uint8Array;
223
+ /** Positions of selected earlier effects. The kernel resolves these to sealed digests. */
224
+ dependsOn?: readonly number[];
204
225
  }[];
205
226
  }
206
227
 
@@ -266,6 +287,7 @@ export interface WireReadAttestation {
266
287
  */
267
288
  export type WireDriver =
268
289
  | "FirstWriterWins"
290
+ | "FirstWriterCandidates"
269
291
  | "Lww"
270
292
  | "LwwCausal"
271
293
  | "AddWins"
@@ -40,12 +40,19 @@ import {
40
40
  OWNED_PREMINTED_RECORDS_PAYLOAD_FIELD,
41
41
  type AggregateHandle,
42
42
  } from "./aggregate.js";
43
- import { admitsNull, type Field, type EvidenceRefValue } from "./fields.js";
43
+ import {
44
+ admitsNull,
45
+ type Field,
46
+ type EvidenceRefValue,
47
+ type ValueObjectDescriptor,
48
+ type ValueObjectFieldDescriptor,
49
+ type ValueObjectFieldMeta,
50
+ } from "./fields.js";
44
51
  import type { Directive, ReferentialMarker } from "./directive.js";
45
- import { isBusinessIntent } from "./intent.js";
52
+ import { isBusinessIntent, validateWorkspaceEffectDependencies } from "./intent.js";
46
53
  import type { PlannedOp, FieldOp } from "./ops.js";
47
54
  import { deterministicPorts, type Ports } from "./ctx.js";
48
- import type { PrincipalFacet } from "./principal_evidence.js";
55
+ import { endorsementOf, type PrincipalFacet } from "./principal_evidence.js";
49
56
  import type {
50
57
  WireEvent,
51
58
  WireHlc,
@@ -55,6 +62,8 @@ import type {
55
62
  WireRefMode,
56
63
  WireSchema,
57
64
  WireValue,
65
+ WireNominalValue,
66
+ WireValueObject,
58
67
  } from "./wire.js";
59
68
  import {
60
69
  zodArrayElement,
@@ -113,6 +122,148 @@ function describeShape(value: unknown): string {
113
122
  return typeof value;
114
123
  }
115
124
 
125
+ function nominalRecordEntries(value: unknown, at: string): Record<string, unknown> {
126
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
127
+ throw new TypeError(`${at} must be an object`);
128
+ }
129
+ return value as Record<string, unknown>;
130
+ }
131
+
132
+ function encodeUntypedNominal(value: unknown, at: string): WireNominalValue {
133
+ if (typeof value === "string") return { String: value };
134
+ if (typeof value === "boolean") return { Bool: value };
135
+ if (typeof value === "number") {
136
+ if (!Number.isFinite(value)) throw new TypeError(`${at} must be finite`);
137
+ if (Number.isInteger(value)) {
138
+ if (!Number.isSafeInteger(value)) throw new TypeError(`${at} is outside JavaScript's exact integer range`);
139
+ return { Int: value };
140
+ }
141
+ return { Double: value };
142
+ }
143
+ if (Array.isArray(value)) {
144
+ return { List: value.map((member, index) => encodeUntypedNominal(member, `${at}[${index}]`)) };
145
+ }
146
+ const object = nominalRecordEntries(value, at);
147
+ return {
148
+ Map: Object.fromEntries(
149
+ Object.entries(object)
150
+ .filter(([, member]) => member !== undefined)
151
+ .sort(([left], [right]) => left.localeCompare(right))
152
+ .map(([key, member]) => [key, encodeUntypedNominal(member, `${at}.${key}`)]),
153
+ ),
154
+ };
155
+ }
156
+
157
+ function encodeDescribedNominal(
158
+ descriptor: ValueObjectFieldDescriptor,
159
+ value: unknown,
160
+ types: ReadonlyMap<string, ValueObjectDescriptor>,
161
+ at: string,
162
+ ): WireNominalValue {
163
+ if (value === null) {
164
+ if (descriptor.nullable === true) return { Null: true };
165
+ throw new TypeError(`${at} does not admit null`);
166
+ }
167
+ switch (descriptor.type) {
168
+ case "string":
169
+ case "enum":
170
+ case "literal":
171
+ if (typeof value !== "string") throw new TypeError(`${at} must be a string`);
172
+ return { String: value };
173
+ case "boolean":
174
+ if (typeof value !== "boolean") throw new TypeError(`${at} must be a boolean`);
175
+ return { Bool: value };
176
+ case "number":
177
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new TypeError(`${at} must be a finite number`);
178
+ if (descriptor.int === true) {
179
+ if (!Number.isSafeInteger(value)) throw new TypeError(`${at} must be an exact integer`);
180
+ return { Int: value };
181
+ }
182
+ return { Double: value };
183
+ case "array":
184
+ if (!Array.isArray(value)) throw new TypeError(`${at} must be an array`);
185
+ return { List: value.map((member, index) => encodeDescribedNominal(descriptor.items, member, types, `${at}[${index}]`)) };
186
+ case "map": {
187
+ const object = nominalRecordEntries(value, at);
188
+ return {
189
+ Map: Object.fromEntries(
190
+ Object.entries(object)
191
+ .sort(([left], [right]) => left.localeCompare(right))
192
+ .map(([key, member]) => [key, encodeDescribedNominal(descriptor.values, member, types, `${at}.${key}`)]),
193
+ ),
194
+ };
195
+ }
196
+ case "object":
197
+ case "geometry": {
198
+ const target = types.get(descriptor.ref);
199
+ if (target === undefined) throw new TypeError(`${at} references unknown value object '${descriptor.ref}'`);
200
+ return { Object: encodeNominalValueObject(target, value, types, at) };
201
+ }
202
+ }
203
+ }
204
+
205
+ /** Internal structural encoder, also used by independent storage-codec fixtures.
206
+ * Not a validator or admission API; callers must validate authored domain data.
207
+ */
208
+ export function encodeNominalValueObject(
209
+ descriptor: ValueObjectDescriptor,
210
+ value: unknown,
211
+ types: ReadonlyMap<string, ValueObjectDescriptor>,
212
+ at = descriptor.name,
213
+ ): WireValueObject {
214
+ const object = nominalRecordEntries(value, at);
215
+ if (descriptor.fields !== undefined) {
216
+ return {
217
+ typeName: descriptor.name,
218
+ fields: Object.fromEntries(
219
+ Object.entries(descriptor.fields)
220
+ .sort(([left], [right]) => left.localeCompare(right))
221
+ .flatMap(([key, member]) => {
222
+ const fieldValue = object[key];
223
+ if (fieldValue === undefined && member.optional === true) return [];
224
+ return [[key, encodeDescribedNominal(member, fieldValue, types, `${at}.${key}`)] as const];
225
+ }),
226
+ ),
227
+ };
228
+ }
229
+ if (descriptor.union !== undefined) {
230
+ const discriminator = object["type"];
231
+ if (typeof discriminator !== "string") throw new TypeError(`${at}.type must be a string discriminator`);
232
+ const arm = descriptor.union[discriminator];
233
+ if (arm === undefined) throw new TypeError(`${at}.type '${discriminator}' is not a declared arm`);
234
+ const fields: Record<string, WireNominalValue> = { type: { String: discriminator } };
235
+ if ("ref" in arm) {
236
+ const target = types.get(arm.ref);
237
+ if (target === undefined) throw new TypeError(`${at} references unknown value object '${arm.ref}'`);
238
+ fields["value"] = { Object: encodeNominalValueObject(target, object["value"], types, `${at}.value`) };
239
+ } else {
240
+ for (const [key, member] of Object.entries(arm.fields).sort(([left], [right]) => left.localeCompare(right))) {
241
+ const fieldValue = object[key];
242
+ if (fieldValue === undefined && member.optional === true) continue;
243
+ fields[key] = encodeDescribedNominal(member, fieldValue, types, `${at}.${key}`);
244
+ }
245
+ }
246
+ return { typeName: descriptor.name, fields };
247
+ }
248
+ // Geometry has a standard fixed shape. Preserve its actual scalar/list/map kinds while retaining
249
+ // the declared RFC 7946 nominal type at the enclosing prim.
250
+ return {
251
+ typeName: descriptor.name,
252
+ fields: Object.fromEntries(
253
+ Object.entries(object)
254
+ .sort(([left], [right]) => left.localeCompare(right))
255
+ .map(([key, member]) => [key, encodeUntypedNominal(member, `${at}.${key}`)]),
256
+ ),
257
+ };
258
+ }
259
+
260
+ function encodeValueObject(meta: ValueObjectFieldMeta, value: unknown): WireValueObject {
261
+ const types = new Map(
262
+ [meta.descriptor, ...meta.dependencies].map((descriptor) => [descriptor.name, descriptor] as const),
263
+ );
264
+ return encodeNominalValueObject(meta.descriptor, value, types);
265
+ }
266
+
116
267
  /** Map an authored op value onto the kernel `Value` shape, given the field kind.
117
268
  * `op` is passed purely for a NAMED diagnostic (aggregate + field + expected VO type) when a value
118
269
  * can't be lowered — never `field '?'`. */
@@ -186,6 +337,9 @@ function encodeKernelValue(field: Field | undefined, value: unknown, op?: FieldO
186
337
  if (field?.kind === "json" && voMetaOf(field.zod)?.name === OwnedDeviceRequestValue.name) {
187
338
  return { OwnedDeviceRequest: OwnedDeviceRequestValue.zod.parse(value) };
188
339
  }
340
+ if (field?.valueObject !== undefined) {
341
+ return { ValueObject: encodeValueObject(field.valueObject, field.zod.parse(value)) };
342
+ }
189
343
  // A `t.json()` leaf lowers to the FIRST-CLASS kernel `Value::Json` (RFC 8785 / JCS), NOT a
190
344
  // `Value::Str` holding JSON-shaped text. Checked BEFORE the scalar fallthroughs so EVERY json
191
345
  // value — object, array, string, number, boolean, null — commits as one consistent value kind
@@ -222,6 +376,9 @@ function encodeKernelValue(field: Field | undefined, value: unknown, op?: FieldO
222
376
  // entries, which the kernel's authz fold parses as Str. Routing strings to Json broke
223
377
  // relation seating repo-wide (warrant_flip/va_core/relation-bootstrap all red). Objects were
224
378
  // previously a REFUSAL here, so giving them the Json lane changes only the case that threw.
379
+ if (field?.kind === "map" && field.mapValueVo !== undefined) {
380
+ return { ValueObject: encodeValueObject(field.mapValueVo, value) };
381
+ }
225
382
  if (field?.kind === "map" && field.mapValueKind === "json" && typeof value === "object" && value !== null) {
226
383
  return { Json: value };
227
384
  }
@@ -243,14 +400,11 @@ function encodeKernelValue(field: Field | undefined, value: unknown, op?: FieldO
243
400
  `(instance/existing/ensure(TargetAgg, id)) so the field kind is known.`,
244
401
  );
245
402
  }
246
- // Name the expected VO: a top-level VO field carries it on `valueObject`, a VO-valued MAP entry on
247
- // `mapValueVo` (the inner value's VO metadata) — so a bad `t.map(VO.field())` entry names its VO too.
248
- const expected = field.valueObject?.name ?? field.mapValueVo?.name;
249
403
  const kindLabel = field.kind === "map" ? `map (${field.mapValueKind ?? "?"}-valued)` : field.kind;
250
404
  throw new Error(
251
- `${aggName}.${fieldName} is a '${kindLabel}' field${expected ? ` (value object ${expected})` : ""}, ` +
405
+ `${aggName}.${fieldName} is a '${kindLabel}' field, ` +
252
406
  `but got ${describeShape(value)} (${JSON.stringify(value)}) — kernel scalars are Str | Bool | Int. ` +
253
- `A structured value needs a declared value-object field; a set/map needs its additive write.`,
407
+ `A structured value needs a declared value-object field; a set/map needs its structural write.`,
254
408
  );
255
409
  }
256
410
 
@@ -320,7 +474,7 @@ function encodeKernelFieldOp(op: FieldOp, field: Field | undefined): WireFieldOp
320
474
  * to test domain LIFECYCLE.
321
475
  *
322
476
  * For a KERNEL-BACKED local harness — admission through the real gate, folds,
323
- * reads, minted ids, the whole lifecycle on the same engine plane the cloud
477
+ * reads, minted ids, the whole lifecycle on the same kernel host the cloud
324
478
  * edge runs — use the CLI, NOT this function:
325
479
  * • `githolon dev --once` — ONE compile → law-live-locally → proof cycle, then
326
480
  * exit (the CI/smoke lane for lifecycle + id-mint tests — a non-zero exit
@@ -418,20 +572,35 @@ export function executeDirectiveToIntent<P>(
418
572
  for (const b of births) meterOps.push(...planBirthRecordOps(b, subject, bornAt));
419
573
  }
420
574
  const planned = [...__drainAuthoring(), ...returned, ...meterOps];
421
- const workspaceEffects = isBusinessIntent(directive)
422
- ? directive.workspaceEffects.map((effect) => {
575
+ const declaredWorkspaceEffects = directive.workspaceEffects ?? [];
576
+ // Resolve names before filtering: a conditional predecessor that did not run must never
577
+ // disappear from the dependency graph and accidentally authorize its dependent.
578
+ validateWorkspaceEffectDependencies(declaredWorkspaceEffects);
579
+ const selectedWorkspaceEffects = declaredWorkspaceEffects
580
+ .filter(effect => effect.when?.(parsed, ctx) ?? true);
581
+ const selectedEffectIndices = new Map(selectedWorkspaceEffects
582
+ .flatMap((effect, index) => effect.key === undefined ? [] : [[effect.key, index] as const]));
583
+ const workspaceEffects = selectedWorkspaceEffects
584
+ .map((effect) => {
585
+ const dependsOn = (effect.after ?? []).map(key => {
586
+ const index = selectedEffectIndices.get(key);
587
+ if (index === undefined) {
588
+ throw new Error(`Directive '${directive.id}' requires workspace effect '${key}', but its condition did not select it`);
589
+ }
590
+ return index;
591
+ }).sort((a, b) => a - b);
423
592
  if (encodeWorkspaceEffect === undefined) {
424
593
  throw new Error(
425
594
  `Directive '${directive.id}' declares a workspace effect, but its compiled plan has no target-call encoder`,
426
595
  );
427
596
  }
428
597
  const workspace = effect.destination.kind === "workspace"
429
- ? effect.destination.address(parsed)
598
+ ? effect.destination.address(parsed, ctx)
430
599
  : "";
431
600
  if (effect.destination.kind === "workspace" && workspace.trim().length === 0) {
432
601
  throw new Error(`Directive '${directive.id}' produced an empty target workspace address`);
433
602
  }
434
- const targetPayload = effect.target.payloadSchema.parse(effect.payload(parsed));
603
+ const targetPayload = effect.target.payloadSchema.parse(effect.payload(parsed, ctx));
435
604
  return {
436
605
  destination: effect.destination.kind === "workspace"
437
606
  ? { workspace }
@@ -443,9 +612,9 @@ export function executeDirectiveToIntent<P>(
443
612
  targetPayload,
444
613
  workspace,
445
614
  ),
615
+ ...(dependsOn.length ? { dependsOn } : {}),
446
616
  };
447
- })
448
- : [];
617
+ });
449
618
  // Workspace-effect payload mappers are source law and may perform declared captured reads while
450
619
  // constructing the target payload. Drain only after both local and cross-workspace consequences.
451
620
  const reads = __drainReads();
@@ -525,14 +694,25 @@ export function executeDirectiveToIntent<P>(
525
694
  bucket.push(encodeKernelFieldOp(op, field));
526
695
  }
527
696
 
697
+ // A `.creates` directive that also declares `.alsoMutates` of its own type (a replacement that supersedes
698
+ // the row it replaces) addresses TWO rows of that type: the one `create()` marks, and an existing one it
699
+ // reaches with `existing(...)`. Only the marked row is created; the other is the declared mutate, or the
700
+ // referential gate refuses it as a create of an id that already exists.
701
+ const mutatesOwnType =
702
+ directive.marker === "creates" &&
703
+ (directive.secondaryOutputs ?? []).some(
704
+ (output) => output.marker === "mutates" && output.aggregateId === agg.id,
705
+ ) &&
706
+ order.some((key) => ownTarget.get(key) === true && explicitMarker.get(key) === "Create");
528
707
  const events: WireEvent[] = order.map((bucketKey) => {
529
708
  const aggregate = idOf.get(bucketKey)!;
530
709
  const ops = byAggregate.get(bucketKey)!;
531
710
  const ty = typeOf.get(bucketKey);
532
711
  // The directive's marker applies to its OWN target; a FAN-OUT bucket is a Mutate of an
533
712
  // existing sibling aggregate (so the id-mint gate never sees a spurious Create for it).
713
+ const ownMarker = mutatesOwnType && ty !== undefined ? "Mutate" : marker;
534
714
  const eventMarker: WireRefMode =
535
- explicitMarker.get(bucketKey) ?? (ownTarget.get(bucketKey) ? marker : "Mutate");
715
+ explicitMarker.get(bucketKey) ?? (ownTarget.get(bucketKey) ? ownMarker : "Mutate");
536
716
  if (ty !== undefined) {
537
717
  // Stamp provenance FIRST so the fold carries the TYPE + canonical id the
538
718
  // structured `view()` projects (the same `__type`/`__id` reserved fields the
@@ -624,6 +804,14 @@ function principalFlowProbeValue(
624
804
  }
625
805
  }
626
806
 
807
+ /** Does `value` hold principal evidence minted by THIS DSL instance anywhere inside it? */
808
+ function carriesUnloweredEvidence(value: unknown, seen = new Set<object>()): boolean {
809
+ if (typeof value !== "object" || value === null || seen.has(value)) return false;
810
+ seen.add(value);
811
+ if (endorsementOf(value) !== null) return true;
812
+ return Object.values(value as Record<string, unknown>).some(inner => carriesUnloweredEvidence(inner, seen));
813
+ }
814
+
627
815
  /** Extract the principal flows carried by a plan's field ops (those with an `endorsement`). The ONE
628
816
  * op→flow mapping shared by the discovery harvest (compile) and the runtime footprint (Slice B). */
629
817
  function flowsFromOps(ops: readonly FieldOp[]): PrincipalFlow[] {
@@ -646,8 +834,14 @@ function flowsFromOps(ops: readonly FieldOp[]): PrincipalFlow[] {
646
834
  * the omission is SAFE because the runtime/replay witness (Slice B) refuses any observed flow absent
647
835
  * from the manifest. The reasons are static law, so they are intent-independent. SORTED for determinism.
648
836
  */
649
- export function harvestPrincipalFlows(directive: Directive<unknown>, domain?: string): PrincipalFlow[] {
650
- const ctx: Ports = deterministicPorts({ principal: "principal:probe" });
837
+ export function harvestPrincipalFlows<P>(directive: Directive<P>, domain?: string): PrincipalFlow[] {
838
+ // Every composed local step is part of the declared law. A later captured read
839
+ // can stop a synthetic probe; it must not erase flows already declared by an
840
+ // earlier step (notably the invitation's possession-proven authority grant).
841
+ const stepFlows = isBusinessIntent(directive)
842
+ ? directive.steps.flatMap(step => harvestPrincipalFlows(step.directive, domain)) : [];
843
+ const ctx: Ports = deterministicPorts({ principal: directive.acceptsVerifiedWorkspaceClaimGate
844
+ ? "workspace:probe" : "user:probe" });
651
845
  let candidate: unknown;
652
846
  try {
653
847
  candidate = principalFlowProbeValue(directive.payloadSchema as z.ZodTypeAny);
@@ -696,11 +890,24 @@ export function harvestPrincipalFlows(directive: Directive<unknown>, domain?: st
696
890
  ops = [...__drainAuthoring(), ...(Array.isArray(returned) ? returned : [])].filter((op): op is FieldOp => op.kind !== "strike");
697
891
  } catch {
698
892
  try { __drainAuthoring(); __drainReads(); } catch { /* sink already drained */ }
699
- return [];
893
+ return stepFlows;
700
894
  } finally {
701
895
  if (domain !== undefined) __setDispatchDomain(undefined);
702
896
  if (hadNomos && prior !== undefined) g.nomos = prior; else delete g.nomos;
703
897
  }
704
898
  __drainReads();
705
- return flowsFromOps(ops);
899
+ const ownFlows = flowsFromOps(ops);
900
+ // AN UNHARVESTABLE ENDORSEMENT IS AN ERROR, not a weaker law. Principal evidence the plan converted
901
+ // (asAuthoritySubject/asStandingParty) is lowered to its raw principal by the framework before it
902
+ // reaches an op. Evidence still inside an op was never lowered: the plan ran against a second
903
+ // @githolon/dsl instance whose registry cannot see this one's endorsements, so the flow would be
904
+ // missing from the compiled manifest.
905
+ if (ops.some(op => carriesUnloweredEvidence(op))) {
906
+ throw new Error(
907
+ `principal-flow discovery for '${directive.id}' found converted principal evidence its plan never ` +
908
+ `lowered: the plan ran against a second @githolon/dsl instance, so its endorsed flows cannot be harvested`,
909
+ );
910
+ }
911
+ return [...stepFlows, ...ownFlows].filter((flow, index, all) =>
912
+ all.findIndex(candidate => candidate.sink === flow.sink && candidate.facet === flow.facet && candidate.reason === flow.reason) === index);
706
913
  }
@@ -79,6 +79,7 @@ import { query } from "./query.js";
79
79
  import { read } from "./read.js";
80
80
  import { sum } from "./sum.js";
81
81
  import { min, max } from "./extremum.js";
82
+ import { valueObject } from "./value_object.js";
82
83
  import {
83
84
  idItem,
84
85
  idPayload,
@@ -439,18 +440,26 @@ export const nomosReceiveAssignment = directive("nomosReceiveAssignment")
439
440
  * receipt-leg obligation ALONGSIDE the unchanged assignment flip; the host orchestration still runs
440
441
  * the move (the obligation is observable on the outbox, not yet consumed). `architecture/sharding_as_outbound_offers.md`.
441
442
  */
443
+ export const NomosMoveHomeKeys = valueObject("NomosMoveHomeKeys", {
444
+ values: z.array(z.string().min(1)).min(1).max(64),
445
+ });
446
+
447
+ export const NomosMigratedIntentIds = valueObject("NomosMigratedIntentIds", {
448
+ values: z.array(z.string().min(1)).max(1024),
449
+ });
450
+
442
451
  export const NomosMoveObligation = impureCapabilityAggregate("NomosMoveObligation", {
443
452
  // the move-leg params (sealed as typed USDA values in the outbound offer):
444
453
  fromShard: t.string().merge(Lww),
445
454
  toShard: t.string().merge(Lww),
446
- homeKeys: t.json().merge(Lww), // the moving home keys (the leg's work list)
455
+ homeKeys: NomosMoveHomeKeys.field().merge(Lww), // the moving home keys (the leg's work list)
447
456
  leg: t.string().merge(Lww), // one of MOVE_LEGS
448
457
  mapVersion: t.int().merge(Lww), // the law-derived map version (S3a), ferried leg-to-leg
449
458
  // S3b — the target shard's ADMITTED set, accumulated migrate→seal (the cross-leg state seal-home
450
459
  // needs to compute `missing` against the source chain). The LAW unions it (the adapter only ferries
451
460
  // arrays it read off the obligation + the shard response — it never accumulates), so the host holds no
452
461
  // cross-leg move state and an adapter restart re-derives everything from the on-chain obligation.
453
- migratedIds: t.json().merge(Lww), // JSON string array; "[]" until the migrate leg's receipt
462
+ migratedIds: NomosMigratedIntentIds.field().merge(Lww), // empty until the migrate leg's receipt
454
463
  });
455
464
 
456
465
  /** S1 receipt-leg obligation window (ms) — the leg's soft deadline past its scheduledAt. */
@@ -509,10 +518,10 @@ export const nomosSplitShard = directive("nomosSplitShard")
509
518
  .set("status", "requested")
510
519
  .set("fromShard", p.fromShard)
511
520
  .set("toShard", p.toShard)
512
- .set("homeKeys", JSON.stringify(p.homeKeys)) // t.json() stores the stringified JSON
521
+ .set("homeKeys", { values: p.homeKeys })
513
522
  .set("leg", "receipt")
514
523
  .set("mapVersion", newMapVersion)
515
- .set("migratedIds", "[]"); // nothing migrated yet — the migrate leg's receipt accumulates it
524
+ .set("migratedIds", { values: [] }); // nothing migrated yet — the migrate leg's receipt accumulates it
516
525
  return ops;
517
526
  })
518
527
  .reads(nomosShardAssignmentByHomeKey);
@@ -573,10 +582,10 @@ export const nomosAdvanceMove = directive("nomosAdvanceMove")
573
582
  .set("status", "requested")
574
583
  .set("fromShard", p.fromShard)
575
584
  .set("toShard", p.toShard)
576
- .set("homeKeys", JSON.stringify(p.homeKeys))
585
+ .set("homeKeys", { values: p.homeKeys })
577
586
  .set("leg", nextLeg)
578
587
  .set("mapVersion", p.mapVersion) // carry the law-derived version forward (S3a)
579
- .set("migratedIds", JSON.stringify(accMigrated)); // S3b — the accumulated admitted set
588
+ .set("migratedIds", { values: accMigrated }); // S3b — the accumulated admitted set
580
589
  }
581
590
  return ops;
582
591
  });