@githolon/dsl 0.108.0 → 0.108.2

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 (95) hide show
  1. package/dart/CHANGELOG.md +5 -0
  2. package/dart/lib/src/application_framework_calls.dart +299 -0
  3. package/dart/lib/src/generated_call.dart +96 -0
  4. package/dart/lib/src/nms_generated_runtime.dart +29 -0
  5. package/dart/lib/src/node_runner_transport.dart +77 -1
  6. package/dart/lib/src/result_usda.dart +487 -0
  7. package/dart/lib/src/runner/node-entry.mjs +6401 -2102
  8. package/dart/lib/src/runner/offline-install.mjs +1538 -163
  9. package/dart/lib/src/runtime_v2_gateway.dart +54 -0
  10. package/dart/lib/src/runtime_v2_transport.dart +117 -0
  11. package/dart/lib/src/session_bound.dart +7 -2
  12. package/dart/lib/src/test_harness.dart +193 -112
  13. package/dart/lib/src/webview_bridge.dart +2120 -569
  14. package/dart/pubspec.yaml +2 -2
  15. package/dart/test/acquire_session_credentials_test.dart +1 -1
  16. package/dart/test/acquire_session_test.dart +68 -14
  17. package/dart/test/adv_realm_interference_test.dart +6 -4
  18. package/dart/test/cluster_births_test.dart +11 -8
  19. package/dart/test/cluster_local_autosync_test.dart +1 -1
  20. package/dart/test/cluster_scale_test.dart +3 -2
  21. package/dart/test/dlq_session_surface_test.dart +52 -35
  22. package/dart/test/first_app_gauntlet_test.dart +1 -1
  23. package/dart/test/local_sync_inert_test.dart +23 -26
  24. package/dart/test/offer_authentication_proof_test.dart +193 -0
  25. package/dart/test/result_usda_test.dart +49 -0
  26. package/dart/test/session_bound_test.dart +8 -11
  27. package/dart/test/stale_bundle_test.dart +18 -3
  28. package/dart/test/test_harness_test.dart +14 -27
  29. package/dart/test/webview_bridge_test.dart +504 -83
  30. package/dart_types/CHANGELOG.md +7 -0
  31. package/dart_types/lib/nomos_kernel_protocol.dart +3 -2
  32. package/dart_types/lib/src/custody.dart +64 -19
  33. package/dart_types/lib/src/local_work_rejected.dart +30 -45
  34. package/dart_types/lib/src/proto/nomos/custody/v1/snapshot.pb.dart +169 -0
  35. package/dart_types/lib/src/proto/nomos/custody/v1/snapshot.pbenum.dart +11 -0
  36. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +2021 -234
  37. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +175 -8
  38. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +8664 -0
  39. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pbenum.dart +369 -0
  40. package/dart_types/lib/src/surfaced.dart +30 -25
  41. package/dart_types/pubspec.yaml +1 -1
  42. package/dart_types/test/surfaced_test.dart +2 -1
  43. package/package.json +1 -1
  44. package/runtime/generated-application-host.js +5736 -2047
  45. package/src/authoring.ts +3 -2
  46. package/src/births.ts +21 -0
  47. package/src/build_package.ts +30 -0
  48. package/src/business_birth.ts +39 -35
  49. package/src/codegen_call_usda.ts +1111 -0
  50. package/src/codegen_dart.ts +207 -282
  51. package/src/codegen_dart_application.ts +195 -252
  52. package/src/codegen_dart_client.ts +1978 -593
  53. package/src/codegen_dart_framework_calls.ts +291 -0
  54. package/src/codegen_dart_scenario.ts +7 -0
  55. package/src/codegen_proof.ts +3 -4
  56. package/src/codegen_provider_dart.ts +21 -7
  57. package/src/codegen_query_usda.ts +214 -0
  58. package/src/codegen_result_usda.ts +685 -0
  59. package/src/codegen_ts.ts +3945 -1111
  60. package/src/compile_package_main.ts +360 -32
  61. package/src/directive.ts +19 -4
  62. package/src/engine_entry.ts +45 -4
  63. package/src/framework/application_home.ts +2 -0
  64. package/src/framework/auth_provider_law.ts +262 -20
  65. package/src/framework/home.ts +32 -28
  66. package/src/framework/owned_custody.ts +19 -11
  67. package/src/framework/owned_custody_value.ts +51 -0
  68. package/src/framework/relations.ts +57 -21
  69. package/src/framework/signing_identity.ts +34 -0
  70. package/src/framework/workspaces.ts +31 -9
  71. package/src/framework_call_definitions.ts +53 -0
  72. package/src/framework_read_definitions.ts +45 -0
  73. package/src/identity_frontier.ts +58 -0
  74. package/src/index.ts +20 -1
  75. package/src/intent.ts +216 -16
  76. package/src/law_check.ts +19 -1
  77. package/src/law_graph.ts +57 -8
  78. package/src/law_graph_usda.ts +73 -5
  79. package/src/law_value_type.ts +106 -19
  80. package/src/read.ts +47 -0
  81. package/src/read_projection_namespace.ts +7 -0
  82. package/src/shape_compat.ts +38 -39
  83. package/src/stable_ids.ts +37 -2
  84. package/src/stable_ids_for_module.ts +64 -4
  85. package/src/test_call_usda.ts +83 -0
  86. package/src/wire.ts +36 -2
  87. package/src/wire_encode.ts +45 -1
  88. package/src/workspace_sharing.ts +27 -10
  89. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbjson.dart +0 -6096
  90. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbserver.dart +0 -50
  91. package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pbjson.dart +0 -178
  92. package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pb.dart +0 -828
  93. package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbenum.dart +0 -465
  94. package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbjson.dart +0 -573
  95. package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbserver.dart +0 -50
@@ -0,0 +1,54 @@
1
+ @internal
2
+ library;
3
+
4
+ import 'package:fixnum/fixnum.dart' as fixnum;
5
+ import 'package:meta/meta.dart' show internal;
6
+ import 'package:nomos_types/nomos_kernel_protocol.dart' as nomos_protocol;
7
+ import 'package:nomos_types/nomos_types.dart'
8
+ show NomosErrorInfo, nomosErrorFromInfo;
9
+
10
+ import 'runtime_v2_transport.dart';
11
+
12
+ /// Correlates generated Runtime v2 frames and translates only the generated
13
+ /// failure arm. It knows nothing about sessions, domains, lifecycle policy, or
14
+ /// application state; those remain with the caller that owns each command.
15
+ final class NmsRuntimeV2Gateway {
16
+ NmsRuntimeV2Gateway(this._transport);
17
+
18
+ final NomosRuntimeV2Transport _transport;
19
+ int _sequence = 0;
20
+
21
+ Future<nomos_protocol.RuntimeResponse> invoke(
22
+ nomos_protocol.RuntimeRequest request,
23
+ ) async {
24
+ final requestId = fixnum.Int64(++_sequence);
25
+ request
26
+ ..protocolVersion = 2
27
+ ..requestId = requestId;
28
+ final response = await _transport.invokeRuntime(request);
29
+ if (response.protocolVersion != 2) {
30
+ throw StateError(
31
+ 'Nomos host returned Runtime protocol ${response.protocolVersion}',
32
+ );
33
+ }
34
+ if (response.requestId != requestId) {
35
+ throw StateError(
36
+ 'Runtime v2 response ${response.requestId} belongs to another request',
37
+ );
38
+ }
39
+ if (response.hasFailure()) {
40
+ final failure = response.failure;
41
+ throw nomosErrorFromInfo(
42
+ NomosErrorInfo(
43
+ code: failure.code,
44
+ message: failure.message,
45
+ component: failure.component,
46
+ workspace: failure.hasWorkspace() ? failure.workspace : null,
47
+ domain: failure.hasDomain() ? failure.domain : null,
48
+ directiveId: failure.hasDirective() ? failure.directive : null,
49
+ ),
50
+ );
51
+ }
52
+ return response;
53
+ }
54
+ }
@@ -0,0 +1,117 @@
1
+ @internal
2
+ library;
3
+
4
+ import 'dart:async';
5
+
6
+ import 'package:fixnum/fixnum.dart' as fixnum;
7
+ import 'package:meta/meta.dart' show internal;
8
+ import 'package:nomos_types/nomos_kernel_protocol.dart' as nomos_protocol;
9
+
10
+ /// Binary generated-message path from an application host to the kernel.
11
+ abstract interface class NomosKernelCallTransport {
12
+ Future<nomos_protocol.CallResponse> callKernel({
13
+ required String session,
14
+ required nomos_protocol.CallRequest request,
15
+ });
16
+ }
17
+
18
+ /// Exact Runtime v2 frame exchange. Platform transports carry bytes; they do
19
+ /// not acquire command names, argument bags, or domain result semantics.
20
+ abstract interface class NomosRuntimeV2Transport {
21
+ Future<nomos_protocol.RuntimeResponse> invokeRuntime(
22
+ nomos_protocol.RuntimeRequest request,
23
+ );
24
+ }
25
+
26
+ /// Generated asynchronous runtime event lane. It is separate from invocation
27
+ /// correlation because a watch/sync event has no request future to complete.
28
+ abstract interface class NomosRuntimeV2EventTransport {
29
+ Stream<nomos_protocol.RuntimeEvent> get runtimeEvents;
30
+ }
31
+
32
+ /// A level-triggered generated event stream. Runtime readiness can arrive while
33
+ /// a WebView script is loading, before [NomosBridge] exists; a broadcast stream
34
+ /// would silently discard it and turn a successful boot into an infinite wait.
35
+ /// Buffering ends when the first bridge subscribes, after which events remain a
36
+ /// normal broadcast stream.
37
+ final class NmsBufferedRuntimeEvents {
38
+ NmsBufferedRuntimeEvents() {
39
+ _controller = StreamController<nomos_protocol.RuntimeEvent>.broadcast(
40
+ onListen: _attach,
41
+ );
42
+ }
43
+
44
+ late final StreamController<nomos_protocol.RuntimeEvent> _controller;
45
+ final List<nomos_protocol.RuntimeEvent> _pending = [];
46
+ bool _attached = false;
47
+
48
+ Stream<nomos_protocol.RuntimeEvent> get stream => _controller.stream;
49
+ bool get isClosed => _controller.isClosed;
50
+
51
+ void _attach() {
52
+ if (_attached) return;
53
+ _attached = true;
54
+ final pending = List<nomos_protocol.RuntimeEvent>.of(_pending);
55
+ _pending.clear();
56
+ for (final event in pending) {
57
+ if (!_controller.isClosed) _controller.add(event);
58
+ }
59
+ }
60
+
61
+ void add(nomos_protocol.RuntimeEvent event) {
62
+ if (_controller.isClosed) return;
63
+ if (_attached) {
64
+ _controller.add(event);
65
+ } else {
66
+ _pending.add(event);
67
+ }
68
+ }
69
+
70
+ void addError(Object error, [StackTrace? stackTrace]) {
71
+ if (_controller.isClosed) return;
72
+ _controller.addError(error, stackTrace);
73
+ }
74
+
75
+ Future<void> close() {
76
+ _pending.clear();
77
+ return _controller.close();
78
+ }
79
+ }
80
+
81
+ /// Shared typed kernel-call lowering for every Runtime v2 transport.
82
+ mixin NomosRuntimeV2KernelCalls
83
+ implements NomosRuntimeV2Transport, NomosKernelCallTransport {
84
+ int _nmsRuntimeRequestSequence = 0;
85
+
86
+ @override
87
+ Future<nomos_protocol.CallResponse> callKernel({
88
+ required String session,
89
+ required nomos_protocol.CallRequest request,
90
+ }) async {
91
+ final requestId = fixnum.Int64(++_nmsRuntimeRequestSequence);
92
+ final response = await invokeRuntime(
93
+ nomos_protocol.RuntimeRequest(
94
+ protocolVersion: 2,
95
+ requestId: requestId,
96
+ kernelCall: nomos_protocol.KernelCallRequest(
97
+ session: session,
98
+ request: request,
99
+ ),
100
+ ),
101
+ );
102
+ if (response.requestId != requestId) {
103
+ throw StateError(
104
+ 'Runtime v2 response ${response.requestId} belongs to another request',
105
+ );
106
+ }
107
+ if (response.hasFailure()) {
108
+ throw StateError('${response.failure.code}: ${response.failure.message}');
109
+ }
110
+ if (!response.hasKernelCall()) {
111
+ throw StateError(
112
+ 'Runtime v2 returned ${response.whichOutcome().name} for a kernel call',
113
+ );
114
+ }
115
+ return response.kernelCall;
116
+ }
117
+ }
@@ -28,6 +28,8 @@ import 'package:meta/meta.dart' show internal;
28
28
 
29
29
  import 'dart:convert';
30
30
 
31
+ import 'package:nomos_types/nomos_kernel_protocol.dart' as nomos_protocol;
32
+
31
33
  import 'webview_bridge.dart';
32
34
 
33
35
  /// The STATIC INTERFACE DESCRIPTOR a generated client exposes as `XClient.interface` — what the
@@ -474,8 +476,11 @@ extension NomosSessionCompat on NomosSession {
474
476
  final m = await metrics();
475
477
  behind = ((m['behind'] as num?) ?? 0).toInt();
476
478
  final listed = await listWorkspaces();
477
- cloudBound = listed.any((w) =>
478
- w['workspace'] == workspace && w['syncPosture'] == 'cloud-bound');
479
+ cloudBound = listed.any((candidate) =>
480
+ candidate.workspace == workspace &&
481
+ candidate.syncPosture ==
482
+ nomos_protocol
483
+ .WorkspaceSyncPosture.WORKSPACE_SYNC_POSTURE_CLOUD_BOUND);
479
484
  } catch (_) {/* best-effort */}
480
485
  if (cloudBound && behind > 0) {
481
486
  cause = SessionMismatchCause.liveCloudHasLawButLocalStale;
@@ -42,10 +42,13 @@ import 'dart:io';
42
42
 
43
43
  import 'package:crypto/crypto.dart' show sha256;
44
44
  import 'package:fixnum/fixnum.dart' show Int64;
45
- import 'package:nomos_types/nomos_kernel_protocol.dart' show BirthCertificate;
45
+ import 'package:nomos_types/nomos_kernel_protocol.dart'
46
+ show BirthCertificate, BirthOutcomeStatus, SyncWorkspaceResponse;
47
+ import 'package:nomos_types/nomos_types.dart' show Sha256Digest;
46
48
  import 'package:package_config/package_config.dart' show loadPackageConfigUri;
47
49
 
48
50
  import 'dev_cloud.dart';
51
+ import 'application_framework_calls.dart';
49
52
  import 'node_runner_transport.dart';
50
53
  import 'session_bound.dart' show DomainNotInstalledForSession;
51
54
  import 'webview_bridge.dart' show NomosBridge, NomosSession;
@@ -250,36 +253,13 @@ Future<String?> _sourceCandidateRuntime(String nodeEntry) async {
250
253
  /// Framework-internal classifier for a sync result that can never become converged by retrying. Generated
251
254
  /// scenarios use it through [NomosTestHarness.converge]; it is public only inside `package:nomos_client/src`
252
255
  /// so the false-green contract has a cheap regression test without booting a cloud.
253
- String? nmsPermanentConvergenceFailure(Map<String, Object?> result) {
254
- final admission = result['admission'];
255
- final sessions = admission is Map && admission['sessions'] is List
256
- ? admission['sessions'] as List
257
- : const [];
258
- final rejected = sessions
259
- .whereType<Map>()
260
- .expand(
261
- (session) => session['rejected'] is List
262
- ? session['rejected'] as List
263
- : const [],
264
- )
265
- .toList();
266
- final deadLetters = admission is Map && admission['deadLetters'] is List
267
- ? admission['deadLetters'] as List
268
- : const [];
269
- final converged = result['converged'];
270
- final replayDeadLetters =
271
- converged is Map && converged['deadLettered'] is List
272
- ? converged['deadLettered'] as List
273
- : const [];
274
- final pushError = result['pushError'];
275
- final refused = pushError is Map && pushError['refused'] == true;
276
- if (rejected.isEmpty &&
277
- deadLetters.isEmpty &&
278
- replayDeadLetters.isEmpty &&
279
- !refused) {
256
+ String? nmsPermanentConvergenceFailure(SyncWorkspaceResponse result) {
257
+ final transportRefused = result.hasPushError() && result.pushError.refused;
258
+ if (result.refused == 0 && !transportRefused) {
280
259
  return null;
281
260
  }
282
- return jsonEncode(result);
261
+ return 'refused=${result.refused}, '
262
+ 'push=${result.hasPushError() ? result.pushError.message : "none"}';
283
263
  }
284
264
 
285
265
  /// The offline-install lane configuration a harness keeps so the CUSTODY DIAGNOSTIC helpers
@@ -615,6 +595,7 @@ class NomosTestHarness {
615
595
  /// [autoTearDown] — when true (default) and [registerTearDown] is given, registers [dispose] as a teardown.
616
596
  static Future<NomosTestHarness> ephemeral({
617
597
  required List<String> lawUsdzPaths,
598
+ NmsApplicationFrameworkCalls? applicationFrameworkCalls,
618
599
  int? applicationMaxChildWorkspacesPerUser,
619
600
  String? ownerSubject,
620
601
  String workspace = 'test-ws',
@@ -665,6 +646,12 @@ class NomosTestHarness {
665
646
  final birthsApplicationHome = owner != null &&
666
647
  owner.isNotEmpty &&
667
648
  workspace == _receivingGroupNameOf(owner);
649
+ if (birthsApplicationHome && applicationFrameworkCalls == null) {
650
+ throw NomosTestHarnessException(
651
+ 'a generated application scenario must pass its compiler-generated framework calls; '
652
+ 'the harness cannot construct domain payloads',
653
+ );
654
+ }
668
655
  // A receiving-group address is not an arbitrary law-install target. It is valid only when a parent
669
656
  // births it with the attestation, signer seating and warrant ordered into genesis. Generated scenarios
670
657
  // therefore install their package on a private throwaway application parent and exercise the same local
@@ -804,49 +791,151 @@ class NomosTestHarness {
804
791
  // scenario carries the value from the same package declaration that compiled the
805
792
  // receiving-group plan; the harness does not choose or default it. The ordinary
806
793
  // seed offer crosses the kernel author gate before the first birth is attempted.
807
- run: () => plane.session(installWorkspace).offerOutcome(
808
- domain: '__nomos_application_home_v1',
809
- directiveId: 'seedBirthPolicy',
810
- actor: owner,
811
- authorSecret: deviceSecret,
812
- payload: <String, Object?>{
813
- 'maxBirthsPerPrincipal': maxChildWorkspaces,
814
- 'setBy': owner,
815
- 'setAt': bornAt,
816
- },
817
- ),
794
+ run: () => plane.session(installWorkspace).offerCallUsdaOutcome(
795
+ call: applicationFrameworkCalls!.seedBirthPolicy(
796
+ domain: '__nomos_application_home_v1',
797
+ workspace: installWorkspace,
798
+ maxBirthsPerPrincipal: maxChildWorkspaces,
799
+ setBy: owner,
800
+ setAt: bornAt,
801
+ ),
802
+ actor: owner,
803
+ ),
804
+ );
805
+ final keyHash = sha256.convert(base64Decode(devicePublic)).toString();
806
+ await _phased(
807
+ phase: 'application: seat the verified parent signer',
808
+ workspace: installWorkspace,
809
+ awaiting: 'law-declared enrollSigner offer',
810
+ timeout: phaseTimeout ?? _kOtherDeadline,
811
+ onDiagnostic: onDiagnostic,
812
+ run: () => plane.session(installWorkspace).offerCallUsdaOutcome(
813
+ call: applicationFrameworkCalls!.enrollSigner(
814
+ domain: '__nomos_application_home_v1',
815
+ workspace: installWorkspace,
816
+ keyHash: keyHash,
817
+ grantedBy: owner,
818
+ grantedAt: 1,
819
+ verifiedVia: 'nomos:test-mint',
820
+ ),
821
+ actor: owner,
822
+ ),
818
823
  );
824
+ final delegationCert =
825
+ await plane.session(installWorkspace).signBirthCert(
826
+ BirthCertificate(
827
+ child: workspace,
828
+ founder: owner,
829
+ verified: true,
830
+ parentKey: base64Decode(devicePublic),
831
+ parentId: 'root',
832
+ epoch: Int64.ZERO,
833
+ ),
834
+ deviceSecret,
835
+ );
836
+ await _phased(
837
+ phase: 'application: record the parent-signed device delegation',
838
+ workspace: installWorkspace,
839
+ awaiting: 'law-declared enrollDelegation offer',
840
+ timeout: phaseTimeout ?? _kOtherDeadline,
841
+ onDiagnostic: onDiagnostic,
842
+ run: () => plane.session(installWorkspace).offerCallUsdaOutcome(
843
+ call: applicationFrameworkCalls!.enrollDelegation(
844
+ domain: '__nomos_application_home_v1',
845
+ workspace: installWorkspace,
846
+ user: owner,
847
+ deviceKey: devicePublic,
848
+ certificate: delegationCert,
849
+ enrolledAt: '1970-01-01T00:00:00.000Z',
850
+ ),
851
+ actor: owner,
852
+ ),
853
+ );
854
+ final parentCert = await plane.session(installWorkspace).signBirthCert(
855
+ BirthCertificate(
856
+ child: installWorkspace,
857
+ founder: owner,
858
+ verified: true,
859
+ parentKey: base64Decode(devicePublic),
860
+ parentId: 'root',
861
+ epoch: Int64.ZERO,
862
+ ),
863
+ deviceSecret,
864
+ );
865
+ await _phased(
866
+ phase: 'application: attest and warrant the application parent',
867
+ workspace: installWorkspace,
868
+ awaiting: 'typed birth attestation and genesis offers',
869
+ timeout: phaseTimeout ?? _kOtherDeadline,
870
+ onDiagnostic: onDiagnostic,
871
+ run: () async {
872
+ await plane.session(installWorkspace).offerCallUsdaOutcome(
873
+ call: applicationFrameworkCalls!.seedBirthAttestation(
874
+ domain: '__nomos_application_home_v1',
875
+ workspace: installWorkspace,
876
+ child: installWorkspace,
877
+ founder: owner,
878
+ verified: 'true',
879
+ parentId: 'root',
880
+ parentKey: devicePublic,
881
+ epoch: 0,
882
+ links: <BirthCertificate>[parentCert],
883
+ ),
884
+ actor: owner,
885
+ );
886
+ await plane.session(installWorkspace).offerCallUsdaOutcome(
887
+ call: applicationFrameworkCalls.seedGenesis(
888
+ domain: '__nomos_application_home_v1',
889
+ workspace: installWorkspace,
890
+ principal: owner,
891
+ workspaceId: installWorkspace,
892
+ bornAt: '1970-01-01T00:00:00.000Z',
893
+ ),
894
+ actor: owner,
895
+ );
896
+ },
897
+ );
898
+ final homeCert = await plane.session(installWorkspace).signBirthCert(
899
+ BirthCertificate(
900
+ child: workspace,
901
+ founder: owner,
902
+ verified: true,
903
+ parentKey: base64Decode(devicePublic),
904
+ parentId: installWorkspace,
905
+ epoch: Int64.ZERO,
906
+ ),
907
+ deviceSecret,
908
+ );
819
909
  final outcome = await _phased(
820
910
  phase: 'application: birth the signed-in application',
821
911
  workspace: workspace,
822
912
  awaiting: 'attested receiving-group birth',
823
913
  timeout: phaseTimeout ?? _kOtherDeadline,
824
914
  onDiagnostic: onDiagnostic,
825
- run: () => plane.session(installWorkspace).offerOutcome(
826
- domain: '__nomos_application_home_v1',
827
- directiveId: 'nomosInternalBirthReceivingGroup7c1f5e',
828
- actor: owner,
829
- authorSecret: deviceSecret,
830
- payload: <String, Object?>{
831
- 'owner': owner,
832
- 'frameworkHash': framework,
833
- // Ordered application deployment is reference packages first, deferred child payload last.
834
- // The parent serves the references; the receiving group is born with only its payload law.
835
- 'lawHash': laws.last,
836
- 'bornAt': bornAt,
837
- },
838
- ),
915
+ run: () => plane.session(installWorkspace).offerCallUsdaOutcome(
916
+ call: applicationFrameworkCalls!.birthReceivingGroup(
917
+ domain: '__nomos_application_home_v1',
918
+ workspace: installWorkspace,
919
+ owner: owner,
920
+ frameworkHash: Sha256Digest(framework),
921
+ lawHash: Sha256Digest(laws.last),
922
+ bornAt: bornAt,
923
+ ),
924
+ actor: owner,
925
+ authorSecret: deviceSecret,
926
+ birthCertificate: homeCert,
927
+ ),
839
928
  );
840
929
  String? bornStatus;
841
930
  for (final entry in outcome.birthOutcomes) {
842
- if (entry['workspace']?.toString() == workspace) {
843
- bornStatus = entry['status']?.toString();
931
+ if (entry.workspace == workspace) {
932
+ bornStatus = entry.status.name;
844
933
  break;
845
934
  }
846
935
  }
847
936
  if (outcome.born.length != 1 ||
848
937
  outcome.born.single != workspace ||
849
- bornStatus != 'born') {
938
+ bornStatus != 'BIRTH_OUTCOME_STATUS_BORN') {
850
939
  throw NomosTestHarnessException(
851
940
  'the offline application parent did not birth "$workspace" cleanly '
852
941
  '(born=${outcome.born}, birthOutcomes=${outcome.birthOutcomes})',
@@ -889,18 +978,17 @@ class NomosTestHarness {
889
978
  // harness device through the installed framework law with the same verified provenance
890
979
  // that the private test root later attests. Production clients never manufacture this
891
980
  // marker; it exists only inside the offline test-mint ceremony.
892
- run: () => plane.session(workspace).offerOutcome(
893
- domain: '__nomos_application_home_v1',
894
- directiveId: 'enrollSigner',
895
- actor: owner,
896
- authorSecret: deviceSecret,
897
- payload: <String, Object?>{
898
- 'keyHash': keyHash,
899
- 'grantedBy': owner,
900
- 'grantedAt': 1,
901
- 'verifiedVia': 'nomos:test-mint',
902
- },
903
- ),
981
+ run: () => plane.session(workspace).offerCallUsdaOutcome(
982
+ call: applicationFrameworkCalls!.enrollSigner(
983
+ domain: '__nomos_application_home_v1',
984
+ workspace: workspace,
985
+ keyHash: keyHash,
986
+ grantedBy: owner,
987
+ grantedAt: 1,
988
+ verifiedVia: 'nomos:test-mint',
989
+ ),
990
+ actor: owner,
991
+ ),
904
992
  );
905
993
  } catch (error) {
906
994
  // Pre-application-home packages neither expose nor enforce this
@@ -921,22 +1009,24 @@ class NomosTestHarness {
921
1009
  timeout: phaseTimeout ?? _kOtherDeadline,
922
1010
  onDiagnostic: onDiagnostic,
923
1011
  run: () async {
924
- await plane.session(workspace).offerOutcome(
925
- domain: '__nomos_application_home_v1',
926
- directiveId: 'nomosInternalSeedReceivingGroupPolicy7c1f5e',
927
- actor: 'host-birth',
928
- payload: <String, Object?>{'setAt': bornAt},
929
- );
930
- await plane.session(workspace).offerOutcome(
931
- domain: '__nomos_application_home_v1',
932
- directiveId: 'nomosInternalSeedReceivingGroup7c1f5e',
933
- actor: 'host-birth',
934
- payload: <String, Object?>{
935
- 'principal': owner,
936
- 'workspaceName': _receivingGroupNameOf(owner),
937
- 'bornAt': bornAt,
938
- },
939
- );
1012
+ await plane.session(workspace).offerCallUsdaOutcome(
1013
+ call: applicationFrameworkCalls!.seedReceivingGroupPolicy(
1014
+ domain: '__nomos_application_home_v1',
1015
+ workspace: workspace,
1016
+ setAt: bornAt,
1017
+ ),
1018
+ actor: 'host-birth',
1019
+ );
1020
+ await plane.session(workspace).offerCallUsdaOutcome(
1021
+ call: applicationFrameworkCalls.seedReceivingGroup(
1022
+ domain: '__nomos_application_home_v1',
1023
+ workspace: workspace,
1024
+ principal: owner,
1025
+ workspaceName: _receivingGroupNameOf(owner),
1026
+ bornAt: bornAt,
1027
+ ),
1028
+ actor: 'host-birth',
1029
+ );
940
1030
  },
941
1031
  );
942
1032
  final parentCert = await plane.signBirthCert(
@@ -1798,14 +1888,9 @@ class NomosTestHarness {
1798
1888
  _birthCertificateLawValue(delegationCert),
1799
1889
  },
1800
1890
  );
1801
- final enrollmentSync = await plane.sync(
1891
+ await plane.sync(
1802
1892
  session: platformAuthorSession,
1803
1893
  );
1804
- if (enrollmentSync['ok'] == false) {
1805
- throw NomosTestHarnessException(
1806
- 'cloudBound: sealed enrollFirstDevice offer did not converge: $enrollmentSync',
1807
- );
1808
- }
1809
1894
  return (
1810
1895
  publicB64: publicB64,
1811
1896
  secret: secret,
@@ -1868,27 +1953,24 @@ class NomosTestHarness {
1868
1953
  final birthSync = await plane.sync(
1869
1954
  session: '$clientId-platform-author',
1870
1955
  );
1871
- if (birthSync['pushFailed'] == true ||
1872
- birthSync['diverged'] == true ||
1873
- birthSync['stillAhead'] == true) {
1956
+ if (birthSync.pushFailed ||
1957
+ birthSync.diverged ||
1958
+ birthSync.stillAhead) {
1874
1959
  throw NomosTestHarnessException(
1875
1960
  'cloudBound: the sealed receiving-group offer did not converge: $birthSync',
1876
1961
  );
1877
1962
  }
1878
- final remoteOutcomes =
1879
- ((birthSync['birthOutcomes'] as List?) ?? const [])
1880
- .whereType<Map>()
1881
- .map((entry) => entry.cast<String, Object?>())
1882
- .toList();
1963
+ final remoteOutcomes = birthSync.birthOutcomes;
1883
1964
  final failedRemoteBirth = remoteOutcomes.where(
1884
1965
  (entry) =>
1885
- entry['workspace']?.toString() == homeName &&
1886
- entry['status']?.toString() != 'born',
1966
+ entry.hasWorkspace() &&
1967
+ entry.workspace == homeName &&
1968
+ entry.status != BirthOutcomeStatus.BIRTH_OUTCOME_STATUS_BORN,
1887
1969
  );
1888
1970
  if (failedRemoteBirth.isNotEmpty) {
1889
1971
  throw NomosTestHarnessException(
1890
1972
  'cloudBound: the remote parent replay refused the receiving-group birth: '
1891
- '${jsonEncode(failedRemoteBirth.toList())}',
1973
+ '${failedRemoteBirth.toList()}',
1892
1974
  );
1893
1975
  }
1894
1976
  home = homeName;
@@ -1985,7 +2067,7 @@ class NomosTestHarness {
1985
2067
  /// each locally-born child's remote custody BEFORE that child's own sync converges against it. Repeats
1986
2068
  /// until every session reports neither `pushFailed` nor `stillAhead`, or [timeout] (then throws with the
1987
2069
  /// per-workspace last verdicts). Returns the final sync result per workspace.
1988
- Future<Map<String, Map<String, Object?>>> converge({
2070
+ Future<Map<String, SyncWorkspaceResponse>> converge({
1989
2071
  Duration timeout = const Duration(seconds: 120),
1990
2072
  List<String>? workspaces,
1991
2073
  }) async {
@@ -1996,36 +2078,35 @@ class NomosTestHarness {
1996
2078
  final hw = homeWorkspace;
1997
2079
  if (hw != null && live.remove(hw)) live.insert(0, hw); // the parent leads
1998
2080
  final targets = workspaces ?? live;
1999
- final last = <String, Map<String, Object?>>{};
2081
+ final last = <String, SyncWorkspaceResponse>{};
2082
+ final lastErrors = <String, String>{};
2000
2083
  final pending = List.of(targets);
2001
2084
  final deadline = DateTime.now().add(timeout);
2002
2085
  while (pending.isNotEmpty) {
2003
2086
  for (final ws in List.of(pending)) {
2004
2087
  try {
2005
- final r = (await bridge.session(ws).sync(admit: true))
2006
- .cast<String, Object?>();
2088
+ final r = await bridge.session(ws).sync(admit: true);
2007
2089
  last[ws] = r;
2090
+ lastErrors.remove(ws);
2008
2091
  final permanentFailure = nmsPermanentConvergenceFailure(r);
2009
2092
  if (permanentFailure != null) {
2010
2093
  throw NomosTestHarnessException(
2011
2094
  'converge: $ws rejected or dead-lettered business work — $permanentFailure',
2012
2095
  );
2013
2096
  }
2014
- if (r['pushFailed'] != true &&
2015
- r['stillAhead'] != true &&
2016
- r['offline'] != true &&
2017
- r['deferred'] != true) pending.remove(ws);
2097
+ if (!r.pushFailed && !r.stillAhead && !r.offline && r.deferred == 0)
2098
+ pending.remove(ws);
2018
2099
  } on NomosTestHarnessException {
2019
2100
  rethrow;
2020
2101
  } catch (e) {
2021
- last[ws] = {'error': e.toString()};
2102
+ lastErrors[ws] = e.toString();
2022
2103
  }
2023
2104
  }
2024
2105
  if (pending.isEmpty) break;
2025
2106
  if (DateTime.now().isAfter(deadline)) {
2026
2107
  throw NomosTestHarnessException(
2027
2108
  'converge: ${pending.length} workspace(s) did not converge within ${timeout.inSeconds}s — '
2028
- '${pending.map((w) => '$w: ${jsonEncode(last[w] ?? {})}').join('; ')}',
2109
+ '${pending.map((w) => '$w: ${lastErrors[w] ?? last[w]?.toString() ?? "no result"}').join('; ')}',
2029
2110
  );
2030
2111
  }
2031
2112
  await Future<void>.delayed(const Duration(milliseconds: 500));