@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
@@ -0,0 +1,170 @@
1
+ import 'dart:async';
2
+ import 'dart:typed_data';
3
+
4
+ import 'package:fixnum/fixnum.dart' as fixnum;
5
+ import 'package:nomos_client/src/nms_generated_runtime.dart';
6
+ import 'package:nomos_types/nomos_kernel_protocol.dart' as p;
7
+ import 'package:nomos_types/src/proto/google/protobuf/any.pb.dart' as protobuf;
8
+ import 'package:test/test.dart';
9
+
10
+ // Exercise the real Reception boundary. A follow-up query is an unexpected
11
+ // command, not a canned successful response that could conceal duplicate work.
12
+ final class _Office implements PeerOfficeConnection {
13
+ final events = StreamController<p.RuntimeEvent>.broadcast(sync: true);
14
+ final calls = <p.RuntimeRequest>[];
15
+ int sequence = 0;
16
+
17
+ @override
18
+ Stream<p.RuntimeEvent> get runtimeEvents => events.stream;
19
+
20
+ @override
21
+ int nextRuntimeRequestId() => ++sequence;
22
+
23
+ @override
24
+ Future<p.RuntimeResponse> invokeRuntime(p.RuntimeRequest request) async {
25
+ calls.add(request.deepCopy());
26
+ final response = p.RuntimeResponse(
27
+ protocolVersion: 2,
28
+ requestId: request.requestId,
29
+ );
30
+ if (request.hasWatch()) {
31
+ response.watchStarted =
32
+ p.WatchStarted(subscriptionId: request.watch.subscriptionId);
33
+ } else if (request.hasUnwatch()) {
34
+ response.watchStopped =
35
+ p.WatchStopped(subscriptionId: request.unwatch.subscriptionId);
36
+ } else if (request.hasCloseRealm()) {
37
+ response.realmClosed = p.RealmClosed();
38
+ } else {
39
+ fail('Unexpected command: ${request.whichCommand()}');
40
+ }
41
+ return response;
42
+ }
43
+
44
+ void emit(p.WatchChanged changed) {
45
+ changed.subscriptionId =
46
+ calls.singleWhere((r) => r.hasWatch()).watch.subscriptionId;
47
+ events.add(p.RuntimeEvent(watchChanged: changed));
48
+ }
49
+ }
50
+
51
+ final _query = NmsInternalGeneratedQueries.create(
52
+ domain: 'estate',
53
+ query: 'canvasAssets',
54
+ protobufType: 'type.nomos.dev/estate.CanvasAssetsArgs',
55
+ protobufPayload: Uint8List.fromList([8, 1]),
56
+ );
57
+ const _resultType = 'type.nomos.dev/estate.CanvasAssetsResult';
58
+
59
+ void main() {
60
+ late _Office office;
61
+ late DartReception reception;
62
+
63
+ setUp(() {
64
+ office = _Office();
65
+ reception = DartReception(office);
66
+ });
67
+ tearDown(() async {
68
+ await reception.dispose();
69
+ await office.events.close();
70
+ });
71
+
72
+ test('typed query watch delivers data and empty results without requerying',
73
+ () async {
74
+ final stream = reception.session('estate').nmsWatch(_query);
75
+ expect(office.calls, isEmpty, reason: 'an unobserved watch is lazy');
76
+ final values = StreamIterator(stream);
77
+ final first = values.moveNext();
78
+ final request = office.calls.single.watch;
79
+ expect(request.session, 'estate');
80
+ expect(request.query.resultFormat,
81
+ p.QueryResultFormat.QUERY_RESULT_FORMAT_APPLICATION_PROTOBUF);
82
+ expect(request.query.rows.id, 'canvasAssets');
83
+ expect(
84
+ request.query.rows.applicationArguments.typeUrl, _query.protobufType);
85
+ expect(request.query.rows.applicationArguments.value, [8, 1]);
86
+
87
+ office.emit(p.WatchChanged(
88
+ applicationResult: protobuf.Any(typeUrl: _resultType, value: [8, 9])));
89
+ expect(await first, isTrue);
90
+ expect(values.current.protobufType, _resultType);
91
+ expect(values.current.protobufPayload, [8, 9]);
92
+
93
+ final second = values.moveNext();
94
+ office.emit(
95
+ p.WatchChanged(applicationResult: protobuf.Any(typeUrl: _resultType)));
96
+ expect(await second, isTrue);
97
+ expect(values.current.protobufType, _resultType);
98
+ expect(values.current.protobufPayload, isEmpty,
99
+ reason: 'an empty Protobuf result is still a typed observation');
100
+ expect(office.calls, hasLength(1),
101
+ reason: 'events are data, not invalidations');
102
+
103
+ await values.cancel();
104
+ expect(office.calls.map((r) => r.whichCommand()),
105
+ [p.RuntimeRequest_Command.watch, p.RuntimeRequest_Command.unwatch]);
106
+ });
107
+
108
+ test('missing application result is observable, not silently requeried',
109
+ () async {
110
+ final result = reception.session('estate').nmsWatch(_query).first;
111
+ final check = expectLater(result, throwsStateError);
112
+ office.emit(p.WatchChanged(resultUsda: [1, 2]));
113
+ await check;
114
+ expect(office.calls.where((r) => r.hasKernelCall()), isEmpty);
115
+ });
116
+
117
+ test(
118
+ 'count watch preserves zero and exact integers without transferring rows',
119
+ () async {
120
+ final values = StreamIterator(reception
121
+ .session('estate')
122
+ .nmsWatchCount('assetCountBySiteId', 'site'));
123
+ final first = values.moveNext();
124
+ final query = office.calls.single.watch.query;
125
+ expect(query.whichQuery(), p.QueryRequest_Query.count);
126
+ expect(query.count.id, 'assetCountBySiteId');
127
+ expect(query.count.groupKey, 'site');
128
+ office.emit(p.WatchChanged(integerValue: fixnum.Int64.ZERO));
129
+ expect(await first, isTrue);
130
+ expect(values.current, 0);
131
+ final second = values.moveNext();
132
+ office.emit(p.WatchChanged(integerValue: fixnum.Int64(9007199254740993)));
133
+ expect(await second, isTrue);
134
+ expect(values.current, 9007199254740993);
135
+ expect(office.calls, hasLength(1));
136
+ await values.cancel();
137
+ });
138
+
139
+ test('count watch rejects missing integer instead of treating it as zero',
140
+ () async {
141
+ final result =
142
+ reception.session('estate').nmsWatchCount('assetCount').first;
143
+ final check = expectLater(result, throwsStateError);
144
+ office.emit(p.WatchChanged());
145
+ await check;
146
+ });
147
+
148
+ test('invalid nominal result identity fails at the transport boundary',
149
+ () async {
150
+ final result = reception.session('estate').nmsWatch(_query).first;
151
+ final check = expectLater(result, throwsArgumentError);
152
+ office.emit(p.WatchChanged(
153
+ applicationResult: protobuf.Any(typeUrl: 'untyped', value: [1])));
154
+ await check;
155
+ });
156
+
157
+ test('watch refusal is delivered to the listener without starting a query',
158
+ () async {
159
+ final result = reception.session('estate').nmsWatch(_query).first;
160
+ final check = expectLater(
161
+ result,
162
+ throwsA(isA<NomosRuntimeError>()
163
+ .having((e) => e.info.code, 'code', 'invalid_credential')));
164
+ office.emit(p.WatchChanged(
165
+ failure: p.RuntimeFailure(
166
+ code: 'invalid_credential', message: 'Credential was rejected')));
167
+ await check;
168
+ expect(office.calls.where((r) => r.hasKernelCall()), isEmpty);
169
+ });
170
+ }
@@ -0,0 +1,249 @@
1
+ import 'dart:async';
2
+ import 'package:nomos_client/src/typed_watch_registry.dart';
3
+ import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
4
+ import 'package:nomos_types/nomos_types.dart'
5
+ show NomosUnavailable, NomosUnavailableReason;
6
+ import 'package:test/test.dart';
7
+
8
+ void main() {
9
+ test(
10
+ 'watch is lazy and retains the initial result during registration',
11
+ () async {
12
+ final registry = NmsTypedWatchRegistry();
13
+ var starts = 0;
14
+ var stops = 0;
15
+ final watch = registry.open(
16
+ session: 'estate',
17
+ decode: (event) => event.resultUsda,
18
+ start: (id) async {
19
+ starts++;
20
+ registry.deliver(
21
+ protocol.WatchChanged(subscriptionId: id, resultUsda: [1]),
22
+ );
23
+ },
24
+ stop: (_) async {
25
+ stops++;
26
+ },
27
+ );
28
+ expect(starts, 0);
29
+ expect(await watch.stream.first, [1]);
30
+ await Future<void>.delayed(Duration.zero);
31
+ expect(starts, 1);
32
+ expect(stops, 1);
33
+ },
34
+ );
35
+
36
+ test(
37
+ 'cancel waits for registration before stopping the peer subscription',
38
+ () async {
39
+ final registry = NmsTypedWatchRegistry();
40
+ final registered = Completer<void>();
41
+ final order = <String>[];
42
+ final watch = registry.open(
43
+ session: 'estate',
44
+ decode: (event) => event.resultUsda,
45
+ start: (_) async {
46
+ order.add('start');
47
+ await registered.future;
48
+ order.add('registered');
49
+ },
50
+ stop: (_) async {
51
+ order.add('stop');
52
+ },
53
+ );
54
+ final sub = watch.stream.listen((_) {});
55
+ final canceled = sub.cancel();
56
+ expect(order, ['start']);
57
+ registered.complete();
58
+ await canceled;
59
+ expect(order, ['start', 'registered', 'stop']);
60
+ await registry.close();
61
+ await Future<void>.delayed(Duration.zero);
62
+ expect(order.where((e) => e == 'stop').length, 1);
63
+ },
64
+ );
65
+
66
+ test('paused listeners retain ordered observations', () async {
67
+ final registry = NmsTypedWatchRegistry();
68
+ final values = <List<int>>[];
69
+ final watch = registry.open(
70
+ session: 'estate',
71
+ decode: (event) => event.resultUsda,
72
+ start: (_) async {},
73
+ stop: (_) async {},
74
+ );
75
+ final sub = watch.stream.listen(values.add)..pause();
76
+ registry.deliver(
77
+ protocol.WatchChanged(subscriptionId: watch.id, resultUsda: [1]),
78
+ );
79
+ registry.deliver(
80
+ protocol.WatchChanged(subscriptionId: watch.id, resultUsda: [2]),
81
+ );
82
+ await Future<void>.delayed(Duration.zero);
83
+ expect(values, isEmpty);
84
+ sub.resume();
85
+ await Future<void>.delayed(Duration.zero);
86
+ expect(values, [
87
+ [1],
88
+ [2],
89
+ ]);
90
+ await sub.cancel();
91
+ });
92
+
93
+ test(
94
+ 'a stream retained past Reception close cannot reopen a watch',
95
+ () async {
96
+ final registry = NmsTypedWatchRegistry();
97
+ var starts = 0;
98
+ var stops = 0;
99
+ final watch = registry.open(
100
+ session: 'estate',
101
+ decode: (event) => event.resultUsda,
102
+ start: (_) async {
103
+ starts++;
104
+ },
105
+ stop: (_) async {
106
+ stops++;
107
+ },
108
+ );
109
+ await registry.close();
110
+ await expectLater(watch.stream.first, throwsStateError);
111
+ expect(starts, 0);
112
+ expect(stops, 0);
113
+ expect(
114
+ () => registry.open(
115
+ session: 'late',
116
+ decode: (event) => event.resultUsda,
117
+ start: (_) async {},
118
+ stop: (_) async {},
119
+ ),
120
+ throwsStateError,
121
+ );
122
+ },
123
+ );
124
+
125
+ test(
126
+ 'Reception close releases a paused watch locally without a per-watch stop',
127
+ () async {
128
+ // The reception closes the realm right after the registry; that one round
129
+ // trip releases every peer subscription. A per-watch unwatch at close
130
+ // would race the transport teardown (a registration still in flight
131
+ // completes only when the transport is disposed), so none is issued —
132
+ // not at close, and not when the retained listener cancels later.
133
+ final registry = NmsTypedWatchRegistry();
134
+ var stops = 0;
135
+ var done = false;
136
+ final watch = registry.open(
137
+ session: 'estate',
138
+ decode: (event) => event.resultUsda,
139
+ start: (_) async {},
140
+ stop: (_) async {
141
+ stops++;
142
+ },
143
+ );
144
+ final sub = watch.stream.listen((_) {}, onDone: () => done = true)
145
+ ..pause();
146
+ await registry.close();
147
+ await registry.close();
148
+ await Future<void>.delayed(Duration.zero);
149
+ expect(stops, 0);
150
+ await sub.cancel();
151
+ await Future<void>.delayed(Duration.zero);
152
+ expect(stops, 0);
153
+ expect(
154
+ done,
155
+ isFalse,
156
+ reason: 'a paused single-subscription stream cannot deliver done',
157
+ );
158
+ },
159
+ );
160
+
161
+ test('registration failure is delivered to the listener', () async {
162
+ final registry = NmsTypedWatchRegistry();
163
+ final watch = registry.open(
164
+ session: 'estate',
165
+ decode: (event) => event.resultUsda,
166
+ start: (_) async => throw StateError('refused'),
167
+ stop: (_) async {},
168
+ );
169
+ await expectLater(watch.stream.first, throwsStateError);
170
+ await registry.close();
171
+ });
172
+
173
+ test(
174
+ 'a replica that is not ready yet delays registration, never ends the stream',
175
+ () async {
176
+ final registry = NmsTypedWatchRegistry();
177
+ var starts = 0;
178
+ final watch = registry.open(
179
+ session: 'estate',
180
+ decode: (event) => event.resultUsda,
181
+ start: (id) async {
182
+ if (++starts < 3) {
183
+ throw const NomosUnavailable(
184
+ reason: NomosUnavailableReason.localReplicaNotReady,
185
+ workspace: 'estate',
186
+ retryable: true,
187
+ retryAfter: Duration(milliseconds: 1),
188
+ );
189
+ }
190
+ registry.deliver(
191
+ protocol.WatchChanged(subscriptionId: id, resultUsda: [7]),
192
+ );
193
+ },
194
+ stop: (_) async {},
195
+ );
196
+ expect(await watch.stream.first, [7]);
197
+ expect(starts, 3);
198
+ },
199
+ );
200
+
201
+ test('a watch waiting for its copy registers the moment it syncs', () async {
202
+ final synced = StreamController<String>.broadcast();
203
+ final registry = NmsTypedWatchRegistry(synced: synced.stream);
204
+ final firstRefused = Completer<void>();
205
+ var starts = 0;
206
+ final watch = registry.open(
207
+ session: 'estate',
208
+ decode: (event) => event.resultUsda,
209
+ start: (id) async {
210
+ if (++starts == 1) {
211
+ firstRefused.complete();
212
+ // Only the sync event can end a pause this long.
213
+ throw const NomosUnavailable(
214
+ reason: NomosUnavailableReason.localReplicaNotReady,
215
+ workspace: 'estate',
216
+ retryable: true,
217
+ retryAfter: Duration(minutes: 1),
218
+ );
219
+ }
220
+ registry.deliver(
221
+ protocol.WatchChanged(subscriptionId: id, resultUsda: [7]),
222
+ );
223
+ },
224
+ stop: (_) async {},
225
+ );
226
+ final first = watch.stream.first;
227
+ await firstRefused.future;
228
+ await pumpEventQueue();
229
+ synced.add('estate');
230
+ expect(await first.timeout(const Duration(seconds: 1)), [7]);
231
+ expect(starts, 2);
232
+ await synced.close();
233
+ });
234
+
235
+ test('a refusal that is not retryable still ends the stream', () async {
236
+ final registry = NmsTypedWatchRegistry();
237
+ final watch = registry.open(
238
+ session: 'estate',
239
+ decode: (event) => event.resultUsda,
240
+ start: (_) async => throw const NomosUnavailable(
241
+ reason: NomosUnavailableReason.remoteOnlyOffline,
242
+ workspace: 'estate',
243
+ retryable: false,
244
+ ),
245
+ stop: (_) async {},
246
+ );
247
+ await expectLater(watch.stream.first, throwsA(isA<NomosUnavailable>()));
248
+ });
249
+ }
@@ -0,0 +1,18 @@
1
+ # Nomos reference checks
2
+
3
+ Add `nomos_lints` and `custom_lint` as development dependencies and enable the
4
+ `custom_lint` analyzer plugin. An existing lint plugin can instead register
5
+ `NomosReferenceIdentityRule` in its rule list (as CO2's `layer_lints` does).
6
+
7
+ The `nomos_reference_identity` rule supplements generated argument types where
8
+ Dart accepts `Object`: equality and map indexing. Keep the complete typed
9
+ reference for identity; use `.id` for display and `.address` for serialization.
10
+
11
+ Run `dart run custom_lint` in CI as well as using the IDE diagnostics. The
12
+ default command treats findings as failures. The rule recognizes Nomos's actual
13
+ reference type, not unrelated classes that happen to have the same name.
14
+
15
+ The focused test uses the Dart analyzer on deliberately invalid consumers:
16
+ wrong aggregate and raw string arguments fail compilation; erased comparisons
17
+ and map indices produce the additional lint. This is not a whole-program
18
+ taint analysis of arbitrary intermediate strings.
@@ -0,0 +1,70 @@
1
+ import 'package:analyzer/dart/ast/ast.dart';
2
+ import 'package:analyzer/dart/element/type.dart';
3
+ import 'package:analyzer/error/listener.dart';
4
+ import 'package:custom_lint_builder/custom_lint_builder.dart';
5
+
6
+ PluginBase createPlugin() => _NomosLints();
7
+
8
+ class _NomosLints extends PluginBase {
9
+ @override
10
+ List<LintRule> getLintRules(CustomLintConfigs configs) =>
11
+ [NomosReferenceIdentityRule()];
12
+ }
13
+
14
+ /// Dart equality and Map.operator[] accept Object, so generic parameter types
15
+ /// alone cannot catch identity erased into a string at these two boundaries.
16
+ class NomosReferenceIdentityRule extends DartLintRule {
17
+ NomosReferenceIdentityRule() : super(code: _code);
18
+ static const _code = LintCode(
19
+ name: 'nomos_reference_identity',
20
+ problemMessage:
21
+ 'Keep the NomosRef when comparing or indexing record identity. '
22
+ 'An extracted id loses its workspace and aggregate type.',
23
+ );
24
+
25
+ @override
26
+ void run(CustomLintResolver resolver, ErrorReporter reporter,
27
+ CustomLintContext context) {
28
+ context.registry.addBinaryExpression((node) {
29
+ if (violatesReferenceEquality(node)) reporter.atNode(node, _code);
30
+ });
31
+ context.registry.addIndexExpression((node) {
32
+ if (extractsReferenceId(node.index)) reporter.atNode(node.index, _code);
33
+ });
34
+ }
35
+ }
36
+
37
+ bool _isReference(DartType? type) =>
38
+ type is InterfaceType &&
39
+ // Analyzer 7.6 deprecates the old element API in favor of this model.
40
+ // ignore: experimental_member_use
41
+ type.element3.name3 == 'NomosRef' &&
42
+ // ignore: experimental_member_use
43
+ type.element3.library2.uri.toString().startsWith('package:nomos_types/');
44
+
45
+ bool extractsReferenceId(Expression expression) {
46
+ if (expression is ParenthesizedExpression)
47
+ return extractsReferenceId(expression.expression);
48
+ if (expression is PropertyAccess) {
49
+ return expression.propertyName.name == 'id' &&
50
+ _isReference(expression.realTarget.staticType);
51
+ }
52
+ return expression is PrefixedIdentifier &&
53
+ expression.identifier.name == 'id' &&
54
+ _isReference(expression.prefix.staticType);
55
+ }
56
+
57
+ bool violatesReferenceEquality(BinaryExpression expression) {
58
+ if (expression.operator.lexeme != '==' && expression.operator.lexeme != '!=')
59
+ return false;
60
+ final left = expression.leftOperand, right = expression.rightOperand;
61
+ if (left is NullLiteral || right is NullLiteral) return false;
62
+ if (extractsReferenceId(left) || extractsReferenceId(right)) return true;
63
+ if (_isReference(left.staticType) && _isReference(right.staticType)) {
64
+ // Different aggregate parameters should never be reconciled by Object.==.
65
+ return left.staticType != right.staticType &&
66
+ (left.staticType as InterfaceType).typeArguments.single !=
67
+ (right.staticType as InterfaceType).typeArguments.single;
68
+ }
69
+ return _isReference(left.staticType) != _isReference(right.staticType);
70
+ }