@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
@@ -1,1930 +0,0 @@
1
- // NOMOS — the WEBVIEW BRIDGE protocol test (the Dart half of the app-binding boundary). A FAKE transport
2
- // stands in for the WebView runner (`webview-runner.mjs`): it auto-answers each request from a stub holon,
3
- // exactly as the real runner would, so the bridge's request/reply correlation, reactive `watch` streams, and
4
- // error framing are proven in pure Dart — no browser, no MemoryPersistence. The bridge and the JS runner are
5
- // the two ends of ONE protocol (`kWebviewBridgeVersion`).
6
- import 'dart:async';
7
- import 'dart:convert';
8
- import 'dart:io';
9
- import 'dart:typed_data';
10
-
11
- import 'package:nomos_client/src/nms_generated_runtime.dart';
12
- import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
13
- import 'package:fixnum/fixnum.dart' as fixnum;
14
- import 'package:test/test.dart';
15
-
16
- protocol.RuntimeResponse _runtimeLifecycleResponse(
17
- protocol.RuntimeRequest request,
18
- ) {
19
- final response = protocol.RuntimeResponse(
20
- protocolVersion: 2,
21
- requestId: request.requestId,
22
- );
23
- switch (request.whichCommand()) {
24
- case protocol.RuntimeRequest_Command.listWorkspaces:
25
- response.workspaceList = protocol.WorkspaceList();
26
- break;
27
- case protocol.RuntimeRequest_Command.workspaceStatus:
28
- response.workspaceStatus = protocol.WorkspaceStatus(
29
- workspace: request.workspaceStatus.session,
30
- head: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
31
- );
32
- break;
33
- case protocol.RuntimeRequest_Command.prepareDomain:
34
- response.domainPrepared = protocol.DomainPrepared(
35
- domain: request.prepareDomain.domain,
36
- packageSha256: request.prepareDomain.packageSha256,
37
- );
38
- break;
39
- case protocol.RuntimeRequest_Command.resolveCustodyBreak:
40
- response.custodyBreakResolved = protocol.CustodyBreakResolved(
41
- session: request.resolveCustodyBreak.session,
42
- decision: request.resolveCustodyBreak.decision,
43
- );
44
- break;
45
- case protocol.RuntimeRequest_Command.startAutoSync:
46
- response.autoSyncStarted =
47
- protocol.AutoSyncStarted(session: request.startAutoSync.session);
48
- break;
49
- case protocol.RuntimeRequest_Command.stopAutoSync:
50
- response.autoSyncStopped =
51
- protocol.AutoSyncStopped(session: request.stopAutoSync.session);
52
- break;
53
- case protocol.RuntimeRequest_Command.syncWorkspace:
54
- response.syncWorkspace = protocol.SyncWorkspaceResponse(
55
- workspace: request.syncWorkspace.session,
56
- );
57
- break;
58
- case protocol.RuntimeRequest_Command.unwatch:
59
- response.watchStopped = protocol.WatchStopped();
60
- break;
61
- case protocol.RuntimeRequest_Command.closeRealm:
62
- response.realmClosed = protocol.RealmClosed();
63
- break;
64
- case protocol.RuntimeRequest_Command.modelLifecycle:
65
- final lifecycle = request.modelLifecycle;
66
- switch (lifecycle.whichOperation()) {
67
- case protocol.ModelLifecycleRequest_Operation.stage:
68
- response.modelLifecycle = protocol.ModelLifecycleResponse(
69
- stage: protocol.ModelLifecycleStage.MODEL_LIFECYCLE_STAGE_PENDING,
70
- accepted: true,
71
- );
72
- break;
73
- case protocol.ModelLifecycleRequest_Operation.adopt:
74
- response.modelLifecycle = protocol.ModelLifecycleResponse(
75
- stage: protocol
76
- .ModelLifecycleStage.MODEL_LIFECYCLE_STAGE_ACCEPTANCE_REQUIRED,
77
- accepted: false,
78
- review: protocol.ModelReview(
79
- checkedActions: fixnum.Int64(1),
80
- candidateActions: fixnum.Int64(1),
81
- sameOutcome: false,
82
- differences: [
83
- protocol.ModelDifference(
84
- kind: protocol
85
- .ModelDifferenceKind.MODEL_DIFFERENCE_KIND_OUTCOME,
86
- action: 'registerSite',
87
- summary: 'registerSite has a different outcome',
88
- ),
89
- ],
90
- ),
91
- );
92
- break;
93
- case protocol.ModelLifecycleRequest_Operation.restore:
94
- response.modelLifecycle = protocol.ModelLifecycleResponse(
95
- stage: protocol
96
- .ModelLifecycleStage.MODEL_LIFECYCLE_STAGE_ACCEPTANCE_REQUIRED,
97
- accepted: false,
98
- review: protocol.ModelReview(
99
- checkedActions: fixnum.Int64(1),
100
- candidateActions: fixnum.Int64(1),
101
- sameOutcome: false,
102
- differences: [
103
- protocol.ModelDifference(
104
- kind:
105
- protocol.ModelDifferenceKind.MODEL_DIFFERENCE_KIND_RULES,
106
- action: 'setMood',
107
- summary: 'setMood is governed by a different rule',
108
- ),
109
- ],
110
- ),
111
- );
112
- break;
113
- case protocol.ModelLifecycleRequest_Operation.notSet:
114
- throw StateError('Model lifecycle operation is required');
115
- }
116
- break;
117
- default:
118
- throw StateError(
119
- 'Fake lifecycle transport does not implement '
120
- '${request.whichCommand().name}',
121
- );
122
- }
123
- return response;
124
- }
125
-
126
- /// A fake runner-over-a-transport: records sent requests, replies the way `webview-runner.mjs` does, and can
127
- /// push reactive `watch` events. Lets us drive [NomosBridge] without a real WebView.
128
- class FakeRunner implements BridgeTransport {
129
- final _incoming = StreamController<Map<String, Object?>>.broadcast();
130
- final List<Map<String, Object?>> sent = [];
131
- final List<protocol.RuntimeRequest> runtimeRequests = [];
132
- String? lastWatchSubId;
133
- bool acknowledgeWatch = true;
134
- bool acknowledgeUnwatch = true;
135
-
136
- FakeRunner() {
137
- // the runner announces readiness at construction.
138
- scheduleMicrotask(() => _incoming.add({
139
- 'v': kWebviewBridgeVersion,
140
- 'event': 'ready',
141
- 'bundleVersion': 'unbundled-dev'
142
- }));
143
- }
144
-
145
- @override
146
- Stream<Map<String, Object?>> get messages => _incoming.stream;
147
-
148
- @override
149
- Future<protocol.RuntimeResponse> invokeRuntime(
150
- protocol.RuntimeRequest request,
151
- ) async {
152
- runtimeRequests.add(request.deepCopy());
153
- if (!acknowledgeUnwatch && request.hasUnwatch()) {
154
- return Completer<protocol.RuntimeResponse>().future;
155
- }
156
- if (request.hasSyncWorkspace() && !request.syncWorkspace.admit) {
157
- return protocol.RuntimeResponse(
158
- protocolVersion: 2,
159
- requestId: request.requestId,
160
- failure: protocol.RuntimeFailure(
161
- code: 'nomos.sync.refused',
162
- message: 'typed sync failed',
163
- component: 'test.runner',
164
- workspace: request.syncWorkspace.session,
165
- ),
166
- );
167
- }
168
- return _runtimeLifecycleResponse(request);
169
- }
170
-
171
- @override
172
- void send(Map<String, Object?> message) {
173
- sent.add(message);
174
- final id = message['id'] as int;
175
- final op = message['op'] as String;
176
- final args = (message['args'] as Map).cast<String, Object?>();
177
- // reply exactly as the runner's ops do (the contract both ends share).
178
- Map<String, Object?> reply(Object? result) =>
179
- {'v': kWebviewBridgeVersion, 'id': id, 'ok': true, 'result': result};
180
- scheduleMicrotask(() {
181
- switch (op) {
182
- case 'connect':
183
- _incoming.add(reply({'workspace': args['workspace']}));
184
- break;
185
- case 'offer':
186
- // a directive the law refuses answers ok:false (the runner frames the gate's verdict).
187
- final intentJson =
188
- utf8.decode(base64Decode(args['intentBytes'] as String));
189
- if (intentJson.contains('"directiveId":"refuseMe"')) {
190
- _incoming.add({
191
- 'v': kWebviewBridgeVersion,
192
- 'id': id,
193
- 'ok': false,
194
- 'error': 'the law refused this intent',
195
- 'errorInfo': {
196
- 'code': 'nomos.law.refused',
197
- 'message': 'the law refused this intent',
198
- 'component': 'test.runner',
199
- 'operation': 'offer',
200
- 'domain': 'x',
201
- 'directiveId': 'refuseMe',
202
- 'trace': message['trace'],
203
- 'attributes': {'test.case': 'refusal'}
204
- }
205
- });
206
- } else if (intentJson.contains('"directiveId":"custodyGap"')) {
207
- // The client's typed custody error: structured errorInfo, code=nomos.custody.*.
208
- _incoming.add({
209
- 'v': kWebviewBridgeVersion,
210
- 'id': id,
211
- 'ok': false,
212
- 'error':
213
- 'shallow-insufficient: fold refs/heads/main: find commit: NotFound',
214
- 'errorInfo': {
215
- 'code': 'nomos.custody.shallow_insufficient',
216
- 'message':
217
- 'shallow-insufficient: fold refs/heads/main: find commit: NotFound',
218
- 'component': 'nomos.custody',
219
- 'operation': 'offer',
220
- 'workspace': 'ws-1',
221
- 'trace': message['trace'],
222
- 'attributes': {
223
- 'nomos.custody.ref': 'refs/heads/main',
224
- 'nomos.custody.head':
225
- 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
226
- 'nomos.custody.missing_object':
227
- 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
228
- 'nomos.custody.phase': 'author',
229
- 'nomos.custody.source': 'localSnapshot',
230
- 'nomos.custody.recoverable': true,
231
- 'nomos.custody.recommended_action':
232
- 'quarantineLocalSnapshotAndReclone',
233
- }
234
- }
235
- });
236
- } else {
237
- _incoming.add(reply({'head': 'HEAD-offer'}));
238
- }
239
- break;
240
- case 'query':
241
- _incoming.add(reply({
242
- 'rows': [
243
- {
244
- 'id': 'a',
245
- 'data': {'q': args['queryId']}
246
- }
247
- ]
248
- }));
249
- break;
250
- case 'queryPage':
251
- _incoming.add(reply({
252
- 'rows': [
253
- {
254
- 'id': 'page-a',
255
- 'data': {'q': args['queryId']}
256
- }
257
- ],
258
- 'cursor': 'opaque-next',
259
- 'done': false,
260
- }));
261
- break;
262
- case 'queryById':
263
- _incoming.add(reply({
264
- 'rows': [
265
- {
266
- 'id': args['id'],
267
- 'data': {'ok': true}
268
- }
269
- ]
270
- }));
271
- break;
272
- case 'checkReferenceVersions':
273
- _incoming.add(reply({
274
- 'unique': [
275
- {
276
- 'ref': (args['refs'] as List).single,
277
- 'outcome': {
278
- 'status': 'outdated',
279
- 'latest': {
280
- 'workspace': 'catalogue',
281
- 'type': 'catalogue.Publication',
282
- 'id': 'publication:2',
283
- },
284
- },
285
- },
286
- ],
287
- 'order': [0],
288
- }));
289
- break;
290
- case 'count':
291
- _incoming.add(reply({'count': 7}));
292
- break;
293
- case 'sync':
294
- if (args['admit'] == false) {
295
- _incoming.add({
296
- 'v': kWebviewBridgeVersion,
297
- 'id': id,
298
- 'ok': false,
299
- 'error': 'legacy sync failed'
300
- });
301
- } else {
302
- _incoming.add(reply({'pushed': true}));
303
- }
304
- break;
305
- case 'autoSync':
306
- _incoming.add(reply({'started': true}));
307
- break;
308
- case 'refreshCredentials':
309
- _incoming.add(reply({'refreshed': true}));
310
- break;
311
- case 'lawStatus':
312
- _incoming.add(reply({'installed': true, 'drifted': false}));
313
- break;
314
- case 'metrics':
315
- final session = args['session'];
316
- _incoming.add(reply(session == 'home'
317
- ? {
318
- 'ahead': 0,
319
- 'behind': false,
320
- 'saved': 2,
321
- 'onMain': 3,
322
- 'deadLetters': 0,
323
- }
324
- : {
325
- 'ahead': 4,
326
- 'behind': true,
327
- 'saved': 5,
328
- 'onMain': 6,
329
- 'deadLetters': 1,
330
- }));
331
- break;
332
- case 'cryptoOwnedDeviceRequest':
333
- _incoming.add(reply({
334
- 'request': {
335
- 'v': 1,
336
- 'workspace': 'notes',
337
- 'principal': 'user:alice',
338
- 'scope': 'nomos_internal_owned_read_7c1f5e',
339
- 'signingPublic': 'AA==',
340
- 'wrappingPublic': 'AQ==',
341
- 'signature': 'Ag==',
342
- },
343
- }));
344
- break;
345
- case 'cryptoOwnedPrepareApproval':
346
- _incoming.add(reply({
347
- 'newKeySubject': 'key:${'a' * 64}',
348
- 'ownedCustody': {
349
- 'v': 1,
350
- 'request': args['request'],
351
- 'hpkeEpk': 'Aw==',
352
- 'ct': 'BA==',
353
- 'keyCommitment': '0' * 64,
354
- 'principalWrappingPublic': 'BQ==',
355
- },
356
- 'verifiedVia': 'nomos:test-root',
357
- 'validFromEpoch': 0,
358
- 'approvedAt': '2026-07-18T14:20:00.000Z',
359
- '__nomos_owned_device_key_subject_7c1f5e': 'key:${'b' * 64}',
360
- }));
361
- break;
362
- case 'cryptoOwnedScopeKeys':
363
- _incoming.add(reply({
364
- 'keys': [
365
- {
366
- 'scope': 'notes.Note:note-1#owned-reader',
367
- 'epoch': 2,
368
- 'key': 'opaque-record-key',
369
- }
370
- ]
371
- }));
372
- break;
373
- case 'cryptoOwnedEnsureIdentity':
374
- _incoming.add(reply({'ready': true}));
375
- break;
376
- case 'cryptoOwnedPrepareRecordAction':
377
- _incoming.add(reply({
378
- '__nomos_owned_device_key_subject_7c1f5e': 'key:${'c' * 64}',
379
- 'recordId': 'note-1',
380
- 'peer': 'user:bob',
381
- 'epoch': 2,
382
- 'sharedAt': '2026-07-18T14:25:00.000Z',
383
- 'wraps': <Object?>[],
384
- }));
385
- break;
386
- case 'adoptGeneratedModel':
387
- _incoming.add(reply({
388
- 'verdict': {
389
- 'ok': false,
390
- 'stage': 'acceptance',
391
- 'report': {
392
- 'replayedIntents': 2,
393
- 'candidateIntents': 2,
394
- 'projection': {'equal': false},
395
- 'divergences': [
396
- {
397
- 'kind': 'plan-divergence',
398
- 'directiveId': 'registerSite',
399
- }
400
- ],
401
- },
402
- 'error': 'exact history changes under the candidate',
403
- }
404
- }));
405
- break;
406
- case 'stageGeneratedModel':
407
- _incoming.add(reply({
408
- 'verdict': {
409
- 'ok': true,
410
- 'status': 'pending',
411
- 'candidateHash': 'candidate-law-hash',
412
- }
413
- }));
414
- break;
415
- case 'restorePreviousGeneratedModel':
416
- _incoming.add(reply({
417
- 'verdict': {
418
- 'ok': false,
419
- 'stage': 'acceptance',
420
- 'report': {
421
- 'replayedIntents': 4,
422
- 'candidateIntents': 3,
423
- 'projection': {'equal': false},
424
- 'divergences': [
425
- {
426
- 'kind': 'plan-divergence',
427
- 'directiveId': 'setMood',
428
- }
429
- ],
430
- },
431
- 'error': 'restoring previous law needs a business reason',
432
- }
433
- }));
434
- break;
435
- case 'offerDirective':
436
- _incoming.add(reply({
437
- 'head': 'HEAD-owned-approval',
438
- 'intentId': 'intent-owned-approval',
439
- 'created': <Object?>[],
440
- 'born': args['directiveId'] == 'birthChild'
441
- ? <Object?>['notes-child']
442
- : <Object?>[],
443
- 'workspace': 'notes',
444
- }));
445
- break;
446
- case 'badop':
447
- _incoming.add({
448
- 'v': kWebviewBridgeVersion,
449
- 'id': id,
450
- 'ok': false,
451
- 'error': 'the law refused this intent'
452
- });
453
- break;
454
- case 'watch':
455
- lastWatchSubId = args['subId'] as String;
456
- if (acknowledgeWatch) {
457
- _incoming.add(reply({'subId': args['subId']}));
458
- }
459
- break;
460
- case 'unwatch':
461
- if (acknowledgeUnwatch) {
462
- _incoming.add(reply({'subId': args['subId']}));
463
- }
464
- break;
465
- default:
466
- _incoming.add({
467
- 'v': kWebviewBridgeVersion,
468
- 'id': id,
469
- 'ok': false,
470
- 'error': "unknown op '$op'"
471
- });
472
- }
473
- });
474
- }
475
-
476
- /// Simulate the holon firing a reactive change to an active watch.
477
- void pushWatch(String subId, List<dynamic> rows) => _incoming.add({
478
- 'v': kWebviewBridgeVersion,
479
- 'event': 'watch',
480
- 'subId': subId,
481
- 'rows': rows
482
- });
483
-
484
- /// Simulate the runner forwarding a signal holon projection.
485
- void pushSignal(Map<String, Object?> signal) => _incoming
486
- .add({'v': kWebviewBridgeVersion, 'event': 'signal', 'signal': signal});
487
-
488
- /// Simulate one background-sync event from the runner.
489
- void pushSync(Map<String, Object?> event) =>
490
- _incoming.add({'v': kWebviewBridgeVersion, 'event': 'sync', ...event});
491
-
492
- void pushKernelProgress({required int completed, required int total}) =>
493
- _incoming.add({
494
- 'v': kWebviewBridgeVersion,
495
- 'event': 'kernelProgress',
496
- 'schemaVersion': 1,
497
- 'sequence': completed,
498
- 'operation': 'restoreMain',
499
- 'phase': 'verifyIntents',
500
- 'completed': completed,
501
- 'total': total,
502
- 'unit': 'intent',
503
- 'terminal': completed == total,
504
- 'kernelAllocatorLiveBytes': 384 * 1024 * 1024,
505
- 'kernelAllocatorPeakBytes': 512 * 1024 * 1024,
506
- 'processResidentBytes': 3 * 1024 * 1024 * 1024,
507
- 'processPeakResidentBytes': 4 * 1024 * 1024 * 1024,
508
- });
509
- }
510
-
511
- /// Proves that generated application reads bypass the legacy operation bus.
512
- /// The ordinary bridge remains present for lifecycle setup, but a query can
513
- /// only succeed through the generated Runtime v2 kernel-call capability.
514
- class FakeTypedRunner extends FakeRunner
515
- implements NomosKernelCallTransport, NomosRuntimeV2Transport {
516
- protocol.CallRequest? lastKernelCall;
517
- String? lastKernelSession;
518
- @override
519
- Future<protocol.CallResponse> callKernel({
520
- required String session,
521
- required protocol.CallRequest request,
522
- }) async {
523
- lastKernelSession = session;
524
- lastKernelCall = request;
525
- if (request.hasQuery() && request.query.hasOwnedDeviceRequest()) {
526
- final value = OwnedDeviceRequest(
527
- v: 1,
528
- workspace: 'home',
529
- principal: 'user:alice',
530
- scope: request.query.ownedDeviceRequest.scope,
531
- signingPublic: 'AA==',
532
- wrappingPublic: 'AQ==',
533
- signature: 'Ag==',
534
- );
535
- return protocol.CallResponse(
536
- protocolVersion: 1,
537
- requestId: request.requestId,
538
- query: protocol.QueryResponse(
539
- ownedDeviceRequest: protocol.OwnedDeviceRequestResponse(
540
- requestUsda: value.encodeUsda(),
541
- ),
542
- ),
543
- );
544
- }
545
- if (request.hasQuery() && request.query.hasOwnedPrepareApproval()) {
546
- final device = OwnedDeviceRequest.decodeUsda(
547
- Uint8List.fromList(
548
- request.query.ownedPrepareApproval.requestUsda,
549
- ),
550
- );
551
- final custody = OwnedCustody(
552
- v: 1,
553
- request: device,
554
- hpkeEpk: 'Aw==',
555
- ct: 'BA==',
556
- keyCommitment: '0' * 64,
557
- principalWrappingPublic: 'BQ==',
558
- );
559
- return protocol.CallResponse(
560
- protocolVersion: 1,
561
- requestId: request.requestId,
562
- query: protocol.QueryResponse(
563
- ownedPrepareApproval: protocol.OwnedPrepareApprovalResponse(
564
- newKeySubject: 'key:${'a' * 64}',
565
- ownedCustodyUsda: custody.encodeUsda(),
566
- verifiedVia: 'nomos:test-root',
567
- validFromEpoch: fixnum.Int64.ZERO,
568
- approvedAtUnixMillis: fixnum.Int64(1787062800000),
569
- ownedKeySubject: 'key:${'b' * 64}',
570
- ),
571
- ),
572
- );
573
- }
574
- return protocol.CallResponse(
575
- protocolVersion: 1,
576
- requestId: request.requestId,
577
- query: protocol.QueryResponse(
578
- resultUsda: utf8.encode('''#usda 1.0
579
- (
580
- defaultPrim = "Result"
581
- )
582
- def NomosReadResult "Result"
583
- {
584
- def NomosListValue "Rows"
585
- {
586
- }
587
- }
588
- '''),
589
- ),
590
- );
591
- }
592
-
593
- @override
594
- Future<protocol.RuntimeResponse> invokeRuntime(
595
- protocol.RuntimeRequest request) async {
596
- runtimeRequests.add(request.deepCopy());
597
- final response = protocol.RuntimeResponse(
598
- protocolVersion: 2,
599
- requestId: request.requestId,
600
- );
601
- switch (request.whichCommand()) {
602
- case protocol.RuntimeRequest_Command.listWorkspaces:
603
- response.workspaceList = protocol.WorkspaceList(workspaces: [
604
- protocol.WorkspaceSummary(
605
- session: 'home',
606
- workspace: 'home',
607
- residency: protocol.WorkspaceResidency.WORKSPACE_RESIDENCY_MOUNTED,
608
- syncPosture: protocol
609
- .WorkspaceSyncPosture.WORKSPACE_SYNC_POSTURE_CLOUD_BOUND,
610
- ),
611
- ]);
612
- break;
613
- case protocol.RuntimeRequest_Command.prepareDomain:
614
- response.domainPrepared = protocol.DomainPrepared(
615
- domain: request.prepareDomain.domain,
616
- packageSha256: request.prepareDomain.packageSha256,
617
- );
618
- break;
619
- case protocol.RuntimeRequest_Command.resolveCustodyBreak:
620
- response.custodyBreakResolved = protocol.CustodyBreakResolved(
621
- session: request.resolveCustodyBreak.session,
622
- decision: request.resolveCustodyBreak.decision,
623
- );
624
- break;
625
- case protocol.RuntimeRequest_Command.startAutoSync:
626
- response.autoSyncStarted =
627
- protocol.AutoSyncStarted(session: request.startAutoSync.session);
628
- break;
629
- case protocol.RuntimeRequest_Command.stopAutoSync:
630
- response.autoSyncStopped =
631
- protocol.AutoSyncStopped(session: request.stopAutoSync.session);
632
- break;
633
- case protocol.RuntimeRequest_Command.syncWorkspace:
634
- response.syncWorkspace = protocol.SyncWorkspaceResponse(
635
- workspace: request.syncWorkspace.session,
636
- );
637
- break;
638
- case protocol.RuntimeRequest_Command.closeRealm:
639
- response.realmClosed = protocol.RealmClosed();
640
- break;
641
- case protocol.RuntimeRequest_Command.modelLifecycle:
642
- response.modelLifecycle =
643
- _runtimeLifecycleResponse(request).modelLifecycle;
644
- break;
645
- default:
646
- throw StateError(
647
- 'FakeTypedRunner does not implement ${request.whichCommand().name}');
648
- }
649
- return response;
650
- }
651
- }
652
-
653
- class ErrorRunner implements BridgeTransport {
654
- final _incoming = StreamController<Map<String, Object?>>.broadcast();
655
-
656
- @override
657
- Stream<Map<String, Object?>> get messages => _incoming.stream;
658
-
659
- @override
660
- Future<protocol.RuntimeResponse> invokeRuntime(
661
- protocol.RuntimeRequest request,
662
- ) async =>
663
- _runtimeLifecycleResponse(request);
664
-
665
- @override
666
- void send(Map<String, Object?> message) {}
667
-
668
- void fail(Object error) => _incoming.addError(error);
669
- Future<void> close() => _incoming.close();
670
- }
671
-
672
- /// A transport that answers EVERY request with one canned `result` (the golden corpus's reply) and records
673
- /// the request the bridge sent — so we can assert both halves of the wire contract from the Dart side.
674
- class ReplayTransport implements BridgeTransport {
675
- final Map<String, Object?> result;
676
- final _incoming = StreamController<Map<String, Object?>>.broadcast();
677
- Map<String, Object?>? lastRequest;
678
- ReplayTransport(this.result) {
679
- scheduleMicrotask(() => _incoming.add({
680
- 'v': kWebviewBridgeVersion,
681
- 'event': 'ready',
682
- 'bundleVersion': 'unbundled-dev'
683
- }));
684
- }
685
- @override
686
- Stream<Map<String, Object?>> get messages => _incoming.stream;
687
- @override
688
- Future<protocol.RuntimeResponse> invokeRuntime(
689
- protocol.RuntimeRequest request,
690
- ) async =>
691
- _runtimeLifecycleResponse(request);
692
- @override
693
- void send(Map<String, Object?> message) {
694
- lastRequest = message;
695
- final id = message['id'];
696
- scheduleMicrotask(() => _incoming.add(
697
- {'v': kWebviewBridgeVersion, 'id': id, 'ok': true, 'result': result}));
698
- }
699
- }
700
-
701
- /// Load the shared golden corpus (the same file the JS runner test replays). Probed across the candidate
702
- /// working dirs `dart test` may run from, so it doesn't depend on one invocation cwd.
703
- Map<String, Object?> _loadCorpus() {
704
- const candidates = [
705
- '../../cloud/web-client/test/fixtures/bridge-protocol.golden.json', // cwd = dsl/dart (the usual)
706
- 'cloud/web-client/test/fixtures/bridge-protocol.golden.json', // cwd = repo root
707
- '../../../cloud/web-client/test/fixtures/bridge-protocol.golden.json',
708
- ];
709
- for (final p in candidates) {
710
- final f = File(p);
711
- if (f.existsSync())
712
- return (jsonDecode(f.readAsStringSync()) as Map).cast<String, Object?>();
713
- }
714
- fail(
715
- 'golden corpus not found from ${Directory.current.path}; tried $candidates');
716
- }
717
-
718
- /// Drive one corpus op through the typed bridge method and return its typed result (so we can compare it to
719
- /// what the corpus says the unwrapped value should be).
720
- Future<Object?> _invoke(
721
- NomosBridge b, String op, Map<String, Object?> a) async {
722
- switch (op) {
723
- case 'connect':
724
- await b.connect(
725
- cloud: a['cloud'] as String,
726
- workspace: a['workspace'] as String,
727
- clientId: a['clientId'] as String?);
728
- return null;
729
- case 'offer':
730
- return b.offer(intentBytes: a['intentBytes'] as String);
731
- case 'query':
732
- return b.query(
733
- a['queryId'] as String, (a['params'] as Map).cast<String, Object?>());
734
- case 'queryById':
735
- return b.queryById(a['id'] as String);
736
- case 'count':
737
- return b.count(a['countId'] as String, a['group'] as String);
738
- case 'sum':
739
- return b.sum(a['sumId'] as String, a['group'] as String);
740
- case 'sync':
741
- return b.sync(admit: a['admit'] as bool);
742
- case 'lawStatus':
743
- return b.lawStatus(a['hash'] as String);
744
- case 'offerDirective':
745
- return b.offerDirective(
746
- domain: a['domain'] as String,
747
- directiveId: a['directiveId'] as String,
748
- payload: a['payload'],
749
- actor: a['actor'] as String?,
750
- domainHash: a['domainHash'] as String?,
751
- force: a['force'] as bool?,
752
- );
753
- default:
754
- fail('corpus op not wired into the Dart bridge: $op');
755
- }
756
- }
757
-
758
- /// What the corpus says the typed (unwrapped) return value should be, given the reply's `result`.
759
- Object? _expectedTyped(String op, Map<String, Object?> result) {
760
- switch (op) {
761
- case 'offer':
762
- case 'offerDirective':
763
- return result['head'];
764
- case 'query':
765
- case 'queryById':
766
- return result['rows'];
767
- case 'count':
768
- return result['count'];
769
- case 'sum':
770
- return result['sum'];
771
- case 'sync':
772
- return isA<protocol.SyncWorkspaceResponse>();
773
- case 'lawStatus':
774
- return result;
775
- default:
776
- return null; // connect: void
777
- }
778
- }
779
-
780
- void main() {
781
- test('transport failure fails readiness immediately', () async {
782
- final transport = ErrorRunner();
783
- final bridge = NomosBridge(transport);
784
- final failure = StateError('runner exited');
785
- transport.fail(failure);
786
- await expectLater(
787
- bridge.ready,
788
- throwsA(isA<StateError>()
789
- .having((e) => e.message, 'message', 'runner exited')),
790
- );
791
- await bridge.dispose();
792
- await transport.close();
793
- });
794
-
795
- // ── THE GOLDEN CORPUS — the cross-language conformance the JS runner replays too ──
796
- // For each case: call the typed bridge method with the corpus `args`, then assert (1) the request the bridge
797
- // SENT carries exactly those args, and (2) the typed value the bridge parsed from the corpus `result` matches.
798
- // The JS runner asserts the SAME corpus from the other side — so neither end can drift the wire alone.
799
- group('golden corpus (shared with the JS runner)', () {
800
- final corpus = _loadCorpus();
801
- final cases = (corpus['cases'] as List).cast<Map<String, Object?>>();
802
- test('version pins the bridge version',
803
- () => expect(corpus['version'], kWebviewBridgeVersion));
804
- for (final c in cases) {
805
- final op = c['op'] as String;
806
- if (op == 'sync')
807
- continue; // Runtime v2 has its own protobuf conformance tests.
808
- final args = (c['args'] as Map).cast<String, Object?>();
809
- final result = (c['result'] as Map).cast<String, Object?>();
810
- test("'$op' request args + parsed result match the corpus", () async {
811
- final transport = ReplayTransport(result);
812
- final bridge = NomosBridge(transport);
813
- await bridge.ready;
814
- final typed = await _invoke(bridge, op, args);
815
- expect((transport.lastRequest!['args'] as Map).cast<String, Object?>(),
816
- equals(args),
817
- reason: 'sent request args');
818
- if (op != 'connect')
819
- expect(typed, equals(_expectedTyped(op, result)),
820
- reason: 'parsed typed result');
821
- await bridge.dispose();
822
- });
823
- }
824
- });
825
-
826
- group('offer span taxonomy (bridge legs)', () {
827
- test(
828
- 'offerOutcome turns the runner timing stamps into bridge-ipc + response-delivery spans on recentSpans',
829
- () async {
830
- final now = DateTime.now().millisecondsSinceEpoch;
831
- final transport = ReplayTransport({
832
- 'head': 'H1',
833
- 'intentId': 'intent-span',
834
- 'created': <Object?>[],
835
- 'workspace': 'ws',
836
- // the runner's op-pickup + durable-ack instants (additive `timing` field)
837
- 'timing': {'recv': now, 'done': now},
838
- });
839
- final bridge = NomosBridge(transport);
840
- await bridge.ready;
841
- await bridge.offerOutcome(
842
- domain: 'guestbook',
843
- directiveId: 'signGuestbook',
844
- payload: {'author': 'ada'});
845
- final kinds = bridge.recentSpans
846
- .map((m) => ((m['span'] as Map?)?['kind'] ?? '').toString())
847
- .toSet();
848
- expect(kinds, contains('bridge-ipc'),
849
- reason: 'up-leg transport attributed as a span');
850
- expect(kinds, contains('response-delivery'),
851
- reason: 'post-ack down-leg attributed as a span');
852
- await bridge.dispose();
853
- });
854
-
855
- test(
856
- 'a runner without timing (older bundle) emits no bridge-leg spans and still answers',
857
- () async {
858
- final transport = ReplayTransport({
859
- 'head': 'H1',
860
- 'intentId': 'intent-old-runner',
861
- 'created': <Object?>[],
862
- 'workspace': 'ws'
863
- });
864
- final bridge = NomosBridge(transport);
865
- await bridge.ready;
866
- final out = await bridge.offerOutcome(
867
- domain: 'guestbook',
868
- directiveId: 'signGuestbook',
869
- payload: {'author': 'ada'});
870
- expect(out.head, 'H1');
871
- final kinds = bridge.recentSpans
872
- .map((m) => ((m['span'] as Map?)?['kind'] ?? '').toString())
873
- .toSet();
874
- expect(kinds, isNot(contains('bridge-ipc')));
875
- await bridge.dispose();
876
- });
877
- });
878
-
879
- // ── #9 — a per-offer authorSecret reaches the runner op (so the runner maps it onto opts.signWith → the
880
- // author path signs THIS offer with the intended enrolled key, not the connect-time one). Both offer lanes. ──
881
- group('per-offer authorSecret threads to the runner op (#9)', () {
882
- test('offerOutcome carries authorSecret to the runner op args', () async {
883
- final transport = ReplayTransport({
884
- 'head': 'H1',
885
- 'intentId': 'intent-secret',
886
- 'created': <Object?>[],
887
- 'workspace': 'ws'
888
- });
889
- final bridge = NomosBridge(transport);
890
- await bridge.ready;
891
- await bridge.offerOutcome(
892
- domain: 'home',
893
- directiveId: 'writeNote',
894
- payload: {'author': 'ada'},
895
- authorSecret: 'ENROLLED-KEY-B64');
896
- final args =
897
- (transport.lastRequest!['args'] as Map).cast<String, Object?>();
898
- expect(transport.lastRequest!['op'], 'offerDirective');
899
- expect(args['authorSecret'], 'ENROLLED-KEY-B64',
900
- reason:
901
- 'the per-offer signing key must reach the runner op (→ opts.signWith)');
902
- await bridge.dispose();
903
- });
904
-
905
- test('offerDirective carries authorSecret to the runner op args', () async {
906
- final transport = ReplayTransport({'head': 'H1'});
907
- final bridge = NomosBridge(transport);
908
- await bridge.ready;
909
- await bridge.offerDirective(
910
- domain: 'home',
911
- directiveId: 'writeNote',
912
- payload: {'author': 'ada'},
913
- authorSecret: 'ENROLLED-KEY-B64');
914
- final args =
915
- (transport.lastRequest!['args'] as Map).cast<String, Object?>();
916
- expect(args['authorSecret'], 'ENROLLED-KEY-B64');
917
- await bridge.dispose();
918
- });
919
-
920
- test(
921
- 'no authorSecret ⇒ the op omits it (byte-identical to the pre-#9 contract)',
922
- () async {
923
- final transport = ReplayTransport({
924
- 'head': 'H1',
925
- 'intentId': 'intent-no-secret',
926
- 'created': <Object?>[],
927
- 'workspace': 'ws'
928
- });
929
- final bridge = NomosBridge(transport);
930
- await bridge.ready;
931
- await bridge.offerOutcome(
932
- domain: 'home', directiveId: 'writeNote', payload: {'author': 'ada'});
933
- final args =
934
- (transport.lastRequest!['args'] as Map).cast<String, Object?>();
935
- expect(args.containsKey('authorSecret'), isFalse);
936
- await bridge.dispose();
937
- });
938
- });
939
-
940
- late FakeRunner runner;
941
- late NomosBridge bridge;
942
- setUp(() {
943
- runner = FakeRunner();
944
- bridge = NomosBridge(runner);
945
- });
946
- tearDown(() => bridge.dispose());
947
-
948
- test('ready completes once the runner boots', () async {
949
- await bridge.ready.timeout(const Duration(seconds: 1));
950
- });
951
-
952
- test('nominal queries use Runtime v2 protobuf and return USDA bytes',
953
- () async {
954
- final typedRunner = FakeTypedRunner();
955
- final typedBridge = NomosBridge(typedRunner);
956
- addTearDown(typedBridge.dispose);
957
- await typedBridge.ready;
958
- await typedBridge.connect(
959
- cloud: 'https://c',
960
- workspace: 'home',
961
- session: 'home',
962
- );
963
- typedRunner.sent.clear();
964
-
965
- final result = await typedBridge.nmsQueryUsda(
966
- NmsInternalGeneratedQueries.create(
967
- domain: 'entries',
968
- query: 'entriesByOwner',
969
- canonicalArgumentsUsda: Uint8List.fromList(utf8.encode('''#usda 1.0
970
- (
971
- defaultPrim = "Arguments"
972
- )
973
- def NomosQueryArguments "Arguments"
974
- {
975
- custom string nomos:owner = "user:ada"
976
- }
977
- ''')),
978
- ),
979
- null,
980
- 'user:ada',
981
- 'home',
982
- );
983
-
984
- expect(result, isA<Uint8List>());
985
- expect(utf8.decode(result), startsWith('#usda 1.0'));
986
- expect(typedRunner.sent, isEmpty,
987
- reason: 'the JSON-shaped BridgeTransport must not carry the query');
988
- expect(typedRunner.lastKernelSession, 'home');
989
- expect(typedRunner.lastKernelCall!.hasQuery(), isTrue);
990
- expect(typedRunner.lastKernelCall!.query.rows.id, 'entriesByOwner');
991
- expect(
992
- typedRunner.lastKernelCall!.query.resultFormat,
993
- protocol.QueryResultFormat.QUERY_RESULT_FORMAT_NOMINAL_USDA,
994
- );
995
- });
996
-
997
- test('steady-state lifecycle uses generated Runtime v2 commands', () async {
998
- final typedRunner = FakeTypedRunner();
999
- final typedBridge = NomosBridge(typedRunner);
1000
- await typedBridge.ready;
1001
- await typedBridge.connect(
1002
- cloud: 'https://c',
1003
- workspace: 'home',
1004
- session: 'home',
1005
- );
1006
- typedRunner.sent.clear();
1007
-
1008
- final workspaces = await typedBridge.listWorkspaces();
1009
- await typedBridge.session('home').prepareDomain(
1010
- domain: 'guestbook',
1011
- packageHash: 'a' * 64,
1012
- );
1013
- await typedBridge.session('home').autoSync(fallbackMs: 60000);
1014
- await typedBridge.session('home').stopAutoSync();
1015
- final custody = await typedBridge.session('home').resolveCustodyBreak(
1016
- protocol.CustodyBreakDecision.CUSTODY_BREAK_DECISION_ADOPT,
1017
- );
1018
-
1019
- expect(workspaces.single.workspace, 'home');
1020
- expect(
1021
- custody.decision,
1022
- protocol.CustodyBreakDecision.CUSTODY_BREAK_DECISION_ADOPT,
1023
- );
1024
- expect(
1025
- typedRunner.runtimeRequests.map((request) => request.whichCommand()),
1026
- [
1027
- protocol.RuntimeRequest_Command.listWorkspaces,
1028
- protocol.RuntimeRequest_Command.prepareDomain,
1029
- protocol.RuntimeRequest_Command.startAutoSync,
1030
- protocol.RuntimeRequest_Command.stopAutoSync,
1031
- protocol.RuntimeRequest_Command.resolveCustodyBreak,
1032
- ],
1033
- );
1034
- expect(typedRunner.sent, isEmpty,
1035
- reason: 'no JSON command envelope carries steady-state lifecycle');
1036
- await typedBridge.dispose();
1037
- });
1038
-
1039
- test('a local durability span remains workspace/head specific', () async {
1040
- await bridge.ready;
1041
- bridge.noteLocalSpan(
1042
- track: 'submit',
1043
- name: 'durable save (disk)',
1044
- kind: 'persist',
1045
- t0: 100,
1046
- t1: 120,
1047
- session: 'born-child',
1048
- head: '2222222222222222222222222222222222222222',
1049
- );
1050
- final saved = bridge.recentSpans
1051
- .where((event) => event['session'] == 'born-child')
1052
- .toList();
1053
- expect(saved, hasLength(2));
1054
- expect((saved.last['span'] as Map)['head'],
1055
- '2222222222222222222222222222222222222222');
1056
- });
1057
-
1058
- test('dispose closes active watches without mutating its iteration',
1059
- () async {
1060
- await bridge.ready;
1061
- final subscription =
1062
- bridge.watch('entriesByOwner', {'owner': 'user:ada'}).listen((_) {});
1063
- await Future<void>.delayed(Duration.zero);
1064
-
1065
- await bridge.dispose();
1066
- await subscription.cancel();
1067
- });
1068
-
1069
- test('dispose does not wait for a paused watch consumer', () async {
1070
- await bridge.ready;
1071
- final subscription =
1072
- bridge.watch('entriesByOwner', {'owner': 'user:ada'}).listen((_) {});
1073
- await Future<void>.delayed(Duration.zero);
1074
- subscription.pause();
1075
-
1076
- await bridge.dispose().timeout(const Duration(seconds: 1));
1077
- await subscription.cancel();
1078
- });
1079
-
1080
- test('dispose contains an unacknowledged watch registration error', () async {
1081
- await bridge.ready;
1082
- runner.acknowledgeWatch = false;
1083
- final subscription =
1084
- bridge.watch('entriesByOwner', {'owner': 'user:ada'}).listen((_) {});
1085
- await Future<void>.delayed(Duration.zero);
1086
-
1087
- await bridge.dispose();
1088
- await subscription.cancel();
1089
- });
1090
-
1091
- test('offer round-trips a typed head; args reach the runner verbatim',
1092
- () async {
1093
- await bridge.ready;
1094
- final intentBytes = base64Encode(utf8.encode(jsonEncode({
1095
- 'v': 2,
1096
- 'intent': {
1097
- 'domain': 'guestbook',
1098
- 'directiveId': 'signGuestbook',
1099
- 'payload': {'author': 'ada'},
1100
- 'domainHash': 'abc'
1101
- }
1102
- })));
1103
- final head = await bridge.offer(intentBytes: intentBytes);
1104
- expect(head, 'HEAD-offer');
1105
- final req = runner.sent.firstWhere((m) => m['op'] == 'offer');
1106
- expect((req['args'] as Map)['intentBytes'], intentBytes);
1107
- });
1108
-
1109
- test('requests carry OpenTelemetry-shaped trace context', () async {
1110
- await bridge.ready;
1111
- await bridge.connect(cloud: 'https://c', workspace: 'ws', clientId: 'c1');
1112
- final req = runner.sent.firstWhere((m) => m['op'] == 'connect');
1113
- final trace = (req['trace'] as Map).cast<String, Object?>();
1114
- expect(trace['traceId'], isA<String>());
1115
- expect((trace['traceId'] as String).length, 32);
1116
- expect(trace['spanId'], isA<String>());
1117
- expect((trace['spanId'] as String).length, 16);
1118
- expect(trace['sampled'], isTrue);
1119
- });
1120
-
1121
- test('connect only sends allowUnborn when explicitly opted in', () async {
1122
- await bridge.ready;
1123
- await bridge.connect(cloud: 'https://c', workspace: 'strict');
1124
- var req = runner.sent.lastWhere((m) => m['op'] == 'connect');
1125
- expect((req['args'] as Map).containsKey('allowUnborn'), isFalse);
1126
-
1127
- await bridge.connect(
1128
- cloud: 'https://c',
1129
- workspace: 'home-unborn',
1130
- allowUnborn: true,
1131
- );
1132
- req = runner.sent.lastWhere((m) => m['op'] == 'connect');
1133
- expect((req['args'] as Map)['allowUnborn'], isTrue);
1134
- });
1135
-
1136
- test(
1137
- 'a session retains the authenticated token subject as its default principal',
1138
- () async {
1139
- await bridge.ready;
1140
- final payload = base64Url
1141
- .encode(utf8.encode(jsonEncode({'sub': 'ada'})))
1142
- .replaceAll('=', '');
1143
- await bridge.connect(
1144
- cloud: 'https://c',
1145
- workspace: 'authenticated',
1146
- authToken: 'header.$payload.signature',
1147
- );
1148
-
1149
- expect(bridge.session('authenticated').principal, 'user:ada');
1150
- expect(bridge.principal, 'user:ada');
1151
- });
1152
-
1153
- test('generated model restoration uses one exact Runtime v2 command',
1154
- () async {
1155
- await bridge.ready;
1156
- await bridge.connect(cloud: 'https://c', workspace: 'evo');
1157
- runner.runtimeRequests.clear();
1158
- final verdict = await bridge.nomosFrameworkRestorePreviousGeneratedModel(
1159
- domain: 'evo',
1160
- currentHash: 'current-law-hash',
1161
- because: 'the previous operating policy is required',
1162
- );
1163
-
1164
- expect(verdict.accepted, isFalse);
1165
- expect(
1166
- verdict.stage,
1167
- protocol.ModelLifecycleStage.MODEL_LIFECYCLE_STAGE_ACCEPTANCE_REQUIRED,
1168
- );
1169
- expect(verdict.review.differences.single.action, 'setMood');
1170
- final request = runner.runtimeRequests.singleWhere(
1171
- (request) =>
1172
- request.hasModelLifecycle() && request.modelLifecycle.hasRestore(),
1173
- );
1174
- expect(request.modelLifecycle.restore.domain, 'evo');
1175
- expect(request.modelLifecycle.restore.currentHash, 'current-law-hash');
1176
- expect(request.modelLifecycle.restore.acceptanceReason,
1177
- 'the previous operating policy is required');
1178
- expect(
1179
- runner.sent.where(
1180
- (message) => message['op'] == 'restorePreviousGeneratedModel'),
1181
- isEmpty);
1182
- });
1183
-
1184
- test(
1185
- 'connect / query / queryPage / queryById / count / sync / lawStatus all wrap the runner',
1186
- () async {
1187
- await bridge.ready;
1188
- await bridge.connect(cloud: 'https://c', workspace: 'ws', clientId: 'c1');
1189
- expect((await bridge.query('byAuthor', {'author': 'ada'})).first['data'],
1190
- {'q': 'byAuthor'});
1191
- final page = await bridge.queryPage('byAuthor',
1192
- params: {'author': 'ada'}, pageSize: 1);
1193
- expect(page.rows.single['id'], 'page-a');
1194
- expect(page.cursor, 'opaque-next');
1195
- expect(page.done, isFalse);
1196
- expect((await bridge.queryById('Entry_1')).first['id'], 'Entry_1');
1197
- expect(await bridge.count('entries'), 7);
1198
- expect((await bridge.sync()).workspace, 'ws');
1199
- expect(
1200
- await bridge.lawStatus('abc'), {'installed': true, 'drifted': false});
1201
- });
1202
-
1203
- test('version status returns the required sum type without resolving history',
1204
- () async {
1205
- await bridge.ready;
1206
- final reference = NmsInternalVersionedNomosRefs.create<Object, Object>(
1207
- pinned: NmsInternalNomosRefs.create<Object>(
1208
- workspace: 'catalogue',
1209
- type: 'catalogue.Publication',
1210
- id: 'publication:1',
1211
- ),
1212
- series: NmsInternalNomosRefs.create<Object>(
1213
- workspace: 'catalogue',
1214
- type: 'catalogue.Series',
1215
- id: 'series:1',
1216
- ),
1217
- currentField: 'currentPublication',
1218
- );
1219
-
1220
- final versionStatus = await bridge.checkReferenceVersion(reference);
1221
- expect(versionStatus, isA<ReferenceVersionOutdated<Object>>());
1222
- expect(
1223
- (versionStatus as ReferenceVersionOutdated<Object>).latest.id,
1224
- 'publication:2',
1225
- );
1226
- final request = runner.sent.last;
1227
- expect(request['op'], 'checkReferenceVersions');
1228
- final args = (request['args'] as Map).cast<String, Object?>();
1229
- final check =
1230
- ((args['refs'] as List).single as Map).cast<String, Object?>();
1231
- expect(check['currentField'], 'currentPublication');
1232
- expect((check['pinned'] as Map)['id'], 'publication:1');
1233
- expect((check['series'] as Map)['id'], 'series:1');
1234
- });
1235
-
1236
- test(
1237
- 'moments and historic reads keep commit plumbing behind an opaque handle',
1238
- () async {
1239
- final momentsTransport = ReplayTransport({
1240
- 'commits': [
1241
- {
1242
- 'oid': 'abc123',
1243
- 'at': 1784383200000,
1244
- 'actorSubject': 'user:ada',
1245
- 'domain': 'estate',
1246
- 'directiveId': 'createSite',
1247
- }
1248
- ]
1249
- });
1250
- final momentsBridge = NomosBridge(momentsTransport);
1251
- await momentsBridge.ready;
1252
- final moments = await momentsBridge.moments(limit: 12);
1253
- expect(moments, hasLength(1));
1254
- expect(moments.single.id, 'abc123');
1255
- expect(moments.single.performedBy, 'user:ada');
1256
- expect(moments.single.action, 'createSite');
1257
- expect((momentsTransport.lastRequest!['args'] as Map)['limit'], 12);
1258
- await momentsBridge.dispose();
1259
-
1260
- final readTransport = ReplayTransport({
1261
- 'rows': [
1262
- {
1263
- 'id': 'EstateSite_1',
1264
- 'data': {'name': 'HQ'}
1265
- }
1266
- ]
1267
- });
1268
- final readBridge = NomosBridge(readTransport);
1269
- await readBridge.ready;
1270
- final rows = await readBridge.queryByIdAt(moments.single, 'EstateSite_1');
1271
- expect(rows.single['data'], {'name': 'HQ'});
1272
- expect(readTransport.lastRequest!['op'], 'queryByIdAt');
1273
- expect((readTransport.lastRequest!['args'] as Map),
1274
- containsPair('commit', 'abc123'));
1275
- await readBridge.dispose();
1276
- });
1277
-
1278
- test(
1279
- 'devices retain nominal request and custody types through one atomic approval',
1280
- () async {
1281
- final typedRunner = FakeTypedRunner();
1282
- final typedBridge = NomosBridge(typedRunner);
1283
- addTearDown(typedBridge.dispose);
1284
- await typedBridge.ready;
1285
- await typedBridge.connect(
1286
- cloud: 'https://c',
1287
- workspace: 'home',
1288
- session: 'home',
1289
- authorSecret: base64Encode(List<int>.filled(32, 7)),
1290
- authToken: 'e30.eyJzdWIiOiJhbGljZSJ9.',
1291
- );
1292
- NomosOwnedDeviceApprovalPreparation? approved;
1293
- final devices = NomosDevices(
1294
- bridge: typedBridge.session('home'),
1295
- domain: 'notes',
1296
- domainHash: 'a' * 64,
1297
- actor: 'user:alice',
1298
- scope: 'nomos_internal_owned_read_7c1f5e',
1299
- approvePrepared: (preparation) async {
1300
- approved = preparation;
1301
- return const NomosOfferOutcome(
1302
- head: 'HEAD-owned-approval',
1303
- created: [],
1304
- workspace: 'notes',
1305
- );
1306
- },
1307
- );
1308
-
1309
- final request = await devices.request();
1310
- expect(request.workspace, 'home');
1311
- expect(request.principal, 'user:alice');
1312
- expect(request.scope, 'nomos_internal_owned_read_7c1f5e');
1313
- expect(typedRunner.lastKernelCall!.query.hasOwnedDeviceRequest(), isTrue);
1314
-
1315
- final outcome = await devices.approve(request);
1316
- expect(outcome.head, 'HEAD-owned-approval');
1317
- expect(typedRunner.lastKernelCall!.query.hasOwnedPrepareApproval(), isTrue);
1318
- expect(approved?.newKeySubject, 'key:${'a' * 64}');
1319
- expect(approved?.ownedCustody.request.workspace, 'home');
1320
- expect(approved?.ownedCustody.keyCommitment, '0' * 64);
1321
- expect(approved?.verifiedVia, 'nomos:test-root');
1322
- expect(approved?.validFromEpoch, 0);
1323
- expect(approved?.approvedAt, '2026-08-18T14:20:00.000Z');
1324
- expect(approved?.ownedKeySubject, 'key:${'b' * 64}');
1325
- });
1326
-
1327
- test(
1328
- 'record sharing keeps keys, identity and action payloads behind the bridge',
1329
- () async {
1330
- await bridge.ready;
1331
- final keys = await bridge.nomosOwnedScopeKeys(
1332
- 'owned-reader',
1333
- recordType: 'notes.Note',
1334
- recordId: 'note-1',
1335
- );
1336
- expect(keys.single.epoch, 2);
1337
- expect(keys.single.key, 'opaque-record-key');
1338
-
1339
- await bridge.nomosEnsureOwnedIdentity(
1340
- scope: 'owned-reader',
1341
- domain: 'notes',
1342
- domainHash: 'a' * 64,
1343
- );
1344
- final payload = await bridge.nomosPrepareOwnedRecordAction(
1345
- action: 'share',
1346
- scope: 'owned-reader',
1347
- record: NmsInternalNomosRefs.create<Object?>(
1348
- workspace: 'ws-1',
1349
- type: 'notes.Note',
1350
- id: 'note-1',
1351
- ),
1352
- peer: 'user:bob',
1353
- );
1354
- expect(payload, isA<NomosOwnedSharePreparation>());
1355
- expect((payload as NomosOwnedSharePreparation).recordId, 'note-1');
1356
- expect(payload.ownedKeySubject, 'key:${'c' * 64}');
1357
-
1358
- final keyCall = runner.sent.firstWhere(
1359
- (message) => message['op'] == 'cryptoOwnedScopeKeys',
1360
- );
1361
- expect((keyCall['args'] as Map).cast<String, Object?>(), {
1362
- 'scope': 'owned-reader',
1363
- 'record': {'type': 'notes.Note', 'id': 'note-1'},
1364
- });
1365
- final identityCall = runner.sent.firstWhere(
1366
- (message) => message['op'] == 'cryptoOwnedEnsureIdentity',
1367
- );
1368
- expect((identityCall['args'] as Map).cast<String, Object?>(), {
1369
- 'scope': 'owned-reader',
1370
- 'domain': 'notes',
1371
- 'domainHash': 'a' * 64,
1372
- });
1373
- final actionCall = runner.sent.firstWhere(
1374
- (message) => message['op'] == 'cryptoOwnedPrepareRecordAction',
1375
- );
1376
- expect((actionCall['args'] as Map).cast<String, Object?>(), {
1377
- 'action': 'share',
1378
- 'scope': 'owned-reader',
1379
- 'record': {'type': 'notes.Note', 'id': 'note-1'},
1380
- 'peer': 'user:bob',
1381
- });
1382
- });
1383
-
1384
- test('generated model adoption uses one exact Runtime v2 command', () async {
1385
- await bridge.ready;
1386
- await bridge.connect(cloud: 'https://c', workspace: 'evo');
1387
- runner.runtimeRequests.clear();
1388
- final verdict = await bridge.nomosFrameworkAdoptGeneratedModel(
1389
- domain: 'evo',
1390
- candidateHash: 'candidate-law-hash',
1391
- because: 'the renamed business action is intentional',
1392
- );
1393
-
1394
- expect(verdict.accepted, isFalse);
1395
- expect(
1396
- verdict.stage,
1397
- protocol.ModelLifecycleStage.MODEL_LIFECYCLE_STAGE_ACCEPTANCE_REQUIRED,
1398
- );
1399
- expect(verdict.review.differences.single.action, 'registerSite');
1400
- final request = runner.runtimeRequests.singleWhere(
1401
- (request) =>
1402
- request.hasModelLifecycle() && request.modelLifecycle.hasAdopt(),
1403
- );
1404
- expect(request.modelLifecycle.adopt.domain, 'evo');
1405
- expect(request.modelLifecycle.adopt.candidateHash, 'candidate-law-hash');
1406
- expect(request.modelLifecycle.adopt.acceptanceReason,
1407
- 'the renamed business action is intentional');
1408
- expect(
1409
- runner.sent.where((message) => message['op'] == 'adoptGeneratedModel'),
1410
- isEmpty);
1411
- });
1412
-
1413
- test(
1414
- 'generated model staging carries USDA bytes in one exact Runtime v2 command',
1415
- () async {
1416
- await bridge.ready;
1417
- await bridge.connect(cloud: 'https://c', workspace: 'evo');
1418
- runner.runtimeRequests.clear();
1419
- await bridge.nomosInternalStageGeneratedModel(
1420
- domain: 'evo',
1421
- candidateHash: 'candidate-law-hash',
1422
- lawUsdz: const [1, 2, 3],
1423
- );
1424
-
1425
- final request = runner.runtimeRequests.singleWhere(
1426
- (request) =>
1427
- request.hasModelLifecycle() && request.modelLifecycle.hasStage(),
1428
- );
1429
- expect(request.modelLifecycle.stage.domain, 'evo');
1430
- expect(request.modelLifecycle.stage.candidateHash, 'candidate-law-hash');
1431
- expect(request.modelLifecycle.stage.lawUsdz, [1, 2, 3]);
1432
- expect(
1433
- runner.sent.where((message) => message['op'] == 'stageGeneratedModel'),
1434
- isEmpty);
1435
- });
1436
-
1437
- test(
1438
- 'watch is a reactive stream; a runner push reaches the listener; cancel unwatches',
1439
- () async {
1440
- await bridge.ready;
1441
- final got = <List<dynamic>>[];
1442
- final sub = bridge.watch('byAuthor', {'author': 'ada'}).listen(got.add);
1443
- await Future<void>.delayed(Duration.zero); // let the watch register
1444
- runner.pushWatch(runner.lastWatchSubId!, [
1445
- {
1446
- 'id': 'a',
1447
- 'data': {'n': 2}
1448
- }
1449
- ]);
1450
- await Future<void>.delayed(Duration.zero);
1451
- expect(got, [
1452
- [
1453
- {
1454
- 'id': 'a',
1455
- 'data': {'n': 2}
1456
- }
1457
- ]
1458
- ]);
1459
- await sub.cancel();
1460
- expect(
1461
- runner.runtimeRequests.any((request) => request.hasUnwatch()), isTrue);
1462
- });
1463
-
1464
- test('watch cancellation is local and never waits for the runner', () async {
1465
- await bridge.ready;
1466
- runner.acknowledgeUnwatch = false;
1467
- final sub = bridge.watch('byAuthor', {'author': 'ada'}).listen((_) {});
1468
- await Future<void>.delayed(Duration.zero);
1469
-
1470
- await sub.cancel().timeout(const Duration(milliseconds: 100));
1471
- expect(
1472
- runner.runtimeRequests.any((request) => request.hasUnwatch()), isTrue);
1473
- });
1474
-
1475
- test('signal events are forwarded on their own stream', () async {
1476
- await bridge.ready;
1477
- final got = <Map<String, Object?>>[];
1478
- final sub = bridge.signalEvents.listen(got.add);
1479
- runner.pushSignal({
1480
- 'type': 'signal.projection',
1481
- 'domain': 'presence',
1482
- 'projection': {
1483
- 'peers': [
1484
- {'clientId': 'prof-a', 'behindBest': 141},
1485
- ],
1486
- },
1487
- });
1488
- await Future<void>.delayed(Duration.zero);
1489
- expect(got.single['signal'], isA<Map>());
1490
- expect(((got.single['signal'] as Map)['projection'] as Map)['peers'],
1491
- isA<List>());
1492
- await sub.cancel();
1493
- });
1494
-
1495
- test('autoSync forwards a scoped signal id', () async {
1496
- await bridge.ready;
1497
- await bridge.session('ws').autoSync(signalId: 'signal:ws:list:main');
1498
- final request = runner.runtimeRequests
1499
- .firstWhere((request) => request.hasStartAutoSync());
1500
- expect(request.startAutoSync.signalId, 'signal:ws:list:main');
1501
- });
1502
-
1503
- test(
1504
- 'an exact write receipt advances from local acceptance to remote convergence',
1505
- () async {
1506
- await bridge.ready;
1507
- final outcome = await bridge.offerOutcome(
1508
- domain: 'notes',
1509
- directiveId: 'writeNote',
1510
- payload: {'text': 'hello'},
1511
- );
1512
- expect(outcome.receipt.current.phase, NomosOperationPhase.locallyAccepted);
1513
- expect(
1514
- bridge.session('notes').currentStatus.phase,
1515
- SyncPhase.syncing,
1516
- reason: 'a locally accepted write cannot remain falsely current',
1517
- );
1518
- expect(bridge.session('notes').currentStatus.ahead, 1);
1519
-
1520
- runner.pushSync({
1521
- 'session': 'notes',
1522
- 'reason': 'write',
1523
- 'result': {
1524
- 'admittedIntentIds': ['intent-owned-approval'],
1525
- },
1526
- });
1527
- await outcome.receipt.admitted.timeout(const Duration(seconds: 1));
1528
- expect(outcome.receipt.current.phase,
1529
- NomosOperationPhase.admittedToRemoteCustody);
1530
-
1531
- runner.pushSync({
1532
- 'session': 'notes',
1533
- 'reason': 'drain-read',
1534
- 'result': {
1535
- 'convergedIntentIds': ['intent-owned-approval'],
1536
- },
1537
- });
1538
- await outcome.receipt.converged.timeout(const Duration(seconds: 1));
1539
- expect(outcome.receipt.current.isConverged, isTrue);
1540
- expect(bridge.session('notes').currentStatus.phase, SyncPhase.idle);
1541
- });
1542
-
1543
- test('offline debt leaves a receipt pending and reconnect settles it',
1544
- () async {
1545
- await bridge.ready;
1546
- final outcome = await bridge.offerOutcome(
1547
- domain: 'notes',
1548
- directiveId: 'writeNote',
1549
- payload: {'text': 'offline'},
1550
- );
1551
- runner.pushSync({
1552
- 'session': 'notes',
1553
- 'reason': 'write',
1554
- 'result': {'offline': true, 'stillAhead': true},
1555
- });
1556
- await Future<void>.delayed(Duration.zero);
1557
- expect(outcome.receipt.current.phase, NomosOperationPhase.locallyAccepted);
1558
-
1559
- runner.pushSync({
1560
- 'session': 'notes',
1561
- 'reason': 'reconnect',
1562
- 'result': {
1563
- 'admittedIntentIds': ['intent-owned-approval'],
1564
- 'convergedIntentIds': ['intent-owned-approval'],
1565
- },
1566
- });
1567
- await outcome.receipt.converged.timeout(const Duration(seconds: 1));
1568
- });
1569
-
1570
- test('a generated child workspace inherits the parent birth receipt',
1571
- () async {
1572
- await bridge.ready;
1573
- final outcome = await bridge.offerOutcome(
1574
- domain: 'notes',
1575
- directiveId: 'birthChild',
1576
- payload: {'name': 'child'},
1577
- );
1578
- final childReceipt =
1579
- bridge.session('notes-child').nomosInternalWorkspaceCreationReceipt();
1580
- expect(childReceipt, same(outcome.receipt));
1581
- });
1582
-
1583
- test(
1584
- 'background authentication failure refreshes through the retained application session',
1585
- () async {
1586
- String token(String marker) =>
1587
- '${base64Url.encode(utf8.encode('{"alg":"none"}')).replaceAll('=', '')}.'
1588
- '${base64Url.encode(utf8.encode('{"sub":"alice","iss":"test","marker":"$marker"}')).replaceAll('=', '')}.x';
1589
- await bridge.ready;
1590
- await bridge.connect(
1591
- cloud: 'https://cloud.example',
1592
- workspace: 'notes',
1593
- session: 'notes',
1594
- authToken: token('initial'),
1595
- );
1596
- bridge.session('notes').nomosInternalRetainCloudAccess(
1597
- cloud: 'https://cloud.example',
1598
- signIn: () async => token('refreshed'),
1599
- );
1600
-
1601
- runner.pushSync({
1602
- 'session': 'notes',
1603
- 'reason': 'reconnect',
1604
- 'result': {
1605
- 'pushFailed': true,
1606
- 'pushError': {'status': 401, 'error': 'token expired'},
1607
- },
1608
- });
1609
- await Future<void>.delayed(const Duration(milliseconds: 10));
1610
- final refresh = runner.sent
1611
- .lastWhere((message) => message['op'] == 'refreshCredentials');
1612
- expect((refresh['args'] as Map)['authToken'], token('refreshed'));
1613
- });
1614
-
1615
- test(
1616
- 'background sync status never calls an offline or failed round successful',
1617
- () async {
1618
- await bridge.ready;
1619
-
1620
- runner.pushSync({
1621
- 'reason': 'reconnect',
1622
- 'result': {
1623
- 'offline': true,
1624
- 'pushError': {'error': 'network unavailable'}
1625
- }
1626
- });
1627
- await Future<void>.delayed(Duration.zero);
1628
- expect(bridge.currentStatus.phase, SyncPhase.offline);
1629
- expect(bridge.currentStatus.lastSyncedAt, isNull);
1630
- expect(bridge.currentStatus.lastError, 'network unavailable');
1631
-
1632
- runner.pushSync({
1633
- 'reason': 'reconnect',
1634
- 'ok': false,
1635
- 'error': 'kernel capability skew',
1636
- });
1637
- await Future<void>.delayed(Duration.zero);
1638
- expect(bridge.currentStatus.phase, SyncPhase.error);
1639
- expect(bridge.currentStatus.lastSyncedAt, isNull);
1640
- expect(bridge.currentStatus.lastError, 'kernel capability skew');
1641
-
1642
- runner.pushSync({
1643
- 'reason': 'reconnect',
1644
- 'result': {'stillAhead': false}
1645
- });
1646
- await Future<void>.delayed(Duration.zero);
1647
- expect(bridge.currentStatus.phase, SyncPhase.idle);
1648
- expect(bridge.currentStatus.lastSyncedAt, isNotNull);
1649
- expect(bridge.currentStatus.lastError, isNull);
1650
- });
1651
-
1652
- test('application sync status scopes and folds every connected workspace',
1653
- () async {
1654
- await bridge.ready;
1655
- await bridge.connect(cloud: 'https://c', workspace: 'home');
1656
- await bridge.connect(cloud: 'https://c', workspace: 'estate');
1657
-
1658
- final status = await bridge.syncStatus.firstWhere(
1659
- (value) => value.ahead == 4 && value.behind == 1,
1660
- );
1661
-
1662
- expect(status.saved, 7);
1663
- expect(status.onMain, 9);
1664
- expect(status.deadLetters, 1);
1665
- final metricRequests =
1666
- runner.sent.where((message) => message['op'] == 'metrics').toList();
1667
- expect(metricRequests, hasLength(2));
1668
- expect(
1669
- metricRequests
1670
- .map((message) => (message['args'] as Map)['session'])
1671
- .toSet(),
1672
- {'home', 'estate'},
1673
- );
1674
- });
1675
-
1676
- test('workspace application status is isolated from sibling workspace debt',
1677
- () async {
1678
- await bridge.ready;
1679
- await bridge.connect(cloud: 'https://c', workspace: 'home');
1680
- await bridge.connect(cloud: 'https://c', workspace: 'estate');
1681
-
1682
- expect(
1683
- bridge.session('home').currentStatus.phase,
1684
- SyncPhase.syncing,
1685
- reason: 'an unmeasured session must not claim to be current',
1686
- );
1687
-
1688
- final home = await bridge.session('home').syncStatus.firstWhere(
1689
- (value) => value.saved == 2,
1690
- );
1691
- final estate = await bridge.session('estate').syncStatus.firstWhere(
1692
- (value) => value.saved == 5,
1693
- );
1694
-
1695
- expect(home.ahead, 0);
1696
- expect(home.behind, 0);
1697
- expect(estate.ahead, 4);
1698
- expect(estate.behind, 1);
1699
-
1700
- runner.pushSync({
1701
- 'session': 'estate',
1702
- 'reason': 'reconnect',
1703
- 'ok': false,
1704
- 'error': 'estate only failed',
1705
- });
1706
- await Future<void>.delayed(Duration.zero);
1707
- expect(bridge.session('home').currentStatus.phase, SyncPhase.idle);
1708
- expect(bridge.session('estate').currentStatus.phase, SyncPhase.error);
1709
- expect(bridge.currentStatus.phase, SyncPhase.error);
1710
- });
1711
-
1712
- test('unchanged workspace metrics do not fan out duplicate status events',
1713
- () async {
1714
- await bridge.ready;
1715
- await bridge.connect(cloud: 'https://c', workspace: 'home');
1716
-
1717
- final firstStatuses = <SyncStatus>[];
1718
- final first = bridge.session('home').syncStatus.listen(firstStatuses.add);
1719
- for (var attempt = 0;
1720
- attempt < 20 && !firstStatuses.any((status) => status.saved == 2);
1721
- attempt += 1) {
1722
- await Future<void>.delayed(const Duration(milliseconds: 10));
1723
- }
1724
- expect(firstStatuses.any((status) => status.saved == 2), isTrue);
1725
- await first.cancel();
1726
-
1727
- final repeatedStatuses = <SyncStatus>[];
1728
- final repeated =
1729
- bridge.session('home').syncStatus.listen(repeatedStatuses.add);
1730
- await Future<void>.delayed(const Duration(milliseconds: 30));
1731
-
1732
- expect(
1733
- repeatedStatuses,
1734
- hasLength(1),
1735
- reason: 'the listener gets one seed; the identical metrics poll is quiet',
1736
- );
1737
- expect(repeatedStatuses.single.saved, 2);
1738
- await repeated.cancel();
1739
- });
1740
-
1741
- test('custody push refusal is scoped, actionable, and cleared by recovery',
1742
- () async {
1743
- await bridge.ready;
1744
- await bridge.connect(cloud: 'https://c', workspace: 'home');
1745
- await bridge.connect(cloud: 'https://c', workspace: 'estate');
1746
-
1747
- runner.pushSync({
1748
- 'session': 'estate',
1749
- 'reason': 'local-write',
1750
- 'result': {
1751
- 'stillAhead': true,
1752
- 'pushFailed': true,
1753
- 'pushError': {
1754
- 'status': 409,
1755
- 'refused': true,
1756
- 'error': 'candidate law is not admitted',
1757
- },
1758
- },
1759
- });
1760
- await Future<void>.delayed(Duration.zero);
1761
-
1762
- expect(bridge.session('home').currentStatus.phase, isNot(SyncPhase.error));
1763
- expect(bridge.session('estate').currentStatus.phase, SyncPhase.error);
1764
- expect(
1765
- bridge.session('estate').currentStatus.lastError,
1766
- 'Shared custody refused this workspace change (HTTP 409): '
1767
- 'candidate law is not admitted',
1768
- );
1769
- expect(bridge.currentStatus.phase, SyncPhase.error);
1770
-
1771
- runner.pushSync({
1772
- 'session': 'estate',
1773
- 'reason': 'retry',
1774
- 'result': {'stillAhead': false},
1775
- });
1776
- await Future<void>.delayed(Duration.zero);
1777
-
1778
- expect(bridge.session('estate').currentStatus.phase, SyncPhase.idle);
1779
- expect(bridge.session('estate').currentStatus.lastError, isNull);
1780
- });
1781
-
1782
- test('status listener attached before connect never sends ambiguous metrics',
1783
- () async {
1784
- await bridge.ready;
1785
- final statuses = <SyncStatus>[];
1786
- final subscription = bridge.syncStatus.listen(statuses.add);
1787
- await Future<void>.delayed(Duration.zero);
1788
-
1789
- expect(
1790
- runner.sent.where((message) => message['op'] == 'metrics'),
1791
- isEmpty,
1792
- );
1793
-
1794
- await bridge.connect(cloud: 'https://c', workspace: 'home');
1795
- await bridge.connect(cloud: 'https://c', workspace: 'estate');
1796
- for (var attempt = 0;
1797
- attempt < 20 &&
1798
- !statuses.any((status) => status.ahead == 4 && status.behind == 1);
1799
- attempt += 1) {
1800
- await Future<void>.delayed(const Duration(milliseconds: 10));
1801
- }
1802
-
1803
- final metricRequests =
1804
- runner.sent.where((message) => message['op'] == 'metrics').toList();
1805
- expect(metricRequests, isNotEmpty);
1806
- expect(
1807
- metricRequests.every(
1808
- (message) =>
1809
- (message['args'] as Map)['session'] is String &&
1810
- ((message['args'] as Map)['session'] as String).isNotEmpty,
1811
- ),
1812
- isTrue,
1813
- );
1814
- expect(
1815
- statuses.any((status) => status.ahead == 4 && status.behind == 1),
1816
- isTrue,
1817
- );
1818
- expect(statuses.last.lastError, isNull);
1819
- await subscription.cancel();
1820
- });
1821
-
1822
- test('a law refusal throws NomosBridgeError with the verbatim message',
1823
- () async {
1824
- await bridge.ready;
1825
- Object? err;
1826
- try {
1827
- await bridge.offer(
1828
- intentBytes: base64Encode(utf8.encode(jsonEncode({
1829
- 'v': 2,
1830
- 'intent': {
1831
- 'domain': 'x',
1832
- 'directiveId': 'refuseMe',
1833
- 'payload': {},
1834
- 'domainHash': 'h'
1835
- }
1836
- }))));
1837
- } on NomosBridgeError catch (e) {
1838
- err = e;
1839
- }
1840
- expect(err, isA<NomosBridgeError>());
1841
- final e = err as NomosBridgeError;
1842
- expect(e.message, contains('law refused'));
1843
- expect(e.info.code, 'nomos.law.refused');
1844
- expect(e.info.component, 'test.runner');
1845
- expect(e.info.operation, 'offer');
1846
- expect(e.info.domain, 'x');
1847
- expect(e.info.directiveId, 'refuseMe');
1848
- expect(e.info.trace.traceId, isNotEmpty);
1849
- expect(e.info.toTelemetryAttributes()['nomos.error.code'],
1850
- 'nomos.law.refused');
1851
- expect(e.info.toTelemetryAttributes()['nomos.operation'], 'offer');
1852
- });
1853
-
1854
- test(
1855
- 'a custody fault surfaces as the TYPED CustodyError (switch on code, never scrape prose)',
1856
- () async {
1857
- await bridge.ready;
1858
- Object? err;
1859
- try {
1860
- await bridge.offer(
1861
- intentBytes: base64Encode(utf8.encode(jsonEncode({
1862
- 'v': 2,
1863
- 'intent': {
1864
- 'domain': 'x',
1865
- 'directiveId': 'custodyGap',
1866
- 'payload': {},
1867
- 'domainHash': 'h'
1868
- }
1869
- }))));
1870
- } on NomosBridgeError catch (e) {
1871
- err = e; // still catchable as the generic bridge error
1872
- }
1873
- expect(err, isA<CustodyError>());
1874
- final e = err as CustodyError;
1875
- expect(e.code, 'nomos.custody.shallow_insufficient');
1876
- expect(e.workspace, 'ws-1');
1877
- expect(e.ref, 'refs/heads/main');
1878
- expect(e.head, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
1879
- expect(e.missingObject, 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb');
1880
- expect(e.phase, CustodyPhase.author);
1881
- expect(e.source, CustodySource.localSnapshot);
1882
- expect(e.recoverable, isTrue);
1883
- expect(e.recommendedAction, 'quarantineLocalSnapshotAndReclone');
1884
- expect(e.shouldEvictLocalCustody, isTrue);
1885
- expect(e.info.trace.traceId,
1886
- isNotEmpty); // trace IDs ride as on every bridge error
1887
- expect(e.message, contains('shallow-insufficient'));
1888
- });
1889
-
1890
- test('Runtime v2 failures preserve their generated error identity', () async {
1891
- await bridge.ready;
1892
- await bridge.connect(
1893
- cloud: 'https://c',
1894
- workspace: 'ws-1',
1895
- session: 'ws-1',
1896
- );
1897
- Object? err;
1898
- try {
1899
- await bridge.sync(admit: false);
1900
- } on NomosBridgeError catch (e) {
1901
- err = e;
1902
- }
1903
- expect(err, isA<NomosBridgeError>());
1904
- final e = err as NomosBridgeError;
1905
- expect(e.message, 'typed sync failed');
1906
- expect(e.info.code, 'nomos.sync.refused');
1907
- expect(e.info.component, 'test.runner');
1908
- expect(e.info.workspace, 'ws-1');
1909
- });
1910
-
1911
- test('typed kernel progress preserves the measured verification fraction',
1912
- () async {
1913
- await bridge.ready;
1914
- final next = bridge.kernelProgress.first;
1915
- runner.pushKernelProgress(completed: 2081, total: 4162);
1916
- final progress = await next;
1917
- expect(progress.operation, KernelProgressOperation.restoreMain);
1918
- expect(progress.phase, KernelProgressPhase.verifyIntents);
1919
- expect(progress.unit, KernelProgressUnit.intent);
1920
- expect(progress.completed, 2081);
1921
- expect(progress.total, 4162);
1922
- expect(progress.fraction, 0.5);
1923
- expect(progress.terminal, isFalse);
1924
- expect(progress.kernelAllocatorLiveBytes, 384 * 1024 * 1024);
1925
- expect(progress.kernelAllocatorPeakBytes, 512 * 1024 * 1024);
1926
- expect(progress.processResidentBytes, 3 * 1024 * 1024 * 1024);
1927
- expect(progress.processPeakResidentBytes, 4 * 1024 * 1024 * 1024);
1928
- expect(progress.unaccountedResidentBytes, (3 * 1024 - 384) * 1024 * 1024);
1929
- });
1930
- }