@githolon/dsl 0.113.4 → 0.115.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 (103) 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 +60 -0
  6. package/dart/lib/src/application_framework_calls.dart +16 -6
  7. package/dart/lib/src/console_enrolment.dart +7 -0
  8. package/dart/lib/src/console_http_io.dart +22 -15
  9. package/dart/lib/src/dev_cloud.dart +62 -0
  10. package/dart/lib/src/generated_call.dart +14 -3
  11. package/dart/lib/src/nms_generated_runtime.dart +7 -0
  12. package/dart/lib/src/node_runner_transport.dart +77 -8
  13. package/dart/lib/src/nomos_trace.dart +107 -0
  14. package/dart/lib/src/operation_receipts.dart +74 -7
  15. package/dart/lib/src/reception/dart_reception.dart +174 -17
  16. package/dart/lib/src/rows_watch.dart +390 -0
  17. package/dart/lib/src/runner/node-entry.mjs +7678 -2860
  18. package/dart/lib/src/runtime_v2_transport.dart +3 -0
  19. package/dart/lib/src/sync_status_projection.dart +94 -1
  20. package/dart/lib/src/sync_status_reducer.dart +5 -2
  21. package/dart/lib/src/test_harness.dart +107 -41
  22. package/dart/lib/src/testing/test_peer.dart +107 -99
  23. package/dart/lib/src/testing/test_peer_host.dart +305 -0
  24. package/dart/lib/src/typed_watch_registry.dart +26 -3
  25. package/dart/pubspec.lock +1 -1
  26. package/dart/pubspec.yaml +2 -2
  27. package/dart/test/background_model_adoption_test.dart +1 -0
  28. package/dart/test/console_enrolment_test.dart +47 -0
  29. package/dart/test/custodian_warming_test.dart +105 -0
  30. package/dart/test/fixtures/offer_watch_order_peer.dart +74 -0
  31. package/dart/test/kernel_author_after_read_test.dart +6 -3
  32. package/dart/test/kernel_fence_test.dart +4 -1
  33. package/dart/test/model_adoption_test.dart +66 -0
  34. package/dart/test/nomos_trace_test.dart +91 -0
  35. package/dart/test/own_offer_watch_order_test.dart +90 -0
  36. package/dart/test/receiving_group_open_test.dart +4 -8
  37. package/dart/test/replica_readiness_test.dart +14 -4
  38. package/dart/test/rows_watch_test.dart +331 -0
  39. package/dart/test/runtime_projections_test.dart +41 -0
  40. package/dart/test/test_peer_host_selection_test.dart +49 -0
  41. package/dart_types/CHANGELOG.md +39 -0
  42. package/dart_types/lib/src/approved_model.dart +100 -6
  43. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +1063 -3
  44. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +89 -0
  45. package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pb.dart +14 -0
  46. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +2272 -130
  47. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pbenum.dart +65 -0
  48. package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +118 -2
  49. package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pb.dart +278 -9
  50. package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pb.dart +7 -85
  51. package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pb.dart +16 -0
  52. package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pb.dart +35 -0
  53. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +43 -2
  54. package/dart_types/pubspec.yaml +1 -1
  55. package/package.json +6 -2
  56. package/src/aggregate.ts +93 -0
  57. package/src/anonymous_value_interning.ts +205 -0
  58. package/src/baseline.ts +27 -0
  59. package/src/baseline_export.ts +432 -0
  60. package/src/baseline_import.ts +312 -0
  61. package/src/baseline_usda.ts +186 -0
  62. package/src/birth_key_rule.ts +472 -0
  63. package/src/births.ts +207 -24
  64. package/src/build_provenance.ts +17 -2
  65. package/src/business_birth.ts +50 -4
  66. package/src/check_package_main.ts +50 -6
  67. package/src/codegen_call_proto.ts +5 -18
  68. package/src/codegen_dart.ts +95 -3
  69. package/src/codegen_dart_client.ts +73 -15
  70. package/src/codegen_dart_facade.ts +58 -35
  71. package/src/codegen_dart_framework_calls.ts +32 -10
  72. package/src/codegen_dart_scenario.ts +7 -0
  73. package/src/codegen_proof.ts +5 -2
  74. package/src/codegen_result_proto.ts +8 -6
  75. package/src/codegen_result_usda.ts +4 -0
  76. package/src/codegen_ts.ts +135 -34
  77. package/src/compatibility_graph.ts +41 -4
  78. package/src/compatibility_usda.ts +11 -1
  79. package/src/compile_package_main.ts +272 -105
  80. package/src/composed_wire_identity.ts +24 -0
  81. package/src/directive.ts +6 -0
  82. package/src/domain_requirements.ts +75 -0
  83. package/src/engine_entry.ts +4 -48
  84. package/src/engine_javascript.ts +12 -6
  85. package/src/fields.ts +109 -0
  86. package/src/identity_usda.ts +77 -51
  87. package/src/index.ts +25 -1
  88. package/src/intent.ts +5 -0
  89. package/src/law_graph.ts +142 -1
  90. package/src/law_graph_usda.ts +25 -141
  91. package/src/law_layer_prims.ts +199 -0
  92. package/src/law_unit_selection.ts +45 -0
  93. package/src/law_unit_set_identity.ts +44 -0
  94. package/src/law_version.ts +107 -0
  95. package/src/package_config.ts +44 -0
  96. package/src/package_dependencies.ts +8 -0
  97. package/src/query/compile.ts +0 -0
  98. package/src/release_inspection.ts +24 -0
  99. package/src/release_publication.ts +298 -0
  100. package/src/value_object.ts +6 -3
  101. package/src/wire.ts +2 -0
  102. package/src/wire_paths.ts +17 -0
  103. package/src/workspace_routing.ts +1 -1
@@ -0,0 +1,107 @@
1
+ /// THE OPT-IN TIMELINE TRACE: `[nomos-trace]` lines across the whole call path.
2
+ ///
3
+ /// One generated call (a save, a read) crosses the reception, the transport, an isolate, the
4
+ /// native host, the Peer Office and the kernel. Every layer writes `[nomos-trace]` lines so one
5
+ /// call can be followed end to end and each millisecond given to a layer:
6
+ ///
7
+ /// * `c=` — this Dart call (one generated offer or query), joined to
8
+ /// * `lr=` — the kernel request id the reception gave it, joined to
9
+ /// * `rr=` — the Runtime v2 request id every lower layer (worker, host, office) logs.
10
+ ///
11
+ /// `t=` is microseconds on Dart's Timeline clock; on Apple the native host and the Peer Office
12
+ /// stamp the same clock (CLOCK_MONOTONIC_RAW), so cross-layer gaps are plain subtraction.
13
+ /// `scripts/nomos_trace_waterfall.ts` turns a log of these lines into a per-call waterfall.
14
+ ///
15
+ /// Off by default, including in debug. Build with `--dart-define=NOMOS_TRACE=true` (a release
16
+ /// phone build, `flutter test`, AppSim) or set [NomosTrace.enabled] before the first call. Every
17
+ /// Runtime v2 request is then sent with a `sampled` trace context, which is what turns the native
18
+ /// host's and the Peer Office's lines on: one switch for the whole path. Disabled, each call site
19
+ /// is one static bool read. Lines name operation kinds, ids, sizes and durations only: never
20
+ /// payloads, credentials or workspace contents.
21
+ library;
22
+
23
+ import 'dart:async';
24
+ import 'dart:developer' as developer;
25
+
26
+ import 'package:meta/meta.dart' show internal;
27
+ import 'package:nomos_types/nomos_kernel_protocol.dart' as nomos_protocol;
28
+
29
+ /// See the library comment.
30
+ abstract final class NomosTrace {
31
+ /// Whether `[nomos-trace]` lines are written. Per isolate: hosts that run a worker isolate pass
32
+ /// it on explicitly.
33
+ static bool enabled = const bool.fromEnvironment('NOMOS_TRACE');
34
+
35
+ static int _calls = 0;
36
+
37
+ /// Microseconds on the Timeline clock (the clock every `t=` uses).
38
+ static int now() => developer.Timeline.now;
39
+
40
+ /// A fresh Dart call id (`c=`).
41
+ static int nextCall() => ++_calls;
42
+
43
+ /// Write one line: `[nomos-trace] t=<now> L=<layer> e=<event> <fields>`. Build [fields] only
44
+ /// behind an `if (NomosTrace.enabled)` so a disabled build pays nothing for them.
45
+ static void emit(String layer, String event, String fields) {
46
+ // A release build's print reaches the device log (idevicesyslog -m nomos); debugPrint
47
+ // would throttle and batch it, which skews the very timings being read.
48
+ // ignore: avoid_print
49
+ print('[nomos-trace] t=${now()} L=$layer e=$event $fields');
50
+ }
51
+
52
+ /// The trace context every Runtime v2 request carries while tracing: `sampled` asks the native
53
+ /// host and the Peer Office to write their lines too. No ids are set, so the office keeps
54
+ /// deriving span ids from the request id exactly as when tracing is off.
55
+ @internal
56
+ static nomos_protocol.TraceContext sampledContext() =>
57
+ nomos_protocol.TraceContext(sampled: true);
58
+
59
+ /// What a Runtime v2 request asks for, as an operation kind (never argument values):
60
+ /// `kernelCall/offer:estate/placeAssetOnDrawing`, `kernelCall/query:rows:assetsByAssetId`, `watch`…
61
+ static String describeRequest(nomos_protocol.RuntimeRequest request) {
62
+ final command = request.whichCommand().name;
63
+ if (request.hasKernelCall()) {
64
+ return '$command/${describeCall(request.kernelCall.request)}';
65
+ }
66
+ if (request.hasWatch() && request.watch.hasQuery()) {
67
+ return '$command/${_describeQuery(request.watch.query)}';
68
+ }
69
+ return command;
70
+ }
71
+
72
+ /// A kernel request's operation kind.
73
+ static String describeCall(nomos_protocol.CallRequest call) {
74
+ if (call.hasOffer()) {
75
+ final offer = call.offer;
76
+ return offer.hasCall()
77
+ ? 'offer:${offer.call.domain}/${offer.call.directive}'
78
+ : 'offer:${offer.whichIntent().name}';
79
+ }
80
+ if (call.hasQuery()) return _describeQuery(call.query);
81
+ return call.whichOperation().name;
82
+ }
83
+
84
+ /// [source] with each data event's synchronous listener time traced as `e=watch.handler`
85
+ /// ([fields] identify the watch). The listener runs inside the transform's data handler, so
86
+ /// this is the time the application spent on one emission before yielding. Unchanged when off.
87
+ @internal
88
+ static Stream<T> timedListener<T>(Stream<T> source, String fields) {
89
+ if (!enabled) return source;
90
+ return source.transform(StreamTransformer<T, T>.fromHandlers(
91
+ handleData: (value, sink) {
92
+ final started = now();
93
+ sink.add(value);
94
+ emit('dart', 'watch.handler', '$fields us=${now() - started}');
95
+ },
96
+ ));
97
+ }
98
+
99
+ static String _describeQuery(nomos_protocol.QueryRequest query) {
100
+ if (query.hasRows()) return 'query:rows:${query.rows.id}';
101
+ if (query.hasPage()) return 'query:page:${query.page.id}';
102
+ if (query.hasById()) return 'query:byId:${query.byId.aggregateType}';
103
+ if (query.hasByType()) return 'query:byType:${query.byType.aggregateType}';
104
+ if (query.hasNamed()) return 'query:named:${query.named.operation}';
105
+ return 'query:${query.whichQuery().name}';
106
+ }
107
+ }
@@ -8,15 +8,43 @@ enum NomosOperationPhase {
8
8
  converged,
9
9
  }
10
10
 
11
+ /// Custody refused a write this device accepted locally: a terminal verdict, never retried.
12
+ ///
13
+ /// Today the one such refusal Nomos reports is a duplicate birth: a workspace name is global to its
14
+ /// namespace and fixed at birth, and another parent already owns this child's name, so the cloud
15
+ /// refused this birth (`[birth-child-already-parented]`). The child's local copy is quarantined on this
16
+ /// device — kept for inspection, never synced. The message names only this device's own workspaces.
17
+ final class NomosOperationRefused implements Exception {
18
+ const NomosOperationRefused({
19
+ required this.code,
20
+ required this.message,
21
+ this.childWorkspace,
22
+ });
23
+
24
+ /// The typed refusal tag, e.g. `birth-child-already-parented`.
25
+ final String code;
26
+ final String message;
27
+
28
+ /// The quarantined local child, for a refused birth.
29
+ final String? childWorkspace;
30
+
31
+ @override
32
+ String toString() => 'NomosOperationRefused[$code]: $message';
33
+ }
34
+
11
35
  /// Product-facing progress for one accepted write.
12
36
  final class NomosOperationStatus {
13
- const NomosOperationStatus(this.phase, {this.updatedAt});
37
+ const NomosOperationStatus(this.phase, {this.updatedAt, this.refusal});
14
38
 
15
39
  final NomosOperationPhase phase;
16
40
  final DateTime? updatedAt;
17
41
 
42
+ /// Set when custody refused this write: terminal; [phase] stays [NomosOperationPhase.locallyAccepted].
43
+ final NomosOperationRefused? refusal;
44
+
18
45
  bool get isAdmitted => phase != NomosOperationPhase.locallyAccepted;
19
46
  bool get isConverged => phase == NomosOperationPhase.converged;
47
+ bool get isRefused => refusal != null;
20
48
  }
21
49
 
22
50
  /// Awaitable projection of typed peer-runtime receipts for one stable intent.
@@ -36,13 +64,36 @@ final class NomosOperationReceipt {
36
64
  yield* _changes.stream;
37
65
  }
38
66
 
39
- Future<void> get admitted => _status.isAdmitted
40
- ? Future<void>.value()
41
- : watch().firstWhere((status) => status.isAdmitted).then((_) {});
67
+ /// Completes when remote custody admits this write; completes with a
68
+ /// [NomosOperationRefused] error when custody refuses it.
69
+ Future<void> get admitted => _settled((status) => status.isAdmitted);
70
+
71
+ /// Completes when this write has converged; completes with a
72
+ /// [NomosOperationRefused] error when custody refuses it.
73
+ Future<void> get converged => _settled((status) => status.isConverged);
74
+
75
+ Future<void> _settled(bool Function(NomosOperationStatus) reached) {
76
+ final refusal = _status.refusal;
77
+ if (refusal != null) return Future<void>.error(refusal);
78
+ if (reached(_status)) return Future<void>.value();
79
+ return watch()
80
+ .firstWhere((status) => status.isRefused || reached(status))
81
+ .then((status) {
82
+ final refused = status.refusal;
83
+ if (refused != null) throw refused;
84
+ });
85
+ }
42
86
 
43
- Future<void> get converged => _status.isConverged
44
- ? Future<void>.value()
45
- : watch().firstWhere((status) => status.isConverged).then((_) {});
87
+ void _refuse(NomosOperationRefused refusal) {
88
+ if (_status.isRefused || _status.isAdmitted) return;
89
+ _status = NomosOperationStatus(
90
+ _status.phase,
91
+ updatedAt: DateTime.now(),
92
+ refusal: refusal,
93
+ );
94
+ if (!_changes.isClosed) _changes.add(_status);
95
+ unawaited(_changes.close());
96
+ }
46
97
 
47
98
  void _advance(NomosOperationPhase phase) {
48
99
  if (phase.index <= _status.phase.index) return;
@@ -81,6 +132,22 @@ final class NmsOperationReceiptProjection {
81
132
  NomosOperationReceipt? birthFor(String workspace) => _births[workspace];
82
133
 
83
134
  void advance(String workspace, nomos_protocol.SyncWorkspaceResponse result) {
135
+ // Custody refused one of this workspace's births (the child's name already has another parent):
136
+ // the creation receipt of that child reports the typed, terminal refusal (CAP-804).
137
+ for (final birth in result.birthOutcomes) {
138
+ if (birth.status !=
139
+ nomos_protocol.BirthOutcomeStatus.BIRTH_OUTCOME_STATUS_REFUSED ||
140
+ birth.code != 'birth-child-already-parented') {
141
+ continue;
142
+ }
143
+ final child =
144
+ birth.childWorkspace.isNotEmpty ? birth.childWorkspace : birth.workspace;
145
+ _births[child]?._refuse(NomosOperationRefused(
146
+ code: birth.code,
147
+ message: birth.hasRefusal() ? birth.refusal.message : birth.reason,
148
+ childWorkspace: child,
149
+ ));
150
+ }
84
151
  final pending = _pending[workspace];
85
152
  if (pending == null || pending.isEmpty) return;
86
153
 
@@ -51,7 +51,8 @@ import '../generated_call.dart'
51
51
  NmsGeneratedCall,
52
52
  NmsGeneratedQuery,
53
53
  NmsGeneratedResult,
54
- NmsInternalGeneratedResults;
54
+ NmsInternalGeneratedResults,
55
+ nmsGeneratedCallCreatedAt;
55
56
  import '../application_model_adoption.dart'
56
57
  show
57
58
  NmsApprovedModelLawPackageSource,
@@ -63,8 +64,10 @@ import '../application_model_adoption.dart'
63
64
  import '../application_framework_calls.dart'
64
65
  show OwnedCustody, OwnedDeviceRequest;
65
66
  import '../kernel_primitives.dart' show NmsKernelPrimitives;
67
+ import '../nomos_trace.dart' show NomosTrace;
66
68
  import '../observed_session_directory.dart' show NmsObservedSessionDirectory;
67
69
  import '../replica_readiness.dart' show nmsUntilReplicaReady;
70
+ import '../rows_watch.dart' show NmsRowsMerge, nmsMergeRows;
68
71
  import '../operation_receipts.dart'
69
72
  show NmsOperationReceiptProjection, NomosOperationReceipt;
70
73
  import 'event_desk.dart' show ReceptionEventDesk;
@@ -91,7 +94,11 @@ export '../sync_status_projection.dart'
91
94
  WorkspaceFollowStatus,
92
95
  WorkspaceFollowPhase;
93
96
  export '../operation_receipts.dart'
94
- show NomosOperationPhase, NomosOperationReceipt, NomosOperationStatus;
97
+ show
98
+ NomosOperationPhase,
99
+ NomosOperationReceipt,
100
+ NomosOperationRefused,
101
+ NomosOperationStatus;
95
102
  export 'progress_desk.dart'
96
103
  show
97
104
  KernelLawHydrationStep,
@@ -565,6 +572,17 @@ class NomosOwnedDeviceApprovalPreparation {
565
572
  });
566
573
  }
567
574
 
575
+ /// Whether generated offers ask the kernel for its phase timings, which this
576
+ /// reception prints as `[nomos-kernel-perf]` diagnostics. On by default in
577
+ /// debug and profile builds. A release build leaves them off: carrying the spans
578
+ /// costs the kernel, the Peer Office and this isolate work on every write that
579
+ /// nobody reads. `--dart-define=NOMOS_KERNEL_SPANS=false` measures a profile
580
+ /// build as a release user experiences it; `=true` restores them in release.
581
+ const bool kNomosOfferKernelSpans = bool.fromEnvironment(
582
+ 'NOMOS_KERNEL_SPANS',
583
+ defaultValue: !bool.fromEnvironment('dart.vm.product'),
584
+ );
585
+
568
586
  /// The result of [DartReception.offerCreates]: the new local [head], every aggregate the plan minted, and —
569
587
  /// for a `.births()` directive — the child workspace(s) the offer brought into being on this local plane.
570
588
  /// The proof material a generated offer presents to the kernel.
@@ -890,6 +908,15 @@ class DartReception {
890
908
  );
891
909
  return;
892
910
  }
911
+ if (event.hasReadAccessPreparation()) {
912
+ // The read itself still ends typed (rows, or NomosUnavailable with
913
+ // credentialUnavailable); this only says why it is taking a moment.
914
+ final preparation = event.readAccessPreparation;
915
+ // ignore: avoid_print
916
+ print('[nomos-read-access] enrolling this device to read '
917
+ 'workspace=${preparation.workspace} request=${preparation.requestId}');
918
+ return;
919
+ }
893
920
  if (event.hasReady()) {
894
921
  final stale = _staleBundleError(event.ready.runtimeVersion);
895
922
  if (stale != null && !allowStaleRunnerBundle) {
@@ -946,6 +973,19 @@ class DartReception {
946
973
  if (event.hasLinkChanged()) {
947
974
  _eventPlane._events.publishSync(event);
948
975
  final session = event.hasSession() ? event.session : null;
976
+ if (NomosTrace.enabled && event.linkChanged.hasClosure()) {
977
+ // How the live connection ended (CAP-683): who closed it, how, and what it had carried.
978
+ final closure = event.linkChanged.closure;
979
+ NomosTrace.emit(
980
+ 'dart',
981
+ 'link.closed',
982
+ 'ws=${closure.workspace} conn=${closure.connectionId} '
983
+ 'by=${closure.socket.initiator.name} '
984
+ 'code=${closure.socket.hasCode() ? closure.socket.code : '-'} '
985
+ 'clean=${closure.socket.clean} openMs=${closure.openMillis} '
986
+ 'admissions=${closure.admissions} '
987
+ 'unanswered=${closure.unansweredBatches}');
988
+ }
949
989
  if (event.linkChanged.status ==
950
990
  nomos_protocol.LinkStatus.LINK_STATUS_CLOSED) {
951
991
  final closedAt = DateTime.now();
@@ -1692,7 +1732,6 @@ class DartReception {
1692
1732
  Future<NomosOfferOutcome> offerGeneratedCallOutcome({
1693
1733
  required NmsGeneratedCall call,
1694
1734
  String? actor,
1695
- String? domainHash,
1696
1735
  String? session,
1697
1736
  String? authorSecret,
1698
1737
  NomosBirthCertificate? birthCertificate,
@@ -1729,11 +1768,27 @@ class DartReception {
1729
1768
  // ignore: avoid_print
1730
1769
  print('[nomos-offer] dispatch ${call.domain}/${call.directive} in '
1731
1770
  '$workspace using ${authenticationProof.name}');
1771
+ // Opt-in timeline (nomos_trace.dart): this call's id, and when the generated code made it.
1772
+ final traceCall = NomosTrace.enabled ? NomosTrace.nextCall() : 0;
1773
+ final traceBegin = NomosTrace.enabled ? NomosTrace.now() : 0;
1774
+ if (NomosTrace.enabled) {
1775
+ final created = nmsGeneratedCallCreatedAt(call);
1776
+ NomosTrace.emit(
1777
+ 'dart',
1778
+ 'call.begin',
1779
+ 'c=$traceCall op=offer:${call.domain}/${call.directive} '
1780
+ 'sinceCreateUs=${created == 0 ? -1 : traceBegin - created}');
1781
+ }
1732
1782
  Future<nomos_protocol.CallResponse> attempt() async {
1733
1783
  final requestNumber = ++plane._sequences.request;
1734
1784
  final requestId = '$runtimeSession:offer:$requestNumber';
1735
1785
  final observer = NomosWrite.captureObserver();
1736
1786
  if (observer != null) plane._writeObservers[requestId] = observer;
1787
+ final sent = NomosTrace.enabled ? NomosTrace.now() : 0;
1788
+ if (NomosTrace.enabled) {
1789
+ NomosTrace.emit('dart', 'call.send',
1790
+ 'c=$traceCall lr=$requestId sinceBeginUs=${sent - traceBegin}');
1791
+ }
1737
1792
  try {
1738
1793
  return await kernelTransport.callKernel(
1739
1794
  session: runtimeSession,
@@ -1758,9 +1813,8 @@ class DartReception {
1758
1813
  ? const <int>[]
1759
1814
  : base64Decode(explicitSecret),
1760
1815
  deferProjection: true,
1761
- spans: true,
1816
+ spans: kNomosOfferKernelSpans,
1762
1817
  ),
1763
- controllerLawHash: domainHash,
1764
1818
  conflictSnapshots: conflictSnapshots,
1765
1819
  birthCertificate: birthCertificate,
1766
1820
  ),
@@ -1770,6 +1824,10 @@ class DartReception {
1770
1824
  } finally {
1771
1825
  plane._writeObservers.remove(requestId);
1772
1826
  observer?.call(const NomosWriteProgress(NomosWritePhase.running));
1827
+ if (NomosTrace.enabled) {
1828
+ NomosTrace.emit('dart', 'call.reply',
1829
+ 'c=$traceCall lr=$requestId us=${NomosTrace.now() - sent}');
1830
+ }
1773
1831
  }
1774
1832
  }
1775
1833
 
@@ -1791,6 +1849,10 @@ class DartReception {
1791
1849
  response = await attempt();
1792
1850
  }
1793
1851
  if (response.hasFailure()) {
1852
+ if (NomosTrace.enabled) {
1853
+ NomosTrace.emit('dart', 'call.end',
1854
+ 'c=$traceCall outcome=failure us=${NomosTrace.now() - traceBegin}');
1855
+ }
1794
1856
  // ignore: avoid_print
1795
1857
  print('[nomos-offer] refused ${call.domain}/${call.directive} in '
1796
1858
  '$workspace: ${response.failure.code}: ${response.failure.message}');
@@ -1850,6 +1912,10 @@ class DartReception {
1850
1912
  for (final child in born) {
1851
1913
  plane._receipts.rememberBirth(child, receipt);
1852
1914
  }
1915
+ if (NomosTrace.enabled) {
1916
+ NomosTrace.emit('dart', 'call.end',
1917
+ 'c=$traceCall outcome=admitted us=${NomosTrace.now() - traceBegin}');
1918
+ }
1853
1919
  return NomosOfferOutcome(
1854
1920
  head: admitted.head,
1855
1921
  intentId: admitted.intentId,
@@ -2066,15 +2132,23 @@ class DartReception {
2066
2132
 
2067
2133
  /// Framework-private organisational read used by generated `describe()`. The runner joins the parent
2068
2134
  /// ledger's verified birth witnesses and lifecycle facts; tenant code only receives safe child links.
2135
+ ///
2136
+ /// On a cold mount the office answers "not ready yet" while it prepares the parent's birth
2137
+ /// records (CAP-824); this waits that out like every other one-shot read, so a caller sees the
2138
+ /// children or a real error, never an empty list that fills in by itself a moment later.
2069
2139
  Future<List<nomos_protocol.ChildWorkspace>> nomosFrameworkChildWorkspaces(
2070
2140
  {String? namespace}) async {
2071
- final response = await _invokeRuntimeV2(
2072
- nomos_protocol.RuntimeRequest(
2073
- listChildWorkspaces: nomos_protocol.ListChildWorkspacesRequest(
2074
- session: _runtimeSession(),
2075
- namespace: namespace,
2141
+ final session = _runtimeSession();
2142
+ final response = await nmsUntilReplicaReady(
2143
+ () => _invokeRuntimeV2(
2144
+ nomos_protocol.RuntimeRequest(
2145
+ listChildWorkspaces: nomos_protocol.ListChildWorkspacesRequest(
2146
+ session: session,
2147
+ namespace: namespace,
2148
+ ),
2076
2149
  ),
2077
2150
  ),
2151
+ synced: _eventPlane._events.workspacesSynced,
2078
2152
  );
2079
2153
  if (!response.hasChildWorkspaces()) {
2080
2154
  throw StateError(
@@ -2550,9 +2624,22 @@ class DartReception {
2550
2624
  // overlapping logical request identities despite sharing one executor.
2551
2625
  // A copy still being fetched or opened answers "not ready yet": ask again
2552
2626
  // rather than hand every caller a state that passes by itself.
2627
+ final traceCall = NomosTrace.enabled ? NomosTrace.nextCall() : 0;
2628
+ final traceBegin = NomosTrace.enabled ? NomosTrace.now() : 0;
2629
+ if (NomosTrace.enabled) {
2630
+ NomosTrace.emit('dart', 'call.begin',
2631
+ 'c=$traceCall op=${NomosTrace.describeCall(nomos_protocol.CallRequest(query: query))}');
2632
+ }
2553
2633
  final response = await nmsUntilReplicaReady(
2554
2634
  () {
2555
2635
  final requestNumber = ++plane._sequences.request;
2636
+ if (NomosTrace.enabled) {
2637
+ NomosTrace.emit(
2638
+ 'dart',
2639
+ 'call.send',
2640
+ 'c=$traceCall lr=$runtimeSession:query:$requestNumber '
2641
+ 'sinceBeginUs=${NomosTrace.now() - traceBegin}');
2642
+ }
2556
2643
  return (transport as NomosKernelCallTransport).callKernel(
2557
2644
  session: runtimeSession,
2558
2645
  // An explicit device key is an additional proof, not a replacement for
@@ -2569,6 +2656,13 @@ class DartReception {
2569
2656
  },
2570
2657
  synced: _eventPlane._events.workspacesSynced,
2571
2658
  );
2659
+ if (NomosTrace.enabled) {
2660
+ NomosTrace.emit(
2661
+ 'dart',
2662
+ 'call.end',
2663
+ 'c=$traceCall outcome=${response.whichOutcome().name} '
2664
+ 'us=${NomosTrace.now() - traceBegin}');
2665
+ }
2572
2666
  if (response.hasFailure()) {
2573
2667
  throw StateError('${response.failure.code}: ${response.failure.message}');
2574
2668
  }
@@ -2664,10 +2758,16 @@ class DartReception {
2664
2758
  birthEvidence: posture.birthEvidence.status,
2665
2759
  birthEvidenceDetail: posture.birthEvidence.detail,
2666
2760
  );
2761
+ // An enrolled device proves its principal with its own key and carries no
2762
+ // provider token by design (D22): name that, rather than `missingToken`
2763
+ // and `unresolved`, which read like a fault.
2764
+ final tokenless = result.enrolledDevice &&
2765
+ result.providerToken == NomosProviderTokenVerification.missingToken;
2667
2766
  print(
2668
2767
  '[nomos-auth-posture] workspace=${_eventPlane._sessions.workspaceFor(session ?? _sessionName ?? '')} '
2669
- 'token=${result.providerToken.name} enrolled=${result.enrolledDevice} '
2670
- 'principal=${result.resolvedPrincipal.isEmpty ? 'unresolved' : 'resolved'}');
2768
+ 'token=${tokenless ? 'notCarried(enrolledDevice)' : result.providerToken.name} '
2769
+ 'enrolled=${result.enrolledDevice} '
2770
+ 'principal=${result.resolvedPrincipal.isNotEmpty ? 'resolved' : tokenless ? 'byDeviceSigner' : 'unresolved'}');
2671
2771
  return result;
2672
2772
  }
2673
2773
 
@@ -2676,16 +2776,15 @@ class DartReception {
2676
2776
  /// must already be bound to the principal's receiving-group workspace. The
2677
2777
  /// shared desk owns whatever placement, identity and birth work the operation
2678
2778
  /// requires. Its convergence tail runs as background correspondence and reports on
2679
- /// [receivingGroupConvergence]. [contract] is the application's law identity
2680
- /// from the compiled descriptor; tenant seed steps are law, never carried
2681
- /// here. A typed refusal surfaces as a [NomosRuntimeError] carrying the
2779
+ /// [receivingGroupConvergence]. The Home is born under the law its platform
2780
+ /// serves, which the desk observes at birth; tenant seed steps are law, never
2781
+ /// carried here. A typed refusal surfaces as a [NomosRuntimeError] carrying the
2682
2782
  /// desk's own code (`nomos.runtime.receiving_group_*`).
2683
2783
  @internal
2684
2784
  Future<nomos_protocol.ReceivingGroupOpened> nmsOpenReceivingGroup({
2685
2785
  required String application,
2686
2786
  required String principal,
2687
2787
  required String authToken,
2688
- required nomos_protocol.ApplicationBirthContract contract,
2689
2788
  bool verifyHistory = false,
2690
2789
  String? session,
2691
2790
  nomos_protocol.ConsoleEnrolmentRoute? enrolment,
@@ -2697,7 +2796,6 @@ class DartReception {
2697
2796
  application: application,
2698
2797
  principal: principal,
2699
2798
  authToken: utf8.encode(authToken),
2700
- contract: contract,
2701
2799
  verifyHistory: verifyHistory,
2702
2800
  enrolment: enrolment,
2703
2801
  ),
@@ -2954,6 +3052,51 @@ class DartReception {
2954
3052
  intervalMs: intervalMs,
2955
3053
  );
2956
3054
 
3055
+ /// Reactive rows of a declared query, kept current by kernel deltas (CAP-731).
3056
+ ///
3057
+ /// The watch opts into row deltas: after its first complete result, each
3058
+ /// local frontier advance delivers only the rows that changed (decoded with
3059
+ /// the same generated [decode]) and the ids that left. The rows are merged
3060
+ /// by [idOf] in the kernel's id order; a row the change did not touch keeps
3061
+ /// its identity, and nothing is emitted when nothing changed. A delta that
3062
+ /// does not start at this watch's position ends the stream with
3063
+ /// [NomosWatchPositionMismatch].
3064
+ Stream<List<T>> nmsWatchRows<T>(
3065
+ NmsGeneratedQuery query, {
3066
+ required List<T> Function(NmsGeneratedResult result) decode,
3067
+ required String Function(T row) idOf,
3068
+ List<NomosScopeKey>? keys,
3069
+ String? principal,
3070
+ String? session,
3071
+ int intervalMs = _defaultWatchSafetyPollMs,
3072
+ }) =>
3073
+ NomosTrace.timedListener(
3074
+ nmsMergeRows(
3075
+ _nmsWatchQuery<nomos_protocol.WatchChanged>(
3076
+ nomos_protocol.QueryRequest(
3077
+ rows: nomos_protocol.RowsQuery(
3078
+ id: query.query,
3079
+ principal: principal ?? '',
3080
+ applicationArguments: google_protobuf.Any(
3081
+ typeUrl: query.protobufType,
3082
+ value: query.protobufPayload,
3083
+ ),
3084
+ ),
3085
+ ),
3086
+ resultFormat: nomos_protocol
3087
+ .QueryResultFormat.QUERY_RESULT_FORMAT_APPLICATION_PROTOBUF,
3088
+ decode: (changed) => changed,
3089
+ acceptRowDelta: true,
3090
+ keys: keys,
3091
+ principal: principal,
3092
+ session: session,
3093
+ intervalMs: intervalMs,
3094
+ ),
3095
+ NmsRowsMerge<T>(decode: decode, idOf: idOf),
3096
+ ),
3097
+ // The application's own listener time per emission (the inner watch's is the merge).
3098
+ 'q=query:rows:${query.query} stage=app');
3099
+
2957
3100
  /// Reactive form of [nmsQueryUsda]. Subscription mechanics are Runtime v2
2958
3101
  /// protobuf; each emission is the kernel's nominal USDA result and is decoded
2959
3102
  /// only by the generated domain client which owns [query].
@@ -3054,6 +3197,7 @@ class DartReception {
3054
3197
  nomos_protocol.QueryRequest request, {
3055
3198
  required nomos_protocol.QueryResultFormat resultFormat,
3056
3199
  required T Function(nomos_protocol.WatchChanged) decode,
3200
+ bool acceptRowDelta = false,
3057
3201
  List<NomosScopeKey>? keys,
3058
3202
  String? principal,
3059
3203
  String? session,
@@ -3070,6 +3214,9 @@ class DartReception {
3070
3214
  ..resultFormat = resultFormat;
3071
3215
  final NmsTypedWatchHandle<T> watch = plane._events.typedWatches.open<T>(
3072
3216
  session: runtimeSession,
3217
+ label: NomosTrace.enabled
3218
+ ? NomosTrace.describeCall(nomos_protocol.CallRequest(query: request))
3219
+ : '',
3073
3220
  decode: decode,
3074
3221
  stop: _unwatch,
3075
3222
  start: (subscriptionId) async {
@@ -3081,6 +3228,7 @@ class DartReception {
3081
3228
  kind: nomos_protocol.WatchKind.WATCH_KIND_QUERY,
3082
3229
  query: request,
3083
3230
  intervalMillis: intervalMs,
3231
+ acceptRowDelta: acceptRowDelta,
3084
3232
  ),
3085
3233
  ),
3086
3234
  );
@@ -4160,9 +4308,18 @@ class NomosSession extends DartReception {
4160
4308
  adoptApplicationModel: nomos_protocol.AdoptApplicationModelRequest(
4161
4309
  session: workspace,
4162
4310
  lawSha256: model.lawSha256,
4311
+ unitSet: model.unitSet,
4163
4312
  domainKeys: model.domainKeys,
4164
4313
  acceptanceReason: model.reason,
4165
4314
  dryRun: dryRun,
4315
+ domainRequirements: [
4316
+ for (final requirement in model.domainRequirements.values)
4317
+ nomos_protocol.DomainUnitRequirement(
4318
+ domainKey: requirement.domainKey,
4319
+ unitSha256: requirement.unitSha256,
4320
+ requirementSha256: requirement.requirementSha256,
4321
+ ),
4322
+ ],
4166
4323
  ),
4167
4324
  ),
4168
4325
  );