@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
@@ -7,58 +7,103 @@ import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
7
7
  import 'package:test/test.dart';
8
8
 
9
9
  final class _ProofTransport
10
- implements BridgeTransport, NomosKernelCallTransport {
11
- final _messages = StreamController<Map<String, Object?>>.broadcast();
10
+ with NmsPlatformCredentialProviderHost
11
+ implements
12
+ PeerOfficeConnection,
13
+ NomosKernelCallTransport,
14
+ NomosRuntimeV2EventTransport {
15
+ final _runtimeEvents = StreamController<protocol.RuntimeEvent>.broadcast();
12
16
  protocol.CallRequest? lastCall;
17
+ protocol.CredentialUse? lastCredentialUse;
13
18
  var providerToken = protocol.ProviderTokenVerificationStatus
14
19
  .PROVIDER_TOKEN_VERIFICATION_STATUS_NO_MATCHING_KEY;
15
20
  var enrolledDevice = true;
16
21
  var resolvedPrincipal = 'user:jack';
17
22
  var syncCalls = 0;
23
+ final syncSessions = <String>[];
24
+ var offerCalls = 0;
25
+ var expireNextSessionOffer = false;
26
+ final refreshedTokens = <String>[];
27
+ final authenticationRequests = <protocol.AwaitAuthenticationRequest>[];
28
+ protocol.AuthenticationReadiness? authenticationResult;
18
29
 
19
30
  _ProofTransport() {
20
31
  scheduleMicrotask(
21
- () => _messages.add({
22
- 'v': kWebviewBridgeVersion,
23
- 'event': 'ready',
24
- 'bundleVersion': 'unbundled-dev',
25
- }),
32
+ () => _runtimeEvents.add(
33
+ protocol.RuntimeEvent(
34
+ ready: protocol.RuntimeReady(runtimeVersion: 'unbundled-dev'),
35
+ ),
36
+ ),
26
37
  );
27
38
  }
28
39
 
29
40
  @override
30
- Stream<Map<String, Object?>> get messages => _messages.stream;
41
+ Stream<protocol.RuntimeEvent> get runtimeEvents => _runtimeEvents.stream;
31
42
 
43
+ void pushRuntime(protocol.RuntimeEvent event) => _runtimeEvents.add(event);
44
+
45
+ int _requests = 0;
32
46
  @override
33
- void send(Map<String, Object?> message) {
34
- if (message['op'] != 'connect') {
35
- throw UnsupportedError('unexpected legacy operation ${message['op']}');
36
- }
37
- final id = message['id']! as int;
38
- final args = (message['args']! as Map).cast<String, Object?>();
39
- scheduleMicrotask(
40
- () => _messages.add({
41
- 'v': kWebviewBridgeVersion,
42
- 'id': id,
43
- 'ok': true,
44
- 'result': {'workspace': args['workspace']},
45
- }),
46
- );
47
- }
47
+ int nextRuntimeRequestId() => ++_requests;
48
48
 
49
49
  @override
50
50
  Future<protocol.RuntimeResponse> invokeRuntime(
51
51
  protocol.RuntimeRequest request,
52
52
  ) async {
53
+ if (request.hasAwaitAuthentication()) {
54
+ authenticationRequests.add(request.awaitAuthentication.deepCopy());
55
+ return protocol.RuntimeResponse(
56
+ protocolVersion: 2,
57
+ requestId: request.requestId,
58
+ authenticationReadiness: authenticationResult ??
59
+ protocol.AuthenticationReadiness(
60
+ outcome: protocol.AuthenticationReadinessOutcome
61
+ .AUTHENTICATION_READINESS_OUTCOME_READY,
62
+ posture: protocol.AuthenticationPostureResponse(
63
+ providerToken: protocol.ProviderTokenVerificationStatus
64
+ .PROVIDER_TOKEN_VERIFICATION_STATUS_VALID,
65
+ enrolledDevice: true,
66
+ resolvedPrincipal: 'user:jack',
67
+ ),
68
+ ),
69
+ );
70
+ }
71
+ if (request.hasBindSession()) {
72
+ return protocol.RuntimeResponse(
73
+ protocolVersion: 2,
74
+ requestId: request.requestId,
75
+ sessionBound: protocol.SessionBound(
76
+ workspace: request.bindSession.workspace,
77
+ session: request.bindSession.hasSession()
78
+ ? request.bindSession.session
79
+ : request.bindSession.workspace,
80
+ credentialCapability: request.bindSession.hasSession()
81
+ ? request.bindSession.session
82
+ : request.bindSession.workspace,
83
+ ),
84
+ );
85
+ }
53
86
  if (request.hasSyncWorkspace()) {
54
87
  syncCalls += 1;
88
+ syncSessions.add(request.syncWorkspace.workspace);
55
89
  providerToken = protocol.ProviderTokenVerificationStatus
56
90
  .PROVIDER_TOKEN_VERIFICATION_STATUS_VALID;
57
91
  return protocol.RuntimeResponse(
58
92
  protocolVersion: 2,
59
93
  requestId: request.requestId,
60
94
  syncWorkspace: protocol.SyncWorkspaceResponse(
61
- workspace: request.syncWorkspace.session,
95
+ workspace: request.syncWorkspace.workspace,
96
+ ),
97
+ );
98
+ }
99
+ if (request.hasRefreshCredentials()) {
100
+ refreshedTokens.add(utf8.decode(request.refreshCredentials.authToken));
101
+ return protocol.RuntimeResponse(
102
+ protocolVersion: 2,
103
+ requestId: request.requestId,
104
+ credentialsRefreshed: protocol.CredentialsRefreshed(
105
+ session: request.refreshCredentials.session,
106
+ hasAuthToken: request.refreshCredentials.authToken.isNotEmpty,
62
107
  ),
63
108
  );
64
109
  }
@@ -73,8 +118,11 @@ final class _ProofTransport
73
118
  Future<protocol.CallResponse> callKernel({
74
119
  required String session,
75
120
  required protocol.CallRequest request,
121
+ protocol.CredentialUse credentialUse =
122
+ protocol.CredentialUse.CREDENTIAL_USE_UNSPECIFIED,
76
123
  }) async {
77
124
  lastCall = request.deepCopy();
125
+ lastCredentialUse = credentialUse;
78
126
  if (request.hasQuery() && request.query.hasAuthenticationPosture()) {
79
127
  return protocol.CallResponse(
80
128
  protocolVersion: 1,
@@ -88,6 +136,21 @@ final class _ProofTransport
88
136
  ),
89
137
  );
90
138
  }
139
+ if (request.hasOffer()) {
140
+ offerCalls += 1;
141
+ if (expireNextSessionOffer &&
142
+ credentialUse == protocol.CredentialUse.CREDENTIAL_USE_SESSION) {
143
+ expireNextSessionOffer = false;
144
+ return protocol.CallResponse(
145
+ protocolVersion: 1,
146
+ requestId: request.requestId,
147
+ failure: protocol.Failure(
148
+ code: 'nomos.auth.token_expired',
149
+ message: 'auth: token verification failed (Expired)',
150
+ ),
151
+ );
152
+ }
153
+ }
91
154
  return protocol.CallResponse(
92
155
  protocolVersion: 1,
93
156
  requestId: request.requestId,
@@ -102,80 +165,231 @@ final class _ProofTransport
102
165
  }
103
166
 
104
167
  void main() {
168
+ test('Reception surfaces a terminal expiry without owning refresh or replay',
169
+ () async {
170
+ final transport = _ProofTransport()..expireNextSessionOffer = true;
171
+ final bridge = DartReception(transport);
172
+ addTearDown(bridge.dispose);
173
+ await bridge.ready;
174
+ final interaction = await bridge.bindSession(
175
+ workspace: 'home',
176
+ session: 'home',
177
+ );
178
+ await bridge.session('home').refreshCredentials(
179
+ authToken: 'expired-token',
180
+ );
181
+ var refreshes = 0;
182
+ bridge.nomosInternalRegisterCredentialProvider(
183
+ interaction: interaction,
184
+ cloud: 'https://nomos.example',
185
+ signIn: ({deviceChallenge}) async {
186
+ refreshes += 1;
187
+ return 'fresh-token';
188
+ },
189
+ );
190
+
191
+ await expectLater(
192
+ bridge.session('home').offerGeneratedCallOutcome(
193
+ call: NmsInternalGeneratedCalls.create(
194
+ domain: 'estate',
195
+ directive: 'seedComplianceDisciplines',
196
+ protobufType:
197
+ 'type.nomos.dev/nomos.operation.v1.Payload_seed_compliance_test',
198
+ protobufPayload: Uint8List.fromList([1]),
199
+ ),
200
+ ),
201
+ throwsA(
202
+ isA<AuthError>()
203
+ .having((error) => error.code, 'code', 'nomos.auth.token_expired')
204
+ .having((error) => error.workspace, 'workspace', 'home')
205
+ .having(
206
+ (error) => error.operation,
207
+ 'operation',
208
+ 'seedComplianceDisciplines',
209
+ ),
210
+ ));
211
+
212
+ // The real shared-runtime retry is exercised through its binary frame
213
+ // driver in credential_recovery_runtime.test.ts. A terminal refusal here
214
+ // must never start a second, language-specific retry procedure.
215
+ expect(transport.offerCalls, 1);
216
+ expect(refreshes, 0);
217
+ // The one token installation above belongs to test arrangement. The
218
+ // terminal refusal must not cause Reception to refresh or replay it.
219
+ expect(transport.refreshedTokens, ['expired-token']);
220
+ });
221
+
222
+ test('diagnostics are an observation-only view of bound sessions', () async {
223
+ final transport = _ProofTransport();
224
+ final reception = DartReception(transport);
225
+ addTearDown(reception.dispose);
226
+ await reception.ready;
227
+ await reception.bindSession(
228
+ workspace: 'workspace-home', session: 'session-home');
229
+ await reception.bindSession(
230
+ workspace: 'workspace-estate', session: 'session-estate');
231
+ final diagnostics = reception.syncDiagnostics;
232
+ expect(diagnostics.read().map((row) => row.workspace),
233
+ ['workspace-home', 'workspace-estate']);
234
+ expect(transport.syncCalls, 0);
235
+ final changed = diagnostics.changes.first;
236
+ transport.pushRuntime(protocol.RuntimeEvent(
237
+ session: 'session-estate',
238
+ syncCompleted: protocol.SyncCompleted(
239
+ result: protocol.SyncWorkspaceResponse(
240
+ remoteRefusal: protocol.SyncRefusal(reason: 'Exact refusal'),
241
+ )),
242
+ ));
243
+ await changed;
244
+ expect(diagnostics.read().last.status.lastError, 'Exact refusal');
245
+ expect(
246
+ identical(diagnostics.read().last.status,
247
+ reception.session('session-estate').currentStatus),
248
+ isTrue);
249
+ });
250
+
251
+ test('a successful typed sync clears locally tracked write debt', () async {
252
+ final transport = _ProofTransport();
253
+ final bridge = DartReception(transport);
254
+ addTearDown(bridge.dispose);
255
+ await bridge.ready;
256
+ await bridge.bindSession(workspace: 'home', session: 'home');
257
+
258
+ final outcome = await bridge.session('home').offerGeneratedCallOutcome(
259
+ call: NmsInternalGeneratedCalls.create(
260
+ domain: 'notes',
261
+ directive: 'writeNote',
262
+ protobufType:
263
+ 'type.nomos.dev/nomos.operation.v1.Payload_write_note_test',
264
+ protobufPayload: Uint8List.fromList([0x0a, 0x01, 0x01]),
265
+ ),
266
+ );
267
+ expect(bridge.session('home').currentStatus.phase, SyncPhase.syncing);
268
+ expect(bridge.session('home').currentStatus.ahead, isNull);
269
+
270
+ transport.pushRuntime(
271
+ protocol.RuntimeEvent(
272
+ session: 'home',
273
+ syncCompleted: protocol.SyncCompleted(
274
+ reason: 'write',
275
+ result: protocol.SyncWorkspaceResponse(
276
+ admittedIntentIds: [outcome.intentId],
277
+ convergedIntentIds: [outcome.intentId],
278
+ currentHead: 'confirmed-head',
279
+ stillAhead: false,
280
+ ),
281
+ ),
282
+ ),
283
+ );
284
+ await outcome.receipt.converged.timeout(const Duration(seconds: 1));
285
+ await Future<void>.delayed(Duration.zero);
286
+
287
+ expect(bridge.session('home').currentStatus.phase, SyncPhase.idle);
288
+ expect(bridge.session('home').currentStatus.ahead, 0);
289
+ });
290
+
105
291
  test('law-selected enrolled-device proof withholds the provider token',
106
292
  () async {
107
293
  final transport = _ProofTransport();
108
- final bridge = NomosBridge(transport);
294
+ final bridge = DartReception(transport);
109
295
  addTearDown(bridge.dispose);
110
296
  final secret = base64Encode(List<int>.filled(32, 7));
111
297
  await bridge.ready;
112
- await bridge.connect(
298
+ await bridge.bindSession(
113
299
  workspace: 'home',
114
300
  session: 'home',
115
- authorSecret: secret,
116
- authToken: 'stale.provider.token',
117
301
  );
302
+ await bridge.session('home').refreshCredentials(
303
+ authorSecret: secret,
304
+ authToken: 'stale.provider.token',
305
+ );
118
306
  final call = NmsInternalGeneratedCalls.create(
119
307
  domain: 'identity',
120
308
  directive: 'recoverProvider',
121
- canonicalUsda: Uint8List.fromList([1]),
309
+ protobufType:
310
+ 'type.nomos.dev/nomos.operation.v1.Payload_recover_provider_test',
311
+ protobufPayload: Uint8List.fromList([1]),
122
312
  );
123
313
 
124
- await bridge.session('home').offerCallUsdaOutcome(call: call);
125
- expect(transport.lastCall!.offer.authoring.authToken, isNotEmpty);
126
- expect(transport.lastCall!.offer.authoring.authorSecret, isNotEmpty);
314
+ await bridge.session('home').offerGeneratedCallOutcome(call: call);
315
+ expect(
316
+ transport.lastCredentialUse,
317
+ protocol.CredentialUse.CREDENTIAL_USE_SESSION,
318
+ );
319
+ expect(transport.lastCall!.offer.authoring.authToken, isEmpty);
320
+ expect(transport.lastCall!.offer.authoring.authorSecret, isEmpty);
127
321
 
128
- await bridge.session('home').offerCallUsdaOutcome(
322
+ await bridge.session('home').offerGeneratedCallOutcome(
129
323
  call: call,
130
324
  authenticationProof: NomosOfferAuthenticationProof.enrolledDevice,
131
325
  );
326
+ expect(
327
+ transport.lastCredentialUse,
328
+ protocol.CredentialUse.CREDENTIAL_USE_ENROLLED_DEVICE,
329
+ );
132
330
  expect(transport.lastCall!.offer.authoring.authToken, isEmpty);
133
- expect(transport.lastCall!.offer.authoring.authorSecret, isNotEmpty);
331
+ expect(transport.lastCall!.offer.authoring.authorSecret, isEmpty);
134
332
  });
135
333
 
136
- test('enrolled-device proof fails before transport without a device key',
334
+ test('enrolled-device proof is selected without copying its key through Dart',
137
335
  () async {
138
336
  final transport = _ProofTransport();
139
- final bridge = NomosBridge(transport);
337
+ final bridge = DartReception(transport);
140
338
  addTearDown(bridge.dispose);
141
339
  await bridge.ready;
142
- await bridge.connect(
340
+ await bridge.bindSession(
143
341
  workspace: 'home',
144
342
  session: 'home',
145
- authToken: 'stale.provider.token',
146
343
  );
344
+ await bridge.session('home').refreshCredentials(
345
+ authToken: 'stale.provider.token',
346
+ );
147
347
  final call = NmsInternalGeneratedCalls.create(
148
348
  domain: 'identity',
149
349
  directive: 'recoverProvider',
150
- canonicalUsda: Uint8List.fromList([1]),
350
+ protobufType:
351
+ 'type.nomos.dev/nomos.operation.v1.Payload_recover_provider_test',
352
+ protobufPayload: Uint8List.fromList([1]),
151
353
  );
152
354
 
153
- await expectLater(
154
- bridge.session('home').offerCallUsdaOutcome(
155
- call: call,
156
- authenticationProof: NomosOfferAuthenticationProof.enrolledDevice,
157
- ),
158
- throwsA(isA<StateError>()),
355
+ await bridge.session('home').offerGeneratedCallOutcome(
356
+ call: call,
357
+ authenticationProof: NomosOfferAuthenticationProof.enrolledDevice,
358
+ );
359
+ expect(
360
+ transport.lastCredentialUse,
361
+ protocol.CredentialUse.CREDENTIAL_USE_ENROLLED_DEVICE,
159
362
  );
160
- expect(transport.lastCall, isNull);
363
+ expect(transport.lastCall!.offer.authoring.authorSecret, isEmpty);
161
364
  });
162
365
 
163
366
  test('authentication recovery waits for the exact typed kernel postcondition',
164
367
  () async {
165
368
  final transport = _ProofTransport();
166
- final bridge = NomosBridge(transport);
369
+ final bridge = DartReception(transport);
167
370
  addTearDown(bridge.dispose);
168
371
  final secret = base64Encode(List<int>.filled(32, 7));
169
372
  await bridge.ready;
170
- await bridge.connect(
373
+ await bridge.bindSession(
171
374
  workspace: 'home',
172
375
  session: 'home',
173
- authorSecret: secret,
174
- authToken: 'stale.provider.token',
175
376
  );
377
+ await bridge.session('home').refreshCredentials(
378
+ authorSecret: secret,
379
+ authToken: 'stale.provider.token',
380
+ );
176
381
  final home = bridge.session('home');
177
382
 
178
- final before = await home.nmsAuthenticationPosture(principal: 'user:jack');
383
+ final before = await home.nmsAuthenticationPosture(
384
+ principal: 'user:jack',
385
+ authorSecret: secret,
386
+ );
387
+ expect(
388
+ transport.lastCredentialUse,
389
+ protocol.CredentialUse.CREDENTIAL_USE_SESSION,
390
+ reason:
391
+ 'an explicit device proof must not suppress the live provider credential',
392
+ );
179
393
  expect(
180
394
  before.providerToken,
181
395
  NomosProviderTokenVerification.noMatchingKey,
@@ -184,10 +398,96 @@ void main() {
184
398
 
185
399
  final after = await home.nmsAwaitAuthenticationReady(
186
400
  principal: 'user:jack',
401
+ initialBackoff: const Duration(milliseconds: 10),
187
402
  );
188
403
  expect(after.providerToken, NomosProviderTokenVerification.valid);
189
404
  expect(after.enrolledDevice, isTrue);
190
405
  expect(after.resolvedPrincipal, 'user:jack');
191
- expect(transport.syncCalls, 1);
406
+ expect(transport.authenticationRequests, hasLength(1));
407
+ final requirement = transport.authenticationRequests.single;
408
+ expect(requirement.session, 'home');
409
+ expect(requirement.principal, 'user:jack');
410
+ expect(requirement.initialBackoffMillis, 10);
411
+ expect(requirement.patienceMillis, 60000);
412
+ expect(requirement.maximumBackoffMillis, 8000);
413
+ expect(transport.syncCalls, 0,
414
+ reason: 'the client drives no sync rounds of its own');
415
+ });
416
+
417
+ test('Reception preserves the runtime last posture when patience expires',
418
+ () async {
419
+ final transport = _ProofTransport();
420
+ final bridge = DartReception(transport, allowStaleRunnerBundle: true);
421
+ addTearDown(bridge.dispose);
422
+ await bridge.ready;
423
+ await bridge.bindSession(workspace: 'home', session: 'home');
424
+ transport.authenticationResult = protocol.AuthenticationReadiness(
425
+ outcome: protocol.AuthenticationReadinessOutcome
426
+ .AUTHENTICATION_READINESS_OUTCOME_PATIENCE_EXHAUSTED,
427
+ posture: protocol.AuthenticationPostureResponse(
428
+ providerToken: protocol.ProviderTokenVerificationStatus
429
+ .PROVIDER_TOKEN_VERIFICATION_STATUS_NO_MATCHING_KEY,
430
+ enrolledDevice: false,
431
+ resolvedPrincipal: '',
432
+ ),
433
+ );
434
+ final posture = await bridge
435
+ .session('home')
436
+ .nmsAwaitAuthenticationReady(principal: 'user:jack');
437
+ expect(posture.providerToken, NomosProviderTokenVerification.noMatchingKey);
438
+ expect(posture.enrolledDevice, isFalse);
439
+ expect(transport.authenticationRequests, hasLength(1));
440
+ expect(transport.syncCalls, 0);
441
+ });
442
+
443
+ test(
444
+ 'birth readiness crosses the runtime boundary without a client sync loop',
445
+ () async {
446
+ final transport = _ProofTransport();
447
+ final bridge = DartReception(transport);
448
+ addTearDown(bridge.dispose);
449
+ await bridge.ready;
450
+ await bridge.bindSession(workspace: 'home', session: 'home');
451
+ final home = bridge.session('home');
452
+
453
+ final unspecified = await home.nmsAuthenticationPosture(
454
+ principal: 'user:jack',
455
+ includeBirthEvidence: true,
456
+ );
457
+ expect(transport.lastCall!.query.authenticationPosture.includeBirthEvidence,
458
+ isTrue);
459
+ expect(unspecified.isReadyToBirthFor('user:jack'), isFalse,
460
+ reason:
461
+ 'an older kernel omitting the field cannot prove birth readiness');
462
+
463
+ transport.authenticationResult = protocol.AuthenticationReadiness(
464
+ outcome: protocol.AuthenticationReadinessOutcome
465
+ .AUTHENTICATION_READINESS_OUTCOME_PATIENCE_EXHAUSTED,
466
+ posture: protocol.AuthenticationPostureResponse(
467
+ providerToken: protocol.ProviderTokenVerificationStatus
468
+ .PROVIDER_TOKEN_VERIFICATION_STATUS_VALID,
469
+ enrolledDevice: true,
470
+ resolvedPrincipal: 'user:jack',
471
+ birthEvidence: protocol.BirthEvidenceReadiness(
472
+ status: protocol
473
+ .BirthEvidenceStatus.BIRTH_EVIDENCE_STATUS_DELEGATION_REQUIRED,
474
+ detail: 'the current device has no retained delegation',
475
+ ),
476
+ ),
477
+ );
478
+ final posture = await home.nmsAwaitAuthenticationReady(
479
+ principal: 'user:jack',
480
+ requireBirthEvidence: true,
481
+ );
482
+ expect(posture.isReadyFor('user:jack'), isTrue);
483
+ expect(posture.isReadyToBirthFor('user:jack'), isFalse);
484
+ expect(posture.needsBirthDelegation, isTrue);
485
+ expect(posture.birthEvidenceDetail,
486
+ 'the current device has no retained delegation');
487
+ expect(
488
+ transport.authenticationRequests.single.criterion,
489
+ protocol.AuthenticationReadinessCriterion
490
+ .AUTHENTICATION_READINESS_CRITERION_BIRTH_EVIDENCE);
491
+ expect(transport.syncCalls, 0);
192
492
  });
193
493
  }
@@ -0,0 +1,151 @@
1
+ import 'dart:async';
2
+ import 'dart:typed_data';
3
+
4
+ import 'package:nomos_client/src/nms_generated_runtime.dart';
5
+ import 'package:nomos_types/nomos_kernel_protocol.dart' as p;
6
+ import 'package:test/test.dart';
7
+
8
+ final class _Office implements PeerOfficeConnection {
9
+ final events = StreamController<p.RuntimeEvent>.broadcast(sync: true);
10
+ final calls = <p.RuntimeRequest>[];
11
+ int sequence = 0;
12
+
13
+ @override
14
+ Stream<p.RuntimeEvent> get runtimeEvents => events.stream;
15
+
16
+ @override
17
+ int nextRuntimeRequestId() => ++sequence;
18
+
19
+ @override
20
+ Future<p.RuntimeResponse> invokeRuntime(p.RuntimeRequest request) async {
21
+ calls.add(request.deepCopy());
22
+ if (!request.hasFramework() ||
23
+ !request.framework.hasCheckInterfaceCompatibility()) {
24
+ throw StateError(
25
+ 'unexpected runtime call ${request.whichCommand().name}');
26
+ }
27
+ return p.RuntimeResponse(
28
+ protocolVersion: 2,
29
+ requestId: request.requestId,
30
+ framework: p.FrameworkResponse(
31
+ interfaceCompatibilityVerdict: p.InterfaceCompatibilityVerdict(
32
+ localLawCurrency: p.LocalLawCurrency.LOCAL_LAW_CURRENCY_CURRENT,
33
+ examined: p.InterfaceCompatibilityResponse(
34
+ compatible: false,
35
+ workspaceFrontier: 'head-${calls.length}',
36
+ members: [
37
+ p.InterfaceMemberVerdict(
38
+ domain: 'estate',
39
+ kind: p.InterfaceMemberKind.INTERFACE_MEMBER_KIND_AGGREGATE,
40
+ member: 'Asset',
41
+ status: p.InterfaceCompatibilityStatus
42
+ .INTERFACE_COMPATIBILITY_STATUS_EXACT,
43
+ ),
44
+ p.InterfaceMemberVerdict(
45
+ domain: 'estate',
46
+ kind: p.InterfaceMemberKind.INTERFACE_MEMBER_KIND_FIELD,
47
+ member: 'createCatalogueAssetGrid',
48
+ field_4: 'fieldRequirements',
49
+ status: p.InterfaceCompatibilityStatus
50
+ .INTERFACE_COMPATIBILITY_STATUS_INCOMPATIBLE,
51
+ reason: p.InterfaceCompatibilityReason
52
+ .INTERFACE_COMPATIBILITY_REASON_WIRE_CONTRACT_CHANGED,
53
+ diagnostic:
54
+ 'application wire 146915208 is not accepted by this law',
55
+ ),
56
+ p.InterfaceMemberVerdict(
57
+ domain: 'estate',
58
+ kind: p.InterfaceMemberKind.INTERFACE_MEMBER_KIND_DIRECTIVE,
59
+ member: 'createCatalogueAssetGrid',
60
+ status: p.InterfaceCompatibilityStatus
61
+ .INTERFACE_COMPATIBILITY_STATUS_INCOMPATIBLE,
62
+ ),
63
+ ],
64
+ ),
65
+ ),
66
+ ),
67
+ );
68
+ }
69
+ }
70
+
71
+ void main() {
72
+ late _Office office;
73
+ late DartReception reception;
74
+ late NomosSession session;
75
+ final matrix = Uint8List.fromList([1, 2, 3]);
76
+
77
+ setUp(() {
78
+ office = _Office();
79
+ reception = DartReception(office);
80
+ session = reception.session('estate');
81
+ });
82
+
83
+ tearDown(() async {
84
+ await reception.dispose();
85
+ await office.events.close();
86
+ });
87
+
88
+ test('the exact unsafe write is typed while unrelated members stay usable',
89
+ () async {
90
+ final unsafe = await session.operationCompatibility(
91
+ matrix,
92
+ domain: 'estate',
93
+ directive: 'createCatalogueAssetGrid',
94
+ );
95
+ expect(
96
+ unsafe,
97
+ isA<NomosOperationUnavailable>()
98
+ .having(
99
+ (value) => value.guidance,
100
+ 'guidance',
101
+ NomosCompatibilityGuidance.controlPlaneDecisionRequired,
102
+ )
103
+ .having(
104
+ (value) =>
105
+ value.differences.map((difference) => difference.field_4),
106
+ 'differences',
107
+ contains('fieldRequirements'),
108
+ ),
109
+ );
110
+ await expectLater(
111
+ session.assertOperationCompatible(
112
+ matrix,
113
+ domain: 'estate',
114
+ directive: 'createCatalogueAssetGrid',
115
+ ),
116
+ throwsA(isA<NomosOperationUnavailable>()),
117
+ );
118
+ expect(
119
+ await session.operationCompatibility(
120
+ matrix,
121
+ domain: 'estate',
122
+ directive: 'renameAsset',
123
+ ),
124
+ isA<NomosOperationAvailable>(),
125
+ reason: 'a partial application is not a global failure',
126
+ );
127
+ expect(office.calls, hasLength(1),
128
+ reason: 'all three checks share one immutable matrix examination');
129
+ });
130
+
131
+ test('a law-change observation invalidates the session cache', () async {
132
+ await session.operationCompatibility(
133
+ matrix,
134
+ domain: 'estate',
135
+ directive: 'createCatalogueAssetGrid',
136
+ );
137
+ office.events.add(p.RuntimeEvent(
138
+ session: 'estate',
139
+ lawChanged: p.LawChanged(
140
+ domain: 'estate',
141
+ packageSha256: 'next-law',
142
+ ),
143
+ ));
144
+ await session.operationCompatibility(
145
+ matrix,
146
+ domain: 'estate',
147
+ directive: 'createCatalogueAssetGrid',
148
+ );
149
+ expect(office.calls, hasLength(2));
150
+ });
151
+ }