@githolon/dsl 0.108.7 → 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 +1994 -1189
  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,386 @@
1
+ import 'dart:async';
2
+ import 'dart:convert';
3
+ import 'dart:typed_data';
4
+
5
+ import 'package:fixnum/fixnum.dart';
6
+ import 'package:crypto/crypto.dart' show sha256;
7
+ import 'package:nomos_client/src/runtime_v2_transport.dart';
8
+ import 'package:nomos_peer_host/nomos_peer_host.dart'
9
+ show NomosDeviceIdentityStore, NomosProviderCredentialStore;
10
+ import 'package:nomos_types/nomos_kernel_protocol.dart' as p;
11
+ import 'package:test/test.dart';
12
+
13
+ class _Platform
14
+ with NmsPlatformCredentialProviderHost
15
+ implements NomosRuntimeV2Transport {
16
+ final requests = <p.RuntimeRequest>[];
17
+ int sequence = 40;
18
+ @override
19
+ int nextRuntimeRequestId() => ++sequence;
20
+ @override
21
+ Future<p.RuntimeResponse> invokeRuntime(p.RuntimeRequest request) async {
22
+ requests.add(request);
23
+ return p.RuntimeResponse(
24
+ protocolVersion: 2,
25
+ requestId: request.requestId,
26
+ credentialsRefreshed: p.CredentialsRefreshed(session: 'home'));
27
+ }
28
+ }
29
+
30
+ p.CorrespondencePending pending() => p.CorrespondencePending(
31
+ workId: 'work',
32
+ refreshCredential: p.PeerCredentialRefreshEffect(
33
+ effectId: 'effect', capability: 'opaque-capability'));
34
+
35
+ class _MemoryIdentityStore implements NomosDeviceIdentityStore {
36
+ final Map<String, Uint8List> saved = {};
37
+ final List<String> log = [];
38
+ @override
39
+ Future<Uint8List?> load(String identity) async {
40
+ log.add('load:$identity');
41
+ return saved[identity];
42
+ }
43
+
44
+ @override
45
+ Future<void> save(String identity, Uint8List secret) async {
46
+ log.add('save:$identity');
47
+ saved[identity] = Uint8List.fromList(secret);
48
+ }
49
+ }
50
+
51
+ class _MemoryProviderCredentialStore implements NomosProviderCredentialStore {
52
+ final Map<String, List<String>> saved = {};
53
+
54
+ @override
55
+ Future<List<String>> loadCandidates(String entry) async =>
56
+ List<String>.from(saved[entry] ?? const []);
57
+
58
+ @override
59
+ Future<void> retainCandidate(String entry, String authToken) async {
60
+ final existing = saved[entry] ?? const [];
61
+ saved[entry] = [
62
+ authToken,
63
+ ...existing.where((candidate) => candidate != authToken),
64
+ ].take(4).toList(growable: false);
65
+ }
66
+ }
67
+
68
+ Uint8List secretOf(String label) => Uint8List.fromList(List<int>.generate(
69
+ 32, (i) => (label.codeUnitAt(i % label.length) ^ i) & 0xff));
70
+
71
+ p.PeerDeviceIdentityEffect loadEffect() => p.PeerDeviceIdentityEffect(
72
+ effectId: 'identity:load',
73
+ identity: 'co2|user:ada',
74
+ load: p.PeerDeviceIdentityLoad());
75
+
76
+ void main() {
77
+ test(
78
+ 'secure recovery journal round-trips separately from the active identity',
79
+ () async {
80
+ final platform = _Platform();
81
+ final store = _MemoryIdentityStore();
82
+ platform.registerDeviceIdentityStore(store);
83
+ const identity = 'independent-app|user:ada';
84
+ final old = secretOf('old');
85
+ final candidate = secretOf('new');
86
+ store.saved[identity] = old;
87
+ final record = p.DeviceRecoveryRecord(
88
+ version: 1,
89
+ application: 'independent-app',
90
+ principal: 'user:ada',
91
+ previousSecret: old,
92
+ candidateSecret: candidate);
93
+ final saved = await platform.performDeviceIdentityEffect(
94
+ p.PeerDeviceIdentityEffect(
95
+ effectId: 'save-recovery',
96
+ identity: identity,
97
+ saveRecovery: p.PeerDeviceRecoverySave(record: record)));
98
+ expect(saved.hasDeviceIdentity(), isTrue);
99
+ expect(store.saved[identity], old, reason: 'staging is not activation');
100
+ final loaded = await platform.performDeviceIdentityEffect(
101
+ p.PeerDeviceIdentityEffect(
102
+ effectId: 'load-recovery',
103
+ identity: identity,
104
+ loadRecovery: p.PeerDeviceRecoveryLoad()));
105
+ expect(loaded.deviceIdentity.hasSecret(), isFalse);
106
+ expect(loaded.deviceIdentity.recovery, record);
107
+ store.saved['nomos-device-recovery-v1|$identity'] =
108
+ Uint8List.fromList([255]);
109
+ final corrupt = await platform.performDeviceIdentityEffect(
110
+ p.PeerDeviceIdentityEffect(
111
+ effectId: 'load-corrupt',
112
+ identity: identity,
113
+ loadRecovery: p.PeerDeviceRecoveryLoad()));
114
+ expect(corrupt.hasFailure(), isTrue);
115
+ expect(store.saved[identity], old);
116
+ store.saved[identity] = Uint8List(0);
117
+ final empty = await platform.performDeviceIdentityEffect(
118
+ p.PeerDeviceIdentityEffect(
119
+ effectId: 'empty',
120
+ identity: identity,
121
+ load: p.PeerDeviceIdentityLoad()));
122
+ expect(empty.hasFailure(), isTrue,
123
+ reason: 'present corruption must not cross protobuf as a missing key');
124
+ expect(empty.failure.message, contains('malformed'));
125
+ });
126
+
127
+ test('a readiness wake leaves the transport available for another request',
128
+ () async {
129
+ final platform = _Platform();
130
+ var frames = 0;
131
+ final waiting = platform.driveCredentialEffects(
132
+ p.RuntimeRequest(protocolVersion: 2, requestId: Int64(7)),
133
+ (request) async {
134
+ frames++;
135
+ if (frames == 1)
136
+ return p.RuntimeResponse(
137
+ protocolVersion: 2,
138
+ requestId: request.requestId,
139
+ correspondencePending: p.CorrespondencePending(
140
+ workId: 'wait',
141
+ wake: p.PeerWakeEffect(effectId: 'wake', delayMillis: 10)));
142
+ expect(request.continueCorrespondence.receipt.hasWoken(), isTrue);
143
+ expect(request.continueCorrespondence.receipt.effectId, 'wake');
144
+ expect(platform.requests, hasLength(1));
145
+ return p.RuntimeResponse(
146
+ protocolVersion: 2,
147
+ requestId: request.requestId,
148
+ authenticationReadiness: p.AuthenticationReadiness());
149
+ });
150
+ await platform.invokeRuntime(
151
+ p.RuntimeRequest(protocolVersion: 2, requestId: Int64(8)));
152
+ expect((await waiting).hasAuthenticationReadiness(), isTrue);
153
+ expect(frames, 2);
154
+ });
155
+
156
+ test('foreground carries capability proof and continues the same request',
157
+ () async {
158
+ final platform = _Platform();
159
+ var calls = 0;
160
+ platform.registerCredentialProvider(
161
+ capability: 'opaque-capability',
162
+ cloud: 'https://example.test',
163
+ signIn: ({deviceChallenge}) async {
164
+ calls++;
165
+ return ' fresh-proof ';
166
+ });
167
+ platform.registerCredentialProvider(
168
+ capability: 'different',
169
+ cloud: 'https://example.test',
170
+ signIn: ({deviceChallenge}) async =>
171
+ throw StateError('wrong provider'));
172
+ final initial = p.RuntimeRequest(protocolVersion: 2, requestId: Int64(7));
173
+ var frames = 0;
174
+ final result =
175
+ await platform.driveCredentialEffects(initial, (request) async {
176
+ frames++;
177
+ expect(request.requestId, Int64(7));
178
+ if (frames == 1)
179
+ return p.RuntimeResponse(
180
+ protocolVersion: 2,
181
+ requestId: request.requestId,
182
+ correspondencePending: pending());
183
+ expect(request.continueCorrespondence.workId, 'work');
184
+ final receipt = request.continueCorrespondence.receipt;
185
+ expect(receipt.effectId, 'effect');
186
+ expect(utf8.decode(receipt.credentialRefreshed.authToken), 'fresh-proof');
187
+ return p.RuntimeResponse(
188
+ protocolVersion: 2,
189
+ requestId: request.requestId,
190
+ kernelCall: p.CallResponse());
191
+ });
192
+ expect(result.hasKernelCall(), isTrue);
193
+ expect(frames, 2);
194
+ expect(calls, 1);
195
+ });
196
+
197
+ test(
198
+ 'native-style durable credentials survive restart and a rejected token falls back through the rolling set',
199
+ () async {
200
+ final durable = _MemoryProviderCredentialStore();
201
+ final first = _Platform()..registerProviderCredentialStore(durable);
202
+ final issued = ['old-proof', 'new-proof'];
203
+ first.registerCredentialProvider(
204
+ capability: 'opaque-capability',
205
+ cloud: 'https://provider-a.example',
206
+ signIn: ({deviceChallenge}) async => issued.removeAt(0));
207
+ for (var i = 0; i < 2; i += 1) {
208
+ final receipt = await first.performCredentialRefresh(
209
+ p.PeerCredentialRefreshEffect(
210
+ effectId: 'online-$i', capability: 'opaque-capability'));
211
+ expect(receipt.hasCredentialRefreshed(), isTrue);
212
+ }
213
+
214
+ var providerCalls = 0;
215
+ final restarted = _Platform()..registerProviderCredentialStore(durable);
216
+ restarted.registerCredentialProvider(
217
+ capability: 'opaque-capability',
218
+ cloud: 'https://provider-a.example',
219
+ signIn: ({deviceChallenge}) async {
220
+ providerCalls += 1;
221
+ return 'network-proof';
222
+ });
223
+ var receipt = await restarted.performCredentialRefresh(
224
+ p.PeerCredentialRefreshEffect(
225
+ effectId: 'restart',
226
+ capability: 'opaque-capability',
227
+ allowCached: true));
228
+ expect(utf8.decode(receipt.credentialRefreshed.authToken), 'new-proof');
229
+ expect(providerCalls, 0,
230
+ reason: 'a process restart must not require Firebase');
231
+
232
+ receipt = await restarted.performCredentialRefresh(
233
+ p.PeerCredentialRefreshEffect(
234
+ effectId: 'rejected',
235
+ capability: 'opaque-capability',
236
+ allowCached: true,
237
+ rejectedAuthTokenSha256: Uint8List.fromList(
238
+ sha256.convert(utf8.encode('new-proof')).bytes)));
239
+ expect(utf8.decode(receipt.credentialRefreshed.authToken), 'old-proof');
240
+ expect(providerCalls, 0,
241
+ reason: 'the preceding admitted proof remains usable during rollout');
242
+ });
243
+
244
+ test(
245
+ 'unavailable and failing providers return receipts without replay policy',
246
+ () async {
247
+ final platform = _Platform();
248
+ var result =
249
+ await platform.performCredentialRefresh(pending().refreshCredential);
250
+ expect(result.hasCredentialRefreshed(), isTrue);
251
+ expect(result.credentialRefreshed.hasAuthToken(), isFalse);
252
+ platform.registerCredentialProvider(
253
+ capability: 'opaque-capability',
254
+ cloud: 'https://example.test',
255
+ signIn: ({deviceChallenge}) async =>
256
+ throw StateError('secret diagnostic'));
257
+ result =
258
+ await platform.performCredentialRefresh(pending().refreshCredential);
259
+ expect(result.hasFailure(), isTrue);
260
+ expect(result.failure.message, isNot(contains('secret diagnostic')));
261
+ });
262
+
263
+ test('background physical event is consumed and returns a correlated receipt',
264
+ () async {
265
+ final platform = _Platform();
266
+ final failures = <Object>[];
267
+ expect(
268
+ platform.consumeCredentialRefreshEvent(
269
+ p.RuntimeEvent(), platform, (error, stack) => failures.add(error)),
270
+ isFalse);
271
+ expect(
272
+ platform.consumeCredentialRefreshEvent(
273
+ p.RuntimeEvent(
274
+ credentialRefreshRequested:
275
+ p.CredentialRefreshRequested(pending: pending())),
276
+ platform,
277
+ (error, stack) => failures.add(error)),
278
+ isTrue);
279
+ await Future<void>.delayed(Duration.zero);
280
+ expect(platform.requests, hasLength(1));
281
+ expect(platform.requests.single.requestId, Int64(41));
282
+ expect(platform.requests.single.continueCorrespondence.workId, 'work');
283
+ expect(failures, isEmpty);
284
+ });
285
+
286
+ test(
287
+ 'the device challenge reaches the provider verbatim and only when carried',
288
+ () async {
289
+ final platform = _Platform();
290
+ final seen = <String?>[];
291
+ platform.registerCredentialProvider(
292
+ capability: 'opaque-capability',
293
+ cloud: 'https://example.test',
294
+ signIn: ({deviceChallenge}) async {
295
+ seen.add(deviceChallenge);
296
+ return 'bound-${deviceChallenge ?? 'plain'}';
297
+ });
298
+ var receipt = await platform.performCredentialRefresh(
299
+ p.PeerCredentialRefreshEffect(
300
+ effectId: 'effect',
301
+ capability: 'opaque-capability',
302
+ deviceChallenge: 'ab' * 32));
303
+ expect(utf8.decode(receipt.credentialRefreshed.authToken),
304
+ 'bound-${'ab' * 32}');
305
+ receipt =
306
+ await platform.performCredentialRefresh(pending().refreshCredential);
307
+ expect(utf8.decode(receipt.credentialRefreshed.authToken), 'bound-plain');
308
+ expect(seen, ['ab' * 32, null]);
309
+ });
310
+
311
+ test(
312
+ 'a device identity load and save round-trip through the registered store',
313
+ () async {
314
+ final platform = _Platform();
315
+ final store = _MemoryIdentityStore();
316
+ platform.registerDeviceIdentityStore(store);
317
+ final secret = secretOf('device-a');
318
+ var receipt = await platform.performDeviceIdentityEffect(loadEffect());
319
+ expect(receipt.effectId, 'identity:load');
320
+ expect(receipt.hasDeviceIdentity(), isTrue);
321
+ expect(receipt.deviceIdentity.hasSecret(), isFalse);
322
+ receipt = await platform.performDeviceIdentityEffect(
323
+ p.PeerDeviceIdentityEffect(
324
+ effectId: 'identity:save',
325
+ identity: 'co2|user:ada',
326
+ save: p.PeerDeviceIdentitySave(secret: secret)));
327
+ expect(receipt.hasDeviceIdentity(), isTrue);
328
+ receipt = await platform.performDeviceIdentityEffect(loadEffect());
329
+ expect(receipt.deviceIdentity.secret, secret);
330
+ expect(store.log,
331
+ ['load:co2|user:ada', 'save:co2|user:ada', 'load:co2|user:ada']);
332
+ });
333
+
334
+ test('a shell without a device identity store refuses typed', () async {
335
+ final platform = _Platform();
336
+ final receipt = await platform.performDeviceIdentityEffect(loadEffect());
337
+ expect(receipt.hasFailure(), isTrue);
338
+ expect(receipt.failure.message, contains('device identity store'));
339
+ });
340
+
341
+ test(
342
+ 'driveCredentialEffects answers a device identity paper on the same request',
343
+ () async {
344
+ final platform = _Platform();
345
+ final store = _MemoryIdentityStore()
346
+ ..saved['co2|user:ada'] = secretOf('device-b');
347
+ platform.registerDeviceIdentityStore(store);
348
+ var frames = 0;
349
+ final result = await platform.driveCredentialEffects(
350
+ p.RuntimeRequest(protocolVersion: 2, requestId: Int64(7)),
351
+ (request) async {
352
+ frames++;
353
+ expect(request.requestId, Int64(7));
354
+ if (frames == 1) {
355
+ return p.RuntimeResponse(
356
+ protocolVersion: 2,
357
+ requestId: request.requestId,
358
+ correspondencePending: p.CorrespondencePending(
359
+ workId: 'device-identity-1', deviceIdentity: loadEffect()));
360
+ }
361
+ expect(request.continueCorrespondence.workId, 'device-identity-1');
362
+ final receipt = request.continueCorrespondence.receipt;
363
+ expect(receipt.effectId, 'identity:load');
364
+ expect(receipt.deviceIdentity.secret, secretOf('device-b'));
365
+ return p.RuntimeResponse(
366
+ protocolVersion: 2,
367
+ requestId: request.requestId,
368
+ credentialsRefreshed:
369
+ p.CredentialsRefreshed(session: 'home', hasAuthorSecret: true));
370
+ });
371
+ expect(result.credentialsRefreshed.hasAuthorSecret, isTrue);
372
+ expect(frames, 2);
373
+ });
374
+
375
+ test('unrelated response cannot invoke a credential provider', () async {
376
+ final platform = _Platform();
377
+ await expectLater(
378
+ platform.driveCredentialEffects(
379
+ p.RuntimeRequest(protocolVersion: 2, requestId: Int64(7)),
380
+ (_) async => p.RuntimeResponse(
381
+ protocolVersion: 2,
382
+ requestId: Int64(8),
383
+ correspondencePending: pending())),
384
+ throwsStateError);
385
+ });
386
+ }
@@ -0,0 +1,213 @@
1
+ // THE RECEIVING-GROUP GATE FROM DART (receiving_group_lifecycle.md cut 4): Reception
2
+ // transports ONE `OpenReceivingGroupRequest` paper and renders the office's answer; it
3
+ // owns no ceremony. The desk's typed refusal surfaces as a NomosRuntimeError carrying the
4
+ // desk's own code, and the convergence tail arrives as `ReceivingGroupConvergence`
5
+ // runtime events scoped to the group's session.
6
+ import 'dart:async';
7
+ import 'dart:convert';
8
+
9
+ import 'package:nomos_client/src/nms_generated_runtime.dart';
10
+ import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
11
+ import 'package:test/test.dart';
12
+
13
+ const _principal = 'user:ada';
14
+ // The office names the receiving group; Reception asks for it. A test scripts
15
+ // that answer like any other paper.
16
+ const _group = 'nomos-rg-scripted';
17
+ final _lawSha256 = '2' * 64;
18
+
19
+ final class _Office implements PeerOfficeConnection {
20
+ _Office({this.refuse});
21
+
22
+ /// A scripted desk refusal, or null for an opened gate.
23
+ final ({String code, String message})? refuse;
24
+ final _events = StreamController<protocol.RuntimeEvent>.broadcast();
25
+ final calls = <protocol.RuntimeRequest>[];
26
+
27
+ @override
28
+ Stream<protocol.RuntimeEvent> get runtimeEvents => _events.stream;
29
+
30
+ void emit(protocol.RuntimeEvent event) => _events.add(event);
31
+
32
+ int _requests = 0;
33
+ @override
34
+ int nextRuntimeRequestId() => ++_requests;
35
+
36
+ @override
37
+ Future<protocol.RuntimeResponse> invokeRuntime(
38
+ protocol.RuntimeRequest request,
39
+ ) async {
40
+ calls.add(request.deepCopy());
41
+ if (request.hasBindSession()) {
42
+ return protocol.RuntimeResponse(
43
+ protocolVersion: 2,
44
+ requestId: request.requestId,
45
+ sessionBound: protocol.SessionBound(
46
+ workspace: request.bindSession.workspace,
47
+ session: request.bindSession.session,
48
+ credentialCapability: request.bindSession.session,
49
+ ),
50
+ );
51
+ }
52
+ if (request.hasOpenReceivingGroup()) {
53
+ final refusal = refuse;
54
+ if (refusal != null) {
55
+ return protocol.RuntimeResponse(
56
+ protocolVersion: 2,
57
+ requestId: request.requestId,
58
+ failure: protocol.RuntimeFailure(
59
+ code: refusal.code,
60
+ message: refusal.message,
61
+ component: 'receiving-group-desk',
62
+ ),
63
+ );
64
+ }
65
+ return protocol.RuntimeResponse(
66
+ protocolVersion: 2,
67
+ requestId: request.requestId,
68
+ receivingGroupOpened: protocol.ReceivingGroupOpened(
69
+ session: request.openReceivingGroup.session,
70
+ workspace: _group,
71
+ deviceKeySubject: 'key:${'a' * 64}',
72
+ ),
73
+ );
74
+ }
75
+ throw StateError('unexpected runtime call ${request.whichCommand().name}');
76
+ }
77
+ }
78
+
79
+ Future<DartReception> _connected(_Office office) async {
80
+ final reception = DartReception(office);
81
+ addTearDown(reception.dispose);
82
+ await reception.bindSession(
83
+ workspace: _group,
84
+ principal: _principal,
85
+ );
86
+ return reception;
87
+ }
88
+
89
+ void main() {
90
+ test(
91
+ 'the session sends exactly one open paper: contract, principal, session, no seed',
92
+ () async {
93
+ final office = _Office();
94
+ final reception = await _connected(office);
95
+
96
+ final opened = await reception.session(_group).nmsOpenReceivingGroup(
97
+ application: 'acme-platform',
98
+ principal: _principal,
99
+ authToken: 'id-token',
100
+ contract: protocol.ApplicationBirthContract(lawSha256: _lawSha256),
101
+ verifyHistory: true,
102
+ );
103
+
104
+ final papers =
105
+ office.calls.where((c) => c.hasOpenReceivingGroup()).toList();
106
+ expect(papers, hasLength(1));
107
+ final paper = papers.single.openReceivingGroup;
108
+ expect(paper.session, _group, reason: 'the session is the group workspace');
109
+ expect(paper.application, 'acme-platform');
110
+ expect(paper.principal, _principal);
111
+ expect(utf8.decode(paper.authToken), 'id-token');
112
+ expect(paper.contract.lawSha256, _lawSha256);
113
+ expect(paper.contract.toProto3Json(), {'lawSha256': _lawSha256},
114
+ reason:
115
+ 'the contract is law-only: the platform controller is observed by the desk');
116
+ expect(paper.genesisSeed, isEmpty, reason: 'seeds are law');
117
+ expect(paper.verifyHistory, isTrue);
118
+ expect(papers.single.protocolVersion, 2);
119
+
120
+ expect(opened.workspace, _group);
121
+ expect(opened.deviceKeySubject, startsWith('key:'));
122
+ });
123
+
124
+ test("the desk's typed refusal surfaces as a runtime error carrying its code",
125
+ () async {
126
+ final office = _Office(
127
+ refuse: (
128
+ code: 'nomos.runtime.receiving_group_platform_controller_unknown',
129
+ message:
130
+ "the platform 'acme-platform' reports no current 'nomos' controller law",
131
+ ),
132
+ );
133
+ final reception = await _connected(office);
134
+
135
+ await expectLater(
136
+ reception.session(_group).nmsOpenReceivingGroup(
137
+ application: 'acme-platform',
138
+ principal: _principal,
139
+ authToken: 'id-token',
140
+ contract: protocol.ApplicationBirthContract(lawSha256: _lawSha256),
141
+ ),
142
+ throwsA(isA<NomosRuntimeError>().having(
143
+ (e) => e.info.code,
144
+ 'code',
145
+ 'nomos.runtime.receiving_group_platform_controller_unknown',
146
+ )),
147
+ );
148
+ });
149
+
150
+ test(
151
+ 'convergence events reach the session bound to the group, and only that one',
152
+ () async {
153
+ final office = _Office();
154
+ final reception = await _connected(office);
155
+ await reception.bindSession(
156
+ workspace: 'estate-1',
157
+ principal: _principal,
158
+ );
159
+ final groupEvents = <protocol.ReceivingGroupConvergence>[];
160
+ final estateEvents = <protocol.ReceivingGroupConvergence>[];
161
+ final planeEvents = <protocol.ReceivingGroupConvergence>[];
162
+ final groupSub = reception
163
+ .session(_group)
164
+ .receivingGroupConvergence
165
+ .listen(groupEvents.add);
166
+ final estateSub = reception
167
+ .session('estate-1')
168
+ .receivingGroupConvergence
169
+ .listen(estateEvents.add);
170
+ final planeSub =
171
+ reception.receivingGroupConvergence.listen(planeEvents.add);
172
+ addTearDown(() async {
173
+ await groupSub.cancel();
174
+ await estateSub.cancel();
175
+ await planeSub.cancel();
176
+ });
177
+
178
+ protocol.RuntimeEvent event(
179
+ protocol.ReceivingGroupConvergencePhase phase,
180
+ protocol.ReceivingGroupConvergenceOutcome outcome,
181
+ ) =>
182
+ protocol.RuntimeEvent(
183
+ session: _group,
184
+ receivingGroupConvergence: protocol.ReceivingGroupConvergence(
185
+ workspace: _group,
186
+ phase: phase,
187
+ outcome: outcome,
188
+ ),
189
+ );
190
+ office.emit(event(
191
+ protocol.ReceivingGroupConvergencePhase
192
+ .RECEIVING_GROUP_CONVERGENCE_PHASE_POSTURE,
193
+ protocol.ReceivingGroupConvergenceOutcome
194
+ .RECEIVING_GROUP_CONVERGENCE_OUTCOME_IN_PROGRESS,
195
+ ));
196
+ office.emit(event(
197
+ protocol.ReceivingGroupConvergencePhase
198
+ .RECEIVING_GROUP_CONVERGENCE_PHASE_VERIFICATION,
199
+ protocol.ReceivingGroupConvergenceOutcome
200
+ .RECEIVING_GROUP_CONVERGENCE_OUTCOME_CONVERGED,
201
+ ));
202
+ await Future<void>.delayed(Duration.zero);
203
+
204
+ expect(groupEvents.map((e) => e.outcome.name), [
205
+ 'RECEIVING_GROUP_CONVERGENCE_OUTCOME_IN_PROGRESS',
206
+ 'RECEIVING_GROUP_CONVERGENCE_OUTCOME_CONVERGED',
207
+ ]);
208
+ expect(estateEvents, isEmpty,
209
+ reason: 'another workspace never sees the group tail');
210
+ expect(planeEvents, hasLength(2),
211
+ reason: 'the root reception sees the plane');
212
+ });
213
+ }
@@ -0,0 +1,103 @@
1
+ import 'package:nomos_client/src/reception/workspace_preparation_report.dart';
2
+ import 'package:nomos_client/src/reception/progress_desk.dart';
3
+ import 'package:nomos_client/src/reception/event_desk.dart';
4
+ import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
5
+ import 'package:test/test.dart';
6
+
7
+ void main() {
8
+ test('a new peer demand clears stale index readiness before its kernel turn',
9
+ () {
10
+ final settled = WorkspacePreparationReport.fromMaintenance(
11
+ protocol.ReadMaintenanceProgress(
12
+ workspace: 'estate-1',
13
+ kernel: protocol.MaintenanceQuantumResponse(
14
+ phase: protocol.MaintenancePhase.MAINTENANCE_PHASE_COMPLETE,
15
+ readFrontier: 'head-a',
16
+ sqliteFrontier: 'head-a',
17
+ ),
18
+ ),
19
+ );
20
+ expect(settled.active, isFalse);
21
+ expect(settled.sqliteCurrent, isTrue);
22
+
23
+ final scheduled = WorkspacePreparationReport.fromMaintenance(
24
+ protocol.ReadMaintenanceProgress(workspace: 'estate-1'),
25
+ previous: settled,
26
+ );
27
+ expect(scheduled.active, isTrue);
28
+ expect(scheduled.sqliteCurrent, isNull);
29
+ });
30
+
31
+ test('peer retry is visible without claiming a sync failure', () {
32
+ final retry = WorkspacePreparationReport.fromMaintenance(
33
+ protocol.ReadMaintenanceProgress(
34
+ workspace: 'estate-1',
35
+ retryAfterMillis: 1000,
36
+ ),
37
+ );
38
+ expect(retry.retrying, isTrue);
39
+ expect(retry.active, isFalse);
40
+ });
41
+
42
+ test('unreadable copy stops preparation', () {
43
+ final stopped = WorkspacePreparationReport.fromMaintenance(
44
+ protocol.ReadMaintenanceProgress(workspace: 'estate-1', stopped: true),
45
+ );
46
+ expect(stopped.active, isFalse);
47
+ expect(stopped.stopped, isTrue);
48
+ });
49
+
50
+ test('an answered read closes the observation without a kernel turn', () {
51
+ final answered = WorkspacePreparationReport.fromMaintenance(
52
+ protocol.ReadMaintenanceProgress(
53
+ workspace: 'estate-1',
54
+ satisfiedByRead: true,
55
+ ),
56
+ );
57
+ expect(answered.active, isFalse);
58
+ expect(answered.step, WorkspacePreparationStep.complete);
59
+ });
60
+
61
+ test('restore law hydration is reported as installing workspace rules', () {
62
+ final law = WorkspacePreparationReport.fromRestore(KernelProgress(
63
+ sequence: 1,
64
+ operation: KernelProgressOperation.restoreMain,
65
+ phase: KernelProgressPhase.hydrateLaw,
66
+ completed: 0,
67
+ total: 1,
68
+ unit: KernelProgressUnit.lawPackage,
69
+ terminal: false,
70
+ lawHydrationStep: KernelLawHydrationStep.composeStage,
71
+ lawPackageSha256: 'law',
72
+ hostObservedElapsedMicros: 0,
73
+ hostObservedPhaseElapsedMicros: 0,
74
+ kernelAllocatorLiveBytes: 0,
75
+ kernelAllocatorPeakBytes: 0,
76
+ processResidentBytes: 0,
77
+ processPeakResidentBytes: 0,
78
+ ));
79
+ expect(law.step, WorkspacePreparationStep.installingLaw);
80
+ expect(law.active, isTrue);
81
+ });
82
+
83
+ test('peer maintenance observations drive the application report', () async {
84
+ final events = ReceptionEventDesk();
85
+ events.observeMaintenance(protocol.ReadMaintenanceProgress(
86
+ workspace: 'estate-1',
87
+ cause: protocol.ReadMaintenanceCause.READ_MAINTENANCE_CAUSE_OPEN,
88
+ ));
89
+ expect(events.syncStatuses.applicationStatus.preparingWorkspaces, 1);
90
+ expect(events.syncStatuses.applicationStatus.preparationActivity,
91
+ 'Getting workspace data ready');
92
+
93
+ events.observeMaintenance(protocol.ReadMaintenanceProgress(
94
+ workspace: 'estate-1',
95
+ cause: protocol.ReadMaintenanceCause.READ_MAINTENANCE_CAUSE_OPEN,
96
+ kernel: protocol.MaintenanceQuantumResponse(
97
+ phase: protocol.MaintenancePhase.MAINTENANCE_PHASE_COMPLETE,
98
+ ),
99
+ ));
100
+ expect(events.syncStatuses.applicationStatus.preparingWorkspaces, 0);
101
+ await events.close();
102
+ });
103
+ }