@githolon/dsl 0.108.6 → 0.110.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 (307) 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 +76 -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 +211 -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 +267 -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 +23905 -15774
  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 +548 -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_span_trace_test.dart +23 -0
  74. package/dart/test/replica_readiness_test.dart +156 -0
  75. package/dart/test/runtime_projections_test.dart +92 -0
  76. package/dart/test/runtime_v2_gateway_test.dart +131 -0
  77. package/dart/test/signing_identity_kernel_test.dart +75 -0
  78. package/dart/test/sync_status_preparation_report_test.dart +29 -0
  79. package/dart/test/sync_status_reducer_test.dart +419 -0
  80. package/dart/test/test_harness_test.dart +71 -95
  81. package/dart/test/typed_query_watch_test.dart +170 -0
  82. package/dart/test/typed_watch_registry_test.dart +249 -0
  83. package/dart_lints/README.md +18 -0
  84. package/dart_lints/lib/nomos_lints.dart +70 -0
  85. package/dart_lints/pubspec.lock +580 -0
  86. package/dart_lints/pubspec.yaml +12 -0
  87. package/dart_lints/test/reference_identity_test.dart +75 -0
  88. package/dart_types/CHANGELOG.md +18 -0
  89. package/dart_types/lib/nomos_kernel_protocol.dart +21 -1
  90. package/dart_types/lib/nomos_types.dart +9 -1
  91. package/dart_types/lib/src/admission_decision.dart +37 -0
  92. package/dart_types/lib/src/aggregate_meta.dart +1 -1
  93. package/dart_types/lib/src/approved_model.dart +65 -0
  94. package/dart_types/lib/src/custody.dart +10 -18
  95. package/dart_types/lib/src/error_info.dart +4 -4
  96. package/dart_types/lib/src/errors.dart +29 -29
  97. package/dart_types/lib/src/foreign_ref.dart +62 -33
  98. package/dart_types/lib/src/local_work_rejected.dart +3 -3
  99. package/dart_types/lib/src/nomos_ref.dart +3 -1
  100. package/dart_types/lib/src/principal.dart +2 -2
  101. package/dart_types/lib/src/proto/google/protobuf/any.pb.dart +163 -0
  102. package/dart_types/lib/src/proto/google/protobuf/any.pbenum.dart +11 -0
  103. package/dart_types/lib/src/proto/nomos/discovery/v1/workspace_placement.pb.dart +137 -0
  104. package/dart_types/lib/src/proto/nomos/discovery/v1/workspace_placement.pbenum.dart +11 -0
  105. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +20721 -10433
  106. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +555 -27
  107. package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pb.dart +387 -0
  108. package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pbenum.dart +46 -0
  109. package/dart_types/lib/src/proto/nomos/runtime/v2/application_readiness_desk.pb.dart +295 -0
  110. package/dart_types/lib/src/proto/nomos/runtime/v2/application_readiness_desk.pbenum.dart +139 -0
  111. package/dart_types/lib/src/proto/nomos/runtime/v2/authentication_desk.pb.dart +269 -0
  112. package/dart_types/lib/src/proto/nomos/runtime/v2/authentication_desk.pbenum.dart +103 -0
  113. package/dart_types/lib/src/proto/nomos/runtime/v2/compatibility_desk.pb.dart +417 -0
  114. package/dart_types/lib/src/proto/nomos/runtime/v2/compatibility_desk.pbenum.dart +62 -0
  115. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +9968 -0
  116. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pbenum.dart +133 -0
  117. package/dart_types/lib/src/proto/nomos/runtime/v2/credentials_desk.pb.dart +204 -0
  118. package/dart_types/lib/src/proto/nomos/runtime/v2/credentials_desk.pbenum.dart +54 -0
  119. package/dart_types/lib/src/proto/nomos/runtime/v2/custody_desk.pb.dart +1550 -0
  120. package/dart_types/lib/src/proto/nomos/runtime/v2/custody_desk.pbenum.dart +11 -0
  121. package/dart_types/lib/src/proto/nomos/runtime/v2/framework.pb.dart +710 -0
  122. package/dart_types/lib/src/proto/nomos/runtime/v2/framework.pbenum.dart +11 -0
  123. package/dart_types/lib/src/proto/nomos/runtime/v2/identity_desk.pb.dart +845 -0
  124. package/dart_types/lib/src/proto/nomos/runtime/v2/identity_desk.pbenum.dart +11 -0
  125. package/dart_types/lib/src/proto/nomos/runtime/v2/interaction_desk.pb.dart +198 -0
  126. package/dart_types/lib/src/proto/nomos/runtime/v2/interaction_desk.pbenum.dart +11 -0
  127. package/dart_types/lib/src/proto/nomos/runtime/v2/law_desk.pb.dart +369 -0
  128. package/dart_types/lib/src/proto/nomos/runtime/v2/law_desk.pbenum.dart +11 -0
  129. package/dart_types/lib/src/proto/nomos/runtime/v2/maintenance_desk.pb.dart +609 -0
  130. package/dart_types/lib/src/proto/nomos/runtime/v2/maintenance_desk.pbenum.dart +65 -0
  131. package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +358 -0
  132. package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pbenum.dart +100 -0
  133. package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pb.dart +2231 -0
  134. package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pbenum.dart +271 -0
  135. package/dart_types/lib/src/proto/nomos/runtime/v2/placement_desk.pb.dart +1286 -0
  136. package/dart_types/lib/src/proto/nomos/runtime/v2/placement_desk.pbenum.dart +11 -0
  137. package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pb.dart +636 -0
  138. package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pbenum.dart +116 -0
  139. package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pb.dart +1637 -0
  140. package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pbenum.dart +175 -0
  141. package/dart_types/lib/src/proto/nomos/runtime/v2/recovery_desk.pb.dart +1269 -0
  142. package/dart_types/lib/src/proto/nomos/runtime/v2/recovery_desk.pbenum.dart +129 -0
  143. package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pb.dart +2782 -0
  144. package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pbenum.dart +97 -0
  145. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +2232 -8627
  146. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pbenum.dart +0 -358
  147. package/dart_types/lib/src/proto/nomos/runtime/v2/sealing_desk.pb.dart +1639 -0
  148. package/dart_types/lib/src/proto/nomos/runtime/v2/sealing_desk.pbenum.dart +46 -0
  149. package/dart_types/lib/src/runtime_outcome.dart +133 -0
  150. package/dart_types/lib/src/subscriptions.dart +116 -23
  151. package/dart_types/pubspec.yaml +1 -1
  152. package/dart_types/test/extremum_subscriptions_test.dart +40 -0
  153. package/dart_types/test/foreign_ref_resolution_test.dart +75 -0
  154. package/dart_types/test/nomos_ref_address_test.dart +25 -0
  155. package/dart_types/test/ordered_subscriptions_test.dart +34 -0
  156. package/operation_contract_check.mjs +24 -0
  157. package/package.json +11 -6
  158. package/src/aggregate.ts +70 -10
  159. package/src/application_topology.ts +18 -11
  160. package/src/attested.ts +3 -0
  161. package/src/attested_core.ts +46 -3
  162. package/src/birth_meter_ensure.ts +19 -7
  163. package/src/births.ts +29 -4
  164. package/src/build_package.ts +140 -13
  165. package/src/business_birth.ts +181 -4
  166. package/src/codegen_call_proto.ts +1274 -0
  167. package/src/codegen_call_usda.ts +49 -6
  168. package/src/codegen_dart.ts +79 -44
  169. package/src/codegen_dart_client.ts +458 -800
  170. package/src/{codegen_dart_application.ts → codegen_dart_facade.ts} +331 -582
  171. package/src/codegen_dart_framework_calls.ts +320 -37
  172. package/src/codegen_dart_indexed_variant.ts +159 -0
  173. package/src/codegen_dart_scenario.ts +42 -13
  174. package/src/codegen_proof.ts +3 -3
  175. package/src/codegen_provider_dart.ts +5 -0
  176. package/src/codegen_query_proto.ts +158 -0
  177. package/src/codegen_refusal.ts +189 -0
  178. package/src/codegen_result_proto.ts +931 -0
  179. package/src/codegen_result_usda.ts +4 -0
  180. package/src/codegen_ts.ts +464 -616
  181. package/src/codegen_workspace_access.ts +105 -0
  182. package/src/compatibility_graph.ts +759 -0
  183. package/src/compatibility_usda.ts +264 -0
  184. package/src/compile_package_main.ts +2083 -1248
  185. package/src/custody_placement.ts +156 -0
  186. package/src/custody_placement_usda.ts +91 -0
  187. package/src/decl_shapes.ts +21 -2
  188. package/src/describe_model.ts +1 -0
  189. package/src/directive.ts +59 -2
  190. package/src/drivers.ts +51 -0
  191. package/src/engine_entry.ts +52 -8
  192. package/src/evolve_preview.ts +11 -4
  193. package/src/exists.ts +2 -0
  194. package/src/fields.ts +75 -11
  195. package/src/flow_gate.ts +2 -1
  196. package/src/framework/application_home.ts +170 -18
  197. package/src/framework/attested_receipts.ts +8 -3
  198. package/src/framework/auth_provider_law.ts +169 -9
  199. package/src/framework/birth_certificate_law.ts +4 -0
  200. package/src/framework/capability.ts +34 -3
  201. package/src/framework/child_authentication.ts +346 -0
  202. package/src/framework/console_governance.ts +295 -0
  203. package/src/framework/delegated_roles.ts +301 -0
  204. package/src/framework/domain_lifecycle.ts +26 -13
  205. package/src/framework/founding_genesis.ts +193 -0
  206. package/src/framework/framework_aggregates.ts +1 -1
  207. package/src/framework/genesis.ts +6 -1
  208. package/src/framework/governance_relations.ts +19 -0
  209. package/src/framework/home.ts +5 -734
  210. package/src/framework/legacy/child_auth_provider_epoch.ts +73 -0
  211. package/src/framework/model_authority_repair.ts +116 -0
  212. package/src/framework/model_authority_seed.ts +185 -0
  213. package/src/framework/model_transition.ts +211 -0
  214. package/src/framework/owned_custody.ts +1 -1
  215. package/src/framework/principal_enrollment.ts +302 -0
  216. package/src/framework/receiving_group.ts +857 -0
  217. package/src/framework/receiving_group_recovery.ts +314 -0
  218. package/src/framework/relations.ts +4 -8
  219. package/src/framework/signing_identity.ts +242 -95
  220. package/src/framework/source_roster.ts +11 -5
  221. package/src/framework/workspace_access.ts +149 -0
  222. package/src/framework/workspace_invariant.ts +73 -52
  223. package/src/framework/workspaces.ts +407 -40
  224. package/src/framework_call_definitions.ts +32 -3
  225. package/src/generated_client_bundle_boundary.ts +97 -0
  226. package/src/generated_client_names.ts +1 -0
  227. package/src/identity_usda.ts +27 -0
  228. package/src/index.ts +183 -12
  229. package/src/indexed_variant.ts +83 -0
  230. package/src/intent.ts +173 -45
  231. package/src/law_check.ts +8 -5
  232. package/src/law_dependency_index.ts +231 -0
  233. package/src/law_graph.ts +508 -71
  234. package/src/law_graph_usda.ts +170 -24
  235. package/src/law_identity.ts +119 -81
  236. package/src/law_source_map.ts +4 -1
  237. package/src/law_units.ts +498 -0
  238. package/src/law_value_type.ts +7 -3
  239. package/src/member_authentication.ts +302 -0
  240. package/src/null_meta.ts +1 -1
  241. package/src/openusd_compiled_layer.ts +132 -0
  242. package/src/operation_contract_check.ts +31 -0
  243. package/src/ordered_read.ts +2 -1
  244. package/src/package_config.ts +33 -12
  245. package/src/package_dependencies.ts +1 -0
  246. package/src/plan_source_slice.ts +30 -1
  247. package/src/protobuf_field_number.ts +36 -0
  248. package/src/provenance_wire.ts +6 -0
  249. package/src/query.ts +2 -1
  250. package/src/read.ts +50 -0
  251. package/src/relation.ts +4 -1
  252. package/src/relation_schema.ts +15 -2
  253. package/src/report.ts +2 -2
  254. package/src/sqlite_computation_bundle.ts +78 -0
  255. package/src/sqlite_computed.ts +102 -0
  256. package/src/sqlite_counts.ts +154 -0
  257. package/src/sqlite_layout.ts +823 -0
  258. package/src/sqlite_reads.ts +275 -0
  259. package/src/sqlite_samples.ts +185 -0
  260. package/src/stable_id_identity_bridge.ts +1 -1
  261. package/src/stable_ids_for_module.ts +16 -4
  262. package/src/test_call_usda.ts +6 -6
  263. package/src/usda_relationship_integrity.ts +20 -3
  264. package/src/usda_text.ts +21 -1
  265. package/src/value_object.ts +81 -11
  266. package/src/wire.ts +22 -0
  267. package/src/wire_encode.ts +227 -20
  268. package/src/workspace_sharding.ts +15 -6
  269. package/src/workspace_sharing.ts +51 -49
  270. package/src/zod_introspection.ts +2 -0
  271. package/compat_package.mjs +0 -46
  272. package/dart/lib/src/home.dart +0 -15
  273. package/dart/lib/src/runner/offline-install.mjs +0 -15367
  274. package/dart/lib/src/source_trust.dart +0 -51
  275. package/dart/lib/src/webview_bridge.dart +0 -6540
  276. package/dart/lib/src/workspace_name.dart +0 -30
  277. package/dart/test/acquire_session_credentials_test.dart +0 -150
  278. package/dart/test/acquire_session_test.dart +0 -534
  279. package/dart/test/adv_realm_interference_test.dart +0 -600
  280. package/dart/test/cloud_bound_topology_test.dart +0 -168
  281. package/dart/test/cluster_births_test.dart +0 -202
  282. package/dart/test/cluster_device_keyed_acceptance_test.dart +0 -105
  283. package/dart/test/cluster_local_autosync_test.dart +0 -123
  284. package/dart/test/cluster_scale_test.dart +0 -122
  285. package/dart/test/compat_test.dart +0 -61
  286. package/dart/test/dlq_session_surface_test.dart +0 -222
  287. package/dart/test/first_app_gauntlet_test.dart +0 -386
  288. package/dart/test/fixtures/wedge/fake-install.mjs +0 -11
  289. package/dart/test/four_layer_nested_topology_test.dart +0 -290
  290. package/dart/test/home_parity_test.dart +0 -13
  291. package/dart/test/local_sync_inert_test.dart +0 -107
  292. package/dart/test/node_runner_transport_test.dart +0 -78
  293. package/dart/test/parent_signer_recovery_test.dart +0 -155
  294. package/dart/test/residency_refusal_test.dart +0 -49
  295. package/dart/test/sealing_client_test.dart +0 -181
  296. package/dart/test/session_bound_test.dart +0 -285
  297. package/dart/test/stale_bundle_test.dart +0 -80
  298. package/dart/test/vo_read_typing_test.dart +0 -131
  299. package/dart/test/watch_after_offer_stall_test.dart +0 -100
  300. package/dart/test/webview_bridge_test.dart +0 -1930
  301. package/dart/test/workspace_name_test.dart +0 -24
  302. package/runtime/generated-application-host.js +0 -22860
  303. package/src/codegen_query_usda.ts +0 -214
  304. package/src/compat_manifest.ts +0 -26
  305. package/src/compat_package_main.ts +0 -224
  306. package/src/shape_compat.ts +0 -352
  307. package/src/usd_state.ts +0 -505
@@ -0,0 +1,295 @@
1
+ // NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
2
+ // remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
3
+ // wasm32-wasip1 artifact {kernel · projection · embedded QuickJS engine}.
4
+ // If a file isn't this / hosting this / authoring for this / proving this — it's gone.
5
+
6
+ /**
7
+ * framework/console_governance.ts — THE CONSOLE'S LAWFUL REACH INTO EVERY WORKSPACE OF ITS JURISDICTION
8
+ * (identity target model D15, D18, D22, D23).
9
+ *
10
+ * A founded child inherits its parent's `operator` and `principalScope:<s>#enroller` tuples (the founding
11
+ * step). Three things make that inheritance usable, and let the real operators replace a stand-in without
12
+ * re-founding anything:
13
+ *
14
+ * 1. **Keys and law authority at founding** — {@link seedInheritedGovernance}. The kernel's signer rule
15
+ * reads `identity:P#signer` only from the child's own state, and law is installed under
16
+ * `workspace:self#installDomain`, so an inherited principal holding neither can do nothing there.
17
+ * The step copies, once, every ACTIVE key the parent holds for each inherited operator and enroller,
18
+ * and seats `installDomain` for each inherited operator (an operator is who installs law, D15). Keys
19
+ * live on every workspace: that is what keeps each one independently replayable. It also copies the
20
+ * parent's active admission-checkpoint workload keys, so a child can receive workspace effects from
21
+ * its first moment instead of waiting for an operator to seat its custodian's key.
22
+ * 2. **Operator handover after birth** — {@link grantConsoleOperator} / {@link revokeConsoleOperator},
23
+ * gated on `operator` itself (a founded child's admin is its founder, a user, who is not the
24
+ * jurisdiction). Only `console:` principals, and always the PAIR `operator` + `installDomain`.
25
+ * Operators already hold `installDomain` (total authority over law), so letting them pass it on grants
26
+ * nothing new. A revoke that would leave no operator is refused.
27
+ * 3. **Key rotation for console principals** — {@link enrollConsoleSigner} / {@link revokeConsoleSigner},
28
+ * self-bound: a principal signs with a current key to add or revoke one of ITS OWN keys, never anyone
29
+ * else's, and never its last one. `console:` only: device self-enrolment for other scopes stays with
30
+ * their enrollers.
31
+ */
32
+ import { z } from "zod";
33
+ import { directive } from "../directive.js";
34
+ import { readFrameworkDeclaredRows } from "../read.js";
35
+ import { CONSOLE_PRINCIPAL_SCOPE, principalScopeOf } from "../member_authentication.js";
36
+ import { endorsementOf } from "../principal_evidence.js";
37
+ import { GENESIS_WARRANT_ID } from "./genesis.js";
38
+ import { MODEL_LIFECYCLE_AUTHORITY } from "./domain_lifecycle.js";
39
+ import { WORKSPACE_OPERATOR_RELATION } from "./governance_relations.js";
40
+ import { RelationTuple, relationTupleInstanceId, relationTuplesByObject, removeTuple, writeTuple } from "./relations.js";
41
+ import {
42
+ admissionCheckpointSigner,
43
+ readAdmissionCheckpointSigners,
44
+ signerObject,
45
+ SIGNER_RELATION,
46
+ writeAdmissionCheckpointSigners,
47
+ type AdmissionCheckpointSigner,
48
+ } from "./signing_identity.js";
49
+ import { readRef, readSet, refAs, workspaceInvariant } from "./workspace_invariant.js";
50
+
51
+ /** The `grantedBy` of everything the founding step {@link seedInheritedGovernance} seats. */
52
+ export const INHERITED_SIGNER_GRANTOR = "inherited-at-birth" as const;
53
+ export const SEED_INHERITED_GOVERNANCE_DIRECTIVE = "seedInheritedGovernance" as const;
54
+
55
+ const sha256Hex = z.string().regex(/^[0-9a-f]{64}$/, "expected lowercase sha256 hex (a public key hash)");
56
+ const scopedPrincipal = z.string().min(1).refine((p) => principalScopeOf(p) !== undefined, "a <scope>:<subject> principal");
57
+ /** `console:<subject>` — the principal-subject grammar (`principalScopeOf`), as a regex the compiler's
58
+ * probe can sample. */
59
+ const consolePrincipal = z
60
+ .string()
61
+ .regex(/^console:[A-Za-z0-9][A-Za-z0-9_.:/-]{0,255}$/, "a console principal is console:<subject>");
62
+
63
+ type ActiveLeaf = { status?: string };
64
+ const activeSubjects = (tuple: unknown): string[] =>
65
+ Object.entries(((tuple as { subjects?: Record<string, ActiveLeaf> } | undefined)?.subjects) ?? {})
66
+ .filter(([, leaf]) => leaf?.status === "active")
67
+ .map(([subject]) => subject);
68
+
69
+ // ─────────────────────────────── 1. at founding ───────────────────────────────
70
+
71
+ export const inheritedGovernancePayload = z
72
+ .object({
73
+ /** Each inherited operator or enroller principal and one of its active parent keys, sorted. */
74
+ signers: z.array(z.object({ principal: scopedPrincipal, keyHash: sha256Hex }).strict()).default([]),
75
+ /** Each inherited operator principal: seated on `workspace:self#installDomain`, sorted. */
76
+ modelAuthorities: z.array(scopedPrincipal).default([]),
77
+ grantedAt: z.number().int().nonnegative(),
78
+ /** The parent's active admission-checkpoint workload keys, so the child can receive workspace
79
+ * effects from birth. Absent when the parent has none (a recipe without it is unchanged). */
80
+ admissionCheckpointSigners: z.array(admissionCheckpointSigner).optional(),
81
+ })
82
+ .strict()
83
+ .refine((p) => p.signers.length + p.modelAuthorities.length + (p.admissionCheckpointSigners?.length ?? 0) > 0,
84
+ { message: "nothing to seat", path: ["signers"] });
85
+
86
+ export type InheritedSigner = { readonly principal: string; readonly keyHash: string };
87
+
88
+ /** `seedInheritedGovernance` — see the module doc (1). Warrant-free prefix only. */
89
+ export const seedInheritedGovernance = directive(SEED_INHERITED_GOVERNANCE_DIRECTIVE)
90
+ .ensures(RelationTuple)
91
+ .payload(inheritedGovernancePayload)
92
+ .plan((p) => [
93
+ ...p.signers.flatMap((signer) =>
94
+ writeTuple({
95
+ object: signerObject(signer.principal),
96
+ relation: SIGNER_RELATION,
97
+ subject: `key:${signer.keyHash}`,
98
+ grantedBy: INHERITED_SIGNER_GRANTOR,
99
+ grantedAt: String(p.grantedAt),
100
+ })),
101
+ ...p.modelAuthorities.flatMap((principal) =>
102
+ writeTuple({
103
+ object: "workspace:self",
104
+ relation: MODEL_LIFECYCLE_AUTHORITY,
105
+ subject: principal,
106
+ grantedBy: INHERITED_SIGNER_GRANTOR,
107
+ grantedAt: String(p.grantedAt),
108
+ })),
109
+ ...writeAdmissionCheckpointSigners(p.admissionCheckpointSigners ?? [], INHERITED_SIGNER_GRANTOR),
110
+ ]);
111
+
112
+ export const inheritedGovernanceBeforeWarrant = workspaceInvariant("InheritedGovernanceBeforeWarrant")
113
+ .on(SEED_INHERITED_GOVERNANCE_DIRECTIVE)
114
+ .reads(readSet(readRef("warrant", "GenesisWarrant").pre().at(GENESIS_WARRANT_ID)))
115
+ .assert(({ warrant }) =>
116
+ (warrant as { principal?: string } | undefined)?.principal
117
+ ? { reject: "inherited-governance-after-warrant" }
118
+ : { accept: true });
119
+
120
+ /** The parent's active keys for each principal, one `relationTuplesByObject` read per principal. */
121
+ export function readInheritedSigners(principals: readonly string[]): readonly InheritedSigner[] {
122
+ const out: InheritedSigner[] = [];
123
+ for (const principal of [...new Set(principals)].filter((p) => principalScopeOf(p) !== undefined).sort()) {
124
+ const rows = readFrameworkDeclaredRows(relationTuplesByObject, RelationTuple, { object: signerObject(principal) });
125
+ const row = rows.find((candidate) => candidate.relation === SIGNER_RELATION);
126
+ for (const subject of activeSubjects(row)) {
127
+ if (/^key:[0-9a-f]{64}$/.test(subject)) out.push({ principal, keyHash: subject.slice("key:".length) });
128
+ }
129
+ }
130
+ return out.sort((a, b) =>
131
+ a.principal < b.principal ? -1 : a.principal > b.principal ? 1 : a.keyHash < b.keyHash ? -1 : a.keyHash > b.keyHash ? 1 : 0);
132
+ }
133
+
134
+ /**
135
+ * The founding step for a founded birth, or none when nothing is inherited. `operators` and `enrollers`
136
+ * are the subjects the founding step seats (`readInheritedOperators` / `readInheritedEnrollers`); bare
137
+ * `key:` subjects are their own principals and need nothing.
138
+ */
139
+ export function inheritedGovernanceStep(
140
+ domain: string,
141
+ actor: string,
142
+ operators: readonly string[],
143
+ enrollers: readonly { readonly subject: string }[],
144
+ grantedAt: number,
145
+ founding: InheritedSigner,
146
+ ): Array<{ domain: string; directiveId: string; actor: string; payload: Record<string, unknown> }> {
147
+ const signers = readInheritedSigners([...operators, ...enrollers.map((e) => e.subject)]);
148
+ return inheritedGovernanceStepFrom(domain, actor, operators, signers, grantedAt, founding,
149
+ readAdmissionCheckpointSigners());
150
+ }
151
+
152
+ /**
153
+ * The pure half of {@link inheritedGovernanceStep}, for recipe builders handed already-read signers.
154
+ *
155
+ * `founding` is the (principal, key) the founding step already seated. That leaf is never rewritten:
156
+ * when the founder is also an inherited operator or enroller, its founding key keeps its `genesis`
157
+ * grant — the mark a founded parent's births select the founded recipe by — and the founder's OTHER
158
+ * parent keys are still copied.
159
+ */
160
+ export function inheritedGovernanceStepFrom(
161
+ domain: string,
162
+ actor: string,
163
+ operators: readonly string[],
164
+ allSigners: readonly InheritedSigner[],
165
+ grantedAt: number,
166
+ founding: InheritedSigner,
167
+ checkpointSigners: readonly AdmissionCheckpointSigner[] = [],
168
+ ): Array<{ domain: string; directiveId: string; actor: string; payload: Record<string, unknown> }> {
169
+ const signers = allSigners.filter((s) => !(s.principal === founding.principal && s.keyHash === founding.keyHash));
170
+ const modelAuthorities = [...new Set(operators)].filter((p) => principalScopeOf(p) !== undefined).sort();
171
+ if (signers.length === 0 && modelAuthorities.length === 0 && checkpointSigners.length === 0) return [];
172
+ return [{
173
+ domain,
174
+ directiveId: seedInheritedGovernance.id,
175
+ actor,
176
+ payload: inheritedGovernancePayload.parse({
177
+ signers: [...signers],
178
+ modelAuthorities,
179
+ grantedAt,
180
+ ...(checkpointSigners.length > 0 ? { admissionCheckpointSigners: [...checkpointSigners] } : {}),
181
+ }) as Record<string, unknown>,
182
+ }];
183
+ }
184
+
185
+ // ─────────────────────────────── 2. operator handover ───────────────────────────────
186
+
187
+ const operatorPayload = z.object({ consolePrincipal, at: z.number().int().nonnegative() }).strict();
188
+
189
+ /** The operator/installDomain pair for one principal. */
190
+ function operatorPair(subject: string, at: string, grantedBy: string) {
191
+ return [
192
+ { object: "workspace:self", relation: WORKSPACE_OPERATOR_RELATION, subject, grantedBy, grantedAt: at },
193
+ { object: "workspace:self", relation: MODEL_LIFECYCLE_AUTHORITY, subject, grantedBy, grantedAt: at },
194
+ ];
195
+ }
196
+
197
+ /** `grantConsoleOperator` — an operator seats another console principal as operator (with installDomain). */
198
+ export const grantConsoleOperator = directive("grantConsoleOperator")
199
+ .ensures(RelationTuple)
200
+ .payload(operatorPayload)
201
+ .plan((p, ctx) => {
202
+ const by = endorsementOf(ctx.principal().asAuthoritySubject<string>("hand the law operator seat to a console principal"))?.principal ?? "";
203
+ return operatorPair(p.consolePrincipal, String(p.at), by).flatMap((tuple) => writeTuple(tuple));
204
+ })
205
+ .requires(WORKSPACE_OPERATOR_RELATION);
206
+
207
+ /** `revokeConsoleOperator` — an operator removes a console principal's operator seat (and installDomain). */
208
+ export const revokeConsoleOperator = directive("revokeConsoleOperator")
209
+ .ensures(RelationTuple)
210
+ .payload(operatorPayload)
211
+ .plan((p, ctx) => {
212
+ const by = endorsementOf(ctx.principal().asAuthoritySubject<string>("withdraw a console principal's law operator seat"))?.principal ?? "";
213
+ return operatorPair(p.consolePrincipal, String(p.at), by).flatMap((tuple) =>
214
+ removeTuple({ ...tuple, revokedBy: by, revokedAt: String(p.at) }));
215
+ })
216
+ .requires(WORKSPACE_OPERATOR_RELATION);
217
+
218
+ /** A workspace always keeps an operator: the jurisdiction never locks itself out of its own law. */
219
+ export const lastOperatorRemains = workspaceInvariant("LastOperatorRemains")
220
+ .on("revokeConsoleOperator")
221
+ .reads(() => [refAs("tuple", "RelationTuple", "pre:" + relationTupleInstanceId("workspace:self", WORKSPACE_OPERATOR_RELATION))])
222
+ .assert(({ tuple }, ctx) => {
223
+ const leaving = String(((ctx?.intent ?? {}) as { consolePrincipal?: string }).consolePrincipal ?? "");
224
+ return activeSubjects(tuple).some((subject) => subject !== leaving)
225
+ ? { accept: true }
226
+ : { reject: "last-operator-cannot-be-revoked" };
227
+ });
228
+
229
+ // ─────────────────────────────── 3. console key rotation ───────────────────────────────
230
+
231
+ const signerPayload = z.object({ keyHash: sha256Hex, at: z.number().int().nonnegative() }).strict();
232
+
233
+ function ownConsolePrincipal(ctx: { principal(): { asAuthoritySubject<T>(why: string): T } }, why: string): string {
234
+ const principal = endorsementOf(ctx.principal().asAuthoritySubject<string>(why))?.principal ?? "";
235
+ if (principalScopeOf(principal) !== CONSOLE_PRINCIPAL_SCOPE) {
236
+ throw new Error("console-signer-lane-is-for-console-principals");
237
+ }
238
+ return principal;
239
+ }
240
+
241
+ /** `enrollConsoleSigner` — a console principal, signing with a current key, adds a key to ITSELF. */
242
+ export const enrollConsoleSigner = directive("enrollConsoleSigner")
243
+ .ensures(RelationTuple)
244
+ .payload(signerPayload)
245
+ .plan((p, ctx) => {
246
+ const me = ownConsolePrincipal(ctx as never, "add a signer key to my own console identity");
247
+ return writeTuple({
248
+ object: signerObject(me),
249
+ relation: SIGNER_RELATION,
250
+ subject: `key:${p.keyHash}`,
251
+ grantedBy: me,
252
+ grantedAt: String(p.at),
253
+ });
254
+ });
255
+
256
+ /** `revokeConsoleSigner` — a console principal revokes one of ITS OWN keys, never its last. */
257
+ export const revokeConsoleSigner = directive("revokeConsoleSigner")
258
+ .ensures(RelationTuple)
259
+ .payload(signerPayload)
260
+ .plan((p, ctx) => {
261
+ const me = ownConsolePrincipal(ctx as never, "revoke a signer key of my own console identity");
262
+ return removeTuple({
263
+ object: signerObject(me),
264
+ relation: SIGNER_RELATION,
265
+ subject: `key:${p.keyHash}`,
266
+ grantedBy: me,
267
+ grantedAt: String(p.at),
268
+ revokedBy: me,
269
+ revokedAt: String(p.at),
270
+ });
271
+ });
272
+
273
+ export const lastConsoleSignerRemains = workspaceInvariant("LastConsoleSignerRemains")
274
+ .on("revokeConsoleSigner")
275
+ .reads((ctx) => [
276
+ refAs("tuple", "RelationTuple", "pre:" + relationTupleInstanceId(signerObject(String(ctx.principal ?? "")), SIGNER_RELATION)),
277
+ ])
278
+ .assert(({ tuple }, ctx) => {
279
+ const leaving = `key:${String(((ctx?.intent ?? {}) as { keyHash?: string }).keyHash ?? "")}`;
280
+ return activeSubjects(tuple).some((subject) => subject !== leaving)
281
+ ? { accept: true }
282
+ : { reject: "last-signer-key-cannot-be-revoked" };
283
+ });
284
+
285
+ /** Everything this module contributes to a law, for the laws that compose it. */
286
+ export const consoleGovernanceLaw = {
287
+ seedInheritedGovernance,
288
+ inheritedGovernanceBeforeWarrant,
289
+ grantConsoleOperator,
290
+ revokeConsoleOperator,
291
+ lastOperatorRemains,
292
+ enrollConsoleSigner,
293
+ revokeConsoleSigner,
294
+ lastConsoleSignerRemains,
295
+ } as const;
@@ -0,0 +1,301 @@
1
+ // NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
2
+ // remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
3
+ // wasm32-wasip1 artifact {kernel · projection · embedded QuickJS engine}.
4
+ // If a file isn't this / hosting this / authoring for this / proving this — it's gone.
5
+
6
+ /**
7
+ * framework/delegated_roles.ts — ROLES A PARENT DELEGATES INTO THE CHILDREN ITS PRINCIPALS FOUND
8
+ * (identity target model D22).
9
+ *
10
+ * Before D22 a platform delegated a role into a child a user founded (a Home's right to birth estates,
11
+ * say) by a signed delegation CERTIFICATE carried on each intent and re-verified per write
12
+ * (`.delegatedRole(...)`). The certificate lane is deleted; the role becomes an ordinary state tuple
13
+ * in the child, seeded once at founding exactly like the inherited operator and enroller tuples (D15).
14
+ *
15
+ * - **The child's law declares what is delegable, by relation-schema reference.** A tenant domain
16
+ * calls {@link delegatedRoles}(relationType, [names]) and exports what it returns: the founding seed
17
+ * and the post-birth adoption lane. Both write through `selfObject(relationType)`, so the tuple object
18
+ * is qualified EXACTLY as the law's own `.requires(...)` gates are (a tenant type lives on
19
+ * `<domain namespace>__<type>:self`, never the bare `<type>:self`), and both refuse any relation the
20
+ * declaration does not list. They run dispatched in the declaring domain.
21
+ * - **The parent states the policy** once per principal scope: `grantDelegatedRole { scope, childKind,
22
+ * domain, objectType, relation }` — "every `<scope>:` principal who founds a `<childKind>` child here
23
+ * holds `<objectType>#<relation>` from `<domain>`'s law in it". It is an ordinary relation tuple,
24
+ * `nomosDelegatedRole:<childKind>.<domain>.<objectType>.<relation>#delegatedTo@principalScope:<scope>`,
25
+ * read through the `relationTuplesByRelation` read every founded birth already declares.
26
+ * `revokeDelegatedRole` withdraws it for children founded afterwards.
27
+ * - **A founded birth** authors one `seedDelegatedRoles` step per named domain for its founder, granted
28
+ * by {@link DELEGATED_ROLE_GRANTOR}, on the warrant-free prefix only. Later grants and revokes reach
29
+ * an existing child through `adoptDelegatedRoles`, authored there by an enroller of the principal's
30
+ * scope (the jurisdiction's hosted backend, which holds a key in every founded child).
31
+ *
32
+ * `childKind` scopes a grant to one kind of child: `receiving-group` for a Home, a business birth's
33
+ * namespace (e.g. `estate`) otherwise. A grant naming a domain whose law does not publish the seed fails
34
+ * that child's birth, so a tenant declares {@link delegatedRoles} in every domain its grants name.
35
+ */
36
+ import { z } from "zod";
37
+ import { directive, type Directive } from "../directive.js";
38
+ import { readFrameworkDeclaredRows } from "../read.js";
39
+ import { principalScopeOf } from "../member_authentication.js";
40
+ import { object, relationType, self, selfObject, type RelationTypeDecl } from "../relation_schema.js";
41
+ import { GENESIS_WARRANT_ID } from "./genesis.js";
42
+ import { RelationTuple, removeTuple, writeTuple } from "./relations.js";
43
+ import {
44
+ PRINCIPAL_SCOPE_OBJECT_TYPE,
45
+ principalScopeRelations,
46
+ providerScopeField,
47
+ relationTuplesByRelation,
48
+ } from "./principal_enrollment.js";
49
+ import { readRef, readSet, workspaceInvariant } from "./workspace_invariant.js";
50
+
51
+ /** The child kind of a receiving group (an application's Home). */
52
+ export const RECEIVING_GROUP_CHILD_KIND = "receiving-group" as const;
53
+ /** The `grantedBy` of every role a founding seat writes. */
54
+ export const DELEGATED_ROLE_GRANTOR = "inherited-at-birth" as const;
55
+ /** The `grantedBy` of a role the jurisdiction's enroller carries into an existing child. */
56
+ export const ADOPTED_ROLE_GRANTOR = "inherited-from-parent" as const;
57
+ /** The framework object type of a delegated-role policy, and its one relation. */
58
+ export const DELEGATED_ROLE_OBJECT_TYPE = "nomosDelegatedRole" as const;
59
+ export const DELEGATED_TO_RELATION = "delegatedTo" as const;
60
+ export const SEED_DELEGATED_ROLES_DIRECTIVE = "seedDelegatedRoles" as const;
61
+ export const ADOPT_DELEGATED_ROLES_DIRECTIVE = "adoptDelegatedRoles" as const;
62
+
63
+ /** Delegation carries business roles, never governance, identity or delegation itself: no framework
64
+ * object type (every framework governance relation lives on one), and — on any type — no relation
65
+ * named like the authority to administer, install law, sign or enrol. */
66
+ const GOVERNANCE_OBJECT_TYPES: readonly string[] = ["workspace", "identity", PRINCIPAL_SCOPE_OBJECT_TYPE, DELEGATED_ROLE_OBJECT_TYPE];
67
+ const GOVERNANCE_RELATIONS: readonly string[] = [
68
+ "admin", "operator", "installDomain", "signer", "enroller", DELEGATED_TO_RELATION,
69
+ ];
70
+
71
+ /** Refuse a role that would carry governance rather than a business role. */
72
+ export function requireBusinessRole(objectType: string, relation: string): void {
73
+ if (GOVERNANCE_OBJECT_TYPES.includes(objectType) || GOVERNANCE_RELATIONS.includes(relation)) {
74
+ throw new Error(`delegated-role-is-governance: '${objectType}#${relation}' is not a delegable business role`);
75
+ }
76
+ }
77
+
78
+ const childKindField = z.string().regex(/^[a-z][a-z0-9-]*$/, "a child kind is a lowercase business noun");
79
+ const domainField = z.string().regex(/^[A-Za-z_][A-Za-z0-9_-]{0,127}$/, "a law domain key");
80
+ const nameField = z.string().regex(/^[A-Za-z][A-Za-z0-9_-]{0,127}$/, "a relation-schema name");
81
+
82
+ // ─────────────────────────────── the parent's policy (framework) ───────────────────────────────
83
+
84
+ /** `nomosDelegatedRole:<childKind>.<domain>.<objectType>.<relation>#delegatedTo@principalScope:<scope>`. */
85
+ export const delegatedRoleRelations = relationType(DELEGATED_ROLE_OBJECT_TYPE)
86
+ .asGlobal()
87
+ .relation(DELEGATED_TO_RELATION, { rewrite: self(), allow: [object("principalScope")] });
88
+
89
+ export function delegatedRoleObject(p: { childKind: string; domain: string; objectType: string; relation: string }): string {
90
+ return `${DELEGATED_ROLE_OBJECT_TYPE}:${p.childKind}.${p.domain}.${p.objectType}.${p.relation}`;
91
+ }
92
+
93
+ const grantPayload = z
94
+ .object({
95
+ scope: providerScopeField,
96
+ childKind: childKindField,
97
+ /** The child-law domain that declares the role ({@link delegatedRoles}) and seats it. */
98
+ domain: domainField,
99
+ objectType: nameField,
100
+ relation: nameField,
101
+ grantedAt: z.number().int().nonnegative(),
102
+ })
103
+ .strict();
104
+
105
+ /** `grantDelegatedRole` — an admin delegates a role into every child a `<scope>:` principal founds here. */
106
+ export const grantDelegatedRole = directive("grantDelegatedRole")
107
+ .ensures(RelationTuple)
108
+ .payload(grantPayload)
109
+ .plan((p, ctx) => {
110
+ requireBusinessRole(p.objectType, p.relation);
111
+ return writeTuple({
112
+ object: delegatedRoleObject(p),
113
+ relation: DELEGATED_TO_RELATION,
114
+ subject: `principalScope:${p.scope}`,
115
+ grantedBy: ctx.principal().asAttribution(),
116
+ grantedAt: String(p.grantedAt),
117
+ });
118
+ })
119
+ .requires("admin");
120
+
121
+ /** `revokeDelegatedRole` — children founded afterwards no longer receive it (existing ones keep their tuple
122
+ * until the enroller carries the revoke into them with `adoptDelegatedRoles`). */
123
+ export const revokeDelegatedRole = directive("revokeDelegatedRole")
124
+ .ensures(RelationTuple)
125
+ .payload(grantPayload)
126
+ .plan((p, ctx) =>
127
+ removeTuple({
128
+ object: delegatedRoleObject(p),
129
+ relation: DELEGATED_TO_RELATION,
130
+ subject: `principalScope:${p.scope}`,
131
+ grantedBy: ctx.principal().asAttribution(),
132
+ grantedAt: String(p.grantedAt),
133
+ revokedBy: ctx.principal().asAttribution(),
134
+ revokedAt: String(p.grantedAt),
135
+ }))
136
+ .requires("admin");
137
+
138
+ export type DelegatedRolesByDomain = ReadonlyArray<{ domain: string; objectType: string; relations: readonly string[] }>;
139
+
140
+ /** The parent's active grants for `principal`'s scope and `childKind`, grouped by (domain, objectType), sorted. */
141
+ export function readDelegatedRoles(principal: string, childKind: string): DelegatedRolesByDomain {
142
+ const scope = principalScopeOf(principal);
143
+ if (scope === undefined) return [];
144
+ const rows = readFrameworkDeclaredRows(relationTuplesByRelation, RelationTuple, { relation: DELEGATED_TO_RELATION });
145
+ const prefix = `${DELEGATED_ROLE_OBJECT_TYPE}:`;
146
+ const groups = new Map<string, { domain: string; objectType: string; relations: Set<string> }>();
147
+ for (const row of rows) {
148
+ if (row.relation !== DELEGATED_TO_RELATION || !row.object?.startsWith(prefix)) continue;
149
+ const parts = row.object.slice(prefix.length).split(".");
150
+ if (parts.length !== 4) continue;
151
+ const [kind, domain, objectType, relation] = parts as [string, string, string, string];
152
+ if (kind !== childKind) continue;
153
+ const subjects = row.subjects as Record<string, { status?: string }> | undefined;
154
+ if (subjects?.[`principalScope:${scope}`]?.status !== "active") continue;
155
+ const key = `${domain}\u001f${objectType}`;
156
+ const group = groups.get(key) ?? { domain, objectType, relations: new Set<string>() };
157
+ group.relations.add(relation);
158
+ groups.set(key, group);
159
+ }
160
+ return [...groups.entries()]
161
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
162
+ .map(([, g]) => ({ domain: g.domain, objectType: g.objectType, relations: [...g.relations].sort() }));
163
+ }
164
+
165
+ /** One `seedDelegatedRoles` step per (domain, objectType) the parent delegates, dispatched in that domain. */
166
+ export function delegatedRolesSteps(
167
+ actor: string,
168
+ principal: string,
169
+ childKind: string,
170
+ grantedAt: number,
171
+ ): Array<{ domain: string; directiveId: string; actor: string; payload: Record<string, unknown> }> {
172
+ return readDelegatedRoles(principal, childKind).map((group) => ({
173
+ domain: group.domain,
174
+ directiveId: SEED_DELEGATED_ROLES_DIRECTIVE,
175
+ actor,
176
+ payload: { principal, objectType: group.objectType, relations: [...group.relations], grantedAt },
177
+ }));
178
+ }
179
+
180
+ // ─────────────────────────────── the child's law (tenant-declared) ───────────────────────────────
181
+
182
+ /** Admitted only on the warrant-free prefix: after birth, roles change by `adoptDelegatedRoles`. */
183
+ export const delegatedRolesBeforeWarrant = workspaceInvariant("DelegatedRolesBeforeWarrant")
184
+ .on(SEED_DELEGATED_ROLES_DIRECTIVE)
185
+ .reads(readSet(readRef("warrant", "GenesisWarrant").pre().at(GENESIS_WARRANT_ID)))
186
+ .assert(({ warrant }) =>
187
+ (warrant as { principal?: string } | undefined)?.principal
188
+ ? { reject: "delegated-roles-after-warrant" }
189
+ : { accept: true });
190
+
191
+ export interface DelegatedRolesLaw {
192
+ /** The founding seat a founded birth dispatches in this domain. */
193
+ readonly seedDelegatedRoles: Directive<{ principal: string; objectType: string; relations: string[]; grantedAt: number }>;
194
+ readonly delegatedRolesBeforeWarrant: typeof delegatedRolesBeforeWarrant;
195
+ /** The post-birth lane: an enroller of the principal's scope carries a later grant or revoke here. */
196
+ readonly adoptDelegatedRoles: Directive<{
197
+ scope: string; principal: string; objectType: string; relation: string; active: boolean; at: number;
198
+ }>;
199
+ /** The enroller relation the adoption lane requires, carried so the declaring domain's closure has it. */
200
+ readonly principalScopeRelations: typeof principalScopeRelations;
201
+ }
202
+
203
+ /**
204
+ * DECLARE the roles of `relations` (a relation type THIS domain declares) that a parent may delegate
205
+ * into this workspace. Export every member of the result from the declaring domain. What may be seated
206
+ * or adopted is this list — law, closed over by the directives, never payload — and every tuple lands
207
+ * on the object `.requires(relation, …)` checks, because it is written through the same schema reference.
208
+ */
209
+ export function delegatedRoles(relations: RelationTypeDecl, roles: readonly string[]): DelegatedRolesLaw {
210
+ if (relations.global === true) {
211
+ throw new Error("delegatedRoles: a global (framework) relation type is never delegable");
212
+ }
213
+ if (roles.length === 0) throw new Error("delegatedRoles: declare at least one delegable role");
214
+ for (const role of roles) {
215
+ nameField.parse(role);
216
+ requireBusinessRole(relations.type, role);
217
+ if (!relations.relations.has(role)) {
218
+ throw new Error(`delegatedRoles: '${relations.type}' declares no relation '${role}'`);
219
+ }
220
+ }
221
+ const allowed = new Set(roles);
222
+ const objectType = relations.type;
223
+ const refuseUndeclared = (type: string, relation: string) => {
224
+ if (type !== objectType || !allowed.has(relation)) {
225
+ throw new Error(`delegated-role-not-declared: '${type}#${relation}' is not a delegable role of this law`);
226
+ }
227
+ };
228
+
229
+ const seedDelegatedRoles = directive(SEED_DELEGATED_ROLES_DIRECTIVE)
230
+ .ensures(RelationTuple)
231
+ .payload(
232
+ z
233
+ .object({
234
+ principal: z.string().min(1),
235
+ objectType: nameField,
236
+ relations: z.array(nameField).min(1),
237
+ grantedAt: z.number().int().nonnegative(),
238
+ })
239
+ .strict()
240
+ .refine((p) => principalScopeOf(p.principal) !== undefined, {
241
+ message: "delegated roles are held by a <scope>:<subject> principal",
242
+ path: ["principal"],
243
+ }),
244
+ )
245
+ .plan((p) =>
246
+ p.relations.flatMap((relation) => {
247
+ refuseUndeclared(p.objectType, relation);
248
+ return writeTuple({
249
+ object: selfObject(relations),
250
+ relation,
251
+ subject: p.principal,
252
+ grantedBy: DELEGATED_ROLE_GRANTOR,
253
+ grantedAt: String(p.grantedAt),
254
+ });
255
+ }));
256
+
257
+ const adoptDelegatedRoles = directive(ADOPT_DELEGATED_ROLES_DIRECTIVE)
258
+ .ensures(RelationTuple)
259
+ .payload(
260
+ z
261
+ .object({
262
+ /** The principal scope whose enroller carries the parent's policy here. */
263
+ scope: providerScopeField,
264
+ principal: z.string().min(1),
265
+ objectType: nameField,
266
+ relation: nameField,
267
+ /** true = the parent currently delegates the role; false = it no longer does. */
268
+ active: z.boolean(),
269
+ at: z.number().int().nonnegative(),
270
+ })
271
+ .strict()
272
+ .refine((p) => principalScopeOf(p.principal) === p.scope, {
273
+ message: "principal must be <scope>:<subject> of the named scope",
274
+ path: ["principal"],
275
+ }),
276
+ )
277
+ .plan((p, ctx) => {
278
+ refuseUndeclared(p.objectType, p.relation);
279
+ if (principalScopeOf(p.principal) !== p.scope) throw new Error("principal-not-in-scope");
280
+ const tuple = {
281
+ object: selfObject(relations),
282
+ relation: p.relation,
283
+ subject: p.principal,
284
+ grantedBy: ADOPTED_ROLE_GRANTOR,
285
+ grantedAt: String(p.at),
286
+ };
287
+ return p.active
288
+ ? writeTuple(tuple)
289
+ : removeTuple({ ...tuple, revokedBy: ctx.principal().asAttribution(), revokedAt: String(p.at) });
290
+ })
291
+ .requires("enroller", {
292
+ objectFrom: { recordType: PRINCIPAL_SCOPE_OBJECT_TYPE, idField: "scope" },
293
+ });
294
+
295
+ return {
296
+ seedDelegatedRoles: seedDelegatedRoles as unknown as DelegatedRolesLaw["seedDelegatedRoles"],
297
+ delegatedRolesBeforeWarrant,
298
+ adoptDelegatedRoles: adoptDelegatedRoles as unknown as DelegatedRolesLaw["adoptDelegatedRoles"],
299
+ principalScopeRelations,
300
+ };
301
+ }