@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.
- package/assets/operation-contract.wasm +0 -0
- package/compile_package.mjs +23 -3
- package/compile_proof.d.mts +7 -0
- package/compile_proof.mjs +38 -3
- package/dart/CHANGELOG.md +60 -0
- package/dart/lib/src/application_framework_calls.dart +16 -6
- package/dart/lib/src/console_enrolment.dart +7 -0
- package/dart/lib/src/console_http_io.dart +22 -15
- package/dart/lib/src/dev_cloud.dart +62 -0
- package/dart/lib/src/generated_call.dart +14 -3
- package/dart/lib/src/nms_generated_runtime.dart +7 -0
- package/dart/lib/src/node_runner_transport.dart +77 -8
- package/dart/lib/src/nomos_trace.dart +107 -0
- package/dart/lib/src/operation_receipts.dart +74 -7
- package/dart/lib/src/reception/dart_reception.dart +174 -17
- package/dart/lib/src/rows_watch.dart +390 -0
- package/dart/lib/src/runner/node-entry.mjs +7678 -2860
- package/dart/lib/src/runtime_v2_transport.dart +3 -0
- package/dart/lib/src/sync_status_projection.dart +94 -1
- package/dart/lib/src/sync_status_reducer.dart +5 -2
- package/dart/lib/src/test_harness.dart +107 -41
- package/dart/lib/src/testing/test_peer.dart +107 -99
- package/dart/lib/src/testing/test_peer_host.dart +305 -0
- package/dart/lib/src/typed_watch_registry.dart +26 -3
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/background_model_adoption_test.dart +1 -0
- package/dart/test/console_enrolment_test.dart +47 -0
- package/dart/test/custodian_warming_test.dart +105 -0
- package/dart/test/fixtures/offer_watch_order_peer.dart +74 -0
- package/dart/test/kernel_author_after_read_test.dart +6 -3
- package/dart/test/kernel_fence_test.dart +4 -1
- package/dart/test/model_adoption_test.dart +66 -0
- package/dart/test/nomos_trace_test.dart +91 -0
- package/dart/test/own_offer_watch_order_test.dart +90 -0
- package/dart/test/receiving_group_open_test.dart +4 -8
- package/dart/test/replica_readiness_test.dart +14 -4
- package/dart/test/rows_watch_test.dart +331 -0
- package/dart/test/runtime_projections_test.dart +41 -0
- package/dart/test/test_peer_host_selection_test.dart +49 -0
- package/dart_types/CHANGELOG.md +39 -0
- package/dart_types/lib/src/approved_model.dart +100 -6
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +1063 -3
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +89 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pb.dart +14 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +2272 -130
- package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pbenum.dart +65 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +118 -2
- package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pb.dart +278 -9
- package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pb.dart +7 -85
- package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pb.dart +16 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pb.dart +35 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +43 -2
- package/dart_types/pubspec.yaml +1 -1
- package/package.json +6 -2
- package/src/aggregate.ts +93 -0
- package/src/anonymous_value_interning.ts +205 -0
- package/src/baseline.ts +27 -0
- package/src/baseline_export.ts +432 -0
- package/src/baseline_import.ts +312 -0
- package/src/baseline_usda.ts +186 -0
- package/src/birth_key_rule.ts +472 -0
- package/src/births.ts +207 -24
- package/src/build_provenance.ts +17 -2
- package/src/business_birth.ts +50 -4
- package/src/check_package_main.ts +50 -6
- package/src/codegen_call_proto.ts +5 -18
- package/src/codegen_dart.ts +95 -3
- package/src/codegen_dart_client.ts +73 -15
- package/src/codegen_dart_facade.ts +58 -35
- package/src/codegen_dart_framework_calls.ts +32 -10
- package/src/codegen_dart_scenario.ts +7 -0
- package/src/codegen_proof.ts +5 -2
- package/src/codegen_result_proto.ts +8 -6
- package/src/codegen_result_usda.ts +4 -0
- package/src/codegen_ts.ts +135 -34
- package/src/compatibility_graph.ts +41 -4
- package/src/compatibility_usda.ts +11 -1
- package/src/compile_package_main.ts +272 -105
- package/src/composed_wire_identity.ts +24 -0
- package/src/directive.ts +6 -0
- package/src/domain_requirements.ts +75 -0
- package/src/engine_entry.ts +4 -48
- package/src/engine_javascript.ts +12 -6
- package/src/fields.ts +109 -0
- package/src/identity_usda.ts +77 -51
- package/src/index.ts +25 -1
- package/src/intent.ts +5 -0
- package/src/law_graph.ts +142 -1
- package/src/law_graph_usda.ts +25 -141
- package/src/law_layer_prims.ts +199 -0
- package/src/law_unit_selection.ts +45 -0
- package/src/law_unit_set_identity.ts +44 -0
- package/src/law_version.ts +107 -0
- package/src/package_config.ts +44 -0
- package/src/package_dependencies.ts +8 -0
- package/src/query/compile.ts +0 -0
- package/src/release_inspection.ts +24 -0
- package/src/release_publication.ts +298 -0
- package/src/value_object.ts +6 -3
- package/src/wire.ts +2 -0
- package/src/wire_paths.ts +17 -0
- 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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
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:
|
|
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
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
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}
|
|
2670
|
-
'
|
|
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].
|
|
2680
|
-
///
|
|
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
|
);
|