@githolon/dsl 0.113.3 → 0.114.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 (58) hide show
  1. package/assets/operation-contract.wasm +0 -0
  2. package/compile_package.mjs +23 -3
  3. package/compile_proof.d.mts +7 -0
  4. package/compile_proof.mjs +38 -3
  5. package/dart/CHANGELOG.md +24 -0
  6. package/dart/lib/src/application_framework_calls.dart +9 -5
  7. package/dart/lib/src/generated_call.dart +14 -3
  8. package/dart/lib/src/nms_generated_runtime.dart +7 -0
  9. package/dart/lib/src/node_runner_transport.dart +77 -8
  10. package/dart/lib/src/nomos_trace.dart +107 -0
  11. package/dart/lib/src/reception/dart_reception.dart +148 -11
  12. package/dart/lib/src/rows_watch.dart +390 -0
  13. package/dart/lib/src/runner/node-entry.mjs +2807 -691
  14. package/dart/lib/src/sync_status_projection.dart +94 -1
  15. package/dart/lib/src/sync_status_reducer.dart +5 -2
  16. package/dart/lib/src/test_harness.dart +107 -41
  17. package/dart/lib/src/testing/test_peer.dart +107 -99
  18. package/dart/lib/src/testing/test_peer_host.dart +305 -0
  19. package/dart/lib/src/typed_watch_registry.dart +26 -3
  20. package/dart/pubspec.lock +1 -1
  21. package/dart/pubspec.yaml +2 -2
  22. package/dart/test/background_model_adoption_test.dart +1 -0
  23. package/dart/test/custodian_warming_test.dart +105 -0
  24. package/dart/test/fixtures/offer_watch_order_peer.dart +74 -0
  25. package/dart/test/kernel_author_after_read_test.dart +6 -3
  26. package/dart/test/kernel_fence_test.dart +4 -1
  27. package/dart/test/model_adoption_test.dart +17 -0
  28. package/dart/test/nomos_trace_test.dart +91 -0
  29. package/dart/test/own_offer_watch_order_test.dart +90 -0
  30. package/dart/test/receiving_group_open_test.dart +4 -8
  31. package/dart/test/replica_readiness_test.dart +14 -4
  32. package/dart/test/rows_watch_test.dart +331 -0
  33. package/dart/test/test_peer_host_selection_test.dart +49 -0
  34. package/dart_types/CHANGELOG.md +15 -0
  35. package/dart_types/lib/src/approved_model.dart +22 -5
  36. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +535 -1
  37. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +65 -0
  38. package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pb.dart +14 -0
  39. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +359 -2
  40. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pbenum.dart +65 -0
  41. package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +20 -2
  42. package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pb.dart +278 -9
  43. package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pb.dart +7 -85
  44. package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pb.dart +35 -0
  45. package/dart_types/pubspec.yaml +1 -1
  46. package/package.json +4 -2
  47. package/src/build_provenance.ts +7 -2
  48. package/src/codegen_dart_client.ts +21 -9
  49. package/src/codegen_dart_facade.ts +1 -0
  50. package/src/codegen_dart_framework_calls.ts +12 -9
  51. package/src/codegen_dart_scenario.ts +7 -0
  52. package/src/codegen_ts.ts +26 -24
  53. package/src/compile_package_main.ts +65 -61
  54. package/src/engine_entry.ts +4 -48
  55. package/src/law_unit_set_identity.ts +44 -0
  56. package/src/query/compile.ts +0 -0
  57. package/src/release_inspection.ts +24 -0
  58. package/src/release_publication.ts +298 -0
@@ -10,9 +10,9 @@ import 'package:fixnum/fixnum.dart' show Int64;
10
10
  import 'package:meta/meta.dart' show internal;
11
11
  import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
12
12
 
13
- import '../node_runner_transport.dart';
14
13
  import '../reception/dart_reception.dart';
15
- import 'test_custody.dart';
14
+ import '../reception/peer_office_connection.dart';
15
+ import 'test_peer_host.dart';
16
16
 
17
17
  @internal
18
18
  final class NmsInstalledTestWorkspace {
@@ -31,78 +31,88 @@ final class NmsInstalledTestWorkspace {
31
31
  final Set<String> domainKeys;
32
32
  }
33
33
 
34
- /// A canonical Peer Office hosted by the Node test Platform Shell.
34
+ /// A canonical Peer Office hosted by one test Platform Shell ([NmsTestPeerHost]).
35
35
  ///
36
36
  /// This class owns fixture setup only. Every operation crosses Runtime v2 and
37
37
  /// the kernel's generated Protobuf ABI; it contains no workspace lifecycle,
38
- /// USDA parser, generic payload map or alternate admission path.
38
+ /// USDA parser, generic payload map or alternate admission path. The host is the
39
+ /// Node shell by default and the Apple native host when `NOMOS_TEST_PEER_HOST=native`.
39
40
  @internal
40
41
  final class NmsTestPeer {
41
42
  NmsTestPeer._({
42
- required this.transport,
43
+ required this.host,
43
44
  required this.reception,
44
- required this.custody,
45
45
  required this.runtimeDirectory,
46
- required String entry,
47
- required String node,
48
- String? resolveFrom,
49
- void Function(String line)? onDiagnostic,
50
- }) : _entry = entry,
51
- _node = node,
52
- _resolveFrom = resolveFrom,
53
- _onDiagnostic = onDiagnostic;
46
+ });
47
+
48
+ /// The platform host this peer stands on.
49
+ final NmsTestPeerHost host;
54
50
 
55
- /// The live process transport; replaced by [reboot].
56
- NodeRunnerTransport transport;
51
+ /// The live Peer Office connection; replaced when the host restarts.
52
+ PeerOfficeConnection get transport => host.connection;
57
53
 
58
- /// Reception over [transport]; replaced by [reboot].
54
+ /// Reception over [transport]; replaced by [reboot] and [trustFoundingRoot].
59
55
  DartReception reception;
60
- final NmsTestCustody custody;
56
+
57
+ /// The temporary directory holding this peer's Git custody.
58
+ Directory get custodyDirectory => host.custodyDirectory;
61
59
  final String runtimeDirectory;
62
- final String _entry;
63
- final String _node;
64
- final String? _resolveFrom;
65
- final void Function(String line)? _onDiagnostic;
66
60
  final Random _random = Random.secure();
67
61
  int _requestSequence = 0;
68
62
  int _authoringSequence = 0;
69
63
 
70
- static Future<NmsTestPeer> start({
71
- required String entry,
64
+ /// Open a canonical test peer on an already started [host]. The peer owns the host from
65
+ /// here on: a failure disposes it.
66
+ static Future<NmsTestPeer> open({
67
+ required NmsTestPeerHost host,
72
68
  required String runtimeDirectory,
73
- String node = 'node',
74
- String? resolveFrom,
75
- void Function(String line)? onDiagnostic,
76
69
  }) async {
77
- final custody = await NmsTestCustody.create();
78
- NodeRunnerTransport? transport;
79
70
  try {
80
- transport = await NodeRunnerTransport.start(
81
- entry: entry,
82
- resolveFrom: resolveFrom,
83
- node: node,
84
- arguments: custody.processArguments(runtimeDirectory),
85
- onDiagnostic: onDiagnostic,
86
- );
87
- final reception = DartReception(transport);
88
- await reception.ready;
71
+ final reception = DartReception(host.connection);
72
+ await host.awaitReady(reception);
89
73
  return NmsTestPeer._(
90
- transport: transport,
74
+ host: host,
91
75
  reception: reception,
92
- custody: custody,
93
76
  runtimeDirectory: runtimeDirectory,
94
- entry: entry,
95
- node: node,
96
- resolveFrom: resolveFrom,
97
- onDiagnostic: onDiagnostic,
98
77
  );
99
78
  } catch (_) {
100
- await transport?.dispose();
101
- await custody.dispose();
79
+ await host.dispose();
102
80
  rethrow;
103
81
  }
104
82
  }
105
83
 
84
+ /// Start a test peer on the host [kind]: the Node shell at [entry] (required exactly for
85
+ /// Node), or the registered Apple native host.
86
+ static Future<NmsTestPeer> start({
87
+ required NmsTestPeerHostKind kind,
88
+ required String runtimeDirectory,
89
+ String? entry,
90
+ String node = 'node',
91
+ String? resolveFrom,
92
+ void Function(String line)? onDiagnostic,
93
+ }) async {
94
+ final request = NmsTestPeerHostRequest(
95
+ runtimeDirectory: runtimeDirectory,
96
+ onDiagnostic: onDiagnostic,
97
+ );
98
+ final NmsTestPeerHost host;
99
+ switch (kind) {
100
+ case NmsTestPeerHostKind.node:
101
+ if (entry == null) {
102
+ throw ArgumentError.notNull('entry');
103
+ }
104
+ host = await NmsNodeTestPeerHost.start(
105
+ entry: entry,
106
+ request: request,
107
+ node: node,
108
+ resolveFrom: resolveFrom,
109
+ );
110
+ case NmsTestPeerHostKind.native:
111
+ host = await NmsTestPeerHosts.requireNative()(request);
112
+ }
113
+ return open(host: host, runtimeDirectory: runtimeDirectory);
114
+ }
115
+
106
116
  protocol.AuthoringOptions _authoring(String actor) {
107
117
  _authoringSequence += 1;
108
118
  return protocol.AuthoringOptions(
@@ -123,7 +133,7 @@ final class NmsTestPeer {
123
133
  String session,
124
134
  protocol.CallRequest request,
125
135
  ) async {
126
- final response = await transport.callKernel(
136
+ final response = await host.kernelCalls.callKernel(
127
137
  session: session,
128
138
  request: request,
129
139
  credentialUse: protocol.CredentialUse.CREDENTIAL_USE_NONE,
@@ -138,31 +148,15 @@ final class NmsTestPeer {
138
148
  Uint8List bytes,
139
149
  String label,
140
150
  ) async {
141
- final id = Int64(++_requestSequence);
142
- final response = await transport.invokeRuntime(
143
- protocol.RuntimeRequest(
144
- protocolVersion: 2,
145
- requestId: id,
146
- inspectLawPackage: protocol.InspectLawPackageRequest(
147
- lawUsdz: bytes,
148
- label: label,
149
- ),
150
- ),
151
- );
152
- if (response.hasFailure()) {
153
- throw StateError('${response.failure.code}: ${response.failure.message}');
154
- }
155
- if (!response.hasLawPackageInspection() ||
156
- response.lawPackageInspection.identity.isEmpty) {
157
- throw StateError(
158
- 'Peer Office returned ${response.whichOutcome().name} while inspecting $label',
159
- );
151
+ final inspection = await host.inspectLawPackage(bytes, label);
152
+ if (inspection.identity.isEmpty) {
153
+ throw StateError('the kernel named no identity for $label');
160
154
  }
161
- return response.lawPackageInspection;
155
+ return inspection;
162
156
  }
163
157
 
164
158
  /// Mint a founding device key on a scratch session BEFORE any custody exists, so the peer can be
165
- /// booted to trust it (typed boot configuration) ahead of the genesis it will sign.
159
+ /// made to trust it (typed boot configuration) ahead of the genesis it will sign.
166
160
  Future<protocol.SigningKeyPair> mintFoundingDevice() async {
167
161
  final scratch = 'nms-founder-${_random.nextInt(1 << 30).toRadixString(36)}';
168
162
  await reception.bindSession(
@@ -182,7 +176,7 @@ final class NmsTestPeer {
182
176
  throw ArgumentError.value(
183
177
  lawUsdzPaths, 'lawUsdzPaths', 'must not be empty');
184
178
  }
185
- await custody.provisionWorkspace(workspace);
179
+ await host.provisionWorkspace(workspace);
186
180
  await reception.bindSession(
187
181
  workspace: workspace,
188
182
  session: workspace,
@@ -270,38 +264,37 @@ final class NmsTestPeer {
270
264
  );
271
265
  }
272
266
 
273
- /// Reboot the peer process on the SAME Git custody with [trustRootPublicKey] as its typed
274
- /// boot configuration, then reopen every workspace this peer installed. The trust root is boot
275
- /// configuration of the shell, never a kernel variant: a ceremony that signs with a founding
276
- /// device key first boots the peer to trust that key.
267
+ /// Trust [trustRootPublicKey] as this peer's universe root before the genesis it will sign.
268
+ /// The trust root is boot configuration of the shell, never a kernel variant: the Node host
269
+ /// reboots its process with it; the native host configures its resident kernel once.
270
+ Future<void> trustFoundingRoot(List<int> trustRootPublicKey) async {
271
+ _requireRootKey(trustRootPublicKey);
272
+ final replaced = await host.trustFoundingRoot(
273
+ trustRootPublicKey,
274
+ beforeReplacingConnection: reception.dispose,
275
+ );
276
+ if (!replaced) return;
277
+ final reopened = DartReception(host.connection);
278
+ await host.awaitReady(reopened);
279
+ reception = reopened;
280
+ }
281
+
282
+ /// Reboot the peer on the SAME Git custody with [trustRootPublicKey] as its typed boot
283
+ /// configuration, then reopen every workspace named in [authorSecrets]. A host that cannot
284
+ /// restart in-process (native: its resident kernel is process-wide) refuses loudly.
277
285
  Future<void> reboot({
278
286
  required List<int> trustRootPublicKey,
279
287
  Map<String, String> authorSecrets = const {},
280
288
  Future<void> Function()? arrangeStoppedCustody,
281
289
  }) async {
282
- if (trustRootPublicKey.length != 32) {
283
- throw ArgumentError.value(
284
- trustRootPublicKey.length,
285
- 'trustRootPublicKey',
286
- 'must be a 32-byte public key',
287
- );
288
- }
290
+ _requireRootKey(trustRootPublicKey);
289
291
  await reception.dispose();
290
- await transport.dispose();
291
- await arrangeStoppedCustody?.call();
292
- final restarted = await NodeRunnerTransport.start(
293
- entry: _entry,
294
- resolveFrom: _resolveFrom,
295
- node: _node,
296
- arguments: custody.processArguments(
297
- runtimeDirectory,
298
- trustRootPublicKey: base64Encode(trustRootPublicKey),
299
- ),
300
- onDiagnostic: _onDiagnostic,
292
+ await host.restart(
293
+ trustRootPublicKey: trustRootPublicKey,
294
+ arrangeStoppedCustody: arrangeStoppedCustody,
301
295
  );
302
- final reopened = DartReception(restarted);
303
- await reopened.ready;
304
- transport = restarted;
296
+ final reopened = DartReception(host.connection);
297
+ await host.awaitReady(reopened);
305
298
  reception = reopened;
306
299
  for (final entry in authorSecrets.entries) {
307
300
  await reception.bindSession(
@@ -314,15 +307,30 @@ final class NmsTestPeer {
314
307
  }
315
308
  }
316
309
 
310
+ void _requireRootKey(List<int> trustRootPublicKey) {
311
+ if (trustRootPublicKey.length != 32) {
312
+ throw ArgumentError.value(
313
+ trustRootPublicKey.length,
314
+ 'trustRootPublicKey',
315
+ 'must be a 32-byte public key',
316
+ );
317
+ }
318
+ }
319
+
320
+ /// Close Reception and stop the host's connection; custody stays on disk.
321
+ Future<void> stop() async {
322
+ try {
323
+ await reception.dispose();
324
+ } finally {
325
+ await host.stop();
326
+ }
327
+ }
328
+
317
329
  Future<void> dispose() async {
318
330
  try {
319
331
  await reception.dispose();
320
332
  } finally {
321
- try {
322
- await transport.dispose();
323
- } finally {
324
- await custody.dispose();
325
- }
333
+ await host.dispose();
326
334
  }
327
335
  }
328
336
  }
@@ -0,0 +1,305 @@
1
+ @internal
2
+ library;
3
+
4
+ import 'dart:convert' show base64Encode;
5
+ import 'dart:io';
6
+ import 'dart:typed_data';
7
+
8
+ import 'package:fixnum/fixnum.dart' show Int64;
9
+ import 'package:meta/meta.dart' show internal;
10
+ import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
11
+
12
+ import '../node_runner_transport.dart';
13
+ import '../reception/dart_reception.dart' show DartReception;
14
+ import '../reception/peer_office_connection.dart';
15
+ import '../runtime_v2_transport.dart' show NomosKernelCallTransport;
16
+ import 'test_custody.dart';
17
+
18
+ /// The environment variable that selects the platform host of the canonical test peer.
19
+ ///
20
+ /// Unset (or `node`) is the default fast iteration loop: the Node Platform Shell over the
21
+ /// WASM kernel. `native` selects the Apple native host (the release-linked AOT kernel and the
22
+ /// Peer Office in JavaScriptCore) for production-equivalent figures on a Mac; it must have been
23
+ /// registered by the caller (see [NmsTestPeerHosts.registerNative]). Any other value is refused.
24
+ const nmsTestPeerHostVariable = 'NOMOS_TEST_PEER_HOST';
25
+
26
+ /// Which platform shell hosts the test peer.
27
+ enum NmsTestPeerHostKind {
28
+ node,
29
+ native;
30
+
31
+ /// The host selected by [nmsTestPeerHostVariable]. A value that names no host is refused —
32
+ /// a typo never silently becomes the default Node lane.
33
+ static NmsTestPeerHostKind selected([Map<String, String>? environment]) {
34
+ final raw = (environment ?? Platform.environment)[nmsTestPeerHostVariable];
35
+ final value = raw?.trim() ?? '';
36
+ switch (value) {
37
+ case '':
38
+ case 'node':
39
+ return NmsTestPeerHostKind.node;
40
+ case 'native':
41
+ return NmsTestPeerHostKind.native;
42
+ default:
43
+ throw StateError(
44
+ '$nmsTestPeerHostVariable="$raw" names no test peer host: '
45
+ 'leave it unset (or "node") for the Node peer, or set "native" for the Apple native host',
46
+ );
47
+ }
48
+ }
49
+ }
50
+
51
+ /// What a host needs to open one canonical test peer.
52
+ final class NmsTestPeerHostRequest {
53
+ const NmsTestPeerHostRequest({
54
+ required this.runtimeDirectory,
55
+ this.onDiagnostic,
56
+ });
57
+
58
+ /// The runtime bundle directory. Every host reads the framework controller
59
+ /// `nomos.law.usdz` from it for the constitutional genesis offer; only the Node host
60
+ /// executes its `holon.wasm`.
61
+ final String runtimeDirectory;
62
+ final void Function(String line)? onDiagnostic;
63
+ }
64
+
65
+ /// One platform host of a canonical test Peer Office: the physical boundary the harness
66
+ /// stands on. It owns process/library lifetime and the custody directory LAYOUT; it owns no
67
+ /// workspace lifecycle, admission or law. Every operation the harness performs still crosses
68
+ /// Runtime v2 through [connection].
69
+ abstract interface class NmsTestPeerHost {
70
+ NmsTestPeerHostKind get kind;
71
+
72
+ /// The live Peer Office connection. A host whose trust-root application replaces the
73
+ /// connection (Node reboots its process) answers the new one afterwards.
74
+ PeerOfficeConnection get connection;
75
+
76
+ /// The generated binary kernel lane of [connection] (fixture genesis and law installs).
77
+ NomosKernelCallTransport get kernelCalls;
78
+
79
+ /// The temporary directory holding this peer's Git custody. Deleted by [dispose].
80
+ Directory get custodyDirectory;
81
+
82
+ /// The bare Git directory of [workspace] in this host's custody layout.
83
+ String workspaceGitDirectory(String workspace);
84
+
85
+ /// Provision the platform-owned empty custody into which the one parentless genesis
86
+ /// offer for [workspace] may write, in this host's layout. Fixture plumbing, not an
87
+ /// application lifecycle operation.
88
+ Future<void> provisionWorkspace(String workspace);
89
+
90
+ /// The kernel's inspection of an opaque law package: its content identity and domain keys.
91
+ /// Node asks its Peer Office (Runtime v2 `inspectLawPackage`). The Apple host's office has no
92
+ /// inspection port, so the native shell asks its resident kernel directly — the same
93
+ /// `custody.lawClosureInfo` call the Node adapter makes for its office.
94
+ Future<protocol.LawPackageInspection> inspectLawPackage(
95
+ Uint8List lawUsdz,
96
+ String label,
97
+ );
98
+
99
+ /// Complete once [reception] (a new Reception over [connection]) may be used: the Node shell
100
+ /// posts a `ready` event when its process has booted; the native host booted its resident
101
+ /// kernel and Peer Office when it started and posts no such event.
102
+ Future<void> awaitReady(DartReception reception);
103
+
104
+ /// Trust [publicKey] as the root of this peer's universe before the genesis it will sign.
105
+ /// Typed boot configuration, never a kernel variant. Node reboots its process with
106
+ /// `--trust-root-public-key` (calling [beforeReplacingConnection] first, and answering true:
107
+ /// the connection was replaced); native issues the kernel's one-time `configure` call on the
108
+ /// live connection and answers false.
109
+ Future<bool> trustFoundingRoot(
110
+ List<int> publicKey, {
111
+ required Future<void> Function() beforeReplacingConnection,
112
+ });
113
+
114
+ /// Restart the peer over the SAME custody with [trustRootPublicKey]. A host that cannot
115
+ /// restart in-process refuses loudly; it never pretends to have restarted.
116
+ Future<void> restart({
117
+ required List<int> trustRootPublicKey,
118
+ Future<void> Function()? arrangeStoppedCustody,
119
+ });
120
+
121
+ /// Stop the host's connection (and process, where it owns one). Custody stays on disk.
122
+ Future<void> stop();
123
+
124
+ /// [stop], then delete [custodyDirectory].
125
+ Future<void> dispose();
126
+ }
127
+
128
+ typedef NmsTestPeerHostFactory = Future<NmsTestPeerHost> Function(
129
+ NmsTestPeerHostRequest request,
130
+ );
131
+
132
+ /// The registry the harness selects a host from. nomos_client cannot depend on the Flutter
133
+ /// shell, so the native host is injected by the caller (`nomos_flutter/testing.dart`).
134
+ abstract final class NmsTestPeerHosts {
135
+ static NmsTestPeerHostFactory? _native;
136
+
137
+ /// Register the Apple native host. Registering does not select it: selection is
138
+ /// [nmsTestPeerHostVariable]=native.
139
+ static void registerNative(NmsTestPeerHostFactory factory) {
140
+ _native = factory;
141
+ }
142
+
143
+ /// The registered native host factory. Selected-but-unregistered is a loud refusal with
144
+ /// the exact remedy, never a quiet fall back to Node.
145
+ static NmsTestPeerHostFactory requireNative() {
146
+ final factory = _native;
147
+ if (factory == null) {
148
+ throw StateError(
149
+ '$nmsTestPeerHostVariable=native selects the Apple native test peer host, but none is '
150
+ 'registered in this process. Generated business scenarios register it themselves; a '
151
+ 'raw harness in a macOS `flutter test` imports '
152
+ "'package:nomos_flutter/testing.dart' and calls registerNomosNativeTestPeerHost() "
153
+ '(e.g. in setUpAll) before opening a NomosTestHarness. Unset $nmsTestPeerHostVariable '
154
+ 'to run on the default Node peer.',
155
+ );
156
+ }
157
+ return factory;
158
+ }
159
+ }
160
+
161
+ /// The default host: the Node test Platform Shell over the WASM kernel, custody laid out as
162
+ /// `<root>/custody/holon-<ws>.git`.
163
+ final class NmsNodeTestPeerHost implements NmsTestPeerHost {
164
+ NmsNodeTestPeerHost._({
165
+ required NodeRunnerTransport transport,
166
+ required this.custody,
167
+ required this.runtimeDirectory,
168
+ required String entry,
169
+ required String node,
170
+ required String? resolveFrom,
171
+ required void Function(String line)? onDiagnostic,
172
+ }) : _transport = transport,
173
+ _entry = entry,
174
+ _node = node,
175
+ _resolveFrom = resolveFrom,
176
+ _onDiagnostic = onDiagnostic;
177
+
178
+ NodeRunnerTransport _transport;
179
+ final NmsTestCustody custody;
180
+ final String runtimeDirectory;
181
+ final String _entry;
182
+ final String _node;
183
+ final String? _resolveFrom;
184
+ final void Function(String line)? _onDiagnostic;
185
+
186
+ static Future<NmsNodeTestPeerHost> start({
187
+ required String entry,
188
+ required NmsTestPeerHostRequest request,
189
+ String node = 'node',
190
+ String? resolveFrom,
191
+ }) async {
192
+ final custody = await NmsTestCustody.create();
193
+ try {
194
+ final transport = await NodeRunnerTransport.start(
195
+ entry: entry,
196
+ resolveFrom: resolveFrom,
197
+ node: node,
198
+ arguments: custody.processArguments(request.runtimeDirectory),
199
+ onDiagnostic: request.onDiagnostic,
200
+ );
201
+ return NmsNodeTestPeerHost._(
202
+ transport: transport,
203
+ custody: custody,
204
+ runtimeDirectory: request.runtimeDirectory,
205
+ entry: entry,
206
+ node: node,
207
+ resolveFrom: resolveFrom,
208
+ onDiagnostic: request.onDiagnostic,
209
+ );
210
+ } catch (_) {
211
+ await custody.dispose();
212
+ rethrow;
213
+ }
214
+ }
215
+
216
+ @override
217
+ NmsTestPeerHostKind get kind => NmsTestPeerHostKind.node;
218
+
219
+ @override
220
+ PeerOfficeConnection get connection => _transport;
221
+
222
+ @override
223
+ NomosKernelCallTransport get kernelCalls => _transport;
224
+
225
+ @override
226
+ Directory get custodyDirectory => custody.directory;
227
+
228
+ @override
229
+ String workspaceGitDirectory(String workspace) =>
230
+ '${custody.directory.path}/custody/holon-$workspace.git';
231
+
232
+ @override
233
+ Future<void> provisionWorkspace(String workspace) =>
234
+ custody.provisionWorkspace(workspace);
235
+
236
+ @override
237
+ Future<void> awaitReady(DartReception reception) => reception.ready;
238
+
239
+ @override
240
+ Future<protocol.LawPackageInspection> inspectLawPackage(
241
+ Uint8List lawUsdz,
242
+ String label,
243
+ ) async {
244
+ final response = await _transport.invokeRuntime(
245
+ protocol.RuntimeRequest(
246
+ protocolVersion: 2,
247
+ requestId: Int64(_transport.nextRuntimeRequestId()),
248
+ inspectLawPackage: protocol.InspectLawPackageRequest(
249
+ lawUsdz: lawUsdz,
250
+ label: label,
251
+ ),
252
+ ),
253
+ );
254
+ if (response.hasFailure()) {
255
+ throw StateError('${response.failure.code}: ${response.failure.message}');
256
+ }
257
+ if (!response.hasLawPackageInspection()) {
258
+ throw StateError(
259
+ 'Peer Office returned ${response.whichOutcome().name} while inspecting $label',
260
+ );
261
+ }
262
+ return response.lawPackageInspection;
263
+ }
264
+
265
+ @override
266
+ Future<bool> trustFoundingRoot(
267
+ List<int> publicKey, {
268
+ required Future<void> Function() beforeReplacingConnection,
269
+ }) async {
270
+ await beforeReplacingConnection();
271
+ await restart(trustRootPublicKey: publicKey);
272
+ return true;
273
+ }
274
+
275
+ @override
276
+ Future<void> restart({
277
+ required List<int> trustRootPublicKey,
278
+ Future<void> Function()? arrangeStoppedCustody,
279
+ }) async {
280
+ await _transport.dispose();
281
+ await arrangeStoppedCustody?.call();
282
+ _transport = await NodeRunnerTransport.start(
283
+ entry: _entry,
284
+ resolveFrom: _resolveFrom,
285
+ node: _node,
286
+ arguments: custody.processArguments(
287
+ runtimeDirectory,
288
+ trustRootPublicKey: base64Encode(trustRootPublicKey),
289
+ ),
290
+ onDiagnostic: _onDiagnostic,
291
+ );
292
+ }
293
+
294
+ @override
295
+ Future<void> stop() => _transport.dispose();
296
+
297
+ @override
298
+ Future<void> dispose() async {
299
+ try {
300
+ await stop();
301
+ } finally {
302
+ await custody.dispose();
303
+ }
304
+ }
305
+ }
@@ -4,6 +4,7 @@ import 'package:nomos_types/nomos_kernel_protocol.dart' as nomos_protocol;
4
4
  import 'package:nomos_types/nomos_types.dart'
5
5
  show NomosErrorInfo, NomosUnavailable, nomosErrorFromInfo;
6
6
 
7
+ import 'nomos_trace.dart' show NomosTrace;
7
8
  import 'replica_readiness.dart' show NmsReplicaWake;
8
9
 
9
10
  final class NmsTypedWatchHandle<T> {
@@ -30,11 +31,13 @@ final class NmsTypedWatchRegistry {
30
31
  required T Function(nomos_protocol.WatchChanged) decode,
31
32
  required Future<void> Function(String subscriptionId) start,
32
33
  required Future<void> Function(String subscriptionId) stop,
34
+ String label = '',
33
35
  }) {
34
36
  if (_closed) throw StateError('Reception watch registry is closed');
35
37
  final id = '$session:watch:${++_sequence}';
36
38
  late final _TypedWatch<T> watch;
37
39
  watch = _TypedWatch<T>(
40
+ traceFields: NomosTrace.enabled ? 'sub=$id q=$label' : '',
38
41
  synced: _synced,
39
42
  decode: decode,
40
43
  start: () {
@@ -51,7 +54,11 @@ final class NmsTypedWatchRegistry {
51
54
  return stop(id);
52
55
  },
53
56
  );
54
- return NmsTypedWatchHandle<T>(id: id, stream: watch.controller.stream);
57
+ return NmsTypedWatchHandle<T>(
58
+ id: id,
59
+ stream: NomosTrace.timedListener(
60
+ watch.controller.stream, 'sub=$id q=$label stage=deliver'),
61
+ );
55
62
  }
56
63
 
57
64
  void deliver(nomos_protocol.WatchChanged changed) {
@@ -82,6 +89,7 @@ abstract interface class _RegisteredWatch {
82
89
  /// exists, and order cancellation after registration even when it is in flight.
83
90
  /// A single-subscription controller buffers the initial response and pauses.
84
91
  final class _TypedWatch<T> implements _RegisteredWatch {
92
+ final String _traceFields;
85
93
  final Stream<String>? _synced;
86
94
  final T Function(nomos_protocol.WatchChanged) _decode;
87
95
  final Future<void> Function() _start;
@@ -91,11 +99,13 @@ final class _TypedWatch<T> implements _RegisteredWatch {
91
99
  Future<void>? _stopping;
92
100
 
93
101
  _TypedWatch({
102
+ required String traceFields,
94
103
  required Stream<String>? synced,
95
104
  required T Function(nomos_protocol.WatchChanged) decode,
96
105
  required Future<void> Function() start,
97
106
  required Future<void> Function() stop,
98
- }) : _synced = synced,
107
+ }) : _traceFields = traceFields,
108
+ _synced = synced,
99
109
  _decode = decode,
100
110
  _start = start,
101
111
  _stop = stop {
@@ -147,7 +157,20 @@ final class _TypedWatch<T> implements _RegisteredWatch {
147
157
  return;
148
158
  }
149
159
  try {
150
- controller.add(_decode(changed));
160
+ if (NomosTrace.enabled) {
161
+ // The emission reached its watch; its listener runs later (watch.handler).
162
+ final started = NomosTrace.now();
163
+ final value = _decode(changed);
164
+ NomosTrace.emit(
165
+ 'dart',
166
+ 'watch.deliver',
167
+ '$_traceFields delta=${changed.hasApplicationDelta()} '
168
+ 'bytes=${changed.applicationResult.value.length + changed.resultUsda.length + changed.applicationDelta.changedRows.value.length} '
169
+ 'decodeUs=${NomosTrace.now() - started}');
170
+ controller.add(value);
171
+ } else {
172
+ controller.add(_decode(changed));
173
+ }
151
174
  } catch (error, stackTrace) {
152
175
  controller.addError(error, stackTrace);
153
176
  }
package/dart/pubspec.lock CHANGED
@@ -222,7 +222,7 @@ packages:
222
222
  path: "../dart_types"
223
223
  relative: true
224
224
  source: path
225
- version: "0.12.0"
225
+ version: "0.13.0"
226
226
  package_config:
227
227
  dependency: "direct main"
228
228
  description: