@githolon/dsl 0.119.11 → 0.119.12
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/dart/CHANGELOG.md +8 -0
- package/dart/lib/src/background_work.dart +11 -0
- package/dart/lib/src/peer_application_streams.dart +8 -3
- package/dart/lib/src/reception/dart_reception.dart +17 -4
- package/dart/lib/src/runner/node-entry.mjs +308 -103
- package/dart/lib/src/sync_status_projection.dart +42 -5
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/sync_reconnect_convergence_test.dart +246 -0
- package/dart/test/sync_status_preparation_report_test.dart +70 -0
- package/dart_types/CHANGELOG.md +8 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +98 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pb.dart +238 -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 +2 -2
- package/src/business_birth.ts +17 -7
- package/src/compile_package_main.ts +6 -3
|
@@ -2,6 +2,8 @@ import 'dart:async';
|
|
|
2
2
|
|
|
3
3
|
import 'package:nomos_types/nomos_kernel_protocol.dart' as nomos_protocol;
|
|
4
4
|
|
|
5
|
+
import 'nomos_trace.dart';
|
|
6
|
+
|
|
5
7
|
import 'application_model_adoption.dart' show NomosModelAdoptionProgress;
|
|
6
8
|
|
|
7
9
|
/// Live byte progress reported by the peer runtime for one custody transfer.
|
|
@@ -587,6 +589,10 @@ final class NmsSyncStatusProjection {
|
|
|
587
589
|
final Map<String, SyncStatus> _bySession = {};
|
|
588
590
|
final Map<String, StreamController<SyncStatus>> _sessionStreams = {};
|
|
589
591
|
SyncStatus _current = const SyncStatus();
|
|
592
|
+
// Keep the session result separately from readiness overlays. A final follow
|
|
593
|
+
// or preparation report can then release checking without another session
|
|
594
|
+
// event, and cannot erase a session's error, offline or pending state.
|
|
595
|
+
SyncStatus _applicationInput = const SyncStatus();
|
|
590
596
|
int? _followed;
|
|
591
597
|
int? _followsInSync;
|
|
592
598
|
int? _followsLive;
|
|
@@ -738,7 +744,7 @@ final class NmsSyncStatusProjection {
|
|
|
738
744
|
if (other != workspace)
|
|
739
745
|
pushSession(other, released(other, _bySession[other]!));
|
|
740
746
|
}
|
|
741
|
-
_pushApplication(released('',
|
|
747
|
+
_pushApplication(released('', _applicationInput));
|
|
742
748
|
}
|
|
743
749
|
|
|
744
750
|
void _republishRepairs(String workspace) {
|
|
@@ -759,7 +765,7 @@ final class NmsSyncStatusProjection {
|
|
|
759
765
|
for (final other in _bySession.keys.toList(growable: false)) {
|
|
760
766
|
if (other != workspace) pushSession(other, _bySession[other]!);
|
|
761
767
|
}
|
|
762
|
-
_pushApplication(
|
|
768
|
+
_pushApplication(_applicationInput);
|
|
763
769
|
}
|
|
764
770
|
|
|
765
771
|
Stream<SyncStatus> get applicationStream => _application.stream;
|
|
@@ -794,7 +800,7 @@ final class NmsSyncStatusProjection {
|
|
|
794
800
|
_followStatuses = List.unmodifiable(statuses);
|
|
795
801
|
_followReportAt = observedAt;
|
|
796
802
|
_pushApplication(
|
|
797
|
-
|
|
803
|
+
_applicationInput.copyWith(
|
|
798
804
|
followed: followed,
|
|
799
805
|
followsInSync: inSync,
|
|
800
806
|
followsLive: live,
|
|
@@ -810,7 +816,7 @@ final class NmsSyncStatusProjection {
|
|
|
810
816
|
_preparingWorkspaces = preparing;
|
|
811
817
|
_retryingWorkspaces = retrying;
|
|
812
818
|
_preparationActivity = activity;
|
|
813
|
-
_pushApplication(
|
|
819
|
+
_pushApplication(_applicationInput.copyWith(
|
|
814
820
|
preparingWorkspaces: preparing,
|
|
815
821
|
retryingWorkspaces: retrying,
|
|
816
822
|
preparationActivity: activity,
|
|
@@ -824,7 +830,7 @@ final class NmsSyncStatusProjection {
|
|
|
824
830
|
required SyncStatus Function(SyncStatus current) change,
|
|
825
831
|
}) {
|
|
826
832
|
if (session == null || session.isEmpty) {
|
|
827
|
-
_pushApplication(change(
|
|
833
|
+
_pushApplication(change(_applicationInput));
|
|
828
834
|
return;
|
|
829
835
|
}
|
|
830
836
|
pushSession(session, change(statusFor(session)));
|
|
@@ -850,6 +856,12 @@ final class NmsSyncStatusProjection {
|
|
|
850
856
|
final previous = _bySession[session];
|
|
851
857
|
if (previous != null && _sameStatus(previous, status)) return;
|
|
852
858
|
_bySession[session] = status;
|
|
859
|
+
if (NomosTrace.enabled) {
|
|
860
|
+
NomosTrace.emit('dart', 'sync.session',
|
|
861
|
+
'session=$session phase=${status.phase.name} live=${status.live} '
|
|
862
|
+
'ahead=${status.ahead} behind=${status.behind} '
|
|
863
|
+
'owed=${_unshared.containsKey(session)} copy=${status.localCopy.name}');
|
|
864
|
+
}
|
|
853
865
|
if (!_changes.isClosed) _changes.add(null);
|
|
854
866
|
final controller = _sessionStreams[session];
|
|
855
867
|
if (controller != null && !controller.isClosed) controller.add(status);
|
|
@@ -970,6 +982,7 @@ final class NmsSyncStatusProjection {
|
|
|
970
982
|
}
|
|
971
983
|
|
|
972
984
|
void _pushApplication(SyncStatus input) {
|
|
985
|
+
_applicationInput = input;
|
|
973
986
|
var status = input.copyWith(
|
|
974
987
|
repairingCopies: repairingCopies, unsharedWork: unsharedWork);
|
|
975
988
|
if (_unshared.isNotEmpty && status.phase == SyncPhase.idle) {
|
|
@@ -978,8 +991,32 @@ final class NmsSyncStatusProjection {
|
|
|
978
991
|
} else {
|
|
979
992
|
_heldBack.remove('');
|
|
980
993
|
}
|
|
994
|
+
// Bound interactions are only part of the application's inventory. A
|
|
995
|
+
// background follow may still be checking/preparing or have queued writes.
|
|
996
|
+
// Its complete live report can withhold currency, never manufacture it for
|
|
997
|
+
// an unknown or pending session. Keep the underlying session counts intact.
|
|
998
|
+
final followsPending = _followed != null && _followed! > 0 &&
|
|
999
|
+
(_followsInSync != _followed || _followsLive != _followed ||
|
|
1000
|
+
_followStatuses.length != _followed ||
|
|
1001
|
+
_followStatuses.any((follow) => !follow.current ||
|
|
1002
|
+
(follow.unsent ?? 0) > 0 || (follow.unacknowledged ?? 0) > 0));
|
|
1003
|
+
if (status.phase == SyncPhase.idle &&
|
|
1004
|
+
(followsPending || _preparingWorkspaces > 0 || _retryingWorkspaces > 0 ||
|
|
1005
|
+
_repairing.isNotEmpty)) {
|
|
1006
|
+
status = status.copyWith(phase: SyncPhase.checking, live: false);
|
|
1007
|
+
}
|
|
981
1008
|
if (_sameStatus(_current, status)) return;
|
|
982
1009
|
_current = status;
|
|
1010
|
+
if (NomosTrace.enabled) {
|
|
1011
|
+
final pending = _bySession.entries
|
|
1012
|
+
.where((entry) => entry.value.phase != SyncPhase.idle)
|
|
1013
|
+
.map((entry) => '${entry.key}:${entry.value.phase.name}')
|
|
1014
|
+
.join(',');
|
|
1015
|
+
NomosTrace.emit('dart', 'sync.aggregate',
|
|
1016
|
+
'phase=${status.phase.name} live=${status.live} '
|
|
1017
|
+
'followed=${status.followed} settled=${status.followsInSync} '
|
|
1018
|
+
'owed=${_unshared.length} pending=[$pending]');
|
|
1019
|
+
}
|
|
983
1020
|
if (!_application.isClosed) _application.add(status);
|
|
984
1021
|
}
|
|
985
1022
|
|
package/dart/pubspec.lock
CHANGED
package/dart/pubspec.yaml
CHANGED
|
@@ -7,7 +7,7 @@ repository: https://github.com/Captain-App/nomos2
|
|
|
7
7
|
description: >-
|
|
8
8
|
Nomos 2 Dart client — generated domain values, intent payloads and application
|
|
9
9
|
lifecycle types over the typed Nomos kernel protocol and USDA custody model.
|
|
10
|
-
version: 0.70.
|
|
10
|
+
version: 0.70.12
|
|
11
11
|
|
|
12
12
|
environment:
|
|
13
13
|
sdk: ^3.5.0
|
|
@@ -18,7 +18,7 @@ dependencies:
|
|
|
18
18
|
# The pure-data core (wire types, offer codec, NomosReads, schema validation, error
|
|
19
19
|
# info). nomos_client depends on and re-exports it, so existing imports are unchanged.
|
|
20
20
|
# For local monorepo dev, pubspec_overrides.yaml points this at ../dart_types.
|
|
21
|
-
nomos_types: ^0.15.
|
|
21
|
+
nomos_types: ^0.15.3
|
|
22
22
|
fixnum: ^1.1.1
|
|
23
23
|
json_schema: ^5.2.2
|
|
24
24
|
crypto: ^3.0.0
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import 'dart:async';
|
|
2
|
+
import 'package:fixnum/fixnum.dart';
|
|
3
|
+
import 'package:nomos_client/src/peer_application_streams.dart';
|
|
4
|
+
import 'package:nomos_client/src/background_work.dart';
|
|
5
|
+
import 'package:nomos_client/src/nms_generated_runtime.dart';
|
|
6
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as p;
|
|
7
|
+
import 'package:test/test.dart';
|
|
8
|
+
|
|
9
|
+
final class _Office
|
|
10
|
+
with NomosRuntimeV2KernelCalls
|
|
11
|
+
implements PeerOfficeConnection {
|
|
12
|
+
final events = StreamController<p.RuntimeEvent>.broadcast(sync: true);
|
|
13
|
+
int sequence = 0;
|
|
14
|
+
@override
|
|
15
|
+
Stream<p.RuntimeEvent> get runtimeEvents => events.stream;
|
|
16
|
+
@override
|
|
17
|
+
int nextRuntimeRequestId() => ++sequence;
|
|
18
|
+
@override
|
|
19
|
+
Future<p.RuntimeResponse> invokeRuntime(p.RuntimeRequest request) async {
|
|
20
|
+
if (request.hasBindSession()) {
|
|
21
|
+
return p.RuntimeResponse(
|
|
22
|
+
protocolVersion: 2,
|
|
23
|
+
requestId: request.requestId,
|
|
24
|
+
sessionBound: p.SessionBound(
|
|
25
|
+
workspace: request.bindSession.workspace,
|
|
26
|
+
session: request.bindSession.session,
|
|
27
|
+
credentialCapability: request.bindSession.session));
|
|
28
|
+
}
|
|
29
|
+
if (request.hasCloseRealm()) {
|
|
30
|
+
return p.RuntimeResponse(
|
|
31
|
+
protocolVersion: 2,
|
|
32
|
+
requestId: request.requestId,
|
|
33
|
+
realmClosed: p.RealmClosed());
|
|
34
|
+
}
|
|
35
|
+
fail('Unexpected command: ${request.whichCommand()}');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
void link(String workspace, bool open) => events.add(p.RuntimeEvent(
|
|
39
|
+
session: workspace,
|
|
40
|
+
linkChanged: p.LinkChanged(
|
|
41
|
+
status: open
|
|
42
|
+
? p.LinkStatus.LINK_STATUS_OPEN
|
|
43
|
+
: p.LinkStatus.LINK_STATUS_CLOSED)));
|
|
44
|
+
void round(String workspace,
|
|
45
|
+
{bool deferred = false,
|
|
46
|
+
bool progressed = false,
|
|
47
|
+
bool awaitingSeal = false}) =>
|
|
48
|
+
events.add(p.RuntimeEvent(
|
|
49
|
+
session: workspace,
|
|
50
|
+
syncCompleted: p.SyncCompleted(
|
|
51
|
+
result: p.SyncWorkspaceResponse(
|
|
52
|
+
workspace: workspace,
|
|
53
|
+
previousHead: 'head',
|
|
54
|
+
currentHead: 'head',
|
|
55
|
+
deferred: deferred ? Int64.ONE : Int64.ZERO,
|
|
56
|
+
directOfferDeferred: deferred,
|
|
57
|
+
stillAhead: deferred || awaitingSeal,
|
|
58
|
+
pulled: progressed,
|
|
59
|
+
mainAdvanced: progressed,
|
|
60
|
+
offerReceipt: awaitingSeal
|
|
61
|
+
? p.CustodyOfferReceipt(
|
|
62
|
+
admission: p.CustodyOfferAdmissionReceipt(
|
|
63
|
+
main: 'head', sealedMain: 'older'))
|
|
64
|
+
: null))));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
void main() {
|
|
68
|
+
late _Office office;
|
|
69
|
+
late DartReception reception;
|
|
70
|
+
setUp(() async {
|
|
71
|
+
office = _Office();
|
|
72
|
+
reception = DartReception(office);
|
|
73
|
+
for (final workspace in ['estate', 'catalogue']) {
|
|
74
|
+
await reception.bindSession(workspace: workspace, session: workspace);
|
|
75
|
+
office.link(workspace, true);
|
|
76
|
+
office.round(workspace);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
tearDown(() async {
|
|
80
|
+
await reception.dispose();
|
|
81
|
+
await office.events.close();
|
|
82
|
+
});
|
|
83
|
+
test(
|
|
84
|
+
'automatic projection refreshes preserve updates without claiming user priority',
|
|
85
|
+
() async {
|
|
86
|
+
final streams = NmsPeerApplicationStreamView(
|
|
87
|
+
session: reception.session('estate'),
|
|
88
|
+
statusProjection: (_) => throw UnimplementedError(),
|
|
89
|
+
);
|
|
90
|
+
final backgroundReads = <bool>[];
|
|
91
|
+
final subscription = streams.refreshOn(
|
|
92
|
+
read: () async {
|
|
93
|
+
await Future<void>.delayed(Duration.zero);
|
|
94
|
+
backgroundReads.add(nomosInBackground);
|
|
95
|
+
return backgroundReads.length;
|
|
96
|
+
},
|
|
97
|
+
changes: [streams.workspaceChanges],
|
|
98
|
+
).listen((_) {});
|
|
99
|
+
addTearDown(subscription.cancel);
|
|
100
|
+
await Future<void>.delayed(const Duration(milliseconds: 10));
|
|
101
|
+
expect(backgroundReads, [false],
|
|
102
|
+
reason: 'the initial visible read retains user priority');
|
|
103
|
+
office.round('catalogue');
|
|
104
|
+
await Future<void>.delayed(const Duration(milliseconds: 10));
|
|
105
|
+
expect(backgroundReads, [false, true],
|
|
106
|
+
reason: 'cross-workspace projections still refresh automatically');
|
|
107
|
+
office.round('estate', progressed: true);
|
|
108
|
+
await Future<void>.delayed(const Duration(milliseconds: 10));
|
|
109
|
+
expect(backgroundReads, [false, true, true]);
|
|
110
|
+
expect(nomosInBackground, isFalse,
|
|
111
|
+
reason: 'automatic work must not demote subsequent user actions');
|
|
112
|
+
});
|
|
113
|
+
test('a refresh coalesced behind the initial read runs in background',
|
|
114
|
+
() async {
|
|
115
|
+
final streams = NmsPeerApplicationStreamView(
|
|
116
|
+
session: reception.session('estate'),
|
|
117
|
+
statusProjection: (_) => throw UnimplementedError(),
|
|
118
|
+
);
|
|
119
|
+
final initial = Completer<void>();
|
|
120
|
+
final priorities = <bool>[];
|
|
121
|
+
final done = Completer<void>();
|
|
122
|
+
final subscription = streams.refreshOn(
|
|
123
|
+
read: () async {
|
|
124
|
+
priorities.add(nomosInBackground);
|
|
125
|
+
if (priorities.length == 1) await initial.future;
|
|
126
|
+
if (priorities.length == 2) done.complete();
|
|
127
|
+
return priorities.length;
|
|
128
|
+
},
|
|
129
|
+
changes: [streams.workspaceChanges],
|
|
130
|
+
).listen((_) {});
|
|
131
|
+
addTearDown(subscription.cancel);
|
|
132
|
+
await Future<void>.delayed(Duration.zero);
|
|
133
|
+
office.round('catalogue');
|
|
134
|
+
await Future<void>.delayed(Duration.zero);
|
|
135
|
+
initial.complete();
|
|
136
|
+
await done.future.timeout(const Duration(seconds: 1));
|
|
137
|
+
expect(priorities, [false, true]);
|
|
138
|
+
});
|
|
139
|
+
test(
|
|
140
|
+
'CAP-791: final reconnect result releases both session and aggregate debt',
|
|
141
|
+
() {
|
|
142
|
+
for (final workspace in ['estate', 'catalogue'])
|
|
143
|
+
office.link(workspace, false);
|
|
144
|
+
expect(reception.currentStatus.phase, SyncPhase.offline);
|
|
145
|
+
for (final workspace in ['estate', 'catalogue'])
|
|
146
|
+
office.link(workspace, true);
|
|
147
|
+
expect(reception.currentStatus.phase, SyncPhase.checking);
|
|
148
|
+
office.round('estate', deferred: true);
|
|
149
|
+
office.round('catalogue');
|
|
150
|
+
expect(reception.currentStatus.phase, SyncPhase.syncing);
|
|
151
|
+
office.round('estate', progressed: true);
|
|
152
|
+
expect(reception.session('estate').currentStatus.phase, SyncPhase.idle);
|
|
153
|
+
expect(reception.currentStatus.unsharedWork, isEmpty);
|
|
154
|
+
expect(reception.currentStatus.phase, SyncPhase.idle,
|
|
155
|
+
reason: 'no later event may be needed to refold released debt');
|
|
156
|
+
expect(reception.currentStatus.live, isTrue);
|
|
157
|
+
expect(reception.currentStatus.ahead, 0);
|
|
158
|
+
});
|
|
159
|
+
test('one recovered workspace cannot clear another workspace\'s debt', () {
|
|
160
|
+
office.round('estate', deferred: true);
|
|
161
|
+
office.round('catalogue', deferred: true);
|
|
162
|
+
office.round('estate', progressed: true);
|
|
163
|
+
expect(reception.currentStatus.phase, SyncPhase.syncing);
|
|
164
|
+
expect(reception.currentStatus.unsharedWork.keys, ['catalogue']);
|
|
165
|
+
office.round('catalogue', progressed: true);
|
|
166
|
+
expect(reception.currentStatus.phase, SyncPhase.idle);
|
|
167
|
+
});
|
|
168
|
+
test('no-progress result cannot clear a real deferral', () {
|
|
169
|
+
office.round('estate', deferred: true);
|
|
170
|
+
office.round('estate');
|
|
171
|
+
expect(reception.currentStatus.phase, SyncPhase.syncing);
|
|
172
|
+
expect(reception.currentStatus.unsharedWork.keys, ['estate']);
|
|
173
|
+
});
|
|
174
|
+
test('clearing debt cannot conceal an offline sibling or unsealed admission',
|
|
175
|
+
() {
|
|
176
|
+
office.round('estate', deferred: true);
|
|
177
|
+
office.link('catalogue', false);
|
|
178
|
+
office.round('estate', progressed: true);
|
|
179
|
+
expect(reception.currentStatus.phase, SyncPhase.offline);
|
|
180
|
+
office.link('catalogue', true);
|
|
181
|
+
office.round('catalogue', awaitingSeal: true);
|
|
182
|
+
expect(reception.currentStatus.phase, SyncPhase.awaitingRemoteDurability);
|
|
183
|
+
office.round('catalogue');
|
|
184
|
+
expect(reception.currentStatus.phase, SyncPhase.idle);
|
|
185
|
+
});
|
|
186
|
+
test('settled inventory does not override an unknown late-bound session',
|
|
187
|
+
() async {
|
|
188
|
+
await reception.bindSession(workspace: 'foreign', session: 'foreign');
|
|
189
|
+
office.events.add(p.RuntimeEvent(
|
|
190
|
+
followsSummary:
|
|
191
|
+
p.FollowsSummary(followed: 3, settled: 3, live: 3, workspaces: [
|
|
192
|
+
for (final name in ['estate', 'catalogue', 'foreign'])
|
|
193
|
+
p.FollowObservation(
|
|
194
|
+
workspace: name,
|
|
195
|
+
phase: p.FollowObservationPhase.FOLLOW_OBSERVATION_PHASE_SETTLED,
|
|
196
|
+
live: true)
|
|
197
|
+
])));
|
|
198
|
+
expect(reception.currentStatus.phase, SyncPhase.checking);
|
|
199
|
+
expect(reception.currentStatus.ahead, isNull);
|
|
200
|
+
// The peer's exact-head retained-follow result, not the tally, confirms it.
|
|
201
|
+
office.round('foreign');
|
|
202
|
+
office.link('foreign', true);
|
|
203
|
+
expect(reception.currentStatus.phase, SyncPhase.idle);
|
|
204
|
+
expect(reception.currentStatus.live, isTrue);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('a genuine failure remains visible when another workspace recovers', () {
|
|
208
|
+
office.round('estate', deferred: true);
|
|
209
|
+
office.events.add(p.RuntimeEvent(
|
|
210
|
+
session: 'catalogue',
|
|
211
|
+
syncCompleted: p.SyncCompleted(
|
|
212
|
+
failure: p.RuntimeFailure(
|
|
213
|
+
code: 'auth.denied', message: 'Access revoked'))));
|
|
214
|
+
office.round('estate', progressed: true);
|
|
215
|
+
expect(reception.currentStatus.phase, SyncPhase.error);
|
|
216
|
+
expect(reception.currentStatus.lastError, contains('Access revoked'));
|
|
217
|
+
});
|
|
218
|
+
test(
|
|
219
|
+
'application waits for unbound background follows and releases on their report',
|
|
220
|
+
() {
|
|
221
|
+
void follows(int settled) => office.events.add(p.RuntimeEvent(
|
|
222
|
+
followsSummary: p.FollowsSummary(
|
|
223
|
+
followed: 3,
|
|
224
|
+
settled: settled,
|
|
225
|
+
live: 3,
|
|
226
|
+
workspaces: [
|
|
227
|
+
for (final name in ['estate', 'catalogue', 'foreign'])
|
|
228
|
+
p.FollowObservation(
|
|
229
|
+
workspace: name,
|
|
230
|
+
live: true,
|
|
231
|
+
phase: name == 'foreign' && settled != 3
|
|
232
|
+
? p.FollowObservationPhase
|
|
233
|
+
.FOLLOW_OBSERVATION_PHASE_RUNNING
|
|
234
|
+
: p.FollowObservationPhase
|
|
235
|
+
.FOLLOW_OBSERVATION_PHASE_SETTLED)
|
|
236
|
+
])));
|
|
237
|
+
follows(2);
|
|
238
|
+
expect(reception.currentStatus.phase, SyncPhase.checking);
|
|
239
|
+
expect(reception.currentStatus.live, isFalse);
|
|
240
|
+
office.round('estate');
|
|
241
|
+
expect(reception.currentStatus.phase, SyncPhase.checking);
|
|
242
|
+
follows(3);
|
|
243
|
+
expect(reception.currentStatus.phase, SyncPhase.idle);
|
|
244
|
+
expect(reception.currentStatus.live, isTrue);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
@@ -26,4 +26,74 @@ void main() {
|
|
|
26
26
|
expect(projection.applicationStatus.preparationActivity, isNull);
|
|
27
27
|
await projection.close();
|
|
28
28
|
});
|
|
29
|
+
test('preparation keeps a settled application non-current until it finishes',
|
|
30
|
+
() async {
|
|
31
|
+
final projection = NmsSyncStatusProjection();
|
|
32
|
+
addTearDown(projection.close);
|
|
33
|
+
projection.pushSession(
|
|
34
|
+
'estate',
|
|
35
|
+
const SyncStatus(
|
|
36
|
+
phase: SyncPhase.idle, ahead: 0, behind: 0, live: true));
|
|
37
|
+
projection.foldApplication(['estate']);
|
|
38
|
+
projection.updatePreparationCounts(1, 1, activity: 'Preparing catalogue');
|
|
39
|
+
expect(projection.applicationStatus.phase, SyncPhase.checking);
|
|
40
|
+
expect(projection.applicationStatus.live, isFalse);
|
|
41
|
+
projection.foldApplication(['estate']);
|
|
42
|
+
expect(projection.applicationStatus.phase, SyncPhase.checking);
|
|
43
|
+
projection.updatePreparationCounts(0, 0);
|
|
44
|
+
expect(projection.applicationStatus.phase, SyncPhase.idle);
|
|
45
|
+
expect(projection.applicationStatus.live, isTrue);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test(
|
|
49
|
+
'completed follow/preparation reports never clear session-owned debt or failures',
|
|
50
|
+
() async {
|
|
51
|
+
for (final phase in [
|
|
52
|
+
SyncPhase.checking,
|
|
53
|
+
SyncPhase.syncing,
|
|
54
|
+
SyncPhase.awaitingRemoteDurability,
|
|
55
|
+
SyncPhase.offline,
|
|
56
|
+
SyncPhase.error
|
|
57
|
+
]) {
|
|
58
|
+
final projection = NmsSyncStatusProjection();
|
|
59
|
+
addTearDown(projection.close);
|
|
60
|
+
projection.pushSession('estate', SyncStatus(phase: phase));
|
|
61
|
+
projection.foldApplication(['estate']);
|
|
62
|
+
projection.updatePreparationCounts(1, 1);
|
|
63
|
+
projection.updateFollows(1, 1, live: 1, statuses: const [
|
|
64
|
+
WorkspaceFollowStatus(
|
|
65
|
+
workspace: 'estate',
|
|
66
|
+
phase: WorkspaceFollowPhase.settled,
|
|
67
|
+
live: true)
|
|
68
|
+
]);
|
|
69
|
+
projection.updatePreparationCounts(0, 0);
|
|
70
|
+
expect(projection.applicationStatus.phase, phase);
|
|
71
|
+
expect(projection.applicationStatus.ahead, isNull);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('queued stream writes and incomplete reports withhold currency',
|
|
76
|
+
() async {
|
|
77
|
+
final projection = NmsSyncStatusProjection();
|
|
78
|
+
addTearDown(projection.close);
|
|
79
|
+
projection.pushSession(
|
|
80
|
+
'estate',
|
|
81
|
+
const SyncStatus(
|
|
82
|
+
phase: SyncPhase.idle, ahead: 0, behind: 0, live: true));
|
|
83
|
+
projection.foldApplication(['estate']);
|
|
84
|
+
projection.updateFollows(1, 1, live: 1);
|
|
85
|
+
expect(projection.applicationStatus.phase, SyncPhase.checking);
|
|
86
|
+
for (final (unsent, unacknowledged) in [(1, 0), (0, 1), (0, 0)]) {
|
|
87
|
+
projection.updateFollows(1, 1, live: 1, statuses: [
|
|
88
|
+
WorkspaceFollowStatus(
|
|
89
|
+
workspace: 'estate',
|
|
90
|
+
phase: WorkspaceFollowPhase.settled,
|
|
91
|
+
live: true,
|
|
92
|
+
unsent: unsent,
|
|
93
|
+
unacknowledged: unacknowledged)
|
|
94
|
+
]);
|
|
95
|
+
expect(projection.applicationStatus.phase,
|
|
96
|
+
unsent + unacknowledged == 0 ? SyncPhase.idle : SyncPhase.checking);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
29
99
|
}
|
package/dart_types/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## 0.15.3
|
|
2
|
+
|
|
3
|
+
- Add a typed physical network-restored observation to wake transport retries without changing sync evidence.
|
|
4
|
+
|
|
5
|
+
- `HostHealth.ingress` (`KernelIngressSnapshot`) in the runtime v2 residency protocol: the kernel execution ingress a host reports (CAP-964).
|
|
6
|
+
|
|
7
|
+
- `KernelIngressSnapshot` reports posted and waiting requests per lane, the in-flight window, abandoned requests and deduplicated law-product imports (CAP-967).
|
|
8
|
+
|
|
1
9
|
## 0.15.2
|
|
2
10
|
|
|
3
11
|
Add the shared kernel protocol fields for owned pack import continuations, cancellation and deferred admission.
|
|
@@ -12270,6 +12270,104 @@ class PeerGitRefsFetched extends $pb.GeneratedMessage {
|
|
|
12270
12270
|
void clearDeferred() => $_clearField(3);
|
|
12271
12271
|
}
|
|
12272
12272
|
|
|
12273
|
+
/// Physical host observation: the network returned after being unavailable.
|
|
12274
|
+
/// Wake eligible retries once; this is never a sync or durability receipt.
|
|
12275
|
+
class NetworkRestoredRequest extends $pb.GeneratedMessage {
|
|
12276
|
+
factory NetworkRestoredRequest() => create();
|
|
12277
|
+
|
|
12278
|
+
NetworkRestoredRequest._();
|
|
12279
|
+
|
|
12280
|
+
factory NetworkRestoredRequest.fromBuffer($core.List<$core.int> data,
|
|
12281
|
+
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
12282
|
+
create()..mergeFromBuffer(data, registry);
|
|
12283
|
+
factory NetworkRestoredRequest.fromJson($core.String json,
|
|
12284
|
+
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
12285
|
+
create()..mergeFromJson(json, registry);
|
|
12286
|
+
|
|
12287
|
+
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
|
12288
|
+
_omitMessageNames ? '' : 'NetworkRestoredRequest',
|
|
12289
|
+
package:
|
|
12290
|
+
const $pb.PackageName(_omitMessageNames ? '' : 'nomos.runtime.v2'),
|
|
12291
|
+
createEmptyInstance: create)
|
|
12292
|
+
..hasRequiredFields = false;
|
|
12293
|
+
|
|
12294
|
+
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
12295
|
+
NetworkRestoredRequest clone() => deepCopy();
|
|
12296
|
+
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
12297
|
+
NetworkRestoredRequest copyWith(
|
|
12298
|
+
void Function(NetworkRestoredRequest) updates) =>
|
|
12299
|
+
super.copyWith((message) => updates(message as NetworkRestoredRequest))
|
|
12300
|
+
as NetworkRestoredRequest;
|
|
12301
|
+
|
|
12302
|
+
@$core.override
|
|
12303
|
+
$pb.BuilderInfo get info_ => _i;
|
|
12304
|
+
|
|
12305
|
+
@$core.pragma('dart2js:noInline')
|
|
12306
|
+
static NetworkRestoredRequest create() => NetworkRestoredRequest._();
|
|
12307
|
+
@$core.override
|
|
12308
|
+
NetworkRestoredRequest createEmptyInstance() => create();
|
|
12309
|
+
@$core.pragma('dart2js:noInline')
|
|
12310
|
+
static NetworkRestoredRequest getDefault() => _defaultInstance ??=
|
|
12311
|
+
$pb.GeneratedMessage.$_defaultFor<NetworkRestoredRequest>(create);
|
|
12312
|
+
static NetworkRestoredRequest? _defaultInstance;
|
|
12313
|
+
}
|
|
12314
|
+
|
|
12315
|
+
class NetworkRestored extends $pb.GeneratedMessage {
|
|
12316
|
+
factory NetworkRestored({
|
|
12317
|
+
$core.int? linksRetried,
|
|
12318
|
+
}) {
|
|
12319
|
+
final result = create();
|
|
12320
|
+
if (linksRetried != null) result.linksRetried = linksRetried;
|
|
12321
|
+
return result;
|
|
12322
|
+
}
|
|
12323
|
+
|
|
12324
|
+
NetworkRestored._();
|
|
12325
|
+
|
|
12326
|
+
factory NetworkRestored.fromBuffer($core.List<$core.int> data,
|
|
12327
|
+
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
12328
|
+
create()..mergeFromBuffer(data, registry);
|
|
12329
|
+
factory NetworkRestored.fromJson($core.String json,
|
|
12330
|
+
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
12331
|
+
create()..mergeFromJson(json, registry);
|
|
12332
|
+
|
|
12333
|
+
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
|
12334
|
+
_omitMessageNames ? '' : 'NetworkRestored',
|
|
12335
|
+
package:
|
|
12336
|
+
const $pb.PackageName(_omitMessageNames ? '' : 'nomos.runtime.v2'),
|
|
12337
|
+
createEmptyInstance: create)
|
|
12338
|
+
..aI(1, _omitFieldNames ? '' : 'linksRetried',
|
|
12339
|
+
fieldType: $pb.PbFieldType.OU3)
|
|
12340
|
+
..hasRequiredFields = false;
|
|
12341
|
+
|
|
12342
|
+
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
12343
|
+
NetworkRestored clone() => deepCopy();
|
|
12344
|
+
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
12345
|
+
NetworkRestored copyWith(void Function(NetworkRestored) updates) =>
|
|
12346
|
+
super.copyWith((message) => updates(message as NetworkRestored))
|
|
12347
|
+
as NetworkRestored;
|
|
12348
|
+
|
|
12349
|
+
@$core.override
|
|
12350
|
+
$pb.BuilderInfo get info_ => _i;
|
|
12351
|
+
|
|
12352
|
+
@$core.pragma('dart2js:noInline')
|
|
12353
|
+
static NetworkRestored create() => NetworkRestored._();
|
|
12354
|
+
@$core.override
|
|
12355
|
+
NetworkRestored createEmptyInstance() => create();
|
|
12356
|
+
@$core.pragma('dart2js:noInline')
|
|
12357
|
+
static NetworkRestored getDefault() => _defaultInstance ??=
|
|
12358
|
+
$pb.GeneratedMessage.$_defaultFor<NetworkRestored>(create);
|
|
12359
|
+
static NetworkRestored? _defaultInstance;
|
|
12360
|
+
|
|
12361
|
+
@$pb.TagNumber(1)
|
|
12362
|
+
$core.int get linksRetried => $_getIZ(0);
|
|
12363
|
+
@$pb.TagNumber(1)
|
|
12364
|
+
set linksRetried($core.int value) => $_setUnsignedInt32(0, value);
|
|
12365
|
+
@$pb.TagNumber(1)
|
|
12366
|
+
$core.bool hasLinksRetried() => $_has(0);
|
|
12367
|
+
@$pb.TagNumber(1)
|
|
12368
|
+
void clearLinksRetried() => $_clearField(1);
|
|
12369
|
+
}
|
|
12370
|
+
|
|
12273
12371
|
const $core.bool _omitFieldNames =
|
|
12274
12372
|
$core.bool.fromEnvironment('protobuf.omit_field_names');
|
|
12275
12373
|
const $core.bool _omitMessageNames =
|