@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,386 @@
|
|
|
1
|
+
import 'dart:async';
|
|
2
|
+
import 'dart:convert';
|
|
3
|
+
import 'dart:typed_data';
|
|
4
|
+
|
|
5
|
+
import 'package:fixnum/fixnum.dart';
|
|
6
|
+
import 'package:crypto/crypto.dart' show sha256;
|
|
7
|
+
import 'package:nomos_client/src/runtime_v2_transport.dart';
|
|
8
|
+
import 'package:nomos_peer_host/nomos_peer_host.dart'
|
|
9
|
+
show NomosDeviceIdentityStore, NomosProviderCredentialStore;
|
|
10
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as p;
|
|
11
|
+
import 'package:test/test.dart';
|
|
12
|
+
|
|
13
|
+
class _Platform
|
|
14
|
+
with NmsPlatformCredentialProviderHost
|
|
15
|
+
implements NomosRuntimeV2Transport {
|
|
16
|
+
final requests = <p.RuntimeRequest>[];
|
|
17
|
+
int sequence = 40;
|
|
18
|
+
@override
|
|
19
|
+
int nextRuntimeRequestId() => ++sequence;
|
|
20
|
+
@override
|
|
21
|
+
Future<p.RuntimeResponse> invokeRuntime(p.RuntimeRequest request) async {
|
|
22
|
+
requests.add(request);
|
|
23
|
+
return p.RuntimeResponse(
|
|
24
|
+
protocolVersion: 2,
|
|
25
|
+
requestId: request.requestId,
|
|
26
|
+
credentialsRefreshed: p.CredentialsRefreshed(session: 'home'));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
p.CorrespondencePending pending() => p.CorrespondencePending(
|
|
31
|
+
workId: 'work',
|
|
32
|
+
refreshCredential: p.PeerCredentialRefreshEffect(
|
|
33
|
+
effectId: 'effect', capability: 'opaque-capability'));
|
|
34
|
+
|
|
35
|
+
class _MemoryIdentityStore implements NomosDeviceIdentityStore {
|
|
36
|
+
final Map<String, Uint8List> saved = {};
|
|
37
|
+
final List<String> log = [];
|
|
38
|
+
@override
|
|
39
|
+
Future<Uint8List?> load(String identity) async {
|
|
40
|
+
log.add('load:$identity');
|
|
41
|
+
return saved[identity];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@override
|
|
45
|
+
Future<void> save(String identity, Uint8List secret) async {
|
|
46
|
+
log.add('save:$identity');
|
|
47
|
+
saved[identity] = Uint8List.fromList(secret);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
class _MemoryProviderCredentialStore implements NomosProviderCredentialStore {
|
|
52
|
+
final Map<String, List<String>> saved = {};
|
|
53
|
+
|
|
54
|
+
@override
|
|
55
|
+
Future<List<String>> loadCandidates(String entry) async =>
|
|
56
|
+
List<String>.from(saved[entry] ?? const []);
|
|
57
|
+
|
|
58
|
+
@override
|
|
59
|
+
Future<void> retainCandidate(String entry, String authToken) async {
|
|
60
|
+
final existing = saved[entry] ?? const [];
|
|
61
|
+
saved[entry] = [
|
|
62
|
+
authToken,
|
|
63
|
+
...existing.where((candidate) => candidate != authToken),
|
|
64
|
+
].take(4).toList(growable: false);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
Uint8List secretOf(String label) => Uint8List.fromList(List<int>.generate(
|
|
69
|
+
32, (i) => (label.codeUnitAt(i % label.length) ^ i) & 0xff));
|
|
70
|
+
|
|
71
|
+
p.PeerDeviceIdentityEffect loadEffect() => p.PeerDeviceIdentityEffect(
|
|
72
|
+
effectId: 'identity:load',
|
|
73
|
+
identity: 'co2|user:ada',
|
|
74
|
+
load: p.PeerDeviceIdentityLoad());
|
|
75
|
+
|
|
76
|
+
void main() {
|
|
77
|
+
test(
|
|
78
|
+
'secure recovery journal round-trips separately from the active identity',
|
|
79
|
+
() async {
|
|
80
|
+
final platform = _Platform();
|
|
81
|
+
final store = _MemoryIdentityStore();
|
|
82
|
+
platform.registerDeviceIdentityStore(store);
|
|
83
|
+
const identity = 'independent-app|user:ada';
|
|
84
|
+
final old = secretOf('old');
|
|
85
|
+
final candidate = secretOf('new');
|
|
86
|
+
store.saved[identity] = old;
|
|
87
|
+
final record = p.DeviceRecoveryRecord(
|
|
88
|
+
version: 1,
|
|
89
|
+
application: 'independent-app',
|
|
90
|
+
principal: 'user:ada',
|
|
91
|
+
previousSecret: old,
|
|
92
|
+
candidateSecret: candidate);
|
|
93
|
+
final saved = await platform.performDeviceIdentityEffect(
|
|
94
|
+
p.PeerDeviceIdentityEffect(
|
|
95
|
+
effectId: 'save-recovery',
|
|
96
|
+
identity: identity,
|
|
97
|
+
saveRecovery: p.PeerDeviceRecoverySave(record: record)));
|
|
98
|
+
expect(saved.hasDeviceIdentity(), isTrue);
|
|
99
|
+
expect(store.saved[identity], old, reason: 'staging is not activation');
|
|
100
|
+
final loaded = await platform.performDeviceIdentityEffect(
|
|
101
|
+
p.PeerDeviceIdentityEffect(
|
|
102
|
+
effectId: 'load-recovery',
|
|
103
|
+
identity: identity,
|
|
104
|
+
loadRecovery: p.PeerDeviceRecoveryLoad()));
|
|
105
|
+
expect(loaded.deviceIdentity.hasSecret(), isFalse);
|
|
106
|
+
expect(loaded.deviceIdentity.recovery, record);
|
|
107
|
+
store.saved['nomos-device-recovery-v1|$identity'] =
|
|
108
|
+
Uint8List.fromList([255]);
|
|
109
|
+
final corrupt = await platform.performDeviceIdentityEffect(
|
|
110
|
+
p.PeerDeviceIdentityEffect(
|
|
111
|
+
effectId: 'load-corrupt',
|
|
112
|
+
identity: identity,
|
|
113
|
+
loadRecovery: p.PeerDeviceRecoveryLoad()));
|
|
114
|
+
expect(corrupt.hasFailure(), isTrue);
|
|
115
|
+
expect(store.saved[identity], old);
|
|
116
|
+
store.saved[identity] = Uint8List(0);
|
|
117
|
+
final empty = await platform.performDeviceIdentityEffect(
|
|
118
|
+
p.PeerDeviceIdentityEffect(
|
|
119
|
+
effectId: 'empty',
|
|
120
|
+
identity: identity,
|
|
121
|
+
load: p.PeerDeviceIdentityLoad()));
|
|
122
|
+
expect(empty.hasFailure(), isTrue,
|
|
123
|
+
reason: 'present corruption must not cross protobuf as a missing key');
|
|
124
|
+
expect(empty.failure.message, contains('malformed'));
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('a readiness wake leaves the transport available for another request',
|
|
128
|
+
() async {
|
|
129
|
+
final platform = _Platform();
|
|
130
|
+
var frames = 0;
|
|
131
|
+
final waiting = platform.driveCredentialEffects(
|
|
132
|
+
p.RuntimeRequest(protocolVersion: 2, requestId: Int64(7)),
|
|
133
|
+
(request) async {
|
|
134
|
+
frames++;
|
|
135
|
+
if (frames == 1)
|
|
136
|
+
return p.RuntimeResponse(
|
|
137
|
+
protocolVersion: 2,
|
|
138
|
+
requestId: request.requestId,
|
|
139
|
+
correspondencePending: p.CorrespondencePending(
|
|
140
|
+
workId: 'wait',
|
|
141
|
+
wake: p.PeerWakeEffect(effectId: 'wake', delayMillis: 10)));
|
|
142
|
+
expect(request.continueCorrespondence.receipt.hasWoken(), isTrue);
|
|
143
|
+
expect(request.continueCorrespondence.receipt.effectId, 'wake');
|
|
144
|
+
expect(platform.requests, hasLength(1));
|
|
145
|
+
return p.RuntimeResponse(
|
|
146
|
+
protocolVersion: 2,
|
|
147
|
+
requestId: request.requestId,
|
|
148
|
+
authenticationReadiness: p.AuthenticationReadiness());
|
|
149
|
+
});
|
|
150
|
+
await platform.invokeRuntime(
|
|
151
|
+
p.RuntimeRequest(protocolVersion: 2, requestId: Int64(8)));
|
|
152
|
+
expect((await waiting).hasAuthenticationReadiness(), isTrue);
|
|
153
|
+
expect(frames, 2);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('foreground carries capability proof and continues the same request',
|
|
157
|
+
() async {
|
|
158
|
+
final platform = _Platform();
|
|
159
|
+
var calls = 0;
|
|
160
|
+
platform.registerCredentialProvider(
|
|
161
|
+
capability: 'opaque-capability',
|
|
162
|
+
cloud: 'https://example.test',
|
|
163
|
+
signIn: ({deviceChallenge}) async {
|
|
164
|
+
calls++;
|
|
165
|
+
return ' fresh-proof ';
|
|
166
|
+
});
|
|
167
|
+
platform.registerCredentialProvider(
|
|
168
|
+
capability: 'different',
|
|
169
|
+
cloud: 'https://example.test',
|
|
170
|
+
signIn: ({deviceChallenge}) async =>
|
|
171
|
+
throw StateError('wrong provider'));
|
|
172
|
+
final initial = p.RuntimeRequest(protocolVersion: 2, requestId: Int64(7));
|
|
173
|
+
var frames = 0;
|
|
174
|
+
final result =
|
|
175
|
+
await platform.driveCredentialEffects(initial, (request) async {
|
|
176
|
+
frames++;
|
|
177
|
+
expect(request.requestId, Int64(7));
|
|
178
|
+
if (frames == 1)
|
|
179
|
+
return p.RuntimeResponse(
|
|
180
|
+
protocolVersion: 2,
|
|
181
|
+
requestId: request.requestId,
|
|
182
|
+
correspondencePending: pending());
|
|
183
|
+
expect(request.continueCorrespondence.workId, 'work');
|
|
184
|
+
final receipt = request.continueCorrespondence.receipt;
|
|
185
|
+
expect(receipt.effectId, 'effect');
|
|
186
|
+
expect(utf8.decode(receipt.credentialRefreshed.authToken), 'fresh-proof');
|
|
187
|
+
return p.RuntimeResponse(
|
|
188
|
+
protocolVersion: 2,
|
|
189
|
+
requestId: request.requestId,
|
|
190
|
+
kernelCall: p.CallResponse());
|
|
191
|
+
});
|
|
192
|
+
expect(result.hasKernelCall(), isTrue);
|
|
193
|
+
expect(frames, 2);
|
|
194
|
+
expect(calls, 1);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test(
|
|
198
|
+
'native-style durable credentials survive restart and a rejected token falls back through the rolling set',
|
|
199
|
+
() async {
|
|
200
|
+
final durable = _MemoryProviderCredentialStore();
|
|
201
|
+
final first = _Platform()..registerProviderCredentialStore(durable);
|
|
202
|
+
final issued = ['old-proof', 'new-proof'];
|
|
203
|
+
first.registerCredentialProvider(
|
|
204
|
+
capability: 'opaque-capability',
|
|
205
|
+
cloud: 'https://provider-a.example',
|
|
206
|
+
signIn: ({deviceChallenge}) async => issued.removeAt(0));
|
|
207
|
+
for (var i = 0; i < 2; i += 1) {
|
|
208
|
+
final receipt = await first.performCredentialRefresh(
|
|
209
|
+
p.PeerCredentialRefreshEffect(
|
|
210
|
+
effectId: 'online-$i', capability: 'opaque-capability'));
|
|
211
|
+
expect(receipt.hasCredentialRefreshed(), isTrue);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
var providerCalls = 0;
|
|
215
|
+
final restarted = _Platform()..registerProviderCredentialStore(durable);
|
|
216
|
+
restarted.registerCredentialProvider(
|
|
217
|
+
capability: 'opaque-capability',
|
|
218
|
+
cloud: 'https://provider-a.example',
|
|
219
|
+
signIn: ({deviceChallenge}) async {
|
|
220
|
+
providerCalls += 1;
|
|
221
|
+
return 'network-proof';
|
|
222
|
+
});
|
|
223
|
+
var receipt = await restarted.performCredentialRefresh(
|
|
224
|
+
p.PeerCredentialRefreshEffect(
|
|
225
|
+
effectId: 'restart',
|
|
226
|
+
capability: 'opaque-capability',
|
|
227
|
+
allowCached: true));
|
|
228
|
+
expect(utf8.decode(receipt.credentialRefreshed.authToken), 'new-proof');
|
|
229
|
+
expect(providerCalls, 0,
|
|
230
|
+
reason: 'a process restart must not require Firebase');
|
|
231
|
+
|
|
232
|
+
receipt = await restarted.performCredentialRefresh(
|
|
233
|
+
p.PeerCredentialRefreshEffect(
|
|
234
|
+
effectId: 'rejected',
|
|
235
|
+
capability: 'opaque-capability',
|
|
236
|
+
allowCached: true,
|
|
237
|
+
rejectedAuthTokenSha256: Uint8List.fromList(
|
|
238
|
+
sha256.convert(utf8.encode('new-proof')).bytes)));
|
|
239
|
+
expect(utf8.decode(receipt.credentialRefreshed.authToken), 'old-proof');
|
|
240
|
+
expect(providerCalls, 0,
|
|
241
|
+
reason: 'the preceding admitted proof remains usable during rollout');
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test(
|
|
245
|
+
'unavailable and failing providers return receipts without replay policy',
|
|
246
|
+
() async {
|
|
247
|
+
final platform = _Platform();
|
|
248
|
+
var result =
|
|
249
|
+
await platform.performCredentialRefresh(pending().refreshCredential);
|
|
250
|
+
expect(result.hasCredentialRefreshed(), isTrue);
|
|
251
|
+
expect(result.credentialRefreshed.hasAuthToken(), isFalse);
|
|
252
|
+
platform.registerCredentialProvider(
|
|
253
|
+
capability: 'opaque-capability',
|
|
254
|
+
cloud: 'https://example.test',
|
|
255
|
+
signIn: ({deviceChallenge}) async =>
|
|
256
|
+
throw StateError('secret diagnostic'));
|
|
257
|
+
result =
|
|
258
|
+
await platform.performCredentialRefresh(pending().refreshCredential);
|
|
259
|
+
expect(result.hasFailure(), isTrue);
|
|
260
|
+
expect(result.failure.message, isNot(contains('secret diagnostic')));
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test('background physical event is consumed and returns a correlated receipt',
|
|
264
|
+
() async {
|
|
265
|
+
final platform = _Platform();
|
|
266
|
+
final failures = <Object>[];
|
|
267
|
+
expect(
|
|
268
|
+
platform.consumeCredentialRefreshEvent(
|
|
269
|
+
p.RuntimeEvent(), platform, (error, stack) => failures.add(error)),
|
|
270
|
+
isFalse);
|
|
271
|
+
expect(
|
|
272
|
+
platform.consumeCredentialRefreshEvent(
|
|
273
|
+
p.RuntimeEvent(
|
|
274
|
+
credentialRefreshRequested:
|
|
275
|
+
p.CredentialRefreshRequested(pending: pending())),
|
|
276
|
+
platform,
|
|
277
|
+
(error, stack) => failures.add(error)),
|
|
278
|
+
isTrue);
|
|
279
|
+
await Future<void>.delayed(Duration.zero);
|
|
280
|
+
expect(platform.requests, hasLength(1));
|
|
281
|
+
expect(platform.requests.single.requestId, Int64(41));
|
|
282
|
+
expect(platform.requests.single.continueCorrespondence.workId, 'work');
|
|
283
|
+
expect(failures, isEmpty);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test(
|
|
287
|
+
'the device challenge reaches the provider verbatim and only when carried',
|
|
288
|
+
() async {
|
|
289
|
+
final platform = _Platform();
|
|
290
|
+
final seen = <String?>[];
|
|
291
|
+
platform.registerCredentialProvider(
|
|
292
|
+
capability: 'opaque-capability',
|
|
293
|
+
cloud: 'https://example.test',
|
|
294
|
+
signIn: ({deviceChallenge}) async {
|
|
295
|
+
seen.add(deviceChallenge);
|
|
296
|
+
return 'bound-${deviceChallenge ?? 'plain'}';
|
|
297
|
+
});
|
|
298
|
+
var receipt = await platform.performCredentialRefresh(
|
|
299
|
+
p.PeerCredentialRefreshEffect(
|
|
300
|
+
effectId: 'effect',
|
|
301
|
+
capability: 'opaque-capability',
|
|
302
|
+
deviceChallenge: 'ab' * 32));
|
|
303
|
+
expect(utf8.decode(receipt.credentialRefreshed.authToken),
|
|
304
|
+
'bound-${'ab' * 32}');
|
|
305
|
+
receipt =
|
|
306
|
+
await platform.performCredentialRefresh(pending().refreshCredential);
|
|
307
|
+
expect(utf8.decode(receipt.credentialRefreshed.authToken), 'bound-plain');
|
|
308
|
+
expect(seen, ['ab' * 32, null]);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test(
|
|
312
|
+
'a device identity load and save round-trip through the registered store',
|
|
313
|
+
() async {
|
|
314
|
+
final platform = _Platform();
|
|
315
|
+
final store = _MemoryIdentityStore();
|
|
316
|
+
platform.registerDeviceIdentityStore(store);
|
|
317
|
+
final secret = secretOf('device-a');
|
|
318
|
+
var receipt = await platform.performDeviceIdentityEffect(loadEffect());
|
|
319
|
+
expect(receipt.effectId, 'identity:load');
|
|
320
|
+
expect(receipt.hasDeviceIdentity(), isTrue);
|
|
321
|
+
expect(receipt.deviceIdentity.hasSecret(), isFalse);
|
|
322
|
+
receipt = await platform.performDeviceIdentityEffect(
|
|
323
|
+
p.PeerDeviceIdentityEffect(
|
|
324
|
+
effectId: 'identity:save',
|
|
325
|
+
identity: 'co2|user:ada',
|
|
326
|
+
save: p.PeerDeviceIdentitySave(secret: secret)));
|
|
327
|
+
expect(receipt.hasDeviceIdentity(), isTrue);
|
|
328
|
+
receipt = await platform.performDeviceIdentityEffect(loadEffect());
|
|
329
|
+
expect(receipt.deviceIdentity.secret, secret);
|
|
330
|
+
expect(store.log,
|
|
331
|
+
['load:co2|user:ada', 'save:co2|user:ada', 'load:co2|user:ada']);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test('a shell without a device identity store refuses typed', () async {
|
|
335
|
+
final platform = _Platform();
|
|
336
|
+
final receipt = await platform.performDeviceIdentityEffect(loadEffect());
|
|
337
|
+
expect(receipt.hasFailure(), isTrue);
|
|
338
|
+
expect(receipt.failure.message, contains('device identity store'));
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test(
|
|
342
|
+
'driveCredentialEffects answers a device identity paper on the same request',
|
|
343
|
+
() async {
|
|
344
|
+
final platform = _Platform();
|
|
345
|
+
final store = _MemoryIdentityStore()
|
|
346
|
+
..saved['co2|user:ada'] = secretOf('device-b');
|
|
347
|
+
platform.registerDeviceIdentityStore(store);
|
|
348
|
+
var frames = 0;
|
|
349
|
+
final result = await platform.driveCredentialEffects(
|
|
350
|
+
p.RuntimeRequest(protocolVersion: 2, requestId: Int64(7)),
|
|
351
|
+
(request) async {
|
|
352
|
+
frames++;
|
|
353
|
+
expect(request.requestId, Int64(7));
|
|
354
|
+
if (frames == 1) {
|
|
355
|
+
return p.RuntimeResponse(
|
|
356
|
+
protocolVersion: 2,
|
|
357
|
+
requestId: request.requestId,
|
|
358
|
+
correspondencePending: p.CorrespondencePending(
|
|
359
|
+
workId: 'device-identity-1', deviceIdentity: loadEffect()));
|
|
360
|
+
}
|
|
361
|
+
expect(request.continueCorrespondence.workId, 'device-identity-1');
|
|
362
|
+
final receipt = request.continueCorrespondence.receipt;
|
|
363
|
+
expect(receipt.effectId, 'identity:load');
|
|
364
|
+
expect(receipt.deviceIdentity.secret, secretOf('device-b'));
|
|
365
|
+
return p.RuntimeResponse(
|
|
366
|
+
protocolVersion: 2,
|
|
367
|
+
requestId: request.requestId,
|
|
368
|
+
credentialsRefreshed:
|
|
369
|
+
p.CredentialsRefreshed(session: 'home', hasAuthorSecret: true));
|
|
370
|
+
});
|
|
371
|
+
expect(result.credentialsRefreshed.hasAuthorSecret, isTrue);
|
|
372
|
+
expect(frames, 2);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test('unrelated response cannot invoke a credential provider', () async {
|
|
376
|
+
final platform = _Platform();
|
|
377
|
+
await expectLater(
|
|
378
|
+
platform.driveCredentialEffects(
|
|
379
|
+
p.RuntimeRequest(protocolVersion: 2, requestId: Int64(7)),
|
|
380
|
+
(_) async => p.RuntimeResponse(
|
|
381
|
+
protocolVersion: 2,
|
|
382
|
+
requestId: Int64(8),
|
|
383
|
+
correspondencePending: pending())),
|
|
384
|
+
throwsStateError);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// THE RECEIVING-GROUP GATE FROM DART (receiving_group_lifecycle.md cut 4): Reception
|
|
2
|
+
// transports ONE `OpenReceivingGroupRequest` paper and renders the office's answer; it
|
|
3
|
+
// owns no ceremony. The desk's typed refusal surfaces as a NomosRuntimeError carrying the
|
|
4
|
+
// desk's own code, and the convergence tail arrives as `ReceivingGroupConvergence`
|
|
5
|
+
// runtime events scoped to the group's session.
|
|
6
|
+
import 'dart:async';
|
|
7
|
+
import 'dart:convert';
|
|
8
|
+
|
|
9
|
+
import 'package:nomos_client/src/nms_generated_runtime.dart';
|
|
10
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
|
|
11
|
+
import 'package:test/test.dart';
|
|
12
|
+
|
|
13
|
+
const _principal = 'user:ada';
|
|
14
|
+
// The office names the receiving group; Reception asks for it. A test scripts
|
|
15
|
+
// that answer like any other paper.
|
|
16
|
+
const _group = 'nomos-rg-scripted';
|
|
17
|
+
final _lawSha256 = '2' * 64;
|
|
18
|
+
|
|
19
|
+
final class _Office implements PeerOfficeConnection {
|
|
20
|
+
_Office({this.refuse});
|
|
21
|
+
|
|
22
|
+
/// A scripted desk refusal, or null for an opened gate.
|
|
23
|
+
final ({String code, String message})? refuse;
|
|
24
|
+
final _events = StreamController<protocol.RuntimeEvent>.broadcast();
|
|
25
|
+
final calls = <protocol.RuntimeRequest>[];
|
|
26
|
+
|
|
27
|
+
@override
|
|
28
|
+
Stream<protocol.RuntimeEvent> get runtimeEvents => _events.stream;
|
|
29
|
+
|
|
30
|
+
void emit(protocol.RuntimeEvent event) => _events.add(event);
|
|
31
|
+
|
|
32
|
+
int _requests = 0;
|
|
33
|
+
@override
|
|
34
|
+
int nextRuntimeRequestId() => ++_requests;
|
|
35
|
+
|
|
36
|
+
@override
|
|
37
|
+
Future<protocol.RuntimeResponse> invokeRuntime(
|
|
38
|
+
protocol.RuntimeRequest request,
|
|
39
|
+
) async {
|
|
40
|
+
calls.add(request.deepCopy());
|
|
41
|
+
if (request.hasBindSession()) {
|
|
42
|
+
return protocol.RuntimeResponse(
|
|
43
|
+
protocolVersion: 2,
|
|
44
|
+
requestId: request.requestId,
|
|
45
|
+
sessionBound: protocol.SessionBound(
|
|
46
|
+
workspace: request.bindSession.workspace,
|
|
47
|
+
session: request.bindSession.session,
|
|
48
|
+
credentialCapability: request.bindSession.session,
|
|
49
|
+
),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (request.hasOpenReceivingGroup()) {
|
|
53
|
+
final refusal = refuse;
|
|
54
|
+
if (refusal != null) {
|
|
55
|
+
return protocol.RuntimeResponse(
|
|
56
|
+
protocolVersion: 2,
|
|
57
|
+
requestId: request.requestId,
|
|
58
|
+
failure: protocol.RuntimeFailure(
|
|
59
|
+
code: refusal.code,
|
|
60
|
+
message: refusal.message,
|
|
61
|
+
component: 'receiving-group-desk',
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
return protocol.RuntimeResponse(
|
|
66
|
+
protocolVersion: 2,
|
|
67
|
+
requestId: request.requestId,
|
|
68
|
+
receivingGroupOpened: protocol.ReceivingGroupOpened(
|
|
69
|
+
session: request.openReceivingGroup.session,
|
|
70
|
+
workspace: _group,
|
|
71
|
+
deviceKeySubject: 'key:${'a' * 64}',
|
|
72
|
+
),
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
throw StateError('unexpected runtime call ${request.whichCommand().name}');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
Future<DartReception> _connected(_Office office) async {
|
|
80
|
+
final reception = DartReception(office);
|
|
81
|
+
addTearDown(reception.dispose);
|
|
82
|
+
await reception.bindSession(
|
|
83
|
+
workspace: _group,
|
|
84
|
+
principal: _principal,
|
|
85
|
+
);
|
|
86
|
+
return reception;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
void main() {
|
|
90
|
+
test(
|
|
91
|
+
'the session sends exactly one open paper: contract, principal, session, no seed',
|
|
92
|
+
() async {
|
|
93
|
+
final office = _Office();
|
|
94
|
+
final reception = await _connected(office);
|
|
95
|
+
|
|
96
|
+
final opened = await reception.session(_group).nmsOpenReceivingGroup(
|
|
97
|
+
application: 'acme-platform',
|
|
98
|
+
principal: _principal,
|
|
99
|
+
authToken: 'id-token',
|
|
100
|
+
contract: protocol.ApplicationBirthContract(lawSha256: _lawSha256),
|
|
101
|
+
verifyHistory: true,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
final papers =
|
|
105
|
+
office.calls.where((c) => c.hasOpenReceivingGroup()).toList();
|
|
106
|
+
expect(papers, hasLength(1));
|
|
107
|
+
final paper = papers.single.openReceivingGroup;
|
|
108
|
+
expect(paper.session, _group, reason: 'the session is the group workspace');
|
|
109
|
+
expect(paper.application, 'acme-platform');
|
|
110
|
+
expect(paper.principal, _principal);
|
|
111
|
+
expect(utf8.decode(paper.authToken), 'id-token');
|
|
112
|
+
expect(paper.contract.lawSha256, _lawSha256);
|
|
113
|
+
expect(paper.contract.toProto3Json(), {'lawSha256': _lawSha256},
|
|
114
|
+
reason:
|
|
115
|
+
'the contract is law-only: the platform controller is observed by the desk');
|
|
116
|
+
expect(paper.genesisSeed, isEmpty, reason: 'seeds are law');
|
|
117
|
+
expect(paper.verifyHistory, isTrue);
|
|
118
|
+
expect(papers.single.protocolVersion, 2);
|
|
119
|
+
|
|
120
|
+
expect(opened.workspace, _group);
|
|
121
|
+
expect(opened.deviceKeySubject, startsWith('key:'));
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("the desk's typed refusal surfaces as a runtime error carrying its code",
|
|
125
|
+
() async {
|
|
126
|
+
final office = _Office(
|
|
127
|
+
refuse: (
|
|
128
|
+
code: 'nomos.runtime.receiving_group_platform_controller_unknown',
|
|
129
|
+
message:
|
|
130
|
+
"the platform 'acme-platform' reports no current 'nomos' controller law",
|
|
131
|
+
),
|
|
132
|
+
);
|
|
133
|
+
final reception = await _connected(office);
|
|
134
|
+
|
|
135
|
+
await expectLater(
|
|
136
|
+
reception.session(_group).nmsOpenReceivingGroup(
|
|
137
|
+
application: 'acme-platform',
|
|
138
|
+
principal: _principal,
|
|
139
|
+
authToken: 'id-token',
|
|
140
|
+
contract: protocol.ApplicationBirthContract(lawSha256: _lawSha256),
|
|
141
|
+
),
|
|
142
|
+
throwsA(isA<NomosRuntimeError>().having(
|
|
143
|
+
(e) => e.info.code,
|
|
144
|
+
'code',
|
|
145
|
+
'nomos.runtime.receiving_group_platform_controller_unknown',
|
|
146
|
+
)),
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test(
|
|
151
|
+
'convergence events reach the session bound to the group, and only that one',
|
|
152
|
+
() async {
|
|
153
|
+
final office = _Office();
|
|
154
|
+
final reception = await _connected(office);
|
|
155
|
+
await reception.bindSession(
|
|
156
|
+
workspace: 'estate-1',
|
|
157
|
+
principal: _principal,
|
|
158
|
+
);
|
|
159
|
+
final groupEvents = <protocol.ReceivingGroupConvergence>[];
|
|
160
|
+
final estateEvents = <protocol.ReceivingGroupConvergence>[];
|
|
161
|
+
final planeEvents = <protocol.ReceivingGroupConvergence>[];
|
|
162
|
+
final groupSub = reception
|
|
163
|
+
.session(_group)
|
|
164
|
+
.receivingGroupConvergence
|
|
165
|
+
.listen(groupEvents.add);
|
|
166
|
+
final estateSub = reception
|
|
167
|
+
.session('estate-1')
|
|
168
|
+
.receivingGroupConvergence
|
|
169
|
+
.listen(estateEvents.add);
|
|
170
|
+
final planeSub =
|
|
171
|
+
reception.receivingGroupConvergence.listen(planeEvents.add);
|
|
172
|
+
addTearDown(() async {
|
|
173
|
+
await groupSub.cancel();
|
|
174
|
+
await estateSub.cancel();
|
|
175
|
+
await planeSub.cancel();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
protocol.RuntimeEvent event(
|
|
179
|
+
protocol.ReceivingGroupConvergencePhase phase,
|
|
180
|
+
protocol.ReceivingGroupConvergenceOutcome outcome,
|
|
181
|
+
) =>
|
|
182
|
+
protocol.RuntimeEvent(
|
|
183
|
+
session: _group,
|
|
184
|
+
receivingGroupConvergence: protocol.ReceivingGroupConvergence(
|
|
185
|
+
workspace: _group,
|
|
186
|
+
phase: phase,
|
|
187
|
+
outcome: outcome,
|
|
188
|
+
),
|
|
189
|
+
);
|
|
190
|
+
office.emit(event(
|
|
191
|
+
protocol.ReceivingGroupConvergencePhase
|
|
192
|
+
.RECEIVING_GROUP_CONVERGENCE_PHASE_POSTURE,
|
|
193
|
+
protocol.ReceivingGroupConvergenceOutcome
|
|
194
|
+
.RECEIVING_GROUP_CONVERGENCE_OUTCOME_IN_PROGRESS,
|
|
195
|
+
));
|
|
196
|
+
office.emit(event(
|
|
197
|
+
protocol.ReceivingGroupConvergencePhase
|
|
198
|
+
.RECEIVING_GROUP_CONVERGENCE_PHASE_VERIFICATION,
|
|
199
|
+
protocol.ReceivingGroupConvergenceOutcome
|
|
200
|
+
.RECEIVING_GROUP_CONVERGENCE_OUTCOME_CONVERGED,
|
|
201
|
+
));
|
|
202
|
+
await Future<void>.delayed(Duration.zero);
|
|
203
|
+
|
|
204
|
+
expect(groupEvents.map((e) => e.outcome.name), [
|
|
205
|
+
'RECEIVING_GROUP_CONVERGENCE_OUTCOME_IN_PROGRESS',
|
|
206
|
+
'RECEIVING_GROUP_CONVERGENCE_OUTCOME_CONVERGED',
|
|
207
|
+
]);
|
|
208
|
+
expect(estateEvents, isEmpty,
|
|
209
|
+
reason: 'another workspace never sees the group tail');
|
|
210
|
+
expect(planeEvents, hasLength(2),
|
|
211
|
+
reason: 'the root reception sees the plane');
|
|
212
|
+
});
|
|
213
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import 'package:nomos_client/src/reception/workspace_preparation_report.dart';
|
|
2
|
+
import 'package:nomos_client/src/reception/progress_desk.dart';
|
|
3
|
+
import 'package:nomos_client/src/reception/event_desk.dart';
|
|
4
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
|
|
5
|
+
import 'package:test/test.dart';
|
|
6
|
+
|
|
7
|
+
void main() {
|
|
8
|
+
test('a new peer demand clears stale index readiness before its kernel turn',
|
|
9
|
+
() {
|
|
10
|
+
final settled = WorkspacePreparationReport.fromMaintenance(
|
|
11
|
+
protocol.ReadMaintenanceProgress(
|
|
12
|
+
workspace: 'estate-1',
|
|
13
|
+
kernel: protocol.MaintenanceQuantumResponse(
|
|
14
|
+
phase: protocol.MaintenancePhase.MAINTENANCE_PHASE_COMPLETE,
|
|
15
|
+
readFrontier: 'head-a',
|
|
16
|
+
sqliteFrontier: 'head-a',
|
|
17
|
+
),
|
|
18
|
+
),
|
|
19
|
+
);
|
|
20
|
+
expect(settled.active, isFalse);
|
|
21
|
+
expect(settled.sqliteCurrent, isTrue);
|
|
22
|
+
|
|
23
|
+
final scheduled = WorkspacePreparationReport.fromMaintenance(
|
|
24
|
+
protocol.ReadMaintenanceProgress(workspace: 'estate-1'),
|
|
25
|
+
previous: settled,
|
|
26
|
+
);
|
|
27
|
+
expect(scheduled.active, isTrue);
|
|
28
|
+
expect(scheduled.sqliteCurrent, isNull);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('peer retry is visible without claiming a sync failure', () {
|
|
32
|
+
final retry = WorkspacePreparationReport.fromMaintenance(
|
|
33
|
+
protocol.ReadMaintenanceProgress(
|
|
34
|
+
workspace: 'estate-1',
|
|
35
|
+
retryAfterMillis: 1000,
|
|
36
|
+
),
|
|
37
|
+
);
|
|
38
|
+
expect(retry.retrying, isTrue);
|
|
39
|
+
expect(retry.active, isFalse);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('unreadable copy stops preparation', () {
|
|
43
|
+
final stopped = WorkspacePreparationReport.fromMaintenance(
|
|
44
|
+
protocol.ReadMaintenanceProgress(workspace: 'estate-1', stopped: true),
|
|
45
|
+
);
|
|
46
|
+
expect(stopped.active, isFalse);
|
|
47
|
+
expect(stopped.stopped, isTrue);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('an answered read closes the observation without a kernel turn', () {
|
|
51
|
+
final answered = WorkspacePreparationReport.fromMaintenance(
|
|
52
|
+
protocol.ReadMaintenanceProgress(
|
|
53
|
+
workspace: 'estate-1',
|
|
54
|
+
satisfiedByRead: true,
|
|
55
|
+
),
|
|
56
|
+
);
|
|
57
|
+
expect(answered.active, isFalse);
|
|
58
|
+
expect(answered.step, WorkspacePreparationStep.complete);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('restore law hydration is reported as installing workspace rules', () {
|
|
62
|
+
final law = WorkspacePreparationReport.fromRestore(KernelProgress(
|
|
63
|
+
sequence: 1,
|
|
64
|
+
operation: KernelProgressOperation.restoreMain,
|
|
65
|
+
phase: KernelProgressPhase.hydrateLaw,
|
|
66
|
+
completed: 0,
|
|
67
|
+
total: 1,
|
|
68
|
+
unit: KernelProgressUnit.lawPackage,
|
|
69
|
+
terminal: false,
|
|
70
|
+
lawHydrationStep: KernelLawHydrationStep.composeStage,
|
|
71
|
+
lawPackageSha256: 'law',
|
|
72
|
+
hostObservedElapsedMicros: 0,
|
|
73
|
+
hostObservedPhaseElapsedMicros: 0,
|
|
74
|
+
kernelAllocatorLiveBytes: 0,
|
|
75
|
+
kernelAllocatorPeakBytes: 0,
|
|
76
|
+
processResidentBytes: 0,
|
|
77
|
+
processPeakResidentBytes: 0,
|
|
78
|
+
));
|
|
79
|
+
expect(law.step, WorkspacePreparationStep.installingLaw);
|
|
80
|
+
expect(law.active, isTrue);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('peer maintenance observations drive the application report', () async {
|
|
84
|
+
final events = ReceptionEventDesk();
|
|
85
|
+
events.observeMaintenance(protocol.ReadMaintenanceProgress(
|
|
86
|
+
workspace: 'estate-1',
|
|
87
|
+
cause: protocol.ReadMaintenanceCause.READ_MAINTENANCE_CAUSE_OPEN,
|
|
88
|
+
));
|
|
89
|
+
expect(events.syncStatuses.applicationStatus.preparingWorkspaces, 1);
|
|
90
|
+
expect(events.syncStatuses.applicationStatus.preparationActivity,
|
|
91
|
+
'Getting workspace data ready');
|
|
92
|
+
|
|
93
|
+
events.observeMaintenance(protocol.ReadMaintenanceProgress(
|
|
94
|
+
workspace: 'estate-1',
|
|
95
|
+
cause: protocol.ReadMaintenanceCause.READ_MAINTENANCE_CAUSE_OPEN,
|
|
96
|
+
kernel: protocol.MaintenanceQuantumResponse(
|
|
97
|
+
phase: protocol.MaintenancePhase.MAINTENANCE_PHASE_COMPLETE,
|
|
98
|
+
),
|
|
99
|
+
));
|
|
100
|
+
expect(events.syncStatuses.applicationStatus.preparingWorkspaces, 0);
|
|
101
|
+
await events.close();
|
|
102
|
+
});
|
|
103
|
+
}
|