@githolon/dsl 0.108.6 → 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 +2083 -1248
- 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,48 @@
|
|
|
1
|
+
import 'dart:convert';
|
|
2
|
+
import 'package:nomos_client/src/admission_decision.dart';
|
|
3
|
+
import 'package:nomos_client/src/reception/dart_reception.dart';
|
|
4
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as p;
|
|
5
|
+
import 'package:test/test.dart';
|
|
6
|
+
|
|
7
|
+
p.OfferResponse refusal({bool domain = true}) => p.OfferResponse(refused: p.OfferRefused(
|
|
8
|
+
refusal: p.KernelRefusal(code: domain ? p.KernelRefusalCode.KERNEL_REFUSAL_CODE_DOMAIN_REFUSED
|
|
9
|
+
: p.KernelRefusalCode.KERNEL_REFUSAL_CODE_INVARIANT_EXECUTION_FAULT, message: 'test'),
|
|
10
|
+
domainRefusal: domain ? p.DomainRefusal(lawSha256: List.filled(32, 7), invariant: '/Rule',
|
|
11
|
+
typeIdentity: '/Problem', valueUsda: utf8.encode('#usda 1.0')) : null));
|
|
12
|
+
void main() {
|
|
13
|
+
test('preserves the successful business result', () async {
|
|
14
|
+
final result = await nmsAdmissionDecision(() async => 42, (_) => 'unused');
|
|
15
|
+
expect(result, isA<Admitted<int, String>>().having((value) => value.value, 'value', 42));
|
|
16
|
+
});
|
|
17
|
+
test('decodes declared refusal and copies its ephemeral evidence', () async {
|
|
18
|
+
final reply = refusal();
|
|
19
|
+
final result = await nmsAdmissionDecision<int, String>(() async => throw NomosTypedOfferFailure(reply), (_) => 'tooShort');
|
|
20
|
+
expect(result, isA<DomainRefused<int, String>>().having((value) => value.reason, 'reason', 'tooShort'));
|
|
21
|
+
reply.refused.domainRefusal.lawSha256[0] = 1;
|
|
22
|
+
final evidence = (result as DomainRefused).evidence;
|
|
23
|
+
expect(evidence.lawSha256[0], 7);
|
|
24
|
+
expect(() => evidence.lawSha256[0] = 0, throwsUnsupportedError);
|
|
25
|
+
});
|
|
26
|
+
test('framework rejection is separate from tenant refusal', () async {
|
|
27
|
+
final result = await nmsAdmissionDecision<int, String>(() async => throw NomosTypedOfferFailure(refusal(domain: false)), (_) => throw StateError('must not decode'));
|
|
28
|
+
expect(result, isA<FrameworkRefused<int, String>>());
|
|
29
|
+
});
|
|
30
|
+
test('unknown family and unknown arm preserve an explicit unsupported result', () async {
|
|
31
|
+
for (final decoder in <String? Function(NomosDomainRefusalEvidence)>[
|
|
32
|
+
(_) => null, (_) => throw const NmsUnsupportedNominalRefusal(),
|
|
33
|
+
]) {
|
|
34
|
+
expect(await nmsAdmissionDecision<int, String>(() async => throw NomosTypedOfferFailure(refusal()), decoder), isA<UnsupportedRefusalContract<int, String>>());
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
test('malformed evidence and invalid codec output remain protocol faults', () async {
|
|
38
|
+
final bad = refusal()..refused.domainRefusal.lawSha256 = [1];
|
|
39
|
+
await expectLater(nmsAdmissionDecision<int, String>(() async => throw NomosTypedOfferFailure(bad), (_) => 'bad'), throwsFormatException);
|
|
40
|
+
await expectLater(nmsAdmissionDecision<int, String>(() async => throw NomosTypedOfferFailure(refusal()), (_) => throw const FormatException('bad body')), throwsFormatException);
|
|
41
|
+
});
|
|
42
|
+
test('transport and dependency failures are never disguised as business outcomes', () async {
|
|
43
|
+
final transport = StateError('offline');
|
|
44
|
+
await expectLater(nmsAdmissionDecision<int, String>(() async => throw transport, (_) => 'unused'), throwsA(same(transport)));
|
|
45
|
+
final blocked = NomosTypedOfferFailure(p.OfferResponse());
|
|
46
|
+
await expectLater(nmsAdmissionDecision<int, String>(() async => throw blocked, (_) => 'unused'), throwsA(same(blocked)));
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import 'dart:async';
|
|
2
|
+
import 'dart:typed_data';
|
|
3
|
+
|
|
4
|
+
import 'package:nomos_client/src/nms_generated_runtime.dart';
|
|
5
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as p;
|
|
6
|
+
import 'package:test/test.dart';
|
|
7
|
+
|
|
8
|
+
// The real Reception, with delayed typed replies instead of a network. No
|
|
9
|
+
// lifecycle decisions belong here; the shared office tests cover those.
|
|
10
|
+
final class _Office
|
|
11
|
+
with NmsPlatformCredentialProviderHost
|
|
12
|
+
implements PeerOfficeConnection {
|
|
13
|
+
final calls = <p.RuntimeRequest>[];
|
|
14
|
+
final replies = <Completer<p.RuntimeResponse>>[];
|
|
15
|
+
int _sequence = 0;
|
|
16
|
+
|
|
17
|
+
@override
|
|
18
|
+
Stream<p.RuntimeEvent> get runtimeEvents => const Stream.empty();
|
|
19
|
+
@override
|
|
20
|
+
int nextRuntimeRequestId() => ++_sequence;
|
|
21
|
+
|
|
22
|
+
@override
|
|
23
|
+
Future<p.RuntimeResponse> invokeRuntime(p.RuntimeRequest request) {
|
|
24
|
+
if (request.hasCloseRealm()) {
|
|
25
|
+
return Future.value(p.RuntimeResponse(
|
|
26
|
+
protocolVersion: 2,
|
|
27
|
+
requestId: request.requestId,
|
|
28
|
+
realmClosed: p.RealmClosed()));
|
|
29
|
+
}
|
|
30
|
+
expect(request.hasAdoptApplicationModel(), isTrue);
|
|
31
|
+
calls.add(request.deepCopy());
|
|
32
|
+
final reply = Completer<p.RuntimeResponse>();
|
|
33
|
+
replies.add(reply);
|
|
34
|
+
return reply.future;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
void finish(int index) => replies[index].complete(p.RuntimeResponse(
|
|
38
|
+
protocolVersion: 2,
|
|
39
|
+
requestId: calls[index].requestId,
|
|
40
|
+
applicationModelAdopted: p.ApplicationModelAdopted(
|
|
41
|
+
workspace: calls[index].adoptApplicationModel.session,
|
|
42
|
+
outcome: p.ModelAdoptionOutcome.MODEL_ADOPTION_OUTCOME_CURRENT)));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// A Reception whose office answers session binding as well, so a real
|
|
46
|
+
// `bindWorkspace` — the door the generated client opens a child through —
|
|
47
|
+
// can be driven end to end.
|
|
48
|
+
final class _BindingOffice
|
|
49
|
+
with NmsPlatformCredentialProviderHost
|
|
50
|
+
implements PeerOfficeConnection {
|
|
51
|
+
final adoptions = <p.RuntimeRequest>[];
|
|
52
|
+
final binds = <p.RuntimeRequest>[];
|
|
53
|
+
int _sequence = 0;
|
|
54
|
+
|
|
55
|
+
@override
|
|
56
|
+
Stream<p.RuntimeEvent> get runtimeEvents => const Stream.empty();
|
|
57
|
+
@override
|
|
58
|
+
int nextRuntimeRequestId() => ++_sequence;
|
|
59
|
+
|
|
60
|
+
@override
|
|
61
|
+
Future<p.RuntimeResponse> invokeRuntime(p.RuntimeRequest request) async {
|
|
62
|
+
if (request.hasCloseRealm()) {
|
|
63
|
+
return p.RuntimeResponse(
|
|
64
|
+
protocolVersion: 2,
|
|
65
|
+
requestId: request.requestId,
|
|
66
|
+
realmClosed: p.RealmClosed());
|
|
67
|
+
}
|
|
68
|
+
if (request.hasBindSession()) {
|
|
69
|
+
binds.add(request.deepCopy());
|
|
70
|
+
return p.RuntimeResponse(
|
|
71
|
+
protocolVersion: 2,
|
|
72
|
+
requestId: request.requestId,
|
|
73
|
+
sessionBound: p.SessionBound(
|
|
74
|
+
session: request.bindSession.session,
|
|
75
|
+
workspace: request.bindSession.workspace,
|
|
76
|
+
credentialCapability: 'capability:test'));
|
|
77
|
+
}
|
|
78
|
+
expect(request.hasAdoptApplicationModel(), isTrue);
|
|
79
|
+
adoptions.add(request.deepCopy());
|
|
80
|
+
return p.RuntimeResponse(
|
|
81
|
+
protocolVersion: 2,
|
|
82
|
+
requestId: request.requestId,
|
|
83
|
+
applicationModelAdopted: p.ApplicationModelAdopted(
|
|
84
|
+
workspace: request.adoptApplicationModel.session,
|
|
85
|
+
outcome: p.ModelAdoptionOutcome.MODEL_ADOPTION_OUTCOME_CURRENT));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
NomosApprovedModel _model([String digit = '1']) => NomosApprovedModel(
|
|
90
|
+
lawSha256: digit * 64,
|
|
91
|
+
reason: 'test release',
|
|
92
|
+
domainKeys: const ['test'],
|
|
93
|
+
loadPackage: () async => Uint8List(0));
|
|
94
|
+
|
|
95
|
+
Future<void> _settle() => Future<void>.delayed(Duration.zero);
|
|
96
|
+
|
|
97
|
+
DartReception _reception(_Office office, NomosApprovedModel model) {
|
|
98
|
+
final reception = DartReception(office)
|
|
99
|
+
..retainApprovedApplicationModel(model);
|
|
100
|
+
addTearDown(reception.dispose);
|
|
101
|
+
return reception;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
void main() {
|
|
105
|
+
test(
|
|
106
|
+
'repeated background requests share one invocation, not a cached verdict',
|
|
107
|
+
() async {
|
|
108
|
+
final office = _Office();
|
|
109
|
+
final reception = _reception(office, _model());
|
|
110
|
+
final session = reception.session('estate');
|
|
111
|
+
for (var count = 0; count < 20; count++) {
|
|
112
|
+
session.nomosInternalAdoptApplicationModelInBackground();
|
|
113
|
+
}
|
|
114
|
+
// Even another view of the same plane must not allocate another flight.
|
|
115
|
+
NomosSession(reception, 'estate')
|
|
116
|
+
.nomosInternalAdoptApplicationModelInBackground();
|
|
117
|
+
expect(office.calls, hasLength(1));
|
|
118
|
+
office.finish(0);
|
|
119
|
+
await _settle();
|
|
120
|
+
session.nomosInternalAdoptApplicationModelInBackground();
|
|
121
|
+
expect(office.calls, hasLength(2));
|
|
122
|
+
office.finish(1);
|
|
123
|
+
await _settle();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('failed transport releases the flight and keeps the failure observable',
|
|
127
|
+
() async {
|
|
128
|
+
final office = _Office();
|
|
129
|
+
final reception = _reception(office, _model());
|
|
130
|
+
final session = reception.session('estate');
|
|
131
|
+
session.nomosInternalAdoptApplicationModelInBackground();
|
|
132
|
+
office.replies[0].completeError(StateError('offline'));
|
|
133
|
+
await _settle();
|
|
134
|
+
expect(session.currentStatus.modelAdoption?.detail, contains('offline'));
|
|
135
|
+
session.nomosInternalAdoptApplicationModelInBackground();
|
|
136
|
+
expect(office.calls, hasLength(2));
|
|
137
|
+
office.finish(1);
|
|
138
|
+
await _settle();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('sessions, approved models and identity planes do not share flights',
|
|
142
|
+
() async {
|
|
143
|
+
final firstOffice = _Office();
|
|
144
|
+
final secondOffice = _Office();
|
|
145
|
+
final approved = _model();
|
|
146
|
+
final first = _reception(firstOffice, approved);
|
|
147
|
+
final second = _reception(secondOffice, approved);
|
|
148
|
+
first.session('estate').nomosInternalAdoptApplicationModelInBackground();
|
|
149
|
+
first.session('catalogue').nomosInternalAdoptApplicationModelInBackground();
|
|
150
|
+
second.session('estate').nomosInternalAdoptApplicationModelInBackground();
|
|
151
|
+
first.retainApprovedApplicationModel(_model('2'));
|
|
152
|
+
first.session('estate').nomosInternalAdoptApplicationModelInBackground();
|
|
153
|
+
expect(firstOffice.calls, hasLength(3));
|
|
154
|
+
expect(secondOffice.calls, hasLength(1));
|
|
155
|
+
expect(firstOffice.calls[2].adoptApplicationModel.lawSha256, '2' * 64);
|
|
156
|
+
for (var index = 0; index < 3; index++) {
|
|
157
|
+
firstOffice.finish(index);
|
|
158
|
+
}
|
|
159
|
+
secondOffice.finish(0);
|
|
160
|
+
await _settle();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test(
|
|
164
|
+
'an explicit adoption remains an independent request with its own result',
|
|
165
|
+
() async {
|
|
166
|
+
final office = _Office();
|
|
167
|
+
final session = _reception(office, _model()).session('estate');
|
|
168
|
+
session.nomosInternalAdoptApplicationModelInBackground();
|
|
169
|
+
final explicit = session.adoptApplicationModel(dryRun: true);
|
|
170
|
+
expect(office.calls, hasLength(2));
|
|
171
|
+
expect(office.calls[1].adoptApplicationModel.dryRun, isTrue);
|
|
172
|
+
office.finish(1);
|
|
173
|
+
expect((await explicit).outcome, NomosModelAdoptionOutcome.current);
|
|
174
|
+
office.finish(0);
|
|
175
|
+
await _settle();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// THE OLD-LAW CHILD BOOTSTRAP TRIGGER (nomos2 architecture/old_law_child_bootstrap.md).
|
|
179
|
+
// A child whose law predates the parent-directed acceptor refuses every new device and has
|
|
180
|
+
// no operator door; the only key it accepts belongs to a device already seated in it. So
|
|
181
|
+
// BINDING the child IS the trigger, and it must not gate the caller on the outcome.
|
|
182
|
+
test('opening a child offers it to the model-adoption desk, once', () async {
|
|
183
|
+
final office = _BindingOffice();
|
|
184
|
+
final reception = DartReception(office)
|
|
185
|
+
..retainApprovedApplicationModel(_model());
|
|
186
|
+
addTearDown(reception.dispose);
|
|
187
|
+
|
|
188
|
+
final child = await reception.bindWorkspace('estate-9c3d');
|
|
189
|
+
await _settle();
|
|
190
|
+
expect(office.binds.single.bindSession.workspace, 'estate-9c3d');
|
|
191
|
+
expect(office.adoptions.single.adoptApplicationModel.session, 'estate-9c3d');
|
|
192
|
+
expect(office.adoptions.single.adoptApplicationModel.dryRun, isFalse);
|
|
193
|
+
|
|
194
|
+
// Re-opening an already-bound child neither re-binds nor re-offers: the
|
|
195
|
+
// transition runs once, and afterwards the workspace carries the acceptor.
|
|
196
|
+
expect(identical(await reception.bindWorkspace('estate-9c3d'), child), isTrue);
|
|
197
|
+
await _settle();
|
|
198
|
+
expect(office.binds, hasLength(1));
|
|
199
|
+
expect(office.adoptions, hasLength(1));
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('a plane with no approved model opens children without offering any',
|
|
203
|
+
() async {
|
|
204
|
+
final office = _BindingOffice();
|
|
205
|
+
final reception = DartReception(office);
|
|
206
|
+
addTearDown(reception.dispose);
|
|
207
|
+
await reception.bindWorkspace('estate-9c3d');
|
|
208
|
+
await _settle();
|
|
209
|
+
expect(office.binds, hasLength(1));
|
|
210
|
+
expect(office.adoptions, isEmpty);
|
|
211
|
+
});
|
|
212
|
+
// Measured on a real device: only 2 of 8 workspace bindings went through `bindWorkspace`.
|
|
213
|
+
// A child reached by a foreign read or a credential-less follow binds through the raw
|
|
214
|
+
// primitive, and a trigger on `bindWorkspace` skipped exactly those — including the estate
|
|
215
|
+
// whose refusal started this work.
|
|
216
|
+
test('a child bound through the raw primitive is offered too, not only bindWorkspace',
|
|
217
|
+
() async {
|
|
218
|
+
final office = _BindingOffice();
|
|
219
|
+
final reception = DartReception(office)
|
|
220
|
+
..retainApprovedApplicationModel(_model());
|
|
221
|
+
addTearDown(reception.dispose);
|
|
222
|
+
|
|
223
|
+
await reception.bindSession(
|
|
224
|
+
workspace: 'estate-9c3d', session: 'estate-9c3d');
|
|
225
|
+
await _settle();
|
|
226
|
+
expect(office.adoptions.single.adoptApplicationModel.session, 'estate-9c3d');
|
|
227
|
+
});
|
|
228
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// THE VERSION-FLOOR TRIPWIRE: the @githolon/
|
|
1
|
+
// THE VERSION-FLOOR TRIPWIRE: the @githolon/runtime built by this checkout must satisfy the minimum
|
|
2
2
|
// runner version declared by nomos_client. Changesets may advance the npm package metadata without
|
|
3
3
|
// changing its protocol, so a newer runner is valid; an older runner than the Dart floor is not.
|
|
4
4
|
import 'dart:convert';
|
|
@@ -8,8 +8,8 @@ import 'package:nomos_client/src/nms_conformance_runtime.dart';
|
|
|
8
8
|
import 'package:test/test.dart';
|
|
9
9
|
|
|
10
10
|
void main() {
|
|
11
|
-
test('kExpectedRunnerBundleVersion pairs with
|
|
12
|
-
final f = File('${Directory.current.path}/../../
|
|
11
|
+
test('kExpectedRunnerBundleVersion pairs with peer-office/package/package.json', () {
|
|
12
|
+
final f = File('${Directory.current.path}/../../peer-office/package/package.json');
|
|
13
13
|
if (!f.existsSync()) {
|
|
14
14
|
markTestSkipped('monorepo layout not present (published-package test run)');
|
|
15
15
|
return;
|
|
@@ -23,7 +23,7 @@ void main() {
|
|
|
23
23
|
(runner[1] > floor[1] ||
|
|
24
24
|
(runner[1] == floor[1] && runner[2] >= floor[2])));
|
|
25
25
|
expect(runnerMeetsFloor, isTrue,
|
|
26
|
-
reason: '@githolon/
|
|
26
|
+
reason: '@githolon/runtime $v is older than nomos_client runner floor '
|
|
27
27
|
'$kExpectedRunnerBundleVersion');
|
|
28
28
|
});
|
|
29
29
|
}
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import 'dart:async';
|
|
2
|
+
import 'dart:convert';
|
|
3
|
+
import 'dart:typed_data';
|
|
4
|
+
|
|
5
|
+
import 'package:nomos_client/src/nms_generated_runtime.dart';
|
|
6
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
|
|
7
|
+
import 'package:test/test.dart';
|
|
8
|
+
|
|
9
|
+
final class _RepairTransport
|
|
10
|
+
with NmsPlatformCredentialProviderHost
|
|
11
|
+
implements
|
|
12
|
+
PeerOfficeConnection,
|
|
13
|
+
NomosKernelCallTransport,
|
|
14
|
+
NomosRuntimeV2EventTransport {
|
|
15
|
+
final _runtimeEvents = StreamController<protocol.RuntimeEvent>.broadcast();
|
|
16
|
+
protocol.CallRequest? lastCall;
|
|
17
|
+
protocol.CredentialUse? lastCredentialUse;
|
|
18
|
+
var providerToken = protocol.ProviderTokenVerificationStatus
|
|
19
|
+
.PROVIDER_TOKEN_VERIFICATION_STATUS_NO_MATCHING_KEY;
|
|
20
|
+
var enrolledDevice = true;
|
|
21
|
+
var resolvedPrincipal = 'user:jack';
|
|
22
|
+
var syncCalls = 0;
|
|
23
|
+
final syncSessions = <String>[];
|
|
24
|
+
var offerCalls = 0;
|
|
25
|
+
var expireNextSessionOffer = false;
|
|
26
|
+
var refuseSignerEnrollmentOffers = 0;
|
|
27
|
+
var refuseKernelSignerOffers = 0;
|
|
28
|
+
String kernelRefusalActor = 'unenrolled-signer-key';
|
|
29
|
+
final refusedWorkspaces = <String>[];
|
|
30
|
+
final refreshedTokens = <String>[];
|
|
31
|
+
final authenticationRequests = <protocol.AwaitAuthenticationRequest>[];
|
|
32
|
+
protocol.AuthenticationReadiness? authenticationResult;
|
|
33
|
+
|
|
34
|
+
_RepairTransport() {
|
|
35
|
+
scheduleMicrotask(
|
|
36
|
+
() => _runtimeEvents.add(
|
|
37
|
+
protocol.RuntimeEvent(
|
|
38
|
+
ready: protocol.RuntimeReady(runtimeVersion: 'unbundled-dev'),
|
|
39
|
+
),
|
|
40
|
+
),
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@override
|
|
45
|
+
Stream<protocol.RuntimeEvent> get runtimeEvents => _runtimeEvents.stream;
|
|
46
|
+
|
|
47
|
+
void pushRuntime(protocol.RuntimeEvent event) => _runtimeEvents.add(event);
|
|
48
|
+
|
|
49
|
+
int _requests = 0;
|
|
50
|
+
@override
|
|
51
|
+
int nextRuntimeRequestId() => ++_requests;
|
|
52
|
+
|
|
53
|
+
@override
|
|
54
|
+
Future<protocol.RuntimeResponse> invokeRuntime(
|
|
55
|
+
protocol.RuntimeRequest request,
|
|
56
|
+
) async {
|
|
57
|
+
if (request.hasAwaitAuthentication()) {
|
|
58
|
+
authenticationRequests.add(request.awaitAuthentication.deepCopy());
|
|
59
|
+
return protocol.RuntimeResponse(
|
|
60
|
+
protocolVersion: 2,
|
|
61
|
+
requestId: request.requestId,
|
|
62
|
+
authenticationReadiness: authenticationResult ??
|
|
63
|
+
protocol.AuthenticationReadiness(
|
|
64
|
+
outcome: protocol.AuthenticationReadinessOutcome
|
|
65
|
+
.AUTHENTICATION_READINESS_OUTCOME_READY,
|
|
66
|
+
posture: protocol.AuthenticationPostureResponse(
|
|
67
|
+
providerToken: protocol.ProviderTokenVerificationStatus
|
|
68
|
+
.PROVIDER_TOKEN_VERIFICATION_STATUS_VALID,
|
|
69
|
+
enrolledDevice: true,
|
|
70
|
+
resolvedPrincipal: 'user:jack',
|
|
71
|
+
),
|
|
72
|
+
),
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (request.hasBindSession()) {
|
|
76
|
+
return protocol.RuntimeResponse(
|
|
77
|
+
protocolVersion: 2,
|
|
78
|
+
requestId: request.requestId,
|
|
79
|
+
sessionBound: protocol.SessionBound(
|
|
80
|
+
workspace: request.bindSession.workspace,
|
|
81
|
+
session: request.bindSession.hasSession()
|
|
82
|
+
? request.bindSession.session
|
|
83
|
+
: request.bindSession.workspace,
|
|
84
|
+
credentialCapability: request.bindSession.hasSession()
|
|
85
|
+
? request.bindSession.session
|
|
86
|
+
: request.bindSession.workspace,
|
|
87
|
+
),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (request.hasSyncWorkspace()) {
|
|
91
|
+
syncCalls += 1;
|
|
92
|
+
syncSessions.add(request.syncWorkspace.workspace);
|
|
93
|
+
providerToken = protocol.ProviderTokenVerificationStatus
|
|
94
|
+
.PROVIDER_TOKEN_VERIFICATION_STATUS_VALID;
|
|
95
|
+
return protocol.RuntimeResponse(
|
|
96
|
+
protocolVersion: 2,
|
|
97
|
+
requestId: request.requestId,
|
|
98
|
+
syncWorkspace: protocol.SyncWorkspaceResponse(
|
|
99
|
+
workspace: request.syncWorkspace.workspace,
|
|
100
|
+
),
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
if (request.hasRefreshCredentials()) {
|
|
104
|
+
refreshedTokens.add(utf8.decode(request.refreshCredentials.authToken));
|
|
105
|
+
return protocol.RuntimeResponse(
|
|
106
|
+
protocolVersion: 2,
|
|
107
|
+
requestId: request.requestId,
|
|
108
|
+
credentialsRefreshed: protocol.CredentialsRefreshed(
|
|
109
|
+
session: request.refreshCredentials.session,
|
|
110
|
+
hasAuthToken: request.refreshCredentials.authToken.isNotEmpty,
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return protocol.RuntimeResponse(
|
|
115
|
+
protocolVersion: 2,
|
|
116
|
+
requestId: request.requestId,
|
|
117
|
+
realmClosed: protocol.RealmClosed(),
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@override
|
|
122
|
+
Future<protocol.CallResponse> callKernel({
|
|
123
|
+
required String session,
|
|
124
|
+
required protocol.CallRequest request,
|
|
125
|
+
protocol.CredentialUse credentialUse =
|
|
126
|
+
protocol.CredentialUse.CREDENTIAL_USE_UNSPECIFIED,
|
|
127
|
+
}) async {
|
|
128
|
+
lastCall = request.deepCopy();
|
|
129
|
+
lastCredentialUse = credentialUse;
|
|
130
|
+
if (request.hasQuery() && request.query.hasAuthenticationPosture()) {
|
|
131
|
+
return protocol.CallResponse(
|
|
132
|
+
protocolVersion: 1,
|
|
133
|
+
requestId: request.requestId,
|
|
134
|
+
query: protocol.QueryResponse(
|
|
135
|
+
authenticationPosture: protocol.AuthenticationPostureResponse(
|
|
136
|
+
providerToken: providerToken,
|
|
137
|
+
enrolledDevice: enrolledDevice,
|
|
138
|
+
resolvedPrincipal: resolvedPrincipal,
|
|
139
|
+
),
|
|
140
|
+
),
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
if (request.hasOffer()) {
|
|
144
|
+
offerCalls += 1;
|
|
145
|
+
if (refuseKernelSignerOffers > 0) {
|
|
146
|
+
refuseKernelSignerOffers -= 1;
|
|
147
|
+
return protocol.CallResponse(
|
|
148
|
+
protocolVersion: 1,
|
|
149
|
+
requestId: request.requestId,
|
|
150
|
+
offer: protocol.OfferResponse(
|
|
151
|
+
refused: protocol.OfferRefused(
|
|
152
|
+
refusal: protocol.KernelRefusal(
|
|
153
|
+
code: protocol
|
|
154
|
+
.KernelRefusalCode.KERNEL_REFUSAL_CODE_FORGED_PRINCIPAL,
|
|
155
|
+
terminal: true,
|
|
156
|
+
message: 'verified author is $kernelRefusalActor',
|
|
157
|
+
details: protocol.NomosStruct(fields: [
|
|
158
|
+
MapEntry(
|
|
159
|
+
'actor', protocol.NomosValue(text: kernelRefusalActor)),
|
|
160
|
+
MapEntry('claimed', protocol.NomosValue(text: 'user:jack')),
|
|
161
|
+
]),
|
|
162
|
+
),
|
|
163
|
+
),
|
|
164
|
+
),
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
if (refuseSignerEnrollmentOffers > 0) {
|
|
168
|
+
refuseSignerEnrollmentOffers -= 1;
|
|
169
|
+
refusedWorkspaces.add(session);
|
|
170
|
+
return protocol.CallResponse(
|
|
171
|
+
protocolVersion: 1,
|
|
172
|
+
requestId: request.requestId,
|
|
173
|
+
failure: protocol.Failure(
|
|
174
|
+
code: 'nomos.authz.signer_enrollment_missing',
|
|
175
|
+
message:
|
|
176
|
+
'auth: ordinary user offers require both a valid provider token and an active enrolled device',
|
|
177
|
+
),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
if (expireNextSessionOffer &&
|
|
181
|
+
credentialUse == protocol.CredentialUse.CREDENTIAL_USE_SESSION) {
|
|
182
|
+
expireNextSessionOffer = false;
|
|
183
|
+
return protocol.CallResponse(
|
|
184
|
+
protocolVersion: 1,
|
|
185
|
+
requestId: request.requestId,
|
|
186
|
+
failure: protocol.Failure(
|
|
187
|
+
code: 'nomos.auth.token_expired',
|
|
188
|
+
message: 'auth: token verification failed (Expired)',
|
|
189
|
+
),
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return protocol.CallResponse(
|
|
194
|
+
protocolVersion: 1,
|
|
195
|
+
requestId: request.requestId,
|
|
196
|
+
offer: protocol.OfferResponse(
|
|
197
|
+
admitted: protocol.OfferAdmitted(
|
|
198
|
+
head: 'HEAD-offer',
|
|
199
|
+
intentId: 'intent-offer',
|
|
200
|
+
),
|
|
201
|
+
),
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
NmsGeneratedCall _estateCall() => NmsInternalGeneratedCalls.create(
|
|
207
|
+
domain: 'estate',
|
|
208
|
+
directive: 'ensureSiteComplianceFolders',
|
|
209
|
+
protobufType:
|
|
210
|
+
'type.nomos.dev/nomos.operation.v1.Payload_ensure_site_compliance_test',
|
|
211
|
+
protobufPayload: Uint8List.fromList([1]),
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
Future<DartReception> _connected(_RepairTransport transport) async {
|
|
215
|
+
final bridge = DartReception(transport);
|
|
216
|
+
await bridge.ready;
|
|
217
|
+
await bridge.bindSession(workspace: 'home', session: 'home');
|
|
218
|
+
await bridge.session('home').refreshCredentials(authToken: 'token');
|
|
219
|
+
await bridge.bindSession(
|
|
220
|
+
workspace: 'estate-1',
|
|
221
|
+
session: 'estate-1',
|
|
222
|
+
);
|
|
223
|
+
await bridge.session('estate-1').refreshCredentials(authToken: 'token');
|
|
224
|
+
return bridge;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
void main() {
|
|
228
|
+
test('kernel unenrolled signer refusal repairs the child and retries once',
|
|
229
|
+
() async {
|
|
230
|
+
final transport = _RepairTransport()..refuseKernelSignerOffers = 1;
|
|
231
|
+
final bridge = await _connected(transport);
|
|
232
|
+
addTearDown(bridge.dispose);
|
|
233
|
+
final repairs = <ParentSignerEnrollmentMissing>[];
|
|
234
|
+
bridge.adoptChildAuthenticationRepair((refusal) async {
|
|
235
|
+
repairs.add(refusal);
|
|
236
|
+
return true;
|
|
237
|
+
});
|
|
238
|
+
await bridge
|
|
239
|
+
.session('estate-1')
|
|
240
|
+
.offerGeneratedCallOutcome(call: _estateCall());
|
|
241
|
+
expect(transport.offerCalls, 2);
|
|
242
|
+
expect(repairs.single.workspace, 'estate-1');
|
|
243
|
+
expect(repairs.single.actor, 'user:jack');
|
|
244
|
+
expect(repairs.single.directiveId, 'ensureSiteComplianceFolders');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('other forged-principal refusals never trigger child repair', () async {
|
|
248
|
+
final transport = _RepairTransport()
|
|
249
|
+
..refuseKernelSignerOffers = 1
|
|
250
|
+
..kernelRefusalActor = 'different-author';
|
|
251
|
+
final bridge = await _connected(transport);
|
|
252
|
+
addTearDown(bridge.dispose);
|
|
253
|
+
var repairs = 0;
|
|
254
|
+
bridge.adoptChildAuthenticationRepair((_) async {
|
|
255
|
+
repairs += 1;
|
|
256
|
+
return true;
|
|
257
|
+
});
|
|
258
|
+
await expectLater(
|
|
259
|
+
bridge.session('estate-1').offerGeneratedCallOutcome(call: _estateCall()),
|
|
260
|
+
throwsA(isA<NomosTypedOfferFailure>()),
|
|
261
|
+
);
|
|
262
|
+
expect(repairs, 0);
|
|
263
|
+
expect(transport.offerCalls, 1);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test('failed kernel signer repair preserves the original refusal', () async {
|
|
267
|
+
final transport = _RepairTransport()..refuseKernelSignerOffers = 1;
|
|
268
|
+
final bridge = await _connected(transport);
|
|
269
|
+
addTearDown(bridge.dispose);
|
|
270
|
+
bridge.adoptChildAuthenticationRepair((_) async => false);
|
|
271
|
+
await expectLater(
|
|
272
|
+
bridge.session('estate-1').offerGeneratedCallOutcome(call: _estateCall()),
|
|
273
|
+
throwsA(isA<NomosTypedOfferFailure>()),
|
|
274
|
+
);
|
|
275
|
+
expect(transport.offerCalls, 1);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test('a registered repair that seats the key makes the offer succeed once',
|
|
279
|
+
() async {
|
|
280
|
+
final transport = _RepairTransport()..refuseSignerEnrollmentOffers = 1;
|
|
281
|
+
final bridge = await _connected(transport);
|
|
282
|
+
addTearDown(bridge.dispose);
|
|
283
|
+
final repairs = <ParentSignerEnrollmentMissing>[];
|
|
284
|
+
bridge.session('home').adoptChildAuthenticationRepair((refusal) async {
|
|
285
|
+
repairs.add(refusal);
|
|
286
|
+
return true;
|
|
287
|
+
});
|
|
288
|
+
final outcome = await bridge
|
|
289
|
+
.session('estate-1')
|
|
290
|
+
.offerGeneratedCallOutcome(call: _estateCall());
|
|
291
|
+
expect(outcome, isNotNull);
|
|
292
|
+
expect(transport.offerCalls, 2);
|
|
293
|
+
expect(repairs, hasLength(1));
|
|
294
|
+
expect(repairs.single.workspace, 'estate-1');
|
|
295
|
+
expect(repairs.single.code, 'nomos.authz.signer_enrollment_missing');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test('a repair that cannot seat the key surfaces the original refusal',
|
|
299
|
+
() async {
|
|
300
|
+
final transport = _RepairTransport()..refuseSignerEnrollmentOffers = 1;
|
|
301
|
+
final bridge = await _connected(transport);
|
|
302
|
+
addTearDown(bridge.dispose);
|
|
303
|
+
bridge.adoptChildAuthenticationRepair((_) async => false);
|
|
304
|
+
await expectLater(
|
|
305
|
+
bridge.session('estate-1').offerGeneratedCallOutcome(call: _estateCall()),
|
|
306
|
+
throwsA(isA<ParentSignerEnrollmentMissing>()
|
|
307
|
+
.having((error) => error.workspace, 'workspace', 'estate-1')),
|
|
308
|
+
);
|
|
309
|
+
expect(transport.offerCalls, 1);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test('a repair that throws never masks the refusal or retries', () async {
|
|
313
|
+
final transport = _RepairTransport()..refuseSignerEnrollmentOffers = 1;
|
|
314
|
+
final bridge = await _connected(transport);
|
|
315
|
+
addTearDown(bridge.dispose);
|
|
316
|
+
bridge.adoptChildAuthenticationRepair(
|
|
317
|
+
(_) async => throw StateError('home is offline'),
|
|
318
|
+
);
|
|
319
|
+
await expectLater(
|
|
320
|
+
bridge.session('estate-1').offerGeneratedCallOutcome(call: _estateCall()),
|
|
321
|
+
throwsA(isA<ParentSignerEnrollmentMissing>()),
|
|
322
|
+
);
|
|
323
|
+
expect(transport.offerCalls, 1);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test('without a registration the refusal is untouched', () async {
|
|
327
|
+
final transport = _RepairTransport()..refuseSignerEnrollmentOffers = 1;
|
|
328
|
+
final bridge = await _connected(transport);
|
|
329
|
+
addTearDown(bridge.dispose);
|
|
330
|
+
await expectLater(
|
|
331
|
+
bridge.session('estate-1').offerGeneratedCallOutcome(call: _estateCall()),
|
|
332
|
+
throwsA(isA<ParentSignerEnrollmentMissing>()),
|
|
333
|
+
);
|
|
334
|
+
expect(transport.offerCalls, 1);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test('concurrent refusals for one workspace share a single repair', () async {
|
|
338
|
+
final transport = _RepairTransport()..refuseSignerEnrollmentOffers = 2;
|
|
339
|
+
final bridge = await _connected(transport);
|
|
340
|
+
addTearDown(bridge.dispose);
|
|
341
|
+
var repairs = 0;
|
|
342
|
+
final gate = Completer<bool>();
|
|
343
|
+
bridge.adoptChildAuthenticationRepair((_) {
|
|
344
|
+
repairs += 1;
|
|
345
|
+
return gate.future;
|
|
346
|
+
});
|
|
347
|
+
final first = bridge
|
|
348
|
+
.session('estate-1')
|
|
349
|
+
.offerGeneratedCallOutcome(call: _estateCall());
|
|
350
|
+
final second = bridge
|
|
351
|
+
.session('estate-1')
|
|
352
|
+
.offerGeneratedCallOutcome(call: _estateCall());
|
|
353
|
+
await Future<void>.delayed(Duration.zero);
|
|
354
|
+
gate.complete(true);
|
|
355
|
+
await Future.wait([first, second]);
|
|
356
|
+
expect(repairs, 1);
|
|
357
|
+
expect(transport.offerCalls, 4);
|
|
358
|
+
});
|
|
359
|
+
}
|