@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.
- package/assets/operation-contract.wasm +0 -0
- package/check_package.mjs +12 -15
- package/compile_package.mjs +17 -18
- package/compile_proof.mjs +16 -1
- package/dart/CHANGELOG.md +76 -1
- package/dart/README.md +25 -2
- package/dart/lib/src/admission_decision.dart +41 -0
- package/dart/lib/src/application_framework_calls.dart +52 -2
- package/dart/lib/src/application_model_adoption.dart +187 -0
- package/dart/lib/src/application_status.dart +73 -0
- package/dart/lib/src/console_enrolment.dart +271 -0
- package/dart/lib/src/console_http_io.dart +26 -0
- package/dart/lib/src/console_http_stub.dart +7 -0
- package/dart/lib/src/dev_cloud.dart +69 -22
- package/dart/lib/src/generated_call.dart +83 -23
- package/dart/lib/src/kernel_primitives.dart +119 -0
- package/dart/lib/src/nms_conformance_runtime.dart +1 -1
- package/dart/lib/src/nms_generated_runtime.dart +32 -10
- package/dart/lib/src/nms_test_environment.dart +1 -1
- package/dart/lib/src/node_runner_transport.dart +41 -46
- package/dart/lib/src/observed_session_directory.dart +70 -0
- package/dart/lib/src/operation_receipts.dart +112 -0
- package/dart/lib/src/peer_application_streams.dart +103 -0
- package/dart/lib/src/reception/dart_reception.dart +4280 -0
- package/dart/lib/src/reception/event_desk.dart +211 -0
- package/dart/lib/src/reception/extremum_read.dart +46 -0
- package/dart/lib/src/reception/local_copies.dart +371 -0
- package/dart/lib/src/reception/peer_office_connection.dart +11 -0
- package/dart/lib/src/reception/progress_desk.dart +267 -0
- package/dart/lib/src/reception/reception_contract.dart +11 -0
- package/dart/lib/src/reception/reception_session.dart +284 -0
- package/dart/lib/src/reception/sync_diagnostics.dart +26 -0
- package/dart/lib/src/reception/workspace_preparation_report.dart +126 -0
- package/dart/lib/src/replica_readiness.dart +123 -0
- package/dart/lib/src/runner/node-entry.mjs +23905 -15774
- package/dart/lib/src/runtime_v2_gateway.dart +56 -3
- package/dart/lib/src/runtime_v2_transport.dart +544 -3
- package/dart/lib/src/session_bound.dart +200 -311
- package/dart/lib/src/signing_identity_kernel.dart +84 -0
- package/dart/lib/src/sync_status_projection.dart +548 -0
- package/dart/lib/src/sync_status_reducer.dart +170 -0
- package/dart/lib/src/test_harness.dart +809 -1153
- package/dart/lib/src/testing/test_custody.dart +66 -0
- package/dart/lib/src/testing/test_peer.dart +328 -0
- package/dart/lib/src/typed_watch_registry.dart +169 -0
- package/dart/lib/src/workspace_open_progress.dart +27 -0
- package/dart/pubspec.lock +24 -1
- package/dart/pubspec.yaml +7 -2
- package/dart/pubspec_overrides.yaml +2 -0
- package/dart/test/admission_decision_test.dart +48 -0
- package/dart/test/background_model_adoption_test.dart +228 -0
- package/dart/test/bundle_version_pairing_test.dart +4 -4
- package/dart/test/child_authentication_repair_test.dart +359 -0
- package/dart/test/cloud_bound_local_shell_test.dart +119 -0
- package/dart/test/console_enrolment_refusal_test.dart +52 -0
- package/dart/test/console_enrolment_test.dart +227 -0
- package/dart/test/errors_test.dart +5 -5
- package/dart/test/event_plane_seam_test.dart +50 -164
- package/dart/test/extremum_read_test.dart +58 -0
- package/dart/test/fixtures/wedge/wedge-entry.mjs +3 -21
- package/dart/test/generated_query_test.dart +32 -0
- package/dart/test/harness_phase_timeout_test.dart +18 -23
- package/dart/test/kernel_author_after_read_test.dart +242 -0
- package/dart/test/kernel_fence_test.dart +137 -0
- package/dart/test/local_copies_test.dart +222 -0
- package/dart/test/model_adoption_test.dart +198 -0
- package/dart/test/node_peer_local_custody_test.dart +153 -0
- package/dart/test/offer_authentication_proof_test.dart +352 -52
- package/dart/test/operation_compatibility_test.dart +151 -0
- package/dart/test/platform_credential_effect_test.dart +386 -0
- package/dart/test/receiving_group_open_test.dart +213 -0
- package/dart/test/reception/workspace_preparation_report_test.dart +103 -0
- package/dart/test/reception_span_trace_test.dart +23 -0
- package/dart/test/replica_readiness_test.dart +156 -0
- package/dart/test/runtime_projections_test.dart +92 -0
- package/dart/test/runtime_v2_gateway_test.dart +131 -0
- package/dart/test/signing_identity_kernel_test.dart +75 -0
- package/dart/test/sync_status_preparation_report_test.dart +29 -0
- package/dart/test/sync_status_reducer_test.dart +419 -0
- package/dart/test/test_harness_test.dart +71 -95
- package/dart/test/typed_query_watch_test.dart +170 -0
- package/dart/test/typed_watch_registry_test.dart +249 -0
- package/dart_lints/README.md +18 -0
- package/dart_lints/lib/nomos_lints.dart +70 -0
- package/dart_lints/pubspec.lock +580 -0
- package/dart_lints/pubspec.yaml +12 -0
- package/dart_lints/test/reference_identity_test.dart +75 -0
- package/dart_types/CHANGELOG.md +18 -0
- package/dart_types/lib/nomos_kernel_protocol.dart +21 -1
- package/dart_types/lib/nomos_types.dart +9 -1
- package/dart_types/lib/src/admission_decision.dart +37 -0
- package/dart_types/lib/src/aggregate_meta.dart +1 -1
- package/dart_types/lib/src/approved_model.dart +65 -0
- package/dart_types/lib/src/custody.dart +10 -18
- package/dart_types/lib/src/error_info.dart +4 -4
- package/dart_types/lib/src/errors.dart +29 -29
- package/dart_types/lib/src/foreign_ref.dart +62 -33
- package/dart_types/lib/src/local_work_rejected.dart +3 -3
- package/dart_types/lib/src/nomos_ref.dart +3 -1
- package/dart_types/lib/src/principal.dart +2 -2
- package/dart_types/lib/src/proto/google/protobuf/any.pb.dart +163 -0
- package/dart_types/lib/src/proto/google/protobuf/any.pbenum.dart +11 -0
- package/dart_types/lib/src/proto/nomos/discovery/v1/workspace_placement.pb.dart +137 -0
- package/dart_types/lib/src/proto/nomos/discovery/v1/workspace_placement.pbenum.dart +11 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +20721 -10433
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +555 -27
- package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pb.dart +387 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pbenum.dart +46 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/application_readiness_desk.pb.dart +295 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/application_readiness_desk.pbenum.dart +139 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/authentication_desk.pb.dart +269 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/authentication_desk.pbenum.dart +103 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/compatibility_desk.pb.dart +417 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/compatibility_desk.pbenum.dart +62 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +9968 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pbenum.dart +133 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/credentials_desk.pb.dart +204 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/credentials_desk.pbenum.dart +54 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/custody_desk.pb.dart +1550 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/custody_desk.pbenum.dart +11 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/framework.pb.dart +710 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/framework.pbenum.dart +11 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/identity_desk.pb.dart +845 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/identity_desk.pbenum.dart +11 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/interaction_desk.pb.dart +198 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/interaction_desk.pbenum.dart +11 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/law_desk.pb.dart +369 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/law_desk.pbenum.dart +11 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/maintenance_desk.pb.dart +609 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/maintenance_desk.pbenum.dart +65 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +358 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pbenum.dart +100 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pb.dart +2231 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pbenum.dart +271 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/placement_desk.pb.dart +1286 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/placement_desk.pbenum.dart +11 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pb.dart +636 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pbenum.dart +116 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pb.dart +1637 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pbenum.dart +175 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/recovery_desk.pb.dart +1269 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/recovery_desk.pbenum.dart +129 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pb.dart +2782 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pbenum.dart +97 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +2232 -8627
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pbenum.dart +0 -358
- package/dart_types/lib/src/proto/nomos/runtime/v2/sealing_desk.pb.dart +1639 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/sealing_desk.pbenum.dart +46 -0
- package/dart_types/lib/src/runtime_outcome.dart +133 -0
- package/dart_types/lib/src/subscriptions.dart +116 -23
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/extremum_subscriptions_test.dart +40 -0
- package/dart_types/test/foreign_ref_resolution_test.dart +75 -0
- package/dart_types/test/nomos_ref_address_test.dart +25 -0
- package/dart_types/test/ordered_subscriptions_test.dart +34 -0
- package/operation_contract_check.mjs +24 -0
- package/package.json +11 -6
- package/src/aggregate.ts +70 -10
- package/src/application_topology.ts +18 -11
- package/src/attested.ts +3 -0
- package/src/attested_core.ts +46 -3
- package/src/birth_meter_ensure.ts +19 -7
- package/src/births.ts +29 -4
- package/src/build_package.ts +140 -13
- package/src/business_birth.ts +181 -4
- package/src/codegen_call_proto.ts +1274 -0
- package/src/codegen_call_usda.ts +49 -6
- package/src/codegen_dart.ts +79 -44
- package/src/codegen_dart_client.ts +458 -800
- package/src/{codegen_dart_application.ts → codegen_dart_facade.ts} +331 -582
- package/src/codegen_dart_framework_calls.ts +320 -37
- package/src/codegen_dart_indexed_variant.ts +159 -0
- package/src/codegen_dart_scenario.ts +42 -13
- package/src/codegen_proof.ts +3 -3
- package/src/codegen_provider_dart.ts +5 -0
- package/src/codegen_query_proto.ts +158 -0
- package/src/codegen_refusal.ts +189 -0
- package/src/codegen_result_proto.ts +931 -0
- package/src/codegen_result_usda.ts +4 -0
- package/src/codegen_ts.ts +464 -616
- package/src/codegen_workspace_access.ts +105 -0
- package/src/compatibility_graph.ts +759 -0
- package/src/compatibility_usda.ts +264 -0
- package/src/compile_package_main.ts +1994 -1189
- package/src/custody_placement.ts +156 -0
- package/src/custody_placement_usda.ts +91 -0
- package/src/decl_shapes.ts +21 -2
- package/src/describe_model.ts +1 -0
- package/src/directive.ts +59 -2
- package/src/drivers.ts +51 -0
- package/src/engine_entry.ts +52 -8
- package/src/evolve_preview.ts +11 -4
- package/src/exists.ts +2 -0
- package/src/fields.ts +75 -11
- package/src/flow_gate.ts +2 -1
- package/src/framework/application_home.ts +170 -18
- package/src/framework/attested_receipts.ts +8 -3
- package/src/framework/auth_provider_law.ts +169 -9
- package/src/framework/birth_certificate_law.ts +4 -0
- package/src/framework/capability.ts +34 -3
- package/src/framework/child_authentication.ts +346 -0
- package/src/framework/console_governance.ts +295 -0
- package/src/framework/delegated_roles.ts +301 -0
- package/src/framework/domain_lifecycle.ts +26 -13
- package/src/framework/founding_genesis.ts +193 -0
- package/src/framework/framework_aggregates.ts +1 -1
- package/src/framework/genesis.ts +6 -1
- package/src/framework/governance_relations.ts +19 -0
- package/src/framework/home.ts +5 -734
- package/src/framework/legacy/child_auth_provider_epoch.ts +73 -0
- package/src/framework/model_authority_repair.ts +116 -0
- package/src/framework/model_authority_seed.ts +185 -0
- package/src/framework/model_transition.ts +211 -0
- package/src/framework/owned_custody.ts +1 -1
- package/src/framework/principal_enrollment.ts +302 -0
- package/src/framework/receiving_group.ts +857 -0
- package/src/framework/receiving_group_recovery.ts +314 -0
- package/src/framework/relations.ts +4 -8
- package/src/framework/signing_identity.ts +242 -95
- package/src/framework/source_roster.ts +11 -5
- package/src/framework/workspace_access.ts +149 -0
- package/src/framework/workspace_invariant.ts +73 -52
- package/src/framework/workspaces.ts +407 -40
- package/src/framework_call_definitions.ts +32 -3
- package/src/generated_client_bundle_boundary.ts +97 -0
- package/src/generated_client_names.ts +1 -0
- package/src/identity_usda.ts +27 -0
- package/src/index.ts +183 -12
- package/src/indexed_variant.ts +83 -0
- package/src/intent.ts +173 -45
- package/src/law_check.ts +8 -5
- package/src/law_dependency_index.ts +231 -0
- package/src/law_graph.ts +508 -71
- package/src/law_graph_usda.ts +170 -24
- package/src/law_identity.ts +119 -81
- package/src/law_source_map.ts +4 -1
- package/src/law_units.ts +498 -0
- package/src/law_value_type.ts +7 -3
- package/src/member_authentication.ts +302 -0
- package/src/null_meta.ts +1 -1
- package/src/openusd_compiled_layer.ts +132 -0
- package/src/operation_contract_check.ts +31 -0
- package/src/ordered_read.ts +2 -1
- package/src/package_config.ts +33 -12
- package/src/package_dependencies.ts +1 -0
- package/src/plan_source_slice.ts +30 -1
- package/src/protobuf_field_number.ts +36 -0
- package/src/provenance_wire.ts +6 -0
- package/src/query.ts +2 -1
- package/src/read.ts +50 -0
- package/src/relation.ts +4 -1
- package/src/relation_schema.ts +15 -2
- package/src/report.ts +2 -2
- package/src/sqlite_computation_bundle.ts +78 -0
- package/src/sqlite_computed.ts +102 -0
- package/src/sqlite_counts.ts +154 -0
- package/src/sqlite_layout.ts +823 -0
- package/src/sqlite_reads.ts +275 -0
- package/src/sqlite_samples.ts +185 -0
- package/src/stable_id_identity_bridge.ts +1 -1
- package/src/stable_ids_for_module.ts +16 -4
- package/src/test_call_usda.ts +6 -6
- package/src/usda_relationship_integrity.ts +20 -3
- package/src/usda_text.ts +21 -1
- package/src/value_object.ts +81 -11
- package/src/wire.ts +22 -0
- package/src/wire_encode.ts +227 -20
- package/src/workspace_sharding.ts +15 -6
- package/src/workspace_sharing.ts +51 -49
- package/src/zod_introspection.ts +2 -0
- package/compat_package.mjs +0 -46
- package/dart/lib/src/home.dart +0 -15
- package/dart/lib/src/runner/offline-install.mjs +0 -15367
- package/dart/lib/src/source_trust.dart +0 -51
- package/dart/lib/src/webview_bridge.dart +0 -6540
- package/dart/lib/src/workspace_name.dart +0 -30
- package/dart/test/acquire_session_credentials_test.dart +0 -150
- package/dart/test/acquire_session_test.dart +0 -534
- package/dart/test/adv_realm_interference_test.dart +0 -600
- package/dart/test/cloud_bound_topology_test.dart +0 -168
- package/dart/test/cluster_births_test.dart +0 -202
- package/dart/test/cluster_device_keyed_acceptance_test.dart +0 -105
- package/dart/test/cluster_local_autosync_test.dart +0 -123
- package/dart/test/cluster_scale_test.dart +0 -122
- package/dart/test/compat_test.dart +0 -61
- package/dart/test/dlq_session_surface_test.dart +0 -222
- package/dart/test/first_app_gauntlet_test.dart +0 -386
- package/dart/test/fixtures/wedge/fake-install.mjs +0 -11
- package/dart/test/four_layer_nested_topology_test.dart +0 -290
- package/dart/test/home_parity_test.dart +0 -13
- package/dart/test/local_sync_inert_test.dart +0 -107
- package/dart/test/node_runner_transport_test.dart +0 -78
- package/dart/test/parent_signer_recovery_test.dart +0 -155
- package/dart/test/residency_refusal_test.dart +0 -49
- package/dart/test/sealing_client_test.dart +0 -181
- package/dart/test/session_bound_test.dart +0 -285
- package/dart/test/stale_bundle_test.dart +0 -80
- package/dart/test/vo_read_typing_test.dart +0 -131
- package/dart/test/watch_after_offer_stall_test.dart +0 -100
- package/dart/test/webview_bridge_test.dart +0 -1930
- package/dart/test/workspace_name_test.dart +0 -24
- package/runtime/generated-application-host.js +0 -22860
- package/src/codegen_query_usda.ts +0 -214
- package/src/compat_manifest.ts +0 -26
- package/src/compat_package_main.ts +0 -224
- package/src/shape_compat.ts +0 -352
- 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
|
+
}
|