@githolon/dsl 0.108.7 → 0.110.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. package/assets/operation-contract.wasm +0 -0
  2. package/check_package.mjs +12 -15
  3. package/compile_package.mjs +17 -18
  4. package/compile_proof.mjs +16 -1
  5. package/dart/CHANGELOG.md +76 -1
  6. package/dart/README.md +25 -2
  7. package/dart/lib/src/admission_decision.dart +41 -0
  8. package/dart/lib/src/application_framework_calls.dart +52 -2
  9. package/dart/lib/src/application_model_adoption.dart +187 -0
  10. package/dart/lib/src/application_status.dart +73 -0
  11. package/dart/lib/src/console_enrolment.dart +271 -0
  12. package/dart/lib/src/console_http_io.dart +26 -0
  13. package/dart/lib/src/console_http_stub.dart +7 -0
  14. package/dart/lib/src/dev_cloud.dart +69 -22
  15. package/dart/lib/src/generated_call.dart +83 -23
  16. package/dart/lib/src/kernel_primitives.dart +119 -0
  17. package/dart/lib/src/nms_conformance_runtime.dart +1 -1
  18. package/dart/lib/src/nms_generated_runtime.dart +32 -10
  19. package/dart/lib/src/nms_test_environment.dart +1 -1
  20. package/dart/lib/src/node_runner_transport.dart +41 -46
  21. package/dart/lib/src/observed_session_directory.dart +70 -0
  22. package/dart/lib/src/operation_receipts.dart +112 -0
  23. package/dart/lib/src/peer_application_streams.dart +103 -0
  24. package/dart/lib/src/reception/dart_reception.dart +4280 -0
  25. package/dart/lib/src/reception/event_desk.dart +211 -0
  26. package/dart/lib/src/reception/extremum_read.dart +46 -0
  27. package/dart/lib/src/reception/local_copies.dart +371 -0
  28. package/dart/lib/src/reception/peer_office_connection.dart +11 -0
  29. package/dart/lib/src/reception/progress_desk.dart +267 -0
  30. package/dart/lib/src/reception/reception_contract.dart +11 -0
  31. package/dart/lib/src/reception/reception_session.dart +284 -0
  32. package/dart/lib/src/reception/sync_diagnostics.dart +26 -0
  33. package/dart/lib/src/reception/workspace_preparation_report.dart +126 -0
  34. package/dart/lib/src/replica_readiness.dart +123 -0
  35. package/dart/lib/src/runner/node-entry.mjs +23905 -15774
  36. package/dart/lib/src/runtime_v2_gateway.dart +56 -3
  37. package/dart/lib/src/runtime_v2_transport.dart +544 -3
  38. package/dart/lib/src/session_bound.dart +200 -311
  39. package/dart/lib/src/signing_identity_kernel.dart +84 -0
  40. package/dart/lib/src/sync_status_projection.dart +548 -0
  41. package/dart/lib/src/sync_status_reducer.dart +170 -0
  42. package/dart/lib/src/test_harness.dart +809 -1153
  43. package/dart/lib/src/testing/test_custody.dart +66 -0
  44. package/dart/lib/src/testing/test_peer.dart +328 -0
  45. package/dart/lib/src/typed_watch_registry.dart +169 -0
  46. package/dart/lib/src/workspace_open_progress.dart +27 -0
  47. package/dart/pubspec.lock +24 -1
  48. package/dart/pubspec.yaml +7 -2
  49. package/dart/pubspec_overrides.yaml +2 -0
  50. package/dart/test/admission_decision_test.dart +48 -0
  51. package/dart/test/background_model_adoption_test.dart +228 -0
  52. package/dart/test/bundle_version_pairing_test.dart +4 -4
  53. package/dart/test/child_authentication_repair_test.dart +359 -0
  54. package/dart/test/cloud_bound_local_shell_test.dart +119 -0
  55. package/dart/test/console_enrolment_refusal_test.dart +52 -0
  56. package/dart/test/console_enrolment_test.dart +227 -0
  57. package/dart/test/errors_test.dart +5 -5
  58. package/dart/test/event_plane_seam_test.dart +50 -164
  59. package/dart/test/extremum_read_test.dart +58 -0
  60. package/dart/test/fixtures/wedge/wedge-entry.mjs +3 -21
  61. package/dart/test/generated_query_test.dart +32 -0
  62. package/dart/test/harness_phase_timeout_test.dart +18 -23
  63. package/dart/test/kernel_author_after_read_test.dart +242 -0
  64. package/dart/test/kernel_fence_test.dart +137 -0
  65. package/dart/test/local_copies_test.dart +222 -0
  66. package/dart/test/model_adoption_test.dart +198 -0
  67. package/dart/test/node_peer_local_custody_test.dart +153 -0
  68. package/dart/test/offer_authentication_proof_test.dart +352 -52
  69. package/dart/test/operation_compatibility_test.dart +151 -0
  70. package/dart/test/platform_credential_effect_test.dart +386 -0
  71. package/dart/test/receiving_group_open_test.dart +213 -0
  72. package/dart/test/reception/workspace_preparation_report_test.dart +103 -0
  73. package/dart/test/reception_span_trace_test.dart +23 -0
  74. package/dart/test/replica_readiness_test.dart +156 -0
  75. package/dart/test/runtime_projections_test.dart +92 -0
  76. package/dart/test/runtime_v2_gateway_test.dart +131 -0
  77. package/dart/test/signing_identity_kernel_test.dart +75 -0
  78. package/dart/test/sync_status_preparation_report_test.dart +29 -0
  79. package/dart/test/sync_status_reducer_test.dart +419 -0
  80. package/dart/test/test_harness_test.dart +71 -95
  81. package/dart/test/typed_query_watch_test.dart +170 -0
  82. package/dart/test/typed_watch_registry_test.dart +249 -0
  83. package/dart_lints/README.md +18 -0
  84. package/dart_lints/lib/nomos_lints.dart +70 -0
  85. package/dart_lints/pubspec.lock +580 -0
  86. package/dart_lints/pubspec.yaml +12 -0
  87. package/dart_lints/test/reference_identity_test.dart +75 -0
  88. package/dart_types/CHANGELOG.md +18 -0
  89. package/dart_types/lib/nomos_kernel_protocol.dart +21 -1
  90. package/dart_types/lib/nomos_types.dart +9 -1
  91. package/dart_types/lib/src/admission_decision.dart +37 -0
  92. package/dart_types/lib/src/aggregate_meta.dart +1 -1
  93. package/dart_types/lib/src/approved_model.dart +65 -0
  94. package/dart_types/lib/src/custody.dart +10 -18
  95. package/dart_types/lib/src/error_info.dart +4 -4
  96. package/dart_types/lib/src/errors.dart +29 -29
  97. package/dart_types/lib/src/foreign_ref.dart +62 -33
  98. package/dart_types/lib/src/local_work_rejected.dart +3 -3
  99. package/dart_types/lib/src/nomos_ref.dart +3 -1
  100. package/dart_types/lib/src/principal.dart +2 -2
  101. package/dart_types/lib/src/proto/google/protobuf/any.pb.dart +163 -0
  102. package/dart_types/lib/src/proto/google/protobuf/any.pbenum.dart +11 -0
  103. package/dart_types/lib/src/proto/nomos/discovery/v1/workspace_placement.pb.dart +137 -0
  104. package/dart_types/lib/src/proto/nomos/discovery/v1/workspace_placement.pbenum.dart +11 -0
  105. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +20721 -10433
  106. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +555 -27
  107. package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pb.dart +387 -0
  108. package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pbenum.dart +46 -0
  109. package/dart_types/lib/src/proto/nomos/runtime/v2/application_readiness_desk.pb.dart +295 -0
  110. package/dart_types/lib/src/proto/nomos/runtime/v2/application_readiness_desk.pbenum.dart +139 -0
  111. package/dart_types/lib/src/proto/nomos/runtime/v2/authentication_desk.pb.dart +269 -0
  112. package/dart_types/lib/src/proto/nomos/runtime/v2/authentication_desk.pbenum.dart +103 -0
  113. package/dart_types/lib/src/proto/nomos/runtime/v2/compatibility_desk.pb.dart +417 -0
  114. package/dart_types/lib/src/proto/nomos/runtime/v2/compatibility_desk.pbenum.dart +62 -0
  115. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +9968 -0
  116. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pbenum.dart +133 -0
  117. package/dart_types/lib/src/proto/nomos/runtime/v2/credentials_desk.pb.dart +204 -0
  118. package/dart_types/lib/src/proto/nomos/runtime/v2/credentials_desk.pbenum.dart +54 -0
  119. package/dart_types/lib/src/proto/nomos/runtime/v2/custody_desk.pb.dart +1550 -0
  120. package/dart_types/lib/src/proto/nomos/runtime/v2/custody_desk.pbenum.dart +11 -0
  121. package/dart_types/lib/src/proto/nomos/runtime/v2/framework.pb.dart +710 -0
  122. package/dart_types/lib/src/proto/nomos/runtime/v2/framework.pbenum.dart +11 -0
  123. package/dart_types/lib/src/proto/nomos/runtime/v2/identity_desk.pb.dart +845 -0
  124. package/dart_types/lib/src/proto/nomos/runtime/v2/identity_desk.pbenum.dart +11 -0
  125. package/dart_types/lib/src/proto/nomos/runtime/v2/interaction_desk.pb.dart +198 -0
  126. package/dart_types/lib/src/proto/nomos/runtime/v2/interaction_desk.pbenum.dart +11 -0
  127. package/dart_types/lib/src/proto/nomos/runtime/v2/law_desk.pb.dart +369 -0
  128. package/dart_types/lib/src/proto/nomos/runtime/v2/law_desk.pbenum.dart +11 -0
  129. package/dart_types/lib/src/proto/nomos/runtime/v2/maintenance_desk.pb.dart +609 -0
  130. package/dart_types/lib/src/proto/nomos/runtime/v2/maintenance_desk.pbenum.dart +65 -0
  131. package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +358 -0
  132. package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pbenum.dart +100 -0
  133. package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pb.dart +2231 -0
  134. package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pbenum.dart +271 -0
  135. package/dart_types/lib/src/proto/nomos/runtime/v2/placement_desk.pb.dart +1286 -0
  136. package/dart_types/lib/src/proto/nomos/runtime/v2/placement_desk.pbenum.dart +11 -0
  137. package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pb.dart +636 -0
  138. package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pbenum.dart +116 -0
  139. package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pb.dart +1637 -0
  140. package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pbenum.dart +175 -0
  141. package/dart_types/lib/src/proto/nomos/runtime/v2/recovery_desk.pb.dart +1269 -0
  142. package/dart_types/lib/src/proto/nomos/runtime/v2/recovery_desk.pbenum.dart +129 -0
  143. package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pb.dart +2782 -0
  144. package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pbenum.dart +97 -0
  145. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +2232 -8627
  146. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pbenum.dart +0 -358
  147. package/dart_types/lib/src/proto/nomos/runtime/v2/sealing_desk.pb.dart +1639 -0
  148. package/dart_types/lib/src/proto/nomos/runtime/v2/sealing_desk.pbenum.dart +46 -0
  149. package/dart_types/lib/src/runtime_outcome.dart +133 -0
  150. package/dart_types/lib/src/subscriptions.dart +116 -23
  151. package/dart_types/pubspec.yaml +1 -1
  152. package/dart_types/test/extremum_subscriptions_test.dart +40 -0
  153. package/dart_types/test/foreign_ref_resolution_test.dart +75 -0
  154. package/dart_types/test/nomos_ref_address_test.dart +25 -0
  155. package/dart_types/test/ordered_subscriptions_test.dart +34 -0
  156. package/operation_contract_check.mjs +24 -0
  157. package/package.json +11 -6
  158. package/src/aggregate.ts +70 -10
  159. package/src/application_topology.ts +18 -11
  160. package/src/attested.ts +3 -0
  161. package/src/attested_core.ts +46 -3
  162. package/src/birth_meter_ensure.ts +19 -7
  163. package/src/births.ts +29 -4
  164. package/src/build_package.ts +140 -13
  165. package/src/business_birth.ts +181 -4
  166. package/src/codegen_call_proto.ts +1274 -0
  167. package/src/codegen_call_usda.ts +49 -6
  168. package/src/codegen_dart.ts +79 -44
  169. package/src/codegen_dart_client.ts +458 -800
  170. package/src/{codegen_dart_application.ts → codegen_dart_facade.ts} +331 -582
  171. package/src/codegen_dart_framework_calls.ts +320 -37
  172. package/src/codegen_dart_indexed_variant.ts +159 -0
  173. package/src/codegen_dart_scenario.ts +42 -13
  174. package/src/codegen_proof.ts +3 -3
  175. package/src/codegen_provider_dart.ts +5 -0
  176. package/src/codegen_query_proto.ts +158 -0
  177. package/src/codegen_refusal.ts +189 -0
  178. package/src/codegen_result_proto.ts +931 -0
  179. package/src/codegen_result_usda.ts +4 -0
  180. package/src/codegen_ts.ts +464 -616
  181. package/src/codegen_workspace_access.ts +105 -0
  182. package/src/compatibility_graph.ts +759 -0
  183. package/src/compatibility_usda.ts +264 -0
  184. package/src/compile_package_main.ts +1994 -1189
  185. package/src/custody_placement.ts +156 -0
  186. package/src/custody_placement_usda.ts +91 -0
  187. package/src/decl_shapes.ts +21 -2
  188. package/src/describe_model.ts +1 -0
  189. package/src/directive.ts +59 -2
  190. package/src/drivers.ts +51 -0
  191. package/src/engine_entry.ts +52 -8
  192. package/src/evolve_preview.ts +11 -4
  193. package/src/exists.ts +2 -0
  194. package/src/fields.ts +75 -11
  195. package/src/flow_gate.ts +2 -1
  196. package/src/framework/application_home.ts +170 -18
  197. package/src/framework/attested_receipts.ts +8 -3
  198. package/src/framework/auth_provider_law.ts +169 -9
  199. package/src/framework/birth_certificate_law.ts +4 -0
  200. package/src/framework/capability.ts +34 -3
  201. package/src/framework/child_authentication.ts +346 -0
  202. package/src/framework/console_governance.ts +295 -0
  203. package/src/framework/delegated_roles.ts +301 -0
  204. package/src/framework/domain_lifecycle.ts +26 -13
  205. package/src/framework/founding_genesis.ts +193 -0
  206. package/src/framework/framework_aggregates.ts +1 -1
  207. package/src/framework/genesis.ts +6 -1
  208. package/src/framework/governance_relations.ts +19 -0
  209. package/src/framework/home.ts +5 -734
  210. package/src/framework/legacy/child_auth_provider_epoch.ts +73 -0
  211. package/src/framework/model_authority_repair.ts +116 -0
  212. package/src/framework/model_authority_seed.ts +185 -0
  213. package/src/framework/model_transition.ts +211 -0
  214. package/src/framework/owned_custody.ts +1 -1
  215. package/src/framework/principal_enrollment.ts +302 -0
  216. package/src/framework/receiving_group.ts +857 -0
  217. package/src/framework/receiving_group_recovery.ts +314 -0
  218. package/src/framework/relations.ts +4 -8
  219. package/src/framework/signing_identity.ts +242 -95
  220. package/src/framework/source_roster.ts +11 -5
  221. package/src/framework/workspace_access.ts +149 -0
  222. package/src/framework/workspace_invariant.ts +73 -52
  223. package/src/framework/workspaces.ts +407 -40
  224. package/src/framework_call_definitions.ts +32 -3
  225. package/src/generated_client_bundle_boundary.ts +97 -0
  226. package/src/generated_client_names.ts +1 -0
  227. package/src/identity_usda.ts +27 -0
  228. package/src/index.ts +183 -12
  229. package/src/indexed_variant.ts +83 -0
  230. package/src/intent.ts +173 -45
  231. package/src/law_check.ts +8 -5
  232. package/src/law_dependency_index.ts +231 -0
  233. package/src/law_graph.ts +508 -71
  234. package/src/law_graph_usda.ts +170 -24
  235. package/src/law_identity.ts +119 -81
  236. package/src/law_source_map.ts +4 -1
  237. package/src/law_units.ts +498 -0
  238. package/src/law_value_type.ts +7 -3
  239. package/src/member_authentication.ts +302 -0
  240. package/src/null_meta.ts +1 -1
  241. package/src/openusd_compiled_layer.ts +132 -0
  242. package/src/operation_contract_check.ts +31 -0
  243. package/src/ordered_read.ts +2 -1
  244. package/src/package_config.ts +33 -12
  245. package/src/package_dependencies.ts +1 -0
  246. package/src/plan_source_slice.ts +30 -1
  247. package/src/protobuf_field_number.ts +36 -0
  248. package/src/provenance_wire.ts +6 -0
  249. package/src/query.ts +2 -1
  250. package/src/read.ts +50 -0
  251. package/src/relation.ts +4 -1
  252. package/src/relation_schema.ts +15 -2
  253. package/src/report.ts +2 -2
  254. package/src/sqlite_computation_bundle.ts +78 -0
  255. package/src/sqlite_computed.ts +102 -0
  256. package/src/sqlite_counts.ts +154 -0
  257. package/src/sqlite_layout.ts +823 -0
  258. package/src/sqlite_reads.ts +275 -0
  259. package/src/sqlite_samples.ts +185 -0
  260. package/src/stable_id_identity_bridge.ts +1 -1
  261. package/src/stable_ids_for_module.ts +16 -4
  262. package/src/test_call_usda.ts +6 -6
  263. package/src/usda_relationship_integrity.ts +20 -3
  264. package/src/usda_text.ts +21 -1
  265. package/src/value_object.ts +81 -11
  266. package/src/wire.ts +22 -0
  267. package/src/wire_encode.ts +227 -20
  268. package/src/workspace_sharding.ts +15 -6
  269. package/src/workspace_sharing.ts +51 -49
  270. package/src/zod_introspection.ts +2 -0
  271. package/compat_package.mjs +0 -46
  272. package/dart/lib/src/home.dart +0 -15
  273. package/dart/lib/src/runner/offline-install.mjs +0 -15367
  274. package/dart/lib/src/source_trust.dart +0 -51
  275. package/dart/lib/src/webview_bridge.dart +0 -6540
  276. package/dart/lib/src/workspace_name.dart +0 -30
  277. package/dart/test/acquire_session_credentials_test.dart +0 -150
  278. package/dart/test/acquire_session_test.dart +0 -534
  279. package/dart/test/adv_realm_interference_test.dart +0 -600
  280. package/dart/test/cloud_bound_topology_test.dart +0 -168
  281. package/dart/test/cluster_births_test.dart +0 -202
  282. package/dart/test/cluster_device_keyed_acceptance_test.dart +0 -105
  283. package/dart/test/cluster_local_autosync_test.dart +0 -123
  284. package/dart/test/cluster_scale_test.dart +0 -122
  285. package/dart/test/compat_test.dart +0 -61
  286. package/dart/test/dlq_session_surface_test.dart +0 -222
  287. package/dart/test/first_app_gauntlet_test.dart +0 -386
  288. package/dart/test/fixtures/wedge/fake-install.mjs +0 -11
  289. package/dart/test/four_layer_nested_topology_test.dart +0 -290
  290. package/dart/test/home_parity_test.dart +0 -13
  291. package/dart/test/local_sync_inert_test.dart +0 -107
  292. package/dart/test/node_runner_transport_test.dart +0 -78
  293. package/dart/test/parent_signer_recovery_test.dart +0 -155
  294. package/dart/test/residency_refusal_test.dart +0 -49
  295. package/dart/test/sealing_client_test.dart +0 -181
  296. package/dart/test/session_bound_test.dart +0 -285
  297. package/dart/test/stale_bundle_test.dart +0 -80
  298. package/dart/test/vo_read_typing_test.dart +0 -131
  299. package/dart/test/watch_after_offer_stall_test.dart +0 -100
  300. package/dart/test/webview_bridge_test.dart +0 -1930
  301. package/dart/test/workspace_name_test.dart +0 -24
  302. package/runtime/generated-application-host.js +0 -22860
  303. package/src/codegen_query_usda.ts +0 -214
  304. package/src/compat_manifest.ts +0 -26
  305. package/src/compat_package_main.ts +0 -224
  306. package/src/shape_compat.ts +0 -352
  307. package/src/usd_state.ts +0 -505
@@ -0,0 +1,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
+ }