@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
@@ -42,24 +42,31 @@
42
42
  * only revokes keys of its OWN identity).
43
43
  */
44
44
  import { z } from "zod";
45
- import { birthCertificatePayload, type BirthCertificatePayload } from "./birth_certificate_law.js";
45
+ import { BirthDelegationCertificate, birthCertificatePayload, type BirthCertificatePayload } from "./birth_certificate_law.js";
46
46
  import { aggregate, instance, type BoundAggregate } from "../aggregate.js";
47
47
  import { t } from "../fields.js";
48
- import { Lww } from "../drivers.js";
48
+ import { FirstWriterWins, Lww, MapOf } from "../drivers.js";
49
49
  import { directive } from "../directive.js";
50
- import { set, withMarker, type FieldOp, type PlannedOp } from "../ops.js";
50
+ import { set, setEntry, withMarker, type FieldOp, type PlannedOp } from "../ops.js";
51
51
  import { query } from "../query.js";
52
52
  import { relationType, self, key } from "../relation_schema.js";
53
53
  import { readRef, readSet, workspaceInvariant, ref } from "./workspace_invariant.js";
54
- import { endorsementOf, type EndorsedAuthority } from "../principal_evidence.js";
54
+ import {
55
+ endorsementOf,
56
+ type AttributionPrincipal,
57
+ type EndorsedAuthority,
58
+ } from "../principal_evidence.js";
55
59
  import {
56
60
  writeTuple,
57
61
  removeTuple,
58
62
  RelationTuple,
63
+ relationTuplesByObject,
59
64
  relationTupleInstanceId,
60
65
  } from "./relations.js";
61
66
  import { MODEL_LIFECYCLE_AUTHORITY } from "./domain_lifecycle.js";
62
67
  import { readFrameworkDeclaredRows } from "../read.js";
68
+ import { formerAggregate, retirement } from "../evolution.js";
69
+ import { valueObject } from "../value_object.js";
63
70
 
64
71
  /**
65
72
  * The framework `identity` relation schema — the `signer` relation, LEAF-ONLY
@@ -91,6 +98,55 @@ export const ADMISSION_CHECKPOINT_PRINCIPAL = "workspace:admission-peer";
91
98
 
92
99
  const sha256Hex = z.string().regex(/^[0-9a-f]{64}$/, "expected lowercase sha256 hex (the device pubkey hash)");
93
100
 
101
+ /** Public, typed disclosure of one active admission workload key. Parents carry this exact shape
102
+ * into child law; the corresponding secret remains runtime custody and never enters a domain. */
103
+ export const admissionCheckpointSigner = z.object({
104
+ keyHash: sha256Hex,
105
+ validFromEpoch: z.number().int().nonnegative(),
106
+ grantedAt: z.string().min(1),
107
+ }).strict();
108
+ export type AdmissionCheckpointSigner = z.infer<typeof admissionCheckpointSigner>;
109
+
110
+ /** Read the current workspace's visible admission workload grants through the framework's declared
111
+ * relation index. This is the law-side twin of the kernel's replay verifier, from the same tuple. */
112
+ export function readAdmissionCheckpointSigners(): AdmissionCheckpointSigner[] {
113
+ const rows = readFrameworkDeclaredRows(
114
+ relationTuplesByObject,
115
+ RelationTuple,
116
+ { object: signerObject(ADMISSION_CHECKPOINT_PRINCIPAL) },
117
+ );
118
+ const signers: AdmissionCheckpointSigner[] = [];
119
+ for (const row of rows) {
120
+ if (row.relation !== SIGNER_RELATION || row.subjects === undefined) continue;
121
+ for (const [subject, leaf] of Object.entries(row.subjects)) {
122
+ if (!subject.startsWith("key:") || leaf.status !== "active") continue;
123
+ signers.push(admissionCheckpointSigner.parse({
124
+ keyHash: subject.slice("key:".length),
125
+ validFromEpoch: leaf.validFromEpoch,
126
+ grantedAt: leaf.grantedAt,
127
+ }));
128
+ }
129
+ }
130
+ return signers.sort((left, right) => left.keyHash.localeCompare(right.keyHash));
131
+ }
132
+
133
+ /** Lower a parent's typed signer disclosure into the child's ordinary relation state. The caller's
134
+ * directive still decides authority; this helper supplies no gate and owns no secret. */
135
+ export function writeAdmissionCheckpointSigners(
136
+ signers: readonly AdmissionCheckpointSigner[],
137
+ grantedBy: string | AttributionPrincipal,
138
+ ): PlannedOp[] {
139
+ return signers.flatMap((signer) => writeTuple({
140
+ object: signerObject(ADMISSION_CHECKPOINT_PRINCIPAL),
141
+ relation: SIGNER_RELATION,
142
+ subject: `key:${signer.keyHash}`,
143
+ grantedBy,
144
+ grantedAt: signer.grantedAt,
145
+ validFromEpoch: signer.validFromEpoch,
146
+ verifiedVia: "workspace-law",
147
+ }));
148
+ }
149
+
94
150
  // ─────────────────────────────── PHASE 0 — SignerEpoch ───────────────────────────────
95
151
 
96
152
  /** The deterministic singleton id of the per-workspace signer epoch — one per chain. */
@@ -251,30 +307,78 @@ export const enrollSigner = directive("enrollSigner")
251
307
  * the secret alone grants nothing. The key certifies that this workspace admitted an exact causal
252
308
  * transition; the target directive's independent Zanzibar requirement remains business authority.
253
309
  */
254
- export const enrollAdmissionCheckpointSigner = directive(
255
- "_nmsEffectEnrollAdmissionCheckpointSigner",
256
- )
310
+ function admissionCheckpointEnrollment(id: string) {
311
+ return directive(id)
312
+ .ensures(RelationTuple)
313
+ .payload(
314
+ z.object({
315
+ keyHash: sha256Hex,
316
+ validFromEpoch: z.number().int().nonnegative(),
317
+ grantedAt: z.string().min(1),
318
+ }).strict(),
319
+ )
320
+ .plan((p, ctx) =>
321
+ writeTuple({
322
+ object: signerObject(ADMISSION_CHECKPOINT_PRINCIPAL),
323
+ relation: SIGNER_RELATION,
324
+ subject: `key:${p.keyHash}`,
325
+ grantedBy: ctx.principal().asAttribution(),
326
+ grantedAt: p.grantedAt,
327
+ validFromEpoch: p.validFromEpoch,
328
+ verifiedVia: "workspace-law",
329
+ }),
330
+ )
331
+ .requires(MODEL_LIFECYCLE_AUTHORITY, { object: "workspace:self" });
332
+ }
333
+ export const enrollAdmissionCheckpointSigner = admissionCheckpointEnrollment("_nmsEffectEnrollAdmissionCheckpointSigner");
334
+ /** The same enrolment, called directly by its public name: the platform governance law (root, platforms),
335
+ * which composes no application-home law, publishes it this way. */
336
+ export const enrollAdmissionCheckpointSignerDirect = admissionCheckpointEnrollment("enrollAdmissionCheckpointSigner");
337
+
338
+ /**
339
+ * Retire one admission-checkpoint workload key: the custodian that held its secret no longer vouches this
340
+ * workspace's admissions (a custody move retires the old custodian's key). The same lifecycle authority
341
+ * that enrols a key revokes it; {@link lastAdmissionCheckpointSignerRemains} refuses leaving the workspace
342
+ * with no active checkpoint key, which would stop it receiving any workspace effect.
343
+ */
344
+ export const REVOKE_ADMISSION_CHECKPOINT_SIGNER_DIRECTIVE = "revokeAdmissionCheckpointSigner" as const;
345
+ export const revokeAdmissionCheckpointSigner = directive(REVOKE_ADMISSION_CHECKPOINT_SIGNER_DIRECTIVE)
257
346
  .ensures(RelationTuple)
258
347
  .payload(
259
348
  z.object({
260
349
  keyHash: sha256Hex,
261
- validFromEpoch: z.number().int().nonnegative(),
262
- grantedAt: z.string().min(1),
350
+ revokedAt: z.string().min(1),
263
351
  }).strict(),
264
352
  )
265
353
  .plan((p, ctx) =>
266
- writeTuple({
354
+ removeTuple({
267
355
  object: signerObject(ADMISSION_CHECKPOINT_PRINCIPAL),
268
356
  relation: SIGNER_RELATION,
269
357
  subject: `key:${p.keyHash}`,
270
358
  grantedBy: ctx.principal().asAttribution(),
271
- grantedAt: p.grantedAt,
272
- validFromEpoch: p.validFromEpoch,
273
- verifiedVia: "workspace-law",
359
+ grantedAt: p.revokedAt,
360
+ revokedBy: ctx.principal().asAttribution(),
361
+ revokedAt: p.revokedAt,
274
362
  }),
275
363
  )
276
364
  .requires(MODEL_LIFECYCLE_AUTHORITY, { object: "workspace:self" });
277
365
 
366
+ export const lastAdmissionCheckpointSignerRemains = workspaceInvariant("LastAdmissionCheckpointSignerRemains")
367
+ .on(REVOKE_ADMISSION_CHECKPOINT_SIGNER_DIRECTIVE)
368
+ .reads(readSet(
369
+ readRef("tuple", RelationTuple).pre()
370
+ .at(relationTupleInstanceId(signerObject(ADMISSION_CHECKPOINT_PRINCIPAL), SIGNER_RELATION)),
371
+ ))
372
+ .assert(({ tuple }, ctx) => {
373
+ const leaving = `key:${String(((ctx?.intent ?? {}) as { keyHash?: string }).keyHash ?? "")}`;
374
+ const subjects = ((tuple as { subjects?: Record<string, { status?: string }> } | undefined)?.subjects) ?? {};
375
+ const active = Object.entries(subjects).filter(([, leaf]) => leaf?.status === "active").map(([subject]) => subject);
376
+ if (!active.includes(leaving)) return { reject: "checkpoint-key-not-active" };
377
+ return active.some((subject) => subject !== leaving)
378
+ ? { accept: true }
379
+ : { reject: "last-checkpoint-key-cannot-be-revoked" };
380
+ });
381
+
278
382
  /**
279
383
  * THE SELF-BIND ENROLL (VA-1 / B2). A device enrolls a key for ITS OWN verified identity — the bound
280
384
  * principal is `ctx.principal()` (the captured authoring principal), NOT a payload field, so a device can
@@ -335,31 +439,36 @@ export const enrollSignerSelfRequiresVerified = workspaceInvariant("EnrollSigner
335
439
 
336
440
  // ─────────────────── THE ENROLL-ONCE DELEGATION CERT (the peer-sovereign root-of-trust) ───────────────────
337
441
 
338
- /**
339
- * THE SIGNER DELEGATION (the enroll-once delegation cert, `executor::birth_cert`). When the TENANT verifies a
340
- * user U's IdP token at U's live enroll gate, the tenant's onboarding signer `S_P` signs — ONCE — a
341
- * DELEGATION CERT binding U's device key `P_U` to the tenant's lineage: a `BirthCert {founder: U,
342
- * parent_key: P_U, …}` signed by `S_P`. This singleton (keyed by U) records it in the PLATFORM's folded
343
- * state, so the kernel's birth offer-effect reads U's delegation by-user (trusted, not from the payload) when
344
- * it assembles U's home cert chain. Every certificate member is stored as typed USDA fields; the kernel
345
- * reconstructs and verifies the nominal link signed by S_P, whose key is attested by the platform chain.
346
- *
347
- * SELF-BOUND / control-plane: enrolled by the tenant onboarding lane (composed into the tenant's governance
348
- * law, off the open session lane), so a tenant cannot forge a delegation for a user another tenant verified.
349
- * The delegation's `founder` IS the gate's binding — the kernel asserts the home cert's founder equals the
350
- * delegation's founder (the crux founder-binding), so a delegation enrolled for A cannot mint a home for V.
351
- *
352
- * Declared before `enrollFirstDevice` because that directive's typed effect signature and optional plan arm
353
- * both refer to this aggregate during module initialisation.
354
- */
442
+ /** Delegation evidence is retained independently of the live signer relation. New law
443
+ * appends whole signed certificates by device and signature; it never replaces another
444
+ * device's evidence. Read-only predecessor fields keep existing typed custody readable
445
+ * during law adoption. The kernel's new lookup is declared by the compiled law. */
446
+ /** The bounded, typed disclosure a parent workspace may carry when its law recovers a child device.
447
+ * Current cross-workspace law consumes only the nominal certificate fields and the exact signer key
448
+ * subject admitted by the parent. */
449
+ export const signerDelegationRecoveryEvidence = z.object({
450
+ user: z.string().min(1),
451
+ deviceKeySubject: z.string().regex(/^key:[0-9a-f]{64}$/),
452
+ enrolledAt: z.string().min(1),
453
+ certificate: BirthDelegationCertificate.zod,
454
+ }).strict();
455
+ export type SignerDelegationRecoveryEvidence = z.infer<
456
+ typeof signerDelegationRecoveryEvidence
457
+ >;
458
+
459
+
460
+ const SignerDelegationEvidence = valueObject(
461
+ "SignerDelegationEvidence", signerDelegationRecoveryEvidence.shape,
462
+ );
463
+
355
464
  export const SIGNER_DELEGATION_PREFIX = "signer-delegation:";
356
465
  export const SignerDelegation = aggregate("SignerDelegation", {
357
- /** The user (a `user:<uid>` subject) this delegation enrolls — the key of the singleton. */
466
+ /** Grow by certificate identity. Each entry is one complete signed nominal value. */
467
+ certificates: t.map(SignerDelegationEvidence.field()).merge(MapOf(FirstWriterWins)),
468
+ /** Principal whose certificate collection this is. */
358
469
  user: t.string().merge(Lww),
359
- /** The user's device PUBLIC KEY (base64) the delegation binds — `P_U`, the home_cert's signer. */
360
- deviceKey: t.string().merge(Lww),
361
- /** Legacy replay slot for histories authored before the typed certificate fields below. */
362
- certJson: t.string().merge(Lww).optional(),
470
+ /** Read-only predecessor fields. Current enrollment/recovery plans never write these. */
471
+ deviceKey: t.string().merge(Lww).optional(),
363
472
  certificateChild: t.string().merge(Lww).optional(),
364
473
  certificateFounder: t.string().merge(Lww).optional(),
365
474
  certificateVerified: t.bool().merge(Lww).optional(),
@@ -374,49 +483,44 @@ export const SignerDelegation = aggregate("SignerDelegation", {
374
483
  certificateAuthProviderJwks: t.string().merge(Lww).optional(),
375
484
  certificateAuthProviderAudience: t.string().merge(Lww).optional(),
376
485
  /** ISO-8601, caller-stamped (determinism — no clock in the plan). */
377
- enrolledAt: t.string().merge(Lww),
486
+ enrolledAt: t.string().merge(Lww).optional(),
378
487
  }).public();
379
488
  export type SignerDelegationHandle = typeof SignerDelegation;
380
489
 
381
- /** The bounded, typed disclosure a parent workspace may carry when its law recovers a child device.
382
- * It deliberately excludes the historical certJson slot: current cross-workspace law consumes only
383
- * the nominal certificate fields and the exact signer key subject admitted by the parent. */
384
- export const signerDelegationRecoveryEvidence = z.object({
385
- user: z.string().min(1),
386
- deviceKeySubject: z.string().regex(/^key:[0-9a-f]{64}$/),
387
- enrolledAt: z.string().min(1),
388
- certificate: birthCertificatePayload,
389
- }).strict();
390
- export type SignerDelegationRecoveryEvidence = z.infer<
391
- typeof signerDelegationRecoveryEvidence
392
- >;
490
+ const formerSignerDelegation = formerAggregate(
491
+ "SignerDelegation",
492
+ ["certJson"] as const,
493
+ );
393
494
 
394
- /** The deterministic singleton id of a user's signer delegation (the kernel reads THIS id by-user). */
495
+ /** The old opaque certificate document has been replaced by the typed certificate
496
+ * members above. Carry the retirement in framework law so every composed domain
497
+ * proves the same transition without restoring a JSON compatibility slot. */
498
+ export const signerDelegationCertJsonRetired = retirement(
499
+ formerSignerDelegation.fields.certJson,
500
+ );
501
+
502
+ /** Law-owned address of a user's certificate collection. */
395
503
  export function signerDelegationInstanceId(user: string): string {
396
504
  return SIGNER_DELEGATION_PREFIX + user;
397
505
  }
398
506
 
507
+ /** Both encodings are fixed by the certificate contract. The signed identity makes retries
508
+ * address the same entry, while renewals and different device keys occupy distinct entries. */
509
+ export function signerDelegationCertificateId(certificate: BirthCertificatePayload): string {
510
+ return `${certificate.parentKey}:${certificate.signature}`;
511
+ }
512
+
399
513
  function writeDelegationCertificate(
400
514
  delegation: BoundAggregate<typeof SignerDelegation.id, typeof SignerDelegation.fields>,
401
- certificate: BirthCertificatePayload,
515
+ evidence: SignerDelegationRecoveryEvidence,
402
516
  marker?: "ensures",
403
517
  ): PlannedOp[] {
404
- const marked = (operation: FieldOp) => marker ? withMarker(operation, marker) : operation;
405
- return [
406
- marked(set(delegation, "certificateChild", certificate.child)),
407
- marked(set(delegation, "certificateFounder", certificate.founder)),
408
- marked(set(delegation, "certificateVerified", certificate.verified)),
409
- marked(set(delegation, "certificateParentKey", certificate.parentKey)),
410
- marked(set(delegation, "certificateParentId", certificate.parentId)),
411
- marked(set(delegation, "certificateEpoch", certificate.epoch)),
412
- marked(set(delegation, "certificateSignature", certificate.signature)),
413
- ...(certificate.verifiedVia ? [marked(set(delegation, "certificateVerifiedVia", certificate.verifiedVia))] : []),
414
- ...(certificate.idpToken ? [marked(set(delegation, "certificateIdpToken", certificate.idpToken))] : []),
415
- ...(certificate.capturedClock === undefined ? [] : [marked(set(delegation, "certificateCapturedClock", certificate.capturedClock))]),
416
- ...(certificate.authProviderIssuer ? [marked(set(delegation, "certificateAuthProviderIssuer", certificate.authProviderIssuer))] : []),
417
- ...(certificate.authProviderJwks ? [marked(set(delegation, "certificateAuthProviderJwks", certificate.authProviderJwks))] : []),
418
- ...(certificate.authProviderAudience ? [marked(set(delegation, "certificateAuthProviderAudience", certificate.authProviderAudience))] : []),
419
- ];
518
+ if (evidence.user !== evidence.certificate.founder) {
519
+ throw new Error("signer-delegation-evidence-must-bind-its-founder");
520
+ }
521
+ const operation = setEntry(delegation, "certificates",
522
+ signerDelegationCertificateId(evidence.certificate), evidence);
523
+ return [marker ? withMarker(operation, marker) : operation];
420
524
  }
421
525
 
422
526
  /** Materialise the exact typed delegation disclosed by a parent during device
@@ -432,9 +536,7 @@ export function writeSignerDelegationRecoveryEvidence(
432
536
  );
433
537
  return [
434
538
  set(delegation, "user", evidence.user),
435
- set(delegation, "deviceKey", evidence.deviceKeySubject),
436
- set(delegation, "enrolledAt", evidence.enrolledAt),
437
- ...writeDelegationCertificate(delegation, evidence.certificate),
539
+ ...writeDelegationCertificate(delegation, evidence),
438
540
  ];
439
541
  }
440
542
 
@@ -472,9 +574,6 @@ export const enrollFirstDevice = directive("enrollFirstDevice")
472
574
  /** THE CAPTURED PREMISE: the IdP's signed identity assertion (JWT), re-verified on every lane against
473
575
  * the on-chain AuthProvider keys. */
474
576
  idpToken: z.string().min(1),
475
- /** Legacy replay input for callers predating `delegationCertificate`. New callers must provide the
476
- * typed certificate below. */
477
- delegationCert: z.string().min(1).optional(),
478
577
  /** THE KEYLESS DELEGATION CERT — the self-serve sibling of `enrollDelegation.certificate`. Built
479
578
  * CLIENT-SIDE via the typed kernel sign operation: DEVICE-self-signed by this
480
579
  * same device key `P_U` (parent_key == P_U), founder == `user:<uid>`, carrying `verifiedVia` +
@@ -506,28 +605,19 @@ export const enrollFirstDevice = directive("enrollFirstDevice")
506
605
  });
507
606
  // (2) fold the KEYLESS delegation cert as the user's `SignerDelegation` singleton (a SECONDARY ensures,
508
607
  // per-op marked — the id is deterministic-by-user, an upsert). This is the seed `birthHome` reads to
509
- // assemble the warranted chain; without it the home is born headless (co2's block).
608
+ // assemble the warranted chain; without it the home is born without its founder's device seat (co2's block).
510
609
  // The delegation is keyed by the AUTHENTICATED principal — EXACTLY the `founder` the kernel's birth
511
610
  // offer-effect reads it back by (`read_signer_delegation(state, &founder)`, founder == captured principal
512
611
  // == this `rawPrincipal`, already in `user:<uid>` form). Do NOT re-prefix.
513
612
  // Absent certificate ⇒ enroll-only (no delegation seeded), byte-identical to a pre-B enroll.
514
- if (!p.delegationCert && !p.delegationCertificate) return tupleOps;
613
+ if (!p.delegationCertificate) return tupleOps;
515
614
  const d = instance(SignerDelegation, signerDelegationInstanceId(rawPrincipal));
516
615
  const delegationOps: PlannedOp[] = [
517
616
  withMarker(set(d, "user", rawPrincipal), "ensures"),
518
- withMarker(set(d, "deviceKey", "key:" + p.keyHash), "ensures"),
519
- ...(p.delegationCertificate
520
- ? [
521
- withMarker(set(d, "enrolledAt", p.grantedAt), "ensures"),
522
- ...writeDelegationCertificate(d, p.delegationCertificate, "ensures"),
523
- ]
524
- : [
525
- // Exact legacy event order is replay identity, even though Lww folding would commute these
526
- // two writes. Historical enrollFirstDevice intents wrote certJson before enrolledAt; retain
527
- // that byte-level outcome when re-authoring an old payload under a candidate law.
528
- withMarker(set(d, "certJson", p.delegationCert!), "ensures"),
529
- withMarker(set(d, "enrolledAt", p.grantedAt), "ensures"),
530
- ]),
617
+ ...writeDelegationCertificate(d, {
618
+ user: rawPrincipal, deviceKeySubject: "key:" + p.keyHash,
619
+ enrolledAt: p.grantedAt, certificate: p.delegationCertificate,
620
+ }, "ensures"),
531
621
  ];
532
622
  return [...tupleOps, ...delegationOps];
533
623
  })
@@ -590,7 +680,7 @@ export const enrollDelegation = directive("enrollDelegation")
590
680
  .payload(
591
681
  z.object({
592
682
  user: z.string().min(1), // the user:<uid> the delegation enrolls
593
- deviceKey: z.string().min(1), // base64 P_U — the user's device public key
683
+ deviceKey: z.string().regex(/^key:[0-9a-f]{64}$/), // enrolled public-key fingerprint
594
684
  certificate: birthCertificatePayload,
595
685
  enrolledAt: z.string().min(1), // ISO-8601, caller-stamped
596
686
  }),
@@ -599,19 +689,62 @@ export const enrollDelegation = directive("enrollDelegation")
599
689
  const d = instance(SignerDelegation, signerDelegationInstanceId(p.user));
600
690
  return [
601
691
  set(d, "user", p.user),
602
- set(d, "deviceKey", p.deviceKey),
603
- set(d, "enrolledAt", p.enrolledAt),
604
- ...writeDelegationCertificate(d, p.certificate),
692
+ ...writeDelegationCertificate(d, {
693
+ user: p.user, deviceKeySubject: p.deviceKey,
694
+ enrolledAt: p.enrolledAt, certificate: p.certificate,
695
+ }),
605
696
  ];
606
697
  });
607
698
 
699
+ /** Retention is a law invariant, not a promise made by a scalar merge driver. */
700
+ function sameEvidenceValue(left: unknown, right: unknown): boolean {
701
+ if (left === right) return true;
702
+ if (left === null || right === null || typeof left !== "object" || typeof right !== "object") return false;
703
+ const a = left as Record<string, unknown>, b = right as Record<string, unknown>;
704
+ const keys = Object.keys(a);
705
+ return keys.length === Object.keys(b).length && keys.every((key) =>
706
+ Object.prototype.hasOwnProperty.call(b, key) && sameEvidenceValue(a[key], b[key]));
707
+ }
708
+
709
+ export function signerDelegationEvidenceIsRetained(
710
+ name: string, directiveId: string, userPayloadPath: string,
711
+ ) {
712
+ return workspaceInvariant(name).on(directiveId).reads(readSet(
713
+ readRef("before", SignerDelegation).pre().fromOptionalPayload(userPayloadPath, { prefix: SIGNER_DELEGATION_PREFIX }),
714
+ readRef("after", SignerDelegation).post().fromOptionalPayload(userPayloadPath, { prefix: SIGNER_DELEGATION_PREFIX }),
715
+ )).assert(({ before, after }) => {
716
+ const oldEntries = (before?.certificates ?? {}) as Record<string, unknown>;
717
+ const newEntries = (after?.certificates ?? {}) as Record<string, unknown>;
718
+ if (!Object.entries(oldEntries).every(([id, value]) => sameEvidenceValue(value, newEntries[id]))) {
719
+ return { reject: "signer-delegation-evidence-is-immutable" };
720
+ }
721
+ for (const [id, value] of Object.entries(newEntries)) {
722
+ const parsed = signerDelegationRecoveryEvidence.safeParse(value);
723
+ if (!parsed.success || parsed.data.user !== after?.user ||
724
+ parsed.data.certificate.founder !== parsed.data.user ||
725
+ signerDelegationCertificateId(parsed.data.certificate) !== id) {
726
+ return { reject: "signer-delegation-evidence-must-bind-its-identity" };
727
+ }
728
+ }
729
+ return { accept: true };
730
+ });
731
+ }
732
+
733
+ export const enrollDelegationRetainsEvidence = signerDelegationEvidenceIsRetained(
734
+ "EnrollDelegationRetainsEvidence", "enrollDelegation", "user",
735
+ );
736
+ export const enrollFirstDeviceRetainsEvidence = signerDelegationEvidenceIsRetained(
737
+ "EnrollFirstDeviceRetainsEvidence", "enrollFirstDevice", "delegationCertificate.founder",
738
+ );
739
+
608
740
  /** Audit: a user's signer delegation (the enroll-once cert binding their device to the tenant lineage). */
609
741
  export const signerDelegationByUser = query("signerDelegationByUser").key("user").returns(SignerDelegation);
610
742
 
611
- /** Read exactly one current typed delegation through its declared O(1) index. Missing or incomplete
612
- * typed certificate members fail closed; no legacy document is parsed or carried. */
743
+ /** Read the exact device's evidence through the law's declared user index. A caller that
744
+ * omits the device is accepted only for a single-device collection; never pick another device. */
613
745
  export function readSignerDelegationRecoveryEvidence(
614
746
  user: string,
747
+ deviceKeySubject?: string,
615
748
  ): SignerDelegationRecoveryEvidence | undefined {
616
749
  const rows = readFrameworkDeclaredRows(
617
750
  signerDelegationByUser,
@@ -625,7 +758,10 @@ export function readSignerDelegationRecoveryEvidence(
625
758
  );
626
759
  }
627
760
  const row = rows[0]!;
628
- return signerDelegationRecoveryEvidence.parse({
761
+ const candidates = Object.values(row.certificates ?? {}).map((value) =>
762
+ signerDelegationRecoveryEvidence.parse(value));
763
+ // Old typed fields are a read-only predecessor. New law never writes them again.
764
+ if (row.certificateSignature !== undefined) candidates.push(signerDelegationRecoveryEvidence.parse({
629
765
  user: row.user,
630
766
  deviceKeySubject: row.deviceKey,
631
767
  enrolledAt: row.enrolledAt,
@@ -650,7 +786,18 @@ export function readSignerDelegationRecoveryEvidence(
650
786
  ? {}
651
787
  : { authProviderAudience: row.certificateAuthProviderAudience }),
652
788
  },
653
- });
789
+ }));
790
+ const matching = candidates.filter((candidate) => candidate.user === user &&
791
+ (deviceKeySubject === undefined || candidate.deviceKeySubject === deviceKeySubject));
792
+ if (new Set(matching.map((candidate) => candidate.deviceKeySubject)).size > 1) {
793
+ throw new Error(`signerDelegationByUser('${user}') requires an exact device key`);
794
+ }
795
+ // Renewal selection is explicit and deterministic; unrelated devices cannot influence it.
796
+ matching.sort((a, b) => b.certificate.epoch - a.certificate.epoch ||
797
+ (b.certificate.capturedClock ?? 0) - (a.certificate.capturedClock ?? 0) ||
798
+ (a.certificate.signature < b.certificate.signature ? -1 :
799
+ a.certificate.signature > b.certificate.signature ? 1 : 0));
800
+ return matching[0];
654
801
  }
655
802
 
656
803
  /** Audit: the signer tuples for an identity (the raw relation aggregate). */
@@ -28,6 +28,8 @@ import { directive, type Directive } from "../directive.js";
28
28
  import { set } from "../ops.js";
29
29
  import { read } from "../read.js";
30
30
  import { foreignQuery } from "../attested_core.js";
31
+ import { valueObject } from "../value_object.js";
32
+ import { RelationshipSubjectValue } from "./relations.js";
31
33
 
32
34
  /** The framework-reserved roster instance-id prefix (kernel mirror:
33
35
  * `nomos_executor::SOURCE_ROSTER_PREFIX`). */
@@ -43,16 +45,20 @@ export const ATTESTOR_ROSTER_QUERY_ID = "attestorRoster";
43
45
 
44
46
  /**
45
47
  * NomosSourceRoster (§1.5) — the reader's folded mirror of one source's read-attestor
46
- * roster, keyed by the source's TRUE identity (`source-roster:<genesis>`). `rosterJson` is
48
+ * roster, keyed by the source's TRUE identity (`source-roster:<genesis>`). `roster` is
47
49
  * the `keyHash → leaf` map exactly as the source's signer chain folds it (a key is ACCEPTED
48
50
  * iff its leaf's `status == "active"` — the position-aware revocation mirror); the kernel's
49
51
  * `accepted_keys` reads THIS under `roster: "tracked"`.
50
52
  */
53
+ export const SourceSignerRoster = valueObject("SourceSignerRoster", {
54
+ keys: z.record(z.string(), RelationshipSubjectValue.zod),
55
+ });
56
+
51
57
  export const NomosSourceRoster = aggregate("NomosSourceRoster", {
52
58
  /** The routable source name the edge was declared under (diagnostics). */
53
59
  source: t.string().merge(Lww),
54
- /** The `keyHash → enrollment leaf` map, JSON — as attested by the source's own chain. */
55
- rosterJson: t.json().merge(Lww),
60
+ /** The typed `keyHash → enrollment leaf` map attested by the source's own chain. */
61
+ roster: SourceSignerRoster.field().merge(Lww),
56
62
  /** The source's `SignerEpoch` at the attested sync. */
57
63
  currentEpoch: t.int().merge(Lww),
58
64
  }).public();
@@ -63,7 +69,7 @@ interface AttestorRosterRow {
63
69
  data: {
64
70
  genesis: string;
65
71
  currentEpoch: number;
66
- keys: Record<string, unknown>;
72
+ keys: Record<string, z.infer<typeof RelationshipSubjectValue.zod>>;
67
73
  };
68
74
  }
69
75
 
@@ -88,7 +94,7 @@ export function sourceRosterSync(sourceWs: string, directiveId = "syncSourceRost
88
94
  const r = instance(NomosSourceRoster, sourceRosterId(d.genesis));
89
95
  return [
90
96
  set(r, "source", sourceWs),
91
- set(r, "rosterJson", JSON.stringify(d.keys ?? {})),
97
+ set(r, "roster", { keys: d.keys ?? {} }),
92
98
  set(r, "currentEpoch", d.currentEpoch ?? 0),
93
99
  ];
94
100
  })