@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.
Files changed (307) hide show
  1. package/assets/operation-contract.wasm +0 -0
  2. package/check_package.mjs +12 -15
  3. package/compile_package.mjs +17 -18
  4. package/compile_proof.mjs +16 -1
  5. package/dart/CHANGELOG.md +76 -1
  6. package/dart/README.md +25 -2
  7. package/dart/lib/src/admission_decision.dart +41 -0
  8. package/dart/lib/src/application_framework_calls.dart +52 -2
  9. package/dart/lib/src/application_model_adoption.dart +187 -0
  10. package/dart/lib/src/application_status.dart +73 -0
  11. package/dart/lib/src/console_enrolment.dart +271 -0
  12. package/dart/lib/src/console_http_io.dart +26 -0
  13. package/dart/lib/src/console_http_stub.dart +7 -0
  14. package/dart/lib/src/dev_cloud.dart +69 -22
  15. package/dart/lib/src/generated_call.dart +83 -23
  16. package/dart/lib/src/kernel_primitives.dart +119 -0
  17. package/dart/lib/src/nms_conformance_runtime.dart +1 -1
  18. package/dart/lib/src/nms_generated_runtime.dart +32 -10
  19. package/dart/lib/src/nms_test_environment.dart +1 -1
  20. package/dart/lib/src/node_runner_transport.dart +41 -46
  21. package/dart/lib/src/observed_session_directory.dart +70 -0
  22. package/dart/lib/src/operation_receipts.dart +112 -0
  23. package/dart/lib/src/peer_application_streams.dart +103 -0
  24. package/dart/lib/src/reception/dart_reception.dart +4280 -0
  25. package/dart/lib/src/reception/event_desk.dart +211 -0
  26. package/dart/lib/src/reception/extremum_read.dart +46 -0
  27. package/dart/lib/src/reception/local_copies.dart +371 -0
  28. package/dart/lib/src/reception/peer_office_connection.dart +11 -0
  29. package/dart/lib/src/reception/progress_desk.dart +267 -0
  30. package/dart/lib/src/reception/reception_contract.dart +11 -0
  31. package/dart/lib/src/reception/reception_session.dart +284 -0
  32. package/dart/lib/src/reception/sync_diagnostics.dart +26 -0
  33. package/dart/lib/src/reception/workspace_preparation_report.dart +126 -0
  34. package/dart/lib/src/replica_readiness.dart +123 -0
  35. package/dart/lib/src/runner/node-entry.mjs +23905 -15774
  36. package/dart/lib/src/runtime_v2_gateway.dart +56 -3
  37. package/dart/lib/src/runtime_v2_transport.dart +544 -3
  38. package/dart/lib/src/session_bound.dart +200 -311
  39. package/dart/lib/src/signing_identity_kernel.dart +84 -0
  40. package/dart/lib/src/sync_status_projection.dart +548 -0
  41. package/dart/lib/src/sync_status_reducer.dart +170 -0
  42. package/dart/lib/src/test_harness.dart +809 -1153
  43. package/dart/lib/src/testing/test_custody.dart +66 -0
  44. package/dart/lib/src/testing/test_peer.dart +328 -0
  45. package/dart/lib/src/typed_watch_registry.dart +169 -0
  46. package/dart/lib/src/workspace_open_progress.dart +27 -0
  47. package/dart/pubspec.lock +24 -1
  48. package/dart/pubspec.yaml +7 -2
  49. package/dart/pubspec_overrides.yaml +2 -0
  50. package/dart/test/admission_decision_test.dart +48 -0
  51. package/dart/test/background_model_adoption_test.dart +228 -0
  52. package/dart/test/bundle_version_pairing_test.dart +4 -4
  53. package/dart/test/child_authentication_repair_test.dart +359 -0
  54. package/dart/test/cloud_bound_local_shell_test.dart +119 -0
  55. package/dart/test/console_enrolment_refusal_test.dart +52 -0
  56. package/dart/test/console_enrolment_test.dart +227 -0
  57. package/dart/test/errors_test.dart +5 -5
  58. package/dart/test/event_plane_seam_test.dart +50 -164
  59. package/dart/test/extremum_read_test.dart +58 -0
  60. package/dart/test/fixtures/wedge/wedge-entry.mjs +3 -21
  61. package/dart/test/generated_query_test.dart +32 -0
  62. package/dart/test/harness_phase_timeout_test.dart +18 -23
  63. package/dart/test/kernel_author_after_read_test.dart +242 -0
  64. package/dart/test/kernel_fence_test.dart +137 -0
  65. package/dart/test/local_copies_test.dart +222 -0
  66. package/dart/test/model_adoption_test.dart +198 -0
  67. package/dart/test/node_peer_local_custody_test.dart +153 -0
  68. package/dart/test/offer_authentication_proof_test.dart +352 -52
  69. package/dart/test/operation_compatibility_test.dart +151 -0
  70. package/dart/test/platform_credential_effect_test.dart +386 -0
  71. package/dart/test/receiving_group_open_test.dart +213 -0
  72. package/dart/test/reception/workspace_preparation_report_test.dart +103 -0
  73. package/dart/test/reception_span_trace_test.dart +23 -0
  74. package/dart/test/replica_readiness_test.dart +156 -0
  75. package/dart/test/runtime_projections_test.dart +92 -0
  76. package/dart/test/runtime_v2_gateway_test.dart +131 -0
  77. package/dart/test/signing_identity_kernel_test.dart +75 -0
  78. package/dart/test/sync_status_preparation_report_test.dart +29 -0
  79. package/dart/test/sync_status_reducer_test.dart +419 -0
  80. package/dart/test/test_harness_test.dart +71 -95
  81. package/dart/test/typed_query_watch_test.dart +170 -0
  82. package/dart/test/typed_watch_registry_test.dart +249 -0
  83. package/dart_lints/README.md +18 -0
  84. package/dart_lints/lib/nomos_lints.dart +70 -0
  85. package/dart_lints/pubspec.lock +580 -0
  86. package/dart_lints/pubspec.yaml +12 -0
  87. package/dart_lints/test/reference_identity_test.dart +75 -0
  88. package/dart_types/CHANGELOG.md +18 -0
  89. package/dart_types/lib/nomos_kernel_protocol.dart +21 -1
  90. package/dart_types/lib/nomos_types.dart +9 -1
  91. package/dart_types/lib/src/admission_decision.dart +37 -0
  92. package/dart_types/lib/src/aggregate_meta.dart +1 -1
  93. package/dart_types/lib/src/approved_model.dart +65 -0
  94. package/dart_types/lib/src/custody.dart +10 -18
  95. package/dart_types/lib/src/error_info.dart +4 -4
  96. package/dart_types/lib/src/errors.dart +29 -29
  97. package/dart_types/lib/src/foreign_ref.dart +62 -33
  98. package/dart_types/lib/src/local_work_rejected.dart +3 -3
  99. package/dart_types/lib/src/nomos_ref.dart +3 -1
  100. package/dart_types/lib/src/principal.dart +2 -2
  101. package/dart_types/lib/src/proto/google/protobuf/any.pb.dart +163 -0
  102. package/dart_types/lib/src/proto/google/protobuf/any.pbenum.dart +11 -0
  103. package/dart_types/lib/src/proto/nomos/discovery/v1/workspace_placement.pb.dart +137 -0
  104. package/dart_types/lib/src/proto/nomos/discovery/v1/workspace_placement.pbenum.dart +11 -0
  105. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +20721 -10433
  106. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +555 -27
  107. package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pb.dart +387 -0
  108. package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pbenum.dart +46 -0
  109. package/dart_types/lib/src/proto/nomos/runtime/v2/application_readiness_desk.pb.dart +295 -0
  110. package/dart_types/lib/src/proto/nomos/runtime/v2/application_readiness_desk.pbenum.dart +139 -0
  111. package/dart_types/lib/src/proto/nomos/runtime/v2/authentication_desk.pb.dart +269 -0
  112. package/dart_types/lib/src/proto/nomos/runtime/v2/authentication_desk.pbenum.dart +103 -0
  113. package/dart_types/lib/src/proto/nomos/runtime/v2/compatibility_desk.pb.dart +417 -0
  114. package/dart_types/lib/src/proto/nomos/runtime/v2/compatibility_desk.pbenum.dart +62 -0
  115. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +9968 -0
  116. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pbenum.dart +133 -0
  117. package/dart_types/lib/src/proto/nomos/runtime/v2/credentials_desk.pb.dart +204 -0
  118. package/dart_types/lib/src/proto/nomos/runtime/v2/credentials_desk.pbenum.dart +54 -0
  119. package/dart_types/lib/src/proto/nomos/runtime/v2/custody_desk.pb.dart +1550 -0
  120. package/dart_types/lib/src/proto/nomos/runtime/v2/custody_desk.pbenum.dart +11 -0
  121. package/dart_types/lib/src/proto/nomos/runtime/v2/framework.pb.dart +710 -0
  122. package/dart_types/lib/src/proto/nomos/runtime/v2/framework.pbenum.dart +11 -0
  123. package/dart_types/lib/src/proto/nomos/runtime/v2/identity_desk.pb.dart +845 -0
  124. package/dart_types/lib/src/proto/nomos/runtime/v2/identity_desk.pbenum.dart +11 -0
  125. package/dart_types/lib/src/proto/nomos/runtime/v2/interaction_desk.pb.dart +198 -0
  126. package/dart_types/lib/src/proto/nomos/runtime/v2/interaction_desk.pbenum.dart +11 -0
  127. package/dart_types/lib/src/proto/nomos/runtime/v2/law_desk.pb.dart +369 -0
  128. package/dart_types/lib/src/proto/nomos/runtime/v2/law_desk.pbenum.dart +11 -0
  129. package/dart_types/lib/src/proto/nomos/runtime/v2/maintenance_desk.pb.dart +609 -0
  130. package/dart_types/lib/src/proto/nomos/runtime/v2/maintenance_desk.pbenum.dart +65 -0
  131. package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +358 -0
  132. package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pbenum.dart +100 -0
  133. package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pb.dart +2231 -0
  134. package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pbenum.dart +271 -0
  135. package/dart_types/lib/src/proto/nomos/runtime/v2/placement_desk.pb.dart +1286 -0
  136. package/dart_types/lib/src/proto/nomos/runtime/v2/placement_desk.pbenum.dart +11 -0
  137. package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pb.dart +636 -0
  138. package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pbenum.dart +116 -0
  139. package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pb.dart +1637 -0
  140. package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pbenum.dart +175 -0
  141. package/dart_types/lib/src/proto/nomos/runtime/v2/recovery_desk.pb.dart +1269 -0
  142. package/dart_types/lib/src/proto/nomos/runtime/v2/recovery_desk.pbenum.dart +129 -0
  143. package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pb.dart +2782 -0
  144. package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pbenum.dart +97 -0
  145. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +2232 -8627
  146. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pbenum.dart +0 -358
  147. package/dart_types/lib/src/proto/nomos/runtime/v2/sealing_desk.pb.dart +1639 -0
  148. package/dart_types/lib/src/proto/nomos/runtime/v2/sealing_desk.pbenum.dart +46 -0
  149. package/dart_types/lib/src/runtime_outcome.dart +133 -0
  150. package/dart_types/lib/src/subscriptions.dart +116 -23
  151. package/dart_types/pubspec.yaml +1 -1
  152. package/dart_types/test/extremum_subscriptions_test.dart +40 -0
  153. package/dart_types/test/foreign_ref_resolution_test.dart +75 -0
  154. package/dart_types/test/nomos_ref_address_test.dart +25 -0
  155. package/dart_types/test/ordered_subscriptions_test.dart +34 -0
  156. package/operation_contract_check.mjs +24 -0
  157. package/package.json +11 -6
  158. package/src/aggregate.ts +70 -10
  159. package/src/application_topology.ts +18 -11
  160. package/src/attested.ts +3 -0
  161. package/src/attested_core.ts +46 -3
  162. package/src/birth_meter_ensure.ts +19 -7
  163. package/src/births.ts +29 -4
  164. package/src/build_package.ts +140 -13
  165. package/src/business_birth.ts +181 -4
  166. package/src/codegen_call_proto.ts +1274 -0
  167. package/src/codegen_call_usda.ts +49 -6
  168. package/src/codegen_dart.ts +79 -44
  169. package/src/codegen_dart_client.ts +458 -800
  170. package/src/{codegen_dart_application.ts → codegen_dart_facade.ts} +331 -582
  171. package/src/codegen_dart_framework_calls.ts +320 -37
  172. package/src/codegen_dart_indexed_variant.ts +159 -0
  173. package/src/codegen_dart_scenario.ts +42 -13
  174. package/src/codegen_proof.ts +3 -3
  175. package/src/codegen_provider_dart.ts +5 -0
  176. package/src/codegen_query_proto.ts +158 -0
  177. package/src/codegen_refusal.ts +189 -0
  178. package/src/codegen_result_proto.ts +931 -0
  179. package/src/codegen_result_usda.ts +4 -0
  180. package/src/codegen_ts.ts +464 -616
  181. package/src/codegen_workspace_access.ts +105 -0
  182. package/src/compatibility_graph.ts +759 -0
  183. package/src/compatibility_usda.ts +264 -0
  184. package/src/compile_package_main.ts +1994 -1189
  185. package/src/custody_placement.ts +156 -0
  186. package/src/custody_placement_usda.ts +91 -0
  187. package/src/decl_shapes.ts +21 -2
  188. package/src/describe_model.ts +1 -0
  189. package/src/directive.ts +59 -2
  190. package/src/drivers.ts +51 -0
  191. package/src/engine_entry.ts +52 -8
  192. package/src/evolve_preview.ts +11 -4
  193. package/src/exists.ts +2 -0
  194. package/src/fields.ts +75 -11
  195. package/src/flow_gate.ts +2 -1
  196. package/src/framework/application_home.ts +170 -18
  197. package/src/framework/attested_receipts.ts +8 -3
  198. package/src/framework/auth_provider_law.ts +169 -9
  199. package/src/framework/birth_certificate_law.ts +4 -0
  200. package/src/framework/capability.ts +34 -3
  201. package/src/framework/child_authentication.ts +346 -0
  202. package/src/framework/console_governance.ts +295 -0
  203. package/src/framework/delegated_roles.ts +301 -0
  204. package/src/framework/domain_lifecycle.ts +26 -13
  205. package/src/framework/founding_genesis.ts +193 -0
  206. package/src/framework/framework_aggregates.ts +1 -1
  207. package/src/framework/genesis.ts +6 -1
  208. package/src/framework/governance_relations.ts +19 -0
  209. package/src/framework/home.ts +5 -734
  210. package/src/framework/legacy/child_auth_provider_epoch.ts +73 -0
  211. package/src/framework/model_authority_repair.ts +116 -0
  212. package/src/framework/model_authority_seed.ts +185 -0
  213. package/src/framework/model_transition.ts +211 -0
  214. package/src/framework/owned_custody.ts +1 -1
  215. package/src/framework/principal_enrollment.ts +302 -0
  216. package/src/framework/receiving_group.ts +857 -0
  217. package/src/framework/receiving_group_recovery.ts +314 -0
  218. package/src/framework/relations.ts +4 -8
  219. package/src/framework/signing_identity.ts +242 -95
  220. package/src/framework/source_roster.ts +11 -5
  221. package/src/framework/workspace_access.ts +149 -0
  222. package/src/framework/workspace_invariant.ts +73 -52
  223. package/src/framework/workspaces.ts +407 -40
  224. package/src/framework_call_definitions.ts +32 -3
  225. package/src/generated_client_bundle_boundary.ts +97 -0
  226. package/src/generated_client_names.ts +1 -0
  227. package/src/identity_usda.ts +27 -0
  228. package/src/index.ts +183 -12
  229. package/src/indexed_variant.ts +83 -0
  230. package/src/intent.ts +173 -45
  231. package/src/law_check.ts +8 -5
  232. package/src/law_dependency_index.ts +231 -0
  233. package/src/law_graph.ts +508 -71
  234. package/src/law_graph_usda.ts +170 -24
  235. package/src/law_identity.ts +119 -81
  236. package/src/law_source_map.ts +4 -1
  237. package/src/law_units.ts +498 -0
  238. package/src/law_value_type.ts +7 -3
  239. package/src/member_authentication.ts +302 -0
  240. package/src/null_meta.ts +1 -1
  241. package/src/openusd_compiled_layer.ts +132 -0
  242. package/src/operation_contract_check.ts +31 -0
  243. package/src/ordered_read.ts +2 -1
  244. package/src/package_config.ts +33 -12
  245. package/src/package_dependencies.ts +1 -0
  246. package/src/plan_source_slice.ts +30 -1
  247. package/src/protobuf_field_number.ts +36 -0
  248. package/src/provenance_wire.ts +6 -0
  249. package/src/query.ts +2 -1
  250. package/src/read.ts +50 -0
  251. package/src/relation.ts +4 -1
  252. package/src/relation_schema.ts +15 -2
  253. package/src/report.ts +2 -2
  254. package/src/sqlite_computation_bundle.ts +78 -0
  255. package/src/sqlite_computed.ts +102 -0
  256. package/src/sqlite_counts.ts +154 -0
  257. package/src/sqlite_layout.ts +823 -0
  258. package/src/sqlite_reads.ts +275 -0
  259. package/src/sqlite_samples.ts +185 -0
  260. package/src/stable_id_identity_bridge.ts +1 -1
  261. package/src/stable_ids_for_module.ts +16 -4
  262. package/src/test_call_usda.ts +6 -6
  263. package/src/usda_relationship_integrity.ts +20 -3
  264. package/src/usda_text.ts +21 -1
  265. package/src/value_object.ts +81 -11
  266. package/src/wire.ts +22 -0
  267. package/src/wire_encode.ts +227 -20
  268. package/src/workspace_sharding.ts +15 -6
  269. package/src/workspace_sharing.ts +51 -49
  270. package/src/zod_introspection.ts +2 -0
  271. package/compat_package.mjs +0 -46
  272. package/dart/lib/src/home.dart +0 -15
  273. package/dart/lib/src/runner/offline-install.mjs +0 -15367
  274. package/dart/lib/src/source_trust.dart +0 -51
  275. package/dart/lib/src/webview_bridge.dart +0 -6540
  276. package/dart/lib/src/workspace_name.dart +0 -30
  277. package/dart/test/acquire_session_credentials_test.dart +0 -150
  278. package/dart/test/acquire_session_test.dart +0 -534
  279. package/dart/test/adv_realm_interference_test.dart +0 -600
  280. package/dart/test/cloud_bound_topology_test.dart +0 -168
  281. package/dart/test/cluster_births_test.dart +0 -202
  282. package/dart/test/cluster_device_keyed_acceptance_test.dart +0 -105
  283. package/dart/test/cluster_local_autosync_test.dart +0 -123
  284. package/dart/test/cluster_scale_test.dart +0 -122
  285. package/dart/test/compat_test.dart +0 -61
  286. package/dart/test/dlq_session_surface_test.dart +0 -222
  287. package/dart/test/first_app_gauntlet_test.dart +0 -386
  288. package/dart/test/fixtures/wedge/fake-install.mjs +0 -11
  289. package/dart/test/four_layer_nested_topology_test.dart +0 -290
  290. package/dart/test/home_parity_test.dart +0 -13
  291. package/dart/test/local_sync_inert_test.dart +0 -107
  292. package/dart/test/node_runner_transport_test.dart +0 -78
  293. package/dart/test/parent_signer_recovery_test.dart +0 -155
  294. package/dart/test/residency_refusal_test.dart +0 -49
  295. package/dart/test/sealing_client_test.dart +0 -181
  296. package/dart/test/session_bound_test.dart +0 -285
  297. package/dart/test/stale_bundle_test.dart +0 -80
  298. package/dart/test/vo_read_typing_test.dart +0 -131
  299. package/dart/test/watch_after_offer_stall_test.dart +0 -100
  300. package/dart/test/webview_bridge_test.dart +0 -1930
  301. package/dart/test/workspace_name_test.dart +0 -24
  302. package/runtime/generated-application-host.js +0 -22860
  303. package/src/codegen_query_usda.ts +0 -214
  304. package/src/compat_manifest.ts +0 -26
  305. package/src/compat_package_main.ts +0 -224
  306. package/src/shape_compat.ts +0 -352
  307. 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
+ }