@githolon/dsl 0.108.7 → 0.110.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +80 -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 +214 -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 +268 -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 +23950 -15776
- 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 +550 -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_event_desk_close_test.dart +16 -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 +1276 -0
- package/src/codegen_call_usda.ts +49 -6
- package/src/codegen_dart.ts +83 -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 +2017 -1195
- 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,419 @@
|
|
|
1
|
+
import 'package:nomos_client/src/sync_status_projection.dart';
|
|
2
|
+
import 'package:nomos_client/src/sync_status_reducer.dart';
|
|
3
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
|
|
4
|
+
import 'package:fixnum/fixnum.dart' as fixnum;
|
|
5
|
+
import 'package:test/test.dart';
|
|
6
|
+
|
|
7
|
+
void main() {
|
|
8
|
+
final at = DateTime.utc(2026, 9, 7);
|
|
9
|
+
SyncStatus reduce(protocol.SyncWorkspaceResponse result,
|
|
10
|
+
[SyncStatus current = const SyncStatus()]) =>
|
|
11
|
+
reduceSyncCompletion(current, protocol.SyncCompleted(result: result), at);
|
|
12
|
+
|
|
13
|
+
test('cold-start status is unknown, not current with zero debt', () {
|
|
14
|
+
const status = SyncStatus();
|
|
15
|
+
expect(status.phase, SyncPhase.checking);
|
|
16
|
+
expect(status.ahead, isNull);
|
|
17
|
+
expect(status.lastSyncedAt, isNull);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('read-only transfer completion never invents unsaved work or currency',
|
|
21
|
+
() {
|
|
22
|
+
for (final kind in ['full', 'delta', 'push']) {
|
|
23
|
+
final settled =
|
|
24
|
+
reduce(protocol.SyncWorkspaceResponse(currentHead: 'head'));
|
|
25
|
+
final started = reduceTransferProgress(
|
|
26
|
+
settled, TransferProgress(received: 4, total: 10, kind: kind));
|
|
27
|
+
expect(started.phase, SyncPhase.checking);
|
|
28
|
+
expect(started.presentationPhase, SyncPhase.transferring);
|
|
29
|
+
final done = reduceTransferProgress(
|
|
30
|
+
started, TransferProgress(received: 10, total: 10, kind: kind));
|
|
31
|
+
expect(done.phase, SyncPhase.checking);
|
|
32
|
+
expect(done.presentationPhase, SyncPhase.checking);
|
|
33
|
+
expect(done.transfer, isNull);
|
|
34
|
+
expect(done.ahead, isNull);
|
|
35
|
+
expect(done.lastSyncedAt, at);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test(
|
|
40
|
+
'transfer overlay preserves actual local work, failure and offline state',
|
|
41
|
+
() {
|
|
42
|
+
for (final phase in [
|
|
43
|
+
SyncPhase.syncing,
|
|
44
|
+
SyncPhase.error,
|
|
45
|
+
SyncPhase.offline
|
|
46
|
+
]) {
|
|
47
|
+
final initial =
|
|
48
|
+
SyncStatus(phase: phase, lastError: 'retained explanation');
|
|
49
|
+
final started = reduceTransferProgress(
|
|
50
|
+
initial, const TransferProgress(received: 4, total: 10));
|
|
51
|
+
final done = reduceTransferProgress(
|
|
52
|
+
started, const TransferProgress(received: 10, total: 10));
|
|
53
|
+
expect(done.phase, phase);
|
|
54
|
+
expect(done.lastError, 'retained explanation');
|
|
55
|
+
}
|
|
56
|
+
final admittedDuringTransfer = locallyAdmitted(reduceTransferProgress(
|
|
57
|
+
const SyncStatus(), const TransferProgress(received: 1, total: 10)));
|
|
58
|
+
expect(
|
|
59
|
+
reduceTransferProgress(admittedDuringTransfer,
|
|
60
|
+
const TransferProgress(received: 10, total: 10))
|
|
61
|
+
.phase,
|
|
62
|
+
SyncPhase.syncing);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('aggregate progress clears to unknown rather than manufactured saving',
|
|
66
|
+
() async {
|
|
67
|
+
final projection = NmsSyncStatusProjection();
|
|
68
|
+
addTearDown(projection.close);
|
|
69
|
+
projection.pushSession(
|
|
70
|
+
'catalogue',
|
|
71
|
+
reduceTransferProgress(
|
|
72
|
+
const SyncStatus(), const TransferProgress(received: 1, total: 2)));
|
|
73
|
+
projection.foldApplication(['catalogue']);
|
|
74
|
+
expect(projection.applicationStatus.phase, SyncPhase.transferring);
|
|
75
|
+
final completed = reduceTransferProgress(projection.applicationStatus,
|
|
76
|
+
const TransferProgress(received: 2, total: 2));
|
|
77
|
+
expect(completed.phase, SyncPhase.checking);
|
|
78
|
+
expect(completed.transfer, isNull);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('unsettled runtime result reconstructs pending without callback counts',
|
|
82
|
+
() {
|
|
83
|
+
final status = reduce(protocol.SyncWorkspaceResponse(stillAhead: true));
|
|
84
|
+
expect(status.phase, SyncPhase.syncing);
|
|
85
|
+
expect(status.ahead, isNull);
|
|
86
|
+
expect(status.lastSyncedAt, isNull);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('only a settled result establishes zero and the successful-sync time',
|
|
90
|
+
() {
|
|
91
|
+
final status = reduce(protocol.SyncWorkspaceResponse(currentHead: 'head'));
|
|
92
|
+
expect(status.phase, SyncPhase.idle);
|
|
93
|
+
expect(status.ahead, 0);
|
|
94
|
+
expect(status.behind, 0);
|
|
95
|
+
expect(status.saved, isNull);
|
|
96
|
+
expect(status.onMain, isNull);
|
|
97
|
+
expect(status.lastSyncedAt, at);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('unsealed remote admission is not unsent local work or confirmed sync',
|
|
101
|
+
() async {
|
|
102
|
+
final pending = protocol.SyncWorkspaceResponse(
|
|
103
|
+
currentHead: 'shared-head',
|
|
104
|
+
stillAhead: true,
|
|
105
|
+
offerReceipt: protocol.CustodyOfferReceipt(
|
|
106
|
+
admission: protocol.CustodyOfferAdmissionReceipt(
|
|
107
|
+
main: 'shared-head', sealedMain: 'older-head')),
|
|
108
|
+
);
|
|
109
|
+
final status = reduce(pending);
|
|
110
|
+
expect(status.phase, SyncPhase.awaitingRemoteDurability);
|
|
111
|
+
expect(status.ahead, isNull);
|
|
112
|
+
expect(status.lastSyncedAt, isNull);
|
|
113
|
+
final projection = NmsSyncStatusProjection();
|
|
114
|
+
addTearDown(projection.close);
|
|
115
|
+
projection.pushSession('catalogue', status);
|
|
116
|
+
projection.foldApplication(['catalogue']);
|
|
117
|
+
expect(
|
|
118
|
+
projection.applicationStatus.phase, SyncPhase.awaitingRemoteDurability);
|
|
119
|
+
pending.currentHead = 'newer-local-head';
|
|
120
|
+
expect(reduce(pending).phase, SyncPhase.syncing);
|
|
121
|
+
pending.currentHead = 'shared-head';
|
|
122
|
+
pending.stillAhead = false;
|
|
123
|
+
pending.offerReceipt.admission.sealedMain = 'shared-head';
|
|
124
|
+
expect(reduce(pending).phase, SyncPhase.idle);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test(
|
|
128
|
+
'local admission invalidates settled counts without maintaining a mirror',
|
|
129
|
+
() {
|
|
130
|
+
final current = reduce(protocol.SyncWorkspaceResponse(currentHead: 'head'));
|
|
131
|
+
final status = locallyAdmitted(locallyAdmitted(current));
|
|
132
|
+
expect(status.phase, SyncPhase.syncing);
|
|
133
|
+
expect(status.ahead, isNull);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test(
|
|
137
|
+
'offline preserves the last successful time and exposes discovery failure',
|
|
138
|
+
() {
|
|
139
|
+
final earlier = DateTime.utc(2026, 9, 6);
|
|
140
|
+
final status = reduce(
|
|
141
|
+
protocol.SyncWorkspaceResponse(
|
|
142
|
+
offline: true,
|
|
143
|
+
stillAhead: true,
|
|
144
|
+
discoveryError:
|
|
145
|
+
protocol.RuntimeTransportFailure(message: 'DNS unavailable'),
|
|
146
|
+
),
|
|
147
|
+
SyncStatus(lastSyncedAt: earlier));
|
|
148
|
+
expect(status.phase, SyncPhase.offline);
|
|
149
|
+
expect(status.lastSyncedAt, earlier);
|
|
150
|
+
expect(status.lastError, 'DNS unavailable');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('push refusal remains attention even with no local callback counter',
|
|
154
|
+
() {
|
|
155
|
+
final status = reduce(protocol.SyncWorkspaceResponse(
|
|
156
|
+
pushFailed: true,
|
|
157
|
+
stillAhead: true,
|
|
158
|
+
pushError: protocol.RuntimeTransportFailure(message: 'Offer refused'),
|
|
159
|
+
));
|
|
160
|
+
expect(status.phase, SyncPhase.error);
|
|
161
|
+
expect(status.lastError, 'Offer refused');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test(
|
|
165
|
+
'terminal reconciliation failure preserves prior receipt and observation age',
|
|
166
|
+
() {
|
|
167
|
+
final earlier = DateTime.utc(2026, 9, 16, 16);
|
|
168
|
+
final receipt =
|
|
169
|
+
protocol.SyncWorkspaceResponse(currentHead: 'confirmed-head');
|
|
170
|
+
final current = SyncStatus(
|
|
171
|
+
phase: SyncPhase.syncing, lastSyncedAt: earlier, lastResult: receipt);
|
|
172
|
+
final failed = reduceSyncCompletion(
|
|
173
|
+
current,
|
|
174
|
+
protocol.SyncCompleted(
|
|
175
|
+
failure: protocol.RuntimeFailure(
|
|
176
|
+
code: 'nomos.runtime.sync_failed',
|
|
177
|
+
message: 'workspace-sync-1: reconciliation failed'),
|
|
178
|
+
),
|
|
179
|
+
DateTime.utc(2026, 9, 16, 17));
|
|
180
|
+
expect(failed.phase, SyncPhase.error);
|
|
181
|
+
expect(failed.lastSyncedAt, earlier);
|
|
182
|
+
expect(identical(failed.lastResult, receipt), isTrue);
|
|
183
|
+
expect(failed.lastError, contains('workspace-sync-1'));
|
|
184
|
+
expect(failed.transfer, isNull);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('local-only and reconnect outcomes are not cloud convergence', () {
|
|
188
|
+
for (final result in [
|
|
189
|
+
protocol.SyncWorkspaceResponse(localOnly: true),
|
|
190
|
+
protocol.SyncWorkspaceResponse(reconnectRequired: true),
|
|
191
|
+
]) {
|
|
192
|
+
expect(reduce(result).phase, SyncPhase.checking);
|
|
193
|
+
expect(reduce(result).lastSyncedAt, isNull);
|
|
194
|
+
}
|
|
195
|
+
expect(
|
|
196
|
+
reduce(protocol.SyncWorkspaceResponse(directOfferDeferred: true)).phase,
|
|
197
|
+
SyncPhase.syncing);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('deferred admission preserves the remote explanation', () {
|
|
201
|
+
final status = reduce(protocol.SyncWorkspaceResponse(
|
|
202
|
+
stillAhead: true,
|
|
203
|
+
offerReceipt: protocol.CustodyOfferReceipt(
|
|
204
|
+
admission: protocol.CustodyOfferAdmissionReceipt(sessions: [
|
|
205
|
+
protocol.CustodySessionAdmission(intents: [
|
|
206
|
+
protocol.CustodyIntentAdmission(
|
|
207
|
+
deadLettered: protocol.CustodyIntentDeadLettered(
|
|
208
|
+
message: 'Required source workspace is unavailable',
|
|
209
|
+
),
|
|
210
|
+
),
|
|
211
|
+
]),
|
|
212
|
+
]),
|
|
213
|
+
),
|
|
214
|
+
));
|
|
215
|
+
expect(status.phase, SyncPhase.error);
|
|
216
|
+
expect(status.lastError, 'Required source workspace is unavailable');
|
|
217
|
+
expect(status.ahead, isNull);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('blocked custody offer names the remote dependency', () {
|
|
221
|
+
final status = reduce(protocol.SyncWorkspaceResponse(
|
|
222
|
+
stillAhead: true,
|
|
223
|
+
directOfferDeferred: true,
|
|
224
|
+
offerReceipt: protocol.CustodyOfferReceipt(
|
|
225
|
+
blocked: protocol.RuntimeBlocked(
|
|
226
|
+
siblings: protocol.SiblingResidenciesRequired(residencies: [
|
|
227
|
+
protocol.SiblingResidencyRequired(workspace: 'catalogue-source'),
|
|
228
|
+
]),
|
|
229
|
+
),
|
|
230
|
+
),
|
|
231
|
+
));
|
|
232
|
+
expect(status.phase, SyncPhase.error);
|
|
233
|
+
expect(status.lastError, contains('catalogue-source'));
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test('typed offer failure cannot be presented as ordinary pending', () {
|
|
237
|
+
final status = reduce(protocol.SyncWorkspaceResponse(
|
|
238
|
+
stillAhead: true,
|
|
239
|
+
offerReceipt: protocol.CustodyOfferReceipt(
|
|
240
|
+
failure: protocol.CustodyOfferFailure(message: 'Admission unavailable'),
|
|
241
|
+
),
|
|
242
|
+
));
|
|
243
|
+
expect(status.phase, SyncPhase.error);
|
|
244
|
+
expect(status.lastError, 'Admission unavailable');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('remote refusal retains code, reason, intent and immutable receipt', () {
|
|
248
|
+
final result = protocol.SyncWorkspaceResponse(
|
|
249
|
+
currentHead: 'local-head',
|
|
250
|
+
remoteRefusal: protocol.SyncRefusal(
|
|
251
|
+
code: 'REFERENCE_TYPE_MISMATCH',
|
|
252
|
+
reason: 'catalogueWorkspace must be a reference',
|
|
253
|
+
intentId: 'offer-17',
|
|
254
|
+
),
|
|
255
|
+
);
|
|
256
|
+
final status = reduce(result);
|
|
257
|
+
expect(status.phase, SyncPhase.error);
|
|
258
|
+
expect(status.lastError, contains('REFERENCE_TYPE_MISMATCH'));
|
|
259
|
+
expect(
|
|
260
|
+
status.lastError, contains('catalogueWorkspace must be a reference'));
|
|
261
|
+
expect(status.lastError, contains('offer-17'));
|
|
262
|
+
result.currentHead = 'mutated';
|
|
263
|
+
expect(status.lastResult!.currentHead, 'local-head');
|
|
264
|
+
expect(status.lastResult!.isFrozen, isTrue);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test('custody disagreement retains both heads and does not invent counts',
|
|
268
|
+
() {
|
|
269
|
+
final status = reduce(protocol.SyncWorkspaceResponse(
|
|
270
|
+
custodyDisagreement: protocol.CustodyDisagreement(
|
|
271
|
+
localHead: 'local',
|
|
272
|
+
candidateHead: 'remote',
|
|
273
|
+
reason: 'Not a fast forward',
|
|
274
|
+
),
|
|
275
|
+
));
|
|
276
|
+
expect(status.lastError, contains('Not a fast forward'));
|
|
277
|
+
expect(status.lastError, contains('Local: local'));
|
|
278
|
+
expect(status.lastError, contains('Remote: remote'));
|
|
279
|
+
expect(status.ahead, isNull);
|
|
280
|
+
expect(status.lastResult!.custodyReplaced, isFalse);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test('per-session changes notify even when application summary is unchanged',
|
|
284
|
+
() async {
|
|
285
|
+
final projection = NmsSyncStatusProjection();
|
|
286
|
+
addTearDown(projection.close);
|
|
287
|
+
projection.ensureSession('home', const SyncStatus(phase: SyncPhase.error));
|
|
288
|
+
projection.ensureSession('estate', const SyncStatus());
|
|
289
|
+
projection.foldApplication(['home', 'estate']);
|
|
290
|
+
final application = projection.applicationStatus;
|
|
291
|
+
final changed = projection.changes.first;
|
|
292
|
+
projection.pushSession(
|
|
293
|
+
'estate', const SyncStatus(lastError: 'Exact issue'));
|
|
294
|
+
await changed;
|
|
295
|
+
expect(projection.statusFor('estate').lastError, 'Exact issue');
|
|
296
|
+
expect(identical(application, projection.applicationStatus), isTrue);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test('missing result is a protocol failure, not a default success', () {
|
|
300
|
+
expect(
|
|
301
|
+
reduceSyncCompletion(const SyncStatus(), protocol.SyncCompleted(), at)
|
|
302
|
+
.phase,
|
|
303
|
+
SyncPhase.error);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test('an empty result does not establish a confirmed frontier', () {
|
|
307
|
+
final status = reduce(protocol.SyncWorkspaceResponse());
|
|
308
|
+
expect(status.phase, SyncPhase.checking);
|
|
309
|
+
expect(status.ahead, isNull);
|
|
310
|
+
expect(status.lastSyncedAt, isNull);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test('aggregation retains unknown sessions and never fabricates totals',
|
|
314
|
+
() async {
|
|
315
|
+
final projection = NmsSyncStatusProjection();
|
|
316
|
+
addTearDown(projection.close);
|
|
317
|
+
projection.pushSession(
|
|
318
|
+
'estate', reduce(protocol.SyncWorkspaceResponse(currentHead: 'head')));
|
|
319
|
+
projection.foldApplication(['estate', 'home']);
|
|
320
|
+
expect(projection.applicationStatus.phase, SyncPhase.checking);
|
|
321
|
+
expect(projection.applicationStatus.ahead, isNull);
|
|
322
|
+
expect(projection.applicationStatus.lastSyncedAt, isNull);
|
|
323
|
+
projection.pushSession(
|
|
324
|
+
'home', reduce(protocol.SyncWorkspaceResponse(stillAhead: true)));
|
|
325
|
+
projection.foldApplication(['estate', 'home']);
|
|
326
|
+
expect(projection.applicationStatus.phase, SyncPhase.syncing);
|
|
327
|
+
projection.foldApplication([]);
|
|
328
|
+
expect(projection.applicationStatus.phase, SyncPhase.checking);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test(
|
|
332
|
+
'a settled workspace with an open live link is current now, not as of its last round',
|
|
333
|
+
() async {
|
|
334
|
+
final projection = NmsSyncStatusProjection();
|
|
335
|
+
addTearDown(projection.close);
|
|
336
|
+
final settled = reduce(protocol.SyncWorkspaceResponse(
|
|
337
|
+
previousHead: 'head', currentHead: 'head'));
|
|
338
|
+
expect(settled.phase, SyncPhase.idle, reason: 'fixture must be settled');
|
|
339
|
+
expect(settled.lastSyncedAt, at);
|
|
340
|
+
projection.pushSession('estate', settled);
|
|
341
|
+
projection.pushSession('home', settled.copyWith(live: true));
|
|
342
|
+
projection.foldApplication(['estate', 'home']);
|
|
343
|
+
expect(projection.applicationStatus.live, isFalse);
|
|
344
|
+
expect(projection.applicationStatus.lastSyncedAt, at,
|
|
345
|
+
reason: 'the quiet, link-less workspace is the oldest confirmation');
|
|
346
|
+
projection.pushSession('estate', settled.copyWith(live: true));
|
|
347
|
+
projection.foldApplication(['estate', 'home']);
|
|
348
|
+
expect(projection.applicationStatus.live, isTrue);
|
|
349
|
+
expect(projection.applicationStatus.lastSyncedAt!.isAfter(at), isTrue,
|
|
350
|
+
reason: 'every workspace is live: confirmed as of now');
|
|
351
|
+
expect(
|
|
352
|
+
reduce(
|
|
353
|
+
protocol.SyncWorkspaceResponse(
|
|
354
|
+
previousHead: 'head', currentHead: 'next'),
|
|
355
|
+
settled.copyWith(live: true))
|
|
356
|
+
.live,
|
|
357
|
+
isTrue,
|
|
358
|
+
reason: 'a completed round never forgets the link state');
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
test(
|
|
362
|
+
'the runtime follow inventory survives every re-fold of the application status',
|
|
363
|
+
() async {
|
|
364
|
+
final projection = NmsSyncStatusProjection();
|
|
365
|
+
addTearDown(projection.close);
|
|
366
|
+
const follow = WorkspaceFollowStatus(
|
|
367
|
+
workspace: 'estate-example',
|
|
368
|
+
phase: WorkspaceFollowPhase.running,
|
|
369
|
+
live: false,
|
|
370
|
+
);
|
|
371
|
+
projection.updateFollows(9, 7, live: 6, statuses: [follow]);
|
|
372
|
+
expect(projection.applicationStatus.followed, 9);
|
|
373
|
+
expect(projection.applicationStatus.followsInSync, 7);
|
|
374
|
+
expect(projection.applicationStatus.followsLive, 6);
|
|
375
|
+
expect(projection.applicationStatus.followStatuses.single.workspace,
|
|
376
|
+
'estate-example');
|
|
377
|
+
projection.pushSession(
|
|
378
|
+
'estate',
|
|
379
|
+
reduce(protocol.SyncWorkspaceResponse(
|
|
380
|
+
previousHead: 'head', currentHead: 'head')));
|
|
381
|
+
projection.foldApplication(['estate']);
|
|
382
|
+
expect(projection.applicationStatus.followed, 9);
|
|
383
|
+
expect(projection.applicationStatus.followsInSync, 7);
|
|
384
|
+
expect(projection.applicationStatus.followStatuses.single.phase,
|
|
385
|
+
WorkspaceFollowPhase.running);
|
|
386
|
+
projection.foldApplication([]);
|
|
387
|
+
expect(projection.applicationStatus.followsInSync, 7);
|
|
388
|
+
expect(projection.applicationStatus.followsLive, 6);
|
|
389
|
+
expect(projection.applicationStatus.followStatuses.single.live, isFalse);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test('follow report keeps the peer operation, schedule and source time',
|
|
393
|
+
() async {
|
|
394
|
+
final projection = NmsSyncStatusProjection();
|
|
395
|
+
addTearDown(projection.close);
|
|
396
|
+
final observedAt = DateTime.fromMillisecondsSinceEpoch(1800000000000);
|
|
397
|
+
final due = WorkspaceFollowStatus.fromRuntime(protocol.FollowObservation(
|
|
398
|
+
workspace: 'catalogue',
|
|
399
|
+
phase: protocol.FollowObservationPhase.FOLLOW_OBSERVATION_PHASE_DUE,
|
|
400
|
+
dueAtUnixMillis: fixnum.Int64(1800000060000),
|
|
401
|
+
));
|
|
402
|
+
final running =
|
|
403
|
+
WorkspaceFollowStatus.fromRuntime(protocol.FollowObservation(
|
|
404
|
+
workspace: 'estate',
|
|
405
|
+
phase: protocol.FollowObservationPhase.FOLLOW_OBSERVATION_PHASE_RUNNING,
|
|
406
|
+
attempt: 2,
|
|
407
|
+
activeEffect: 'gitFetch',
|
|
408
|
+
));
|
|
409
|
+
projection.updateFollows(2, 0,
|
|
410
|
+
statuses: [due, running], observedAt: observedAt);
|
|
411
|
+
projection.foldApplication(const []);
|
|
412
|
+
final status = projection.applicationStatus;
|
|
413
|
+
expect(status.followReportAt, observedAt);
|
|
414
|
+
expect(status.followStatuses.first.dueAt,
|
|
415
|
+
DateTime.fromMillisecondsSinceEpoch(1800000060000));
|
|
416
|
+
expect(status.followStatuses.last.attempt, 2);
|
|
417
|
+
expect(status.followStatuses.last.activeEffect, 'gitFetch');
|
|
418
|
+
});
|
|
419
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// NomosTestHarness — the EPHEMERAL, FULLY-OFFLINE fixture loop: install real tenant law into a fresh local
|
|
2
2
|
// workspace (no cloud), drive one directive + one read through the REAL githolon over the node runner, and
|
|
3
|
-
// tear down deterministically. This is the
|
|
3
|
+
// tear down deterministically. This is the UI-less app-test lane: the same bridge + generated-client surface
|
|
4
4
|
// the app UI uses, under `dart test` / `flutter test`, with no network.
|
|
5
5
|
//
|
|
6
6
|
// Prerequisites (skipped-with-reason when absent, so CI stays green either way): `node` on PATH; the
|
|
7
|
-
// in-repo runner; a warm `~/.holon/runtime` cache (holon
|
|
7
|
+
// in-repo runner; a warm `~/.holon/runtime` cache (holon.wasm + bootstrap.law.usdz + nomos.law.usdz — one `githolon` run fills it);
|
|
8
8
|
// and the compiled `examples/guestbook/build/guestbook.law.usdz` (`cd examples/guestbook && npx nomos-compile`).
|
|
9
9
|
@TestOn('vm')
|
|
10
10
|
library;
|
|
@@ -18,15 +18,15 @@ import 'package:nomos_types/nomos_kernel_protocol.dart'
|
|
|
18
18
|
import 'package:test/test.dart';
|
|
19
19
|
|
|
20
20
|
String get _repoEntry =>
|
|
21
|
-
'${Directory.current.path}/../../
|
|
21
|
+
'${Directory.current.path}/../../peer-office/package/runner/node-entry.mjs';
|
|
22
22
|
String get _guestbookLawUsdz =>
|
|
23
23
|
'${Directory.current.path}/../../examples/guestbook/build/guestbook.law.usdz';
|
|
24
24
|
String get _runtimeDir =>
|
|
25
|
+
// Same precedence as the harness itself: an explicit candidate runtime first.
|
|
26
|
+
Platform.environment['NOMOS_CANDIDATE_RUNTIME'] ??
|
|
25
27
|
Platform.environment['NOMOS_DART_RUNTIME'] ??
|
|
26
28
|
Platform.environment['NOMOS_OFFLINE_RUNTIME'] ??
|
|
27
29
|
'${Platform.environment['HOME'] ?? ''}/.holon/runtime';
|
|
28
|
-
// The built CLI bin — drives the `githolon harness-install` SUBCOMMAND lane (the published-harness path).
|
|
29
|
-
String get _cliDist => '${Directory.current.path}/../../cli/dist/cli.mjs';
|
|
30
30
|
|
|
31
31
|
/// The prerequisite that isn't installable in-test (a warm runtime cache + the compiled fixture). Returns a
|
|
32
32
|
/// skip reason, or null when everything the harness needs is present.
|
|
@@ -36,7 +36,7 @@ String? _missingPrereq() {
|
|
|
36
36
|
if (!File(_guestbookLawUsdz).existsSync()) {
|
|
37
37
|
return 'compile the fixture first: cd examples/guestbook && npx nomos-compile';
|
|
38
38
|
}
|
|
39
|
-
if (!File('$_runtimeDir/holon
|
|
39
|
+
if (!File('$_runtimeDir/holon.wasm').existsSync() ||
|
|
40
40
|
!File('$_runtimeDir/bootstrap.law.usdz').existsSync() ||
|
|
41
41
|
!File('$_runtimeDir/nomos.law.usdz').existsSync()) {
|
|
42
42
|
return 'provide the coordinated candidate with NOMOS_DART_RUNTIME, or warm ~/.holon/runtime once';
|
|
@@ -45,6 +45,23 @@ String? _missingPrereq() {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
void main() {
|
|
48
|
+
test('remote convergence requires an actual settled frontier', () {
|
|
49
|
+
expect(nmsRemoteConvergenceSettled(SyncWorkspaceResponse()), isFalse);
|
|
50
|
+
expect(nmsRemoteConvergenceSettled(SyncWorkspaceResponse(
|
|
51
|
+
currentHead: 'settled-head',
|
|
52
|
+
)), isTrue);
|
|
53
|
+
for (final pending in <SyncWorkspaceResponse>[
|
|
54
|
+
SyncWorkspaceResponse(currentHead: 'head', reconnectRequired: true),
|
|
55
|
+
SyncWorkspaceResponse(currentHead: 'head', directOfferDeferred: true),
|
|
56
|
+
SyncWorkspaceResponse(currentHead: 'head', localOnly: true),
|
|
57
|
+
SyncWorkspaceResponse(currentHead: 'head', discoveryError: RuntimeTransportFailure(status: 503)),
|
|
58
|
+
SyncWorkspaceResponse(currentHead: 'head', deferred: Int64.ONE),
|
|
59
|
+
SyncWorkspaceResponse(currentHead: 'head', refused: Int64.ONE),
|
|
60
|
+
]) {
|
|
61
|
+
expect(nmsRemoteConvergenceSettled(pending), isFalse, reason: pending.toString());
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
48
65
|
test(
|
|
49
66
|
'convergence never reports rejected or dead-lettered business work as success',
|
|
50
67
|
() {
|
|
@@ -71,7 +88,7 @@ void main() {
|
|
|
71
88
|
);
|
|
72
89
|
});
|
|
73
90
|
|
|
74
|
-
test('OFFLINE: install real law
|
|
91
|
+
test('OFFLINE: install real law into Git custody and cold-remount it',
|
|
75
92
|
() async {
|
|
76
93
|
final skip = _missingPrereq();
|
|
77
94
|
if (skip != null) {
|
|
@@ -86,27 +103,15 @@ void main() {
|
|
|
86
103
|
addTearDown, // deterministic auto-dispose even if an expect() throws
|
|
87
104
|
);
|
|
88
105
|
|
|
89
|
-
|
|
90
|
-
|
|
106
|
+
await harness.assertDomainInstalled(
|
|
107
|
+
workspace: harness.workspace,
|
|
108
|
+
domain: 'guestbook',
|
|
109
|
+
);
|
|
110
|
+
await harness.reopenWorkspaceCustody(harness.workspace);
|
|
111
|
+
await harness.assertDomainInstalled(
|
|
112
|
+
workspace: harness.workspace,
|
|
91
113
|
domain: 'guestbook',
|
|
92
|
-
directiveId: 'signGuestbook',
|
|
93
|
-
payload: {
|
|
94
|
-
'author': 'harness',
|
|
95
|
-
'message': 'offline fixture green',
|
|
96
|
-
'mood': 'happy',
|
|
97
|
-
'signedAt': DateTime.now().toUtc().toIso8601String(),
|
|
98
|
-
},
|
|
99
114
|
);
|
|
100
|
-
expect(outcome.created, hasLength(1));
|
|
101
|
-
expect(outcome.created.single.type, 'guestbook.GuestbookEntry');
|
|
102
|
-
|
|
103
|
-
final rows =
|
|
104
|
-
await harness.bridge.query('entriesByAuthor', {'author': 'harness'});
|
|
105
|
-
expect(rows, hasLength(1));
|
|
106
|
-
expect(rows.first['id'], outcome.created.single.id);
|
|
107
|
-
expect((rows.first['data'] as Map)['message'], 'offline fixture green');
|
|
108
|
-
expect((rows.first['data'] as Map)['moodEmoji'],
|
|
109
|
-
'😊'); // proves the law's plan ran, not a stub
|
|
110
115
|
|
|
111
116
|
stdout.writeln('offline harness green: ws=${harness.workspace}');
|
|
112
117
|
}, timeout: const Timeout(Duration(minutes: 2)));
|
|
@@ -125,114 +130,85 @@ void main() {
|
|
|
125
130
|
runtimeDir: _runtimeDir,
|
|
126
131
|
registerTearDown: addTearDown,
|
|
127
132
|
);
|
|
128
|
-
// harness.
|
|
129
|
-
expect(harness.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
// …but the RAW PLANE holds the decoy too, so an op that forgot its session refuses HERE —
|
|
133
|
-
// exactly the refusal a real multi-workspace app would hit (never a silent sole-session guess).
|
|
134
|
-
final plane = (harness.bridge as NomosSession).parent;
|
|
135
|
-
await expectLater(
|
|
136
|
-
plane.query('entriesByAuthor', {'author': 'x'}),
|
|
137
|
-
throwsA(predicate((e) => '$e'.contains('multiple sessions'))),
|
|
138
|
-
);
|
|
133
|
+
// harness.reception is a SESSION HANDLE and the raw plane also holds the decoy.
|
|
134
|
+
expect(harness.reception, isA<NomosSession>());
|
|
135
|
+
final plane = (harness.reception as NomosSession).parent;
|
|
136
|
+
expect(plane.boundSessions, containsAll(['test-ws', '__nomos-decoy']));
|
|
139
137
|
}, timeout: const Timeout(Duration(minutes: 2)));
|
|
140
138
|
|
|
141
139
|
test(
|
|
142
|
-
'
|
|
140
|
+
'CLUSTER: many workspaces share one Peer Office and keep separate Git custody',
|
|
143
141
|
() async {
|
|
144
142
|
final skip = _missingPrereq();
|
|
145
143
|
if (skip != null) {
|
|
146
144
|
markTestSkipped(skip);
|
|
147
145
|
return;
|
|
148
146
|
}
|
|
149
|
-
final
|
|
150
|
-
|
|
151
|
-
|
|
147
|
+
final harness = await NomosTestHarness.cluster(
|
|
148
|
+
workspaces: {
|
|
149
|
+
'guestbook-a': [_guestbookLawUsdz],
|
|
150
|
+
'guestbook-b': [_guestbookLawUsdz],
|
|
151
|
+
},
|
|
152
152
|
nodeEntry: _repoEntry,
|
|
153
153
|
runtimeDir: _runtimeDir,
|
|
154
154
|
registerTearDown: addTearDown,
|
|
155
|
-
onDiagnostic: lines.add,
|
|
156
155
|
);
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
expect(
|
|
156
|
+
expect(harness.sessions, ['guestbook-a', 'guestbook-b']);
|
|
157
|
+
await harness.assertDomainInstalled(
|
|
158
|
+
workspace: 'guestbook-a',
|
|
159
|
+
domain: 'guestbook',
|
|
160
|
+
);
|
|
161
|
+
await harness.assertDomainInstalled(
|
|
162
|
+
workspace: 'guestbook-b',
|
|
163
|
+
domain: 'guestbook',
|
|
164
|
+
);
|
|
165
|
+
expect(
|
|
166
|
+
harness.session('guestbook-a'), isNot(harness.session('guestbook-b')));
|
|
167
167
|
}, timeout: const Timeout(Duration(minutes: 2)));
|
|
168
168
|
|
|
169
|
-
test('
|
|
169
|
+
test('DX: ephemeral emits canonical Peer Office and install progress',
|
|
170
|
+
() async {
|
|
170
171
|
final skip = _missingPrereq();
|
|
171
172
|
if (skip != null) {
|
|
172
173
|
markTestSkipped(skip);
|
|
173
174
|
return;
|
|
174
175
|
}
|
|
176
|
+
final lines = <String>[];
|
|
175
177
|
final harness = await NomosTestHarness.ephemeral(
|
|
176
178
|
lawUsdzPaths: [_guestbookLawUsdz],
|
|
177
179
|
nodeEntry: _repoEntry,
|
|
178
180
|
runtimeDir: _runtimeDir,
|
|
179
|
-
|
|
181
|
+
registerTearDown: addTearDown,
|
|
182
|
+
onDiagnostic: lines.add,
|
|
180
183
|
);
|
|
181
|
-
|
|
182
|
-
expect(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
await harness
|
|
187
|
-
.dispose(); // idempotent: the second call is a no-op, never throws
|
|
188
|
-
// The bridge is dead after dispose — a call fails rather than silently reaching a leaked runner.
|
|
189
|
-
await expectLater(harness.bridge.query('entriesByAuthor', {'author': 'x'}),
|
|
190
|
-
throwsA(anything));
|
|
184
|
+
final joined = lines.join('\n');
|
|
185
|
+
expect(joined, contains('phase 1/2: open canonical test peer'));
|
|
186
|
+
expect(joined, contains('phase 2/2: create Git custody and install law'));
|
|
187
|
+
expect(joined, contains('ready — session "test-ws" connected'));
|
|
188
|
+
expect(harness.reception, isNotNull);
|
|
191
189
|
}, timeout: const Timeout(Duration(minutes: 2)));
|
|
192
190
|
|
|
193
|
-
test(
|
|
194
|
-
'OFFLINE: the `githolon harness-install` SUBCOMMAND lane installs law and drives a read',
|
|
195
|
-
() async {
|
|
191
|
+
test('OFFLINE: dispose is idempotent and removes the temp dir', () async {
|
|
196
192
|
final skip = _missingPrereq();
|
|
197
193
|
if (skip != null) {
|
|
198
194
|
markTestSkipped(skip);
|
|
199
195
|
return;
|
|
200
196
|
}
|
|
201
|
-
if (!File(_cliDist).existsSync()) {
|
|
202
|
-
markTestSkipped(
|
|
203
|
-
'build the CLI first: cd cli && npm run build (needs dist/cli.mjs)');
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
// FORCE the published subcommand lane via the installHelper seam: a githolon command line (here the
|
|
207
|
-
// in-repo built bin) whose `harness-install` subcommand does the offline install a published tenant gets.
|
|
208
197
|
final harness = await NomosTestHarness.ephemeral(
|
|
209
198
|
lawUsdzPaths: [_guestbookLawUsdz],
|
|
210
199
|
nodeEntry: _repoEntry,
|
|
211
200
|
runtimeDir: _runtimeDir,
|
|
212
|
-
|
|
213
|
-
'node $_cliDist', // NOT an existing .mjs file ⇒ the subcommand lane
|
|
214
|
-
registerTearDown: addTearDown,
|
|
201
|
+
autoTearDown: false, // drive dispose explicitly to assert its determinism
|
|
215
202
|
);
|
|
216
|
-
|
|
217
|
-
|
|
203
|
+
await harness.assertDomainInstalled(
|
|
204
|
+
workspace: harness.workspace,
|
|
218
205
|
domain: 'guestbook',
|
|
219
|
-
directiveId: 'signGuestbook',
|
|
220
|
-
payload: {
|
|
221
|
-
'author': 'subcommand',
|
|
222
|
-
'message': 'installed via githolon harness-install',
|
|
223
|
-
'mood': 'happy',
|
|
224
|
-
'signedAt': DateTime.now().toUtc().toIso8601String(),
|
|
225
|
-
},
|
|
226
206
|
);
|
|
227
|
-
expect(outcome.created, hasLength(1));
|
|
228
|
-
final rows =
|
|
229
|
-
await harness.bridge.query('entriesByAuthor', {'author': 'subcommand'});
|
|
230
|
-
expect(rows, hasLength(1));
|
|
231
|
-
expect((rows.first['data'] as Map)['message'],
|
|
232
|
-
'installed via githolon harness-install');
|
|
233
|
-
expect((rows.first['data'] as Map)['moodEmoji'],
|
|
234
|
-
'😊'); // the law's plan ran through the subcommand-built snapshot
|
|
235
207
|
|
|
236
|
-
|
|
208
|
+
await harness.dispose();
|
|
209
|
+
await harness
|
|
210
|
+
.dispose(); // idempotent: the second call is a no-op, never throws
|
|
211
|
+
// The bridge is dead after dispose — a call fails rather than silently reaching a leaked runner.
|
|
212
|
+
await expectLater(harness.reception.authorKeygen(), throwsA(anything));
|
|
237
213
|
}, timeout: const Timeout(Duration(minutes: 2)));
|
|
238
214
|
}
|