@githolon/dsl 0.108.7 → 0.110.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/operation-contract.wasm +0 -0
- package/check_package.mjs +12 -15
- package/compile_package.mjs +17 -18
- package/compile_proof.mjs +16 -1
- package/dart/CHANGELOG.md +76 -1
- package/dart/README.md +25 -2
- package/dart/lib/src/admission_decision.dart +41 -0
- package/dart/lib/src/application_framework_calls.dart +52 -2
- package/dart/lib/src/application_model_adoption.dart +187 -0
- package/dart/lib/src/application_status.dart +73 -0
- package/dart/lib/src/console_enrolment.dart +271 -0
- package/dart/lib/src/console_http_io.dart +26 -0
- package/dart/lib/src/console_http_stub.dart +7 -0
- package/dart/lib/src/dev_cloud.dart +69 -22
- package/dart/lib/src/generated_call.dart +83 -23
- package/dart/lib/src/kernel_primitives.dart +119 -0
- package/dart/lib/src/nms_conformance_runtime.dart +1 -1
- package/dart/lib/src/nms_generated_runtime.dart +32 -10
- package/dart/lib/src/nms_test_environment.dart +1 -1
- package/dart/lib/src/node_runner_transport.dart +41 -46
- package/dart/lib/src/observed_session_directory.dart +70 -0
- package/dart/lib/src/operation_receipts.dart +112 -0
- package/dart/lib/src/peer_application_streams.dart +103 -0
- package/dart/lib/src/reception/dart_reception.dart +4280 -0
- package/dart/lib/src/reception/event_desk.dart +211 -0
- package/dart/lib/src/reception/extremum_read.dart +46 -0
- package/dart/lib/src/reception/local_copies.dart +371 -0
- package/dart/lib/src/reception/peer_office_connection.dart +11 -0
- package/dart/lib/src/reception/progress_desk.dart +267 -0
- package/dart/lib/src/reception/reception_contract.dart +11 -0
- package/dart/lib/src/reception/reception_session.dart +284 -0
- package/dart/lib/src/reception/sync_diagnostics.dart +26 -0
- package/dart/lib/src/reception/workspace_preparation_report.dart +126 -0
- package/dart/lib/src/replica_readiness.dart +123 -0
- package/dart/lib/src/runner/node-entry.mjs +23905 -15774
- package/dart/lib/src/runtime_v2_gateway.dart +56 -3
- package/dart/lib/src/runtime_v2_transport.dart +544 -3
- package/dart/lib/src/session_bound.dart +200 -311
- package/dart/lib/src/signing_identity_kernel.dart +84 -0
- package/dart/lib/src/sync_status_projection.dart +548 -0
- package/dart/lib/src/sync_status_reducer.dart +170 -0
- package/dart/lib/src/test_harness.dart +809 -1153
- package/dart/lib/src/testing/test_custody.dart +66 -0
- package/dart/lib/src/testing/test_peer.dart +328 -0
- package/dart/lib/src/typed_watch_registry.dart +169 -0
- package/dart/lib/src/workspace_open_progress.dart +27 -0
- package/dart/pubspec.lock +24 -1
- package/dart/pubspec.yaml +7 -2
- package/dart/pubspec_overrides.yaml +2 -0
- package/dart/test/admission_decision_test.dart +48 -0
- package/dart/test/background_model_adoption_test.dart +228 -0
- package/dart/test/bundle_version_pairing_test.dart +4 -4
- package/dart/test/child_authentication_repair_test.dart +359 -0
- package/dart/test/cloud_bound_local_shell_test.dart +119 -0
- package/dart/test/console_enrolment_refusal_test.dart +52 -0
- package/dart/test/console_enrolment_test.dart +227 -0
- package/dart/test/errors_test.dart +5 -5
- package/dart/test/event_plane_seam_test.dart +50 -164
- package/dart/test/extremum_read_test.dart +58 -0
- package/dart/test/fixtures/wedge/wedge-entry.mjs +3 -21
- package/dart/test/generated_query_test.dart +32 -0
- package/dart/test/harness_phase_timeout_test.dart +18 -23
- package/dart/test/kernel_author_after_read_test.dart +242 -0
- package/dart/test/kernel_fence_test.dart +137 -0
- package/dart/test/local_copies_test.dart +222 -0
- package/dart/test/model_adoption_test.dart +198 -0
- package/dart/test/node_peer_local_custody_test.dart +153 -0
- package/dart/test/offer_authentication_proof_test.dart +352 -52
- package/dart/test/operation_compatibility_test.dart +151 -0
- package/dart/test/platform_credential_effect_test.dart +386 -0
- package/dart/test/receiving_group_open_test.dart +213 -0
- package/dart/test/reception/workspace_preparation_report_test.dart +103 -0
- package/dart/test/reception_span_trace_test.dart +23 -0
- package/dart/test/replica_readiness_test.dart +156 -0
- package/dart/test/runtime_projections_test.dart +92 -0
- package/dart/test/runtime_v2_gateway_test.dart +131 -0
- package/dart/test/signing_identity_kernel_test.dart +75 -0
- package/dart/test/sync_status_preparation_report_test.dart +29 -0
- package/dart/test/sync_status_reducer_test.dart +419 -0
- package/dart/test/test_harness_test.dart +71 -95
- package/dart/test/typed_query_watch_test.dart +170 -0
- package/dart/test/typed_watch_registry_test.dart +249 -0
- package/dart_lints/README.md +18 -0
- package/dart_lints/lib/nomos_lints.dart +70 -0
- package/dart_lints/pubspec.lock +580 -0
- package/dart_lints/pubspec.yaml +12 -0
- package/dart_lints/test/reference_identity_test.dart +75 -0
- package/dart_types/CHANGELOG.md +18 -0
- package/dart_types/lib/nomos_kernel_protocol.dart +21 -1
- package/dart_types/lib/nomos_types.dart +9 -1
- package/dart_types/lib/src/admission_decision.dart +37 -0
- package/dart_types/lib/src/aggregate_meta.dart +1 -1
- package/dart_types/lib/src/approved_model.dart +65 -0
- package/dart_types/lib/src/custody.dart +10 -18
- package/dart_types/lib/src/error_info.dart +4 -4
- package/dart_types/lib/src/errors.dart +29 -29
- package/dart_types/lib/src/foreign_ref.dart +62 -33
- package/dart_types/lib/src/local_work_rejected.dart +3 -3
- package/dart_types/lib/src/nomos_ref.dart +3 -1
- package/dart_types/lib/src/principal.dart +2 -2
- package/dart_types/lib/src/proto/google/protobuf/any.pb.dart +163 -0
- package/dart_types/lib/src/proto/google/protobuf/any.pbenum.dart +11 -0
- package/dart_types/lib/src/proto/nomos/discovery/v1/workspace_placement.pb.dart +137 -0
- package/dart_types/lib/src/proto/nomos/discovery/v1/workspace_placement.pbenum.dart +11 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +20721 -10433
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +555 -27
- package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pb.dart +387 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pbenum.dart +46 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/application_readiness_desk.pb.dart +295 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/application_readiness_desk.pbenum.dart +139 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/authentication_desk.pb.dart +269 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/authentication_desk.pbenum.dart +103 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/compatibility_desk.pb.dart +417 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/compatibility_desk.pbenum.dart +62 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +9968 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pbenum.dart +133 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/credentials_desk.pb.dart +204 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/credentials_desk.pbenum.dart +54 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/custody_desk.pb.dart +1550 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/custody_desk.pbenum.dart +11 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/framework.pb.dart +710 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/framework.pbenum.dart +11 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/identity_desk.pb.dart +845 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/identity_desk.pbenum.dart +11 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/interaction_desk.pb.dart +198 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/interaction_desk.pbenum.dart +11 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/law_desk.pb.dart +369 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/law_desk.pbenum.dart +11 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/maintenance_desk.pb.dart +609 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/maintenance_desk.pbenum.dart +65 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +358 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pbenum.dart +100 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pb.dart +2231 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pbenum.dart +271 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/placement_desk.pb.dart +1286 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/placement_desk.pbenum.dart +11 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pb.dart +636 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pbenum.dart +116 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pb.dart +1637 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pbenum.dart +175 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/recovery_desk.pb.dart +1269 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/recovery_desk.pbenum.dart +129 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pb.dart +2782 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pbenum.dart +97 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +2232 -8627
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pbenum.dart +0 -358
- package/dart_types/lib/src/proto/nomos/runtime/v2/sealing_desk.pb.dart +1639 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/sealing_desk.pbenum.dart +46 -0
- package/dart_types/lib/src/runtime_outcome.dart +133 -0
- package/dart_types/lib/src/subscriptions.dart +116 -23
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/extremum_subscriptions_test.dart +40 -0
- package/dart_types/test/foreign_ref_resolution_test.dart +75 -0
- package/dart_types/test/nomos_ref_address_test.dart +25 -0
- package/dart_types/test/ordered_subscriptions_test.dart +34 -0
- package/operation_contract_check.mjs +24 -0
- package/package.json +11 -6
- package/src/aggregate.ts +70 -10
- package/src/application_topology.ts +18 -11
- package/src/attested.ts +3 -0
- package/src/attested_core.ts +46 -3
- package/src/birth_meter_ensure.ts +19 -7
- package/src/births.ts +29 -4
- package/src/build_package.ts +140 -13
- package/src/business_birth.ts +181 -4
- package/src/codegen_call_proto.ts +1274 -0
- package/src/codegen_call_usda.ts +49 -6
- package/src/codegen_dart.ts +79 -44
- package/src/codegen_dart_client.ts +458 -800
- package/src/{codegen_dart_application.ts → codegen_dart_facade.ts} +331 -582
- package/src/codegen_dart_framework_calls.ts +320 -37
- package/src/codegen_dart_indexed_variant.ts +159 -0
- package/src/codegen_dart_scenario.ts +42 -13
- package/src/codegen_proof.ts +3 -3
- package/src/codegen_provider_dart.ts +5 -0
- package/src/codegen_query_proto.ts +158 -0
- package/src/codegen_refusal.ts +189 -0
- package/src/codegen_result_proto.ts +931 -0
- package/src/codegen_result_usda.ts +4 -0
- package/src/codegen_ts.ts +464 -616
- package/src/codegen_workspace_access.ts +105 -0
- package/src/compatibility_graph.ts +759 -0
- package/src/compatibility_usda.ts +264 -0
- package/src/compile_package_main.ts +1994 -1189
- package/src/custody_placement.ts +156 -0
- package/src/custody_placement_usda.ts +91 -0
- package/src/decl_shapes.ts +21 -2
- package/src/describe_model.ts +1 -0
- package/src/directive.ts +59 -2
- package/src/drivers.ts +51 -0
- package/src/engine_entry.ts +52 -8
- package/src/evolve_preview.ts +11 -4
- package/src/exists.ts +2 -0
- package/src/fields.ts +75 -11
- package/src/flow_gate.ts +2 -1
- package/src/framework/application_home.ts +170 -18
- package/src/framework/attested_receipts.ts +8 -3
- package/src/framework/auth_provider_law.ts +169 -9
- package/src/framework/birth_certificate_law.ts +4 -0
- package/src/framework/capability.ts +34 -3
- package/src/framework/child_authentication.ts +346 -0
- package/src/framework/console_governance.ts +295 -0
- package/src/framework/delegated_roles.ts +301 -0
- package/src/framework/domain_lifecycle.ts +26 -13
- package/src/framework/founding_genesis.ts +193 -0
- package/src/framework/framework_aggregates.ts +1 -1
- package/src/framework/genesis.ts +6 -1
- package/src/framework/governance_relations.ts +19 -0
- package/src/framework/home.ts +5 -734
- package/src/framework/legacy/child_auth_provider_epoch.ts +73 -0
- package/src/framework/model_authority_repair.ts +116 -0
- package/src/framework/model_authority_seed.ts +185 -0
- package/src/framework/model_transition.ts +211 -0
- package/src/framework/owned_custody.ts +1 -1
- package/src/framework/principal_enrollment.ts +302 -0
- package/src/framework/receiving_group.ts +857 -0
- package/src/framework/receiving_group_recovery.ts +314 -0
- package/src/framework/relations.ts +4 -8
- package/src/framework/signing_identity.ts +242 -95
- package/src/framework/source_roster.ts +11 -5
- package/src/framework/workspace_access.ts +149 -0
- package/src/framework/workspace_invariant.ts +73 -52
- package/src/framework/workspaces.ts +407 -40
- package/src/framework_call_definitions.ts +32 -3
- package/src/generated_client_bundle_boundary.ts +97 -0
- package/src/generated_client_names.ts +1 -0
- package/src/identity_usda.ts +27 -0
- package/src/index.ts +183 -12
- package/src/indexed_variant.ts +83 -0
- package/src/intent.ts +173 -45
- package/src/law_check.ts +8 -5
- package/src/law_dependency_index.ts +231 -0
- package/src/law_graph.ts +508 -71
- package/src/law_graph_usda.ts +170 -24
- package/src/law_identity.ts +119 -81
- package/src/law_source_map.ts +4 -1
- package/src/law_units.ts +498 -0
- package/src/law_value_type.ts +7 -3
- package/src/member_authentication.ts +302 -0
- package/src/null_meta.ts +1 -1
- package/src/openusd_compiled_layer.ts +132 -0
- package/src/operation_contract_check.ts +31 -0
- package/src/ordered_read.ts +2 -1
- package/src/package_config.ts +33 -12
- package/src/package_dependencies.ts +1 -0
- package/src/plan_source_slice.ts +30 -1
- package/src/protobuf_field_number.ts +36 -0
- package/src/provenance_wire.ts +6 -0
- package/src/query.ts +2 -1
- package/src/read.ts +50 -0
- package/src/relation.ts +4 -1
- package/src/relation_schema.ts +15 -2
- package/src/report.ts +2 -2
- package/src/sqlite_computation_bundle.ts +78 -0
- package/src/sqlite_computed.ts +102 -0
- package/src/sqlite_counts.ts +154 -0
- package/src/sqlite_layout.ts +823 -0
- package/src/sqlite_reads.ts +275 -0
- package/src/sqlite_samples.ts +185 -0
- package/src/stable_id_identity_bridge.ts +1 -1
- package/src/stable_ids_for_module.ts +16 -4
- package/src/test_call_usda.ts +6 -6
- package/src/usda_relationship_integrity.ts +20 -3
- package/src/usda_text.ts +21 -1
- package/src/value_object.ts +81 -11
- package/src/wire.ts +22 -0
- package/src/wire_encode.ts +227 -20
- package/src/workspace_sharding.ts +15 -6
- package/src/workspace_sharing.ts +51 -49
- package/src/zod_introspection.ts +2 -0
- package/compat_package.mjs +0 -46
- package/dart/lib/src/home.dart +0 -15
- package/dart/lib/src/runner/offline-install.mjs +0 -15367
- package/dart/lib/src/source_trust.dart +0 -51
- package/dart/lib/src/webview_bridge.dart +0 -6540
- package/dart/lib/src/workspace_name.dart +0 -30
- package/dart/test/acquire_session_credentials_test.dart +0 -150
- package/dart/test/acquire_session_test.dart +0 -534
- package/dart/test/adv_realm_interference_test.dart +0 -600
- package/dart/test/cloud_bound_topology_test.dart +0 -168
- package/dart/test/cluster_births_test.dart +0 -202
- package/dart/test/cluster_device_keyed_acceptance_test.dart +0 -105
- package/dart/test/cluster_local_autosync_test.dart +0 -123
- package/dart/test/cluster_scale_test.dart +0 -122
- package/dart/test/compat_test.dart +0 -61
- package/dart/test/dlq_session_surface_test.dart +0 -222
- package/dart/test/first_app_gauntlet_test.dart +0 -386
- package/dart/test/fixtures/wedge/fake-install.mjs +0 -11
- package/dart/test/four_layer_nested_topology_test.dart +0 -290
- package/dart/test/home_parity_test.dart +0 -13
- package/dart/test/local_sync_inert_test.dart +0 -107
- package/dart/test/node_runner_transport_test.dart +0 -78
- package/dart/test/parent_signer_recovery_test.dart +0 -155
- package/dart/test/residency_refusal_test.dart +0 -49
- package/dart/test/sealing_client_test.dart +0 -181
- package/dart/test/session_bound_test.dart +0 -285
- package/dart/test/stale_bundle_test.dart +0 -80
- package/dart/test/vo_read_typing_test.dart +0 -131
- package/dart/test/watch_after_offer_stall_test.dart +0 -100
- package/dart/test/webview_bridge_test.dart +0 -1930
- package/dart/test/workspace_name_test.dart +0 -24
- package/runtime/generated-application-host.js +0 -22860
- package/src/codegen_query_usda.ts +0 -214
- package/src/compat_manifest.ts +0 -26
- package/src/compat_package_main.ts +0 -224
- package/src/shape_compat.ts +0 -352
- package/src/usd_state.ts +0 -505
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// Kernel Office seam test — the author lane must resolve a directive that the
|
|
2
|
+
// workspace's ACTIVE law declares, even when the kernel first met that law
|
|
3
|
+
// through a cold read.
|
|
4
|
+
//
|
|
5
|
+
// Production failure (2026-09-03): after a cold read of `co2-platform`, the
|
|
6
|
+
// container kernel refused `co2_platform/enrollPlatformFirstDevice` with
|
|
7
|
+
// "directive 'enrollPlatformFirstDevice' is absent from authenticated law"
|
|
8
|
+
// although the active law package in custody declares it. A cold read leaves a
|
|
9
|
+
// population-masked read stage in the package cache; the author lane's
|
|
10
|
+
// classifiers resolved the label against that stage.
|
|
11
|
+
//
|
|
12
|
+
// This test drives the REAL kernel through the physical testing shell over the
|
|
13
|
+
// golden `co2-platform` custody: open it cold, read its law surface, then author
|
|
14
|
+
// the enrolment directive. The offer is expected to be refused (there is no
|
|
15
|
+
// authenticated principal here) but never with the false negative above.
|
|
16
|
+
//
|
|
17
|
+
// Inputs (environment): NOMOS_CANDIDATE_RUNTIME, NOMOS_FENCE_CUSTODY — see
|
|
18
|
+
// git-holon/fence/run.sh. Skipped when the golden custody is not present.
|
|
19
|
+
import 'dart:io';
|
|
20
|
+
import 'dart:isolate';
|
|
21
|
+
import 'dart:math';
|
|
22
|
+
|
|
23
|
+
import 'package:fixnum/fixnum.dart';
|
|
24
|
+
|
|
25
|
+
import 'package:nomos_client/src/testing/test_peer.dart';
|
|
26
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as nomos_protocol;
|
|
27
|
+
import 'package:nomos_types/src/proto/google/protobuf/any.pb.dart'
|
|
28
|
+
as google_protobuf;
|
|
29
|
+
import 'package:test/test.dart';
|
|
30
|
+
|
|
31
|
+
const _workspace = 'co2-platform';
|
|
32
|
+
// The application Home that shares the production kernel with the platform and
|
|
33
|
+
// runs an OLDER CO2 law package than the platform's active one.
|
|
34
|
+
const _home = 'nomos-rg-2f87efc35eb3c549a50ef80afe46902cdda4e364';
|
|
35
|
+
const _domain = 'co2_platform';
|
|
36
|
+
const _directive = 'enrollPlatformFirstDevice';
|
|
37
|
+
const _falseNegative = 'is absent from authenticated law';
|
|
38
|
+
|
|
39
|
+
Future<String> _testEntry() async {
|
|
40
|
+
final resolved = await Isolate.resolvePackageUri(
|
|
41
|
+
Uri.parse('package:nomos_client/src/runner/node-entry.mjs'),
|
|
42
|
+
);
|
|
43
|
+
if (resolved == null || resolved.scheme != 'file') {
|
|
44
|
+
throw StateError('cannot resolve the node testing shell entry');
|
|
45
|
+
}
|
|
46
|
+
return resolved.toFilePath();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
void main() {
|
|
50
|
+
final custodyRoot = Platform.environment['NOMOS_FENCE_CUSTODY'];
|
|
51
|
+
final runtime = Platform.environment['NOMOS_CANDIDATE_RUNTIME'];
|
|
52
|
+
|
|
53
|
+
test(
|
|
54
|
+
'author resolves an active-law directive after a cold read of the same workspace',
|
|
55
|
+
() async {
|
|
56
|
+
if (custodyRoot == null ||
|
|
57
|
+
!Directory('$custodyRoot/holon-$_workspace.git').existsSync()) {
|
|
58
|
+
markTestSkipped('golden custody not present (NOMOS_FENCE_CUSTODY)');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (runtime == null || !File('$runtime/holon.wasm').existsSync()) {
|
|
62
|
+
fail('NOMOS_CANDIDATE_RUNTIME must point at a staged runtime');
|
|
63
|
+
}
|
|
64
|
+
final peer = await NmsTestPeer.start(
|
|
65
|
+
entry: await _testEntry(),
|
|
66
|
+
runtimeDirectory: runtime,
|
|
67
|
+
resolveFrom: Directory.current.path,
|
|
68
|
+
onDiagnostic: (line) => stderr.writeln('[shell] $line'),
|
|
69
|
+
);
|
|
70
|
+
addTearDown(peer.dispose);
|
|
71
|
+
|
|
72
|
+
final custodyDir = Directory('${peer.custody.directory.path}/custody');
|
|
73
|
+
await custodyDir.create(recursive: true);
|
|
74
|
+
for (final ws in [_workspace, _home]) {
|
|
75
|
+
if (!Directory('$custodyRoot/holon-$ws.git').existsSync()) {
|
|
76
|
+
markTestSkipped('golden custody for $ws not present');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
final copy = await Process.run('cp', [
|
|
80
|
+
'-R',
|
|
81
|
+
'$custodyRoot/holon-$ws.git',
|
|
82
|
+
'${custodyDir.path}/holon-$ws.git',
|
|
83
|
+
]);
|
|
84
|
+
if (copy.exitCode != 0) fail('copy custody: ${copy.stderr}');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 1. Cold open.
|
|
88
|
+
await peer.reception.bindSession(
|
|
89
|
+
workspace: _workspace,
|
|
90
|
+
session: _workspace,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
var sequence = 0;
|
|
94
|
+
Future<nomos_protocol.CallResponse> describe(String session) =>
|
|
95
|
+
peer.transport.callKernel(
|
|
96
|
+
session: session,
|
|
97
|
+
request: nomos_protocol.CallRequest(
|
|
98
|
+
protocolVersion: 1,
|
|
99
|
+
requestId: '$session:seam:${++sequence}',
|
|
100
|
+
query: nomos_protocol.QueryRequest(
|
|
101
|
+
authority: nomos_protocol.QueryAuthority(
|
|
102
|
+
principalClaim: 'user:kernel-seam',
|
|
103
|
+
nowUnix: Int64(DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
|
104
|
+
),
|
|
105
|
+
named: nomos_protocol.NamedQuery(
|
|
106
|
+
operation:
|
|
107
|
+
nomos_protocol.QueryOperation.QUERY_OPERATION_DESCRIBE,
|
|
108
|
+
),
|
|
109
|
+
),
|
|
110
|
+
),
|
|
111
|
+
credentialUse: nomos_protocol.CredentialUse.CREDENTIAL_USE_NONE,
|
|
112
|
+
);
|
|
113
|
+
final random = Random.secure();
|
|
114
|
+
Future<String> authorCall(String session, String domain, String directive,
|
|
115
|
+
String typeUrl) async {
|
|
116
|
+
final nomos_protocol.CallResponse offered;
|
|
117
|
+
try {
|
|
118
|
+
offered = await peer.transport.callKernel(
|
|
119
|
+
session: session,
|
|
120
|
+
request: nomos_protocol.CallRequest(
|
|
121
|
+
protocolVersion: 1,
|
|
122
|
+
requestId: '$_workspace:seam:${++sequence}',
|
|
123
|
+
offer: nomos_protocol.OfferRequest(
|
|
124
|
+
call: nomos_protocol.DirectiveCall(
|
|
125
|
+
domain: domain,
|
|
126
|
+
directive: directive,
|
|
127
|
+
operation: google_protobuf.Any(
|
|
128
|
+
typeUrl: typeUrl,
|
|
129
|
+
value: const <int>[],
|
|
130
|
+
),
|
|
131
|
+
),
|
|
132
|
+
authoring: nomos_protocol.AuthoringOptions(
|
|
133
|
+
actor: 'user:kernel-seam',
|
|
134
|
+
rng: List<double>.generate(64, (_) => random.nextDouble()),
|
|
135
|
+
deferProjection: true,
|
|
136
|
+
spans: true,
|
|
137
|
+
sequence: Int64(sequence),
|
|
138
|
+
clock: nomos_protocol.CapturedClock(
|
|
139
|
+
physicalMillis:
|
|
140
|
+
Int64(DateTime.now().toUtc().millisecondsSinceEpoch),
|
|
141
|
+
logical: 0,
|
|
142
|
+
replica: Int64(7),
|
|
143
|
+
),
|
|
144
|
+
),
|
|
145
|
+
),
|
|
146
|
+
),
|
|
147
|
+
credentialUse: nomos_protocol.CredentialUse.CREDENTIAL_USE_NONE,
|
|
148
|
+
);
|
|
149
|
+
} on StateError catch (error) {
|
|
150
|
+
// The transport surfaces kernel refusals as StateError; the refusal text is the outcome.
|
|
151
|
+
return 'refused: ${error.message}';
|
|
152
|
+
}
|
|
153
|
+
return offered.hasFailure()
|
|
154
|
+
? 'failure ${offered.failure.code}: ${offered.failure.message}'
|
|
155
|
+
: offered.hasOffer() && offered.offer.hasRefused()
|
|
156
|
+
? 'refused: ${offered.offer.refused}'
|
|
157
|
+
: 'outcome: ${offered.whichOutcome().name}';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 2. A read first: the law surface, exactly what a describe/OpenAPI call asks for.
|
|
161
|
+
final described = await describe(_workspace);
|
|
162
|
+
expect(described.hasFailure(), isFalse,
|
|
163
|
+
reason: 'describe failed: ${described.failure.message}');
|
|
164
|
+
|
|
165
|
+
// 3. Author the enrolment directive on the cold-read platform. No principal is
|
|
166
|
+
// proven here, so the offer is refused downstream; the classifiers must still
|
|
167
|
+
// have found the directive in the ACTIVE law.
|
|
168
|
+
final first = await authorCall(_workspace, _domain, _directive,
|
|
169
|
+
'type.nomos.dev/nomos.operation.v1.Payload_350c19c4a90e0210');
|
|
170
|
+
stdout.writeln('seam: author after cold read → $first');
|
|
171
|
+
expect(first, isNot(contains(_falseNegative)));
|
|
172
|
+
|
|
173
|
+
// 4. The Home workspace (older CO2 law package) shares the kernel: open it and
|
|
174
|
+
// read it, as its sync does in production between platform offers.
|
|
175
|
+
await peer.reception.bindSession(
|
|
176
|
+
workspace: _home,
|
|
177
|
+
session: _home,
|
|
178
|
+
);
|
|
179
|
+
final homeDescribed = await describe(_home);
|
|
180
|
+
expect(homeDescribed.hasFailure(), isFalse,
|
|
181
|
+
reason: 'home describe failed: ${homeDescribed.failure.message}');
|
|
182
|
+
// Home's sync also verifies its chain, which replays under Home's own law.
|
|
183
|
+
final homeVerdict = await peer.reception.session(_home).verifyChain();
|
|
184
|
+
stdout.writeln('seam: home verify → valid=${homeVerdict.valid}');
|
|
185
|
+
// Home authors too (its own domain, under its own older law package).
|
|
186
|
+
final homeAuthored = await authorCall(_home, 'home', 'enrollFirstDevice',
|
|
187
|
+
'type.nomos.dev/nomos.operation.v1.Payload_cbec744b661de8bc');
|
|
188
|
+
stdout.writeln('seam: home author → $homeAuthored');
|
|
189
|
+
|
|
190
|
+
// 5. Author on the platform again. The route was prepared in step 3; the Home
|
|
191
|
+
// work in step 4 must not turn the platform's active law into negative evidence.
|
|
192
|
+
final second = await authorCall(_workspace, _domain, _directive,
|
|
193
|
+
'type.nomos.dev/nomos.operation.v1.Payload_350c19c4a90e0210');
|
|
194
|
+
stdout.writeln('seam: author after Home interleaving → $second');
|
|
195
|
+
expect(
|
|
196
|
+
second,
|
|
197
|
+
isNot(contains(_falseNegative)),
|
|
198
|
+
reason:
|
|
199
|
+
'the active law declares $_domain/$_directive; work on another workspace must not make it vanish',
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// 6. The kernel process restarts on the same custody (a rolled container) and
|
|
203
|
+
// restores its law view from the persisted parsed caches before the first
|
|
204
|
+
// read; then the platform is read and authored again.
|
|
205
|
+
final founder = await peer.mintFoundingDevice();
|
|
206
|
+
await peer.reboot(trustRootPublicKey: founder.publicKey);
|
|
207
|
+
// Home first, as in production: its sync reads and authors under its own
|
|
208
|
+
// older law package before the platform is touched at all.
|
|
209
|
+
await peer.reception.bindSession(
|
|
210
|
+
workspace: _home,
|
|
211
|
+
session: _home,
|
|
212
|
+
);
|
|
213
|
+
final homeDescribedAfterRestart = await describe(_home);
|
|
214
|
+
expect(homeDescribedAfterRestart.hasFailure(), isFalse);
|
|
215
|
+
final homeAuthoredAfterRestart = await authorCall(
|
|
216
|
+
_home,
|
|
217
|
+
'home',
|
|
218
|
+
'enrollFirstDevice',
|
|
219
|
+
'type.nomos.dev/nomos.operation.v1.Payload_cbec744b661de8bc');
|
|
220
|
+
stdout.writeln(
|
|
221
|
+
'seam: home author after restart → $homeAuthoredAfterRestart');
|
|
222
|
+
await peer.reception.bindSession(
|
|
223
|
+
workspace: _workspace,
|
|
224
|
+
session: _workspace,
|
|
225
|
+
);
|
|
226
|
+
final restoredDescribe = await describe(_workspace);
|
|
227
|
+
expect(restoredDescribe.hasFailure(), isFalse,
|
|
228
|
+
reason:
|
|
229
|
+
'describe after restart failed: ${restoredDescribe.failure.message}');
|
|
230
|
+
final third = await authorCall(_workspace, _domain, _directive,
|
|
231
|
+
'type.nomos.dev/nomos.operation.v1.Payload_350c19c4a90e0210');
|
|
232
|
+
stdout.writeln('seam: author after restart + cold read → $third');
|
|
233
|
+
expect(
|
|
234
|
+
third,
|
|
235
|
+
isNot(contains(_falseNegative)),
|
|
236
|
+
reason:
|
|
237
|
+
'a restart restores the law view from cache; the author lane must still see the active law',
|
|
238
|
+
);
|
|
239
|
+
},
|
|
240
|
+
timeout: const Timeout(Duration(minutes: 10)),
|
|
241
|
+
);
|
|
242
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// Kernel Office fence — golden custody replayed by the CURRENT kernel through
|
|
2
|
+
// the physical testing shell.
|
|
3
|
+
//
|
|
4
|
+
// The fence proves that a kernel rebuilt from moved code still answers the
|
|
5
|
+
// same paper with the same verdicts over real custody. It records nothing the
|
|
6
|
+
// kernel does not already prove: the branch head it mounted, the verify-chain
|
|
7
|
+
// verdict, and how many plans replay required.
|
|
8
|
+
//
|
|
9
|
+
// Inputs (environment):
|
|
10
|
+
// NOMOS_CANDIDATE_RUNTIME runtime directory staged by git-holon/fence/stage_runtime.sh
|
|
11
|
+
// NOMOS_FENCE_CUSTODY directory holding bare repos named holon-<workspace>.git
|
|
12
|
+
// NOMOS_FENCE_WORKSPACES comma-separated workspace names to mount
|
|
13
|
+
// NOMOS_FENCE_LOCK path of the golden lock file (JSON)
|
|
14
|
+
// NOMOS_FENCE_RECORD=1 rewrite the lock file from this run instead of comparing
|
|
15
|
+
//
|
|
16
|
+
// Without NOMOS_FENCE_CUSTODY the test is skipped; the fence is an operator
|
|
17
|
+
// lane, not a unit test.
|
|
18
|
+
import 'dart:convert';
|
|
19
|
+
import 'dart:io';
|
|
20
|
+
import 'dart:isolate';
|
|
21
|
+
|
|
22
|
+
import 'package:nomos_client/src/testing/test_peer.dart';
|
|
23
|
+
import 'package:test/test.dart';
|
|
24
|
+
|
|
25
|
+
Future<String> _testEntry() async {
|
|
26
|
+
final resolved = await Isolate.resolvePackageUri(
|
|
27
|
+
Uri.parse('package:nomos_client/src/runner/node-entry.mjs'),
|
|
28
|
+
);
|
|
29
|
+
if (resolved == null || resolved.scheme != 'file') {
|
|
30
|
+
throw StateError('cannot resolve the node testing shell entry');
|
|
31
|
+
}
|
|
32
|
+
final path = resolved.toFilePath();
|
|
33
|
+
if (!File(path).existsSync()) {
|
|
34
|
+
throw StateError('testing shell entry is not built: $path');
|
|
35
|
+
}
|
|
36
|
+
return path;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
void main() {
|
|
40
|
+
final custodyRoot = Platform.environment['NOMOS_FENCE_CUSTODY'];
|
|
41
|
+
final workspaces = (Platform.environment['NOMOS_FENCE_WORKSPACES'] ?? '')
|
|
42
|
+
.split(',')
|
|
43
|
+
.map((w) => w.trim())
|
|
44
|
+
.where((w) => w.isNotEmpty)
|
|
45
|
+
.toList();
|
|
46
|
+
final lockPath = Platform.environment['NOMOS_FENCE_LOCK'];
|
|
47
|
+
final record = Platform.environment['NOMOS_FENCE_RECORD'] == '1';
|
|
48
|
+
final runtime = Platform.environment['NOMOS_CANDIDATE_RUNTIME'];
|
|
49
|
+
|
|
50
|
+
test(
|
|
51
|
+
'golden custody: the current kernel mounts, verifies and folds recorded custody identically',
|
|
52
|
+
() async {
|
|
53
|
+
if (custodyRoot == null || workspaces.isEmpty || lockPath == null) {
|
|
54
|
+
markTestSkipped(
|
|
55
|
+
'set NOMOS_FENCE_CUSTODY, NOMOS_FENCE_WORKSPACES and NOMOS_FENCE_LOCK to run the fence',
|
|
56
|
+
);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (runtime == null || !File('$runtime/holon.wasm').existsSync()) {
|
|
60
|
+
fail(
|
|
61
|
+
'NOMOS_CANDIDATE_RUNTIME must point at a staged runtime (see git-holon/fence/stage_runtime.sh)');
|
|
62
|
+
}
|
|
63
|
+
final kernelSha =
|
|
64
|
+
(await Process.run('shasum', ['-a', '256', '$runtime/holon.wasm']))
|
|
65
|
+
.stdout
|
|
66
|
+
.toString()
|
|
67
|
+
.split(' ')
|
|
68
|
+
.first;
|
|
69
|
+
|
|
70
|
+
final peer = await NmsTestPeer.start(
|
|
71
|
+
entry: await _testEntry(),
|
|
72
|
+
runtimeDirectory: runtime,
|
|
73
|
+
resolveFrom: Directory.current.path,
|
|
74
|
+
onDiagnostic: (line) => stderr.writeln('[shell] $line'),
|
|
75
|
+
);
|
|
76
|
+
addTearDown(peer.dispose);
|
|
77
|
+
|
|
78
|
+
final custodyDir = Directory('${peer.custody.directory.path}/custody');
|
|
79
|
+
await custodyDir.create(recursive: true);
|
|
80
|
+
final answers = <String, Object?>{};
|
|
81
|
+
for (final workspace in workspaces) {
|
|
82
|
+
final source = Directory('$custodyRoot/holon-$workspace.git');
|
|
83
|
+
if (!source.existsSync()) {
|
|
84
|
+
fail('golden custody missing: ${source.path}');
|
|
85
|
+
}
|
|
86
|
+
final copy = await Process.run(
|
|
87
|
+
'cp',
|
|
88
|
+
['-R', source.path, '${custodyDir.path}/holon-$workspace.git'],
|
|
89
|
+
);
|
|
90
|
+
if (copy.exitCode != 0) fail('copy custody: ${copy.stderr}');
|
|
91
|
+
|
|
92
|
+
await peer.reception.bindSession(
|
|
93
|
+
workspace: workspace,
|
|
94
|
+
session: workspace,
|
|
95
|
+
);
|
|
96
|
+
final head = await peer.reception.head();
|
|
97
|
+
final verdict = await peer.reception.verifyChain();
|
|
98
|
+
answers[workspace] = <String, Object?>{
|
|
99
|
+
'head': head,
|
|
100
|
+
'valid': verdict.valid,
|
|
101
|
+
'error': verdict.hasError() ? verdict.error : null,
|
|
102
|
+
'plansRerun':
|
|
103
|
+
verdict.hasPlansRerun() ? verdict.plansRerun.toInt() : null,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
final observed = <String, Object?>{
|
|
108
|
+
'kernel': kernelSha,
|
|
109
|
+
'workspaces': answers,
|
|
110
|
+
};
|
|
111
|
+
final lock = File(lockPath);
|
|
112
|
+
if (record || !lock.existsSync()) {
|
|
113
|
+
await lock.writeAsString(
|
|
114
|
+
const JsonEncoder.withIndent(' ').convert(observed) + '\n',
|
|
115
|
+
);
|
|
116
|
+
stdout.writeln('fence: recorded ${lock.path}');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
final golden =
|
|
120
|
+
jsonDecode(await lock.readAsString()) as Map<String, Object?>;
|
|
121
|
+
final goldenAnswers = golden['workspaces'] as Map<String, Object?>;
|
|
122
|
+
for (final workspace in workspaces) {
|
|
123
|
+
expect(
|
|
124
|
+
answers[workspace],
|
|
125
|
+
equals(goldenAnswers[workspace]),
|
|
126
|
+
reason:
|
|
127
|
+
'workspace $workspace answered differently under kernel ${kernelSha.substring(0, 12)} '
|
|
128
|
+
'(golden recorded under ${(golden['kernel'] as String).substring(0, 12)})',
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
stdout.writeln(
|
|
132
|
+
'fence: ${workspaces.length} workspace(s) answered identically under kernel ${kernelSha.substring(0, 12)}',
|
|
133
|
+
);
|
|
134
|
+
},
|
|
135
|
+
timeout: const Timeout(Duration(minutes: 15)),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import 'package:fixnum/fixnum.dart';
|
|
2
|
+
import 'package:nomos_client/src/reception/event_desk.dart';
|
|
3
|
+
import 'package:nomos_client/src/reception/local_copies.dart';
|
|
4
|
+
import 'package:nomos_client/src/sync_status_projection.dart';
|
|
5
|
+
import 'package:nomos_client/src/sync_status_reducer.dart';
|
|
6
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
|
|
7
|
+
import 'package:test/test.dart';
|
|
8
|
+
|
|
9
|
+
void main() {
|
|
10
|
+
final rebirth = protocol.WorkspaceRebirth(
|
|
11
|
+
localGenesis: 'a' * 40,
|
|
12
|
+
remoteGenesis: 'b' * 40,
|
|
13
|
+
localHead: '1' * 40,
|
|
14
|
+
remoteHead: '3' * 40,
|
|
15
|
+
detail: 'The provider now serves this workspace from a different genesis.',
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
({ReceptionLocalCopies copies, List<protocol.RuntimeRequest> sent})
|
|
19
|
+
localCopies(
|
|
20
|
+
protocol.RuntimeResponse Function(protocol.RuntimeRequest) answer) {
|
|
21
|
+
final sent = <protocol.RuntimeRequest>[];
|
|
22
|
+
return (
|
|
23
|
+
copies: ReceptionLocalCopies(
|
|
24
|
+
invoke: (request) async {
|
|
25
|
+
sent.add(request);
|
|
26
|
+
return answer(request);
|
|
27
|
+
},
|
|
28
|
+
changes: const Stream.empty(),
|
|
29
|
+
),
|
|
30
|
+
sent: sent,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
test('lists every local copy with its reborn state and pending drop',
|
|
35
|
+
() async {
|
|
36
|
+
final (:copies, :sent) = localCopies((_) => protocol.RuntimeResponse(
|
|
37
|
+
workspaceList: protocol.WorkspaceList(workspaces: [
|
|
38
|
+
protocol.WorkspaceSummary(
|
|
39
|
+
workspace: 'home', followed: true, rebornRemotely: rebirth),
|
|
40
|
+
protocol.WorkspaceSummary(
|
|
41
|
+
workspace: 'estate',
|
|
42
|
+
followed: true,
|
|
43
|
+
localCopyDropPending: true),
|
|
44
|
+
]),
|
|
45
|
+
));
|
|
46
|
+
final listed = await copies.list();
|
|
47
|
+
expect(sent.single.hasListWorkspaces(), isTrue);
|
|
48
|
+
expect(listed.first.workspace, 'home');
|
|
49
|
+
expect(listed.first.rebornRemotely,
|
|
50
|
+
NomosWorkspaceRebirth.fromRuntime(rebirth));
|
|
51
|
+
expect(listed.first.dropPending, isFalse);
|
|
52
|
+
expect(listed.last.rebornRemotely, isNull);
|
|
53
|
+
expect(listed.last.dropPending, isTrue);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test(
|
|
57
|
+
'a preview counts what the drop loses, and the drop names the previewed head',
|
|
58
|
+
() async {
|
|
59
|
+
final (:copies, :sent) =
|
|
60
|
+
localCopies((request) => request.hasPreviewLocalCopyDrop()
|
|
61
|
+
? protocol.RuntimeResponse(
|
|
62
|
+
localCopyDropPreview: protocol.LocalCopyDropPreview(
|
|
63
|
+
workspace: 'home',
|
|
64
|
+
localHead: '1' * 40,
|
|
65
|
+
syncedHead: '2' * 40,
|
|
66
|
+
unsyncedLocalChanges: Int64(3),
|
|
67
|
+
parkedLocalChanges: Int64(2),
|
|
68
|
+
undeliveredLetters: Int64(1),
|
|
69
|
+
lettersCounted: true,
|
|
70
|
+
changesCounted: true,
|
|
71
|
+
followed: true,
|
|
72
|
+
rebornRemotely: rebirth,
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
: protocol.RuntimeResponse(
|
|
76
|
+
localCopyDropScheduled: protocol.LocalCopyDropScheduled(
|
|
77
|
+
workspace: 'home', droppedHead: '1' * 40),
|
|
78
|
+
));
|
|
79
|
+
final preview = await copies.preview('home');
|
|
80
|
+
expect(sent.single.previewLocalCopyDrop.workspace, 'home');
|
|
81
|
+
expect(preview.changesLost, 6);
|
|
82
|
+
expect(preview.losesWork, isTrue);
|
|
83
|
+
expect(preview.canDrop, isTrue);
|
|
84
|
+
expect(preview.rebornRemotely?.remoteGenesis, 'b' * 40);
|
|
85
|
+
|
|
86
|
+
await copies.drop(preview);
|
|
87
|
+
expect(sent.last.dropLocalCopy.workspace, 'home');
|
|
88
|
+
expect(sent.last.dropLocalCopy.expectedLocalHead, '1' * 40);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test(
|
|
92
|
+
'a clean copy loses nothing; without a provider the drop is refused locally',
|
|
93
|
+
() async {
|
|
94
|
+
final (:copies, :sent) = localCopies((_) => protocol.RuntimeResponse(
|
|
95
|
+
localCopyDropPreview: protocol.LocalCopyDropPreview(
|
|
96
|
+
workspace: 'catalogue',
|
|
97
|
+
localHead: '4' * 40,
|
|
98
|
+
lettersCounted: true,
|
|
99
|
+
changesCounted: true,
|
|
100
|
+
followed: false,
|
|
101
|
+
),
|
|
102
|
+
));
|
|
103
|
+
final preview = await copies.preview('catalogue');
|
|
104
|
+
expect(preview.changesLost, 0);
|
|
105
|
+
expect(preview.losesWork, isFalse);
|
|
106
|
+
expect(preview.canDrop, isFalse);
|
|
107
|
+
await expectLater(copies.drop(preview), throwsStateError);
|
|
108
|
+
expect(sent, hasLength(1),
|
|
109
|
+
reason: 'the refused drop never reached the peer');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('a reborn sync result needs attention and says so in plain words', () {
|
|
113
|
+
final status = reduceSyncCompletion(
|
|
114
|
+
const SyncStatus(),
|
|
115
|
+
protocol.SyncCompleted(
|
|
116
|
+
result: protocol.SyncWorkspaceResponse(
|
|
117
|
+
currentHead: '1' * 40,
|
|
118
|
+
reconnectRequired: true,
|
|
119
|
+
custodyDisagreement: protocol.CustodyDisagreement(
|
|
120
|
+
localHead: '1' * 40, candidateHead: '3' * 40, reason: 'x'),
|
|
121
|
+
rebornRemotely: rebirth,
|
|
122
|
+
),
|
|
123
|
+
),
|
|
124
|
+
DateTime.utc(2026, 9, 22),
|
|
125
|
+
);
|
|
126
|
+
expect(status.phase, SyncPhase.error);
|
|
127
|
+
expect(status.lastError, rebirth.detail);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test(
|
|
131
|
+
'an unreadable copy is listed with its reason; unknown unsynced work is never "none"',
|
|
132
|
+
() async {
|
|
133
|
+
final (
|
|
134
|
+
:copies,
|
|
135
|
+
sent: _
|
|
136
|
+
) = localCopies((request) => request.hasListWorkspaces()
|
|
137
|
+
? protocol.RuntimeResponse(
|
|
138
|
+
workspaceList: protocol.WorkspaceList(workspaces: [
|
|
139
|
+
protocol.WorkspaceSummary(
|
|
140
|
+
workspace: 'estate',
|
|
141
|
+
followed: true,
|
|
142
|
+
localCopyUnreadable:
|
|
143
|
+
'local copy is from an older storage format'),
|
|
144
|
+
]),
|
|
145
|
+
)
|
|
146
|
+
: protocol.RuntimeResponse(
|
|
147
|
+
localCopyDropPreview: protocol.LocalCopyDropPreview(
|
|
148
|
+
workspace: 'estate',
|
|
149
|
+
localHead: '5' * 40,
|
|
150
|
+
lettersCounted: false,
|
|
151
|
+
changesCounted: false,
|
|
152
|
+
followed: true,
|
|
153
|
+
localCopyUnreadable: 'local copy is from an older storage format',
|
|
154
|
+
),
|
|
155
|
+
));
|
|
156
|
+
final listed = await copies.list();
|
|
157
|
+
expect(listed.single.unreadableReason,
|
|
158
|
+
'local copy is from an older storage format');
|
|
159
|
+
final preview = await copies.preview('estate');
|
|
160
|
+
expect(preview.changesCounted, isFalse);
|
|
161
|
+
expect(preview.changesLost, 0);
|
|
162
|
+
expect(preview.losesWork, isTrue,
|
|
163
|
+
reason: 'an undeterminable count is not reported as nothing lost');
|
|
164
|
+
expect(preview.unreadableReason, isNotNull);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('a preserved copy stays listed with its loss after the refetch',
|
|
168
|
+
() async {
|
|
169
|
+
final (:copies, sent: _) = localCopies((_) => protocol.RuntimeResponse(
|
|
170
|
+
workspaceList: protocol.WorkspaceList(workspaces: [
|
|
171
|
+
protocol.WorkspaceSummary(
|
|
172
|
+
workspace: 'home',
|
|
173
|
+
followed: true,
|
|
174
|
+
preservedCopies: [
|
|
175
|
+
protocol.PreservedLocalCopy(
|
|
176
|
+
workspace: 'home',
|
|
177
|
+
preservedHead: '1' * 40,
|
|
178
|
+
preservedRef: 'refs/heads/custody-preserved/${'1' * 40}',
|
|
179
|
+
reason: 'local copy is from an older storage format',
|
|
180
|
+
unsyncedLocalChanges: Int64(3),
|
|
181
|
+
changesCounted: true,
|
|
182
|
+
automatic: true,
|
|
183
|
+
preservedAtUnixMillis: Int64(1758500000000),
|
|
184
|
+
),
|
|
185
|
+
],
|
|
186
|
+
),
|
|
187
|
+
]),
|
|
188
|
+
));
|
|
189
|
+
final preserved = (await copies.list()).single.preservedCopies.single;
|
|
190
|
+
expect(preserved.unsyncedLocalChanges, 3);
|
|
191
|
+
expect(preserved.changesCounted, isTrue);
|
|
192
|
+
expect(preserved.automatic, isTrue);
|
|
193
|
+
expect(preserved.reason, 'local copy is from an older storage format');
|
|
194
|
+
expect(preserved.preservedAt, isNotNull);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('the diagnostics log surfaces each preservation as a typed event',
|
|
198
|
+
() async {
|
|
199
|
+
final desk = ReceptionEventDesk();
|
|
200
|
+
final copies = ReceptionLocalCopies(
|
|
201
|
+
invoke: (_) async => throw StateError('unused'),
|
|
202
|
+
changes: const Stream.empty(),
|
|
203
|
+
preservations: desk.localCopiesPreserved,
|
|
204
|
+
recentPreservations: () => desk.recentLocalCopiesPreserved,
|
|
205
|
+
);
|
|
206
|
+
final seen = copies.preservations.first;
|
|
207
|
+
final copy = NomosPreservedLocalCopy.fromRuntime(
|
|
208
|
+
protocol.PreservedLocalCopy(
|
|
209
|
+
workspace: 'home',
|
|
210
|
+
preservedHead: '1' * 40,
|
|
211
|
+
reason: 'local copy is from an older storage format',
|
|
212
|
+
changesCounted: false,
|
|
213
|
+
automatic: true,
|
|
214
|
+
),
|
|
215
|
+
);
|
|
216
|
+
desk.publishLocalCopyPreserved(copy);
|
|
217
|
+
expect(await seen, copy);
|
|
218
|
+
expect(copies.recentPreservations, [copy]);
|
|
219
|
+
expect(copies.recentPreservations.single.changesCounted, isFalse);
|
|
220
|
+
await desk.close();
|
|
221
|
+
});
|
|
222
|
+
}
|