@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
|
@@ -14,7 +14,6 @@ const _principal = 'user:ada';
|
|
|
14
14
|
// The office names the receiving group; Reception asks for it. A test scripts
|
|
15
15
|
// that answer like any other paper.
|
|
16
16
|
const _group = 'nomos-rg-scripted';
|
|
17
|
-
final _lawSha256 = '2' * 64;
|
|
18
17
|
|
|
19
18
|
final class _Office implements PeerOfficeConnection {
|
|
20
19
|
_Office({this.refuse});
|
|
@@ -88,7 +87,7 @@ Future<DartReception> _connected(_Office office) async {
|
|
|
88
87
|
|
|
89
88
|
void main() {
|
|
90
89
|
test(
|
|
91
|
-
'the session sends exactly one open paper:
|
|
90
|
+
'the session sends exactly one open paper: principal, session, no seed',
|
|
92
91
|
() async {
|
|
93
92
|
final office = _Office();
|
|
94
93
|
final reception = await _connected(office);
|
|
@@ -97,7 +96,6 @@ void main() {
|
|
|
97
96
|
application: 'acme-platform',
|
|
98
97
|
principal: _principal,
|
|
99
98
|
authToken: 'id-token',
|
|
100
|
-
contract: protocol.ApplicationBirthContract(lawSha256: _lawSha256),
|
|
101
99
|
verifyHistory: true,
|
|
102
100
|
);
|
|
103
101
|
|
|
@@ -109,10 +107,9 @@ void main() {
|
|
|
109
107
|
expect(paper.application, 'acme-platform');
|
|
110
108
|
expect(paper.principal, _principal);
|
|
111
109
|
expect(utf8.decode(paper.authToken), 'id-token');
|
|
112
|
-
expect(paper.
|
|
113
|
-
expect(paper.contract.toProto3Json(), {'lawSha256': _lawSha256},
|
|
110
|
+
expect((paper.toProto3Json()! as Map).keys, isNot(contains('contract')),
|
|
114
111
|
reason:
|
|
115
|
-
'
|
|
112
|
+
'no compiled law rides the paper: the Home takes what its platform serves (CAP-773)');
|
|
116
113
|
expect(paper.genesisSeed, isEmpty, reason: 'seeds are law');
|
|
117
114
|
expect(paper.verifyHistory, isTrue);
|
|
118
115
|
expect(papers.single.protocolVersion, 2);
|
|
@@ -137,8 +134,7 @@ void main() {
|
|
|
137
134
|
application: 'acme-platform',
|
|
138
135
|
principal: _principal,
|
|
139
136
|
authToken: 'id-token',
|
|
140
|
-
|
|
141
|
-
),
|
|
137
|
+
),
|
|
142
138
|
throwsA(isA<NomosRuntimeError>().having(
|
|
143
139
|
(e) => e.info.code,
|
|
144
140
|
'code',
|
|
@@ -128,15 +128,20 @@ void main() {
|
|
|
128
128
|
|
|
129
129
|
// The guard: every one-shot read reaches the kernel through one of these two
|
|
130
130
|
// request shapes. Each must sit inside nmsUntilReplicaReady, so a new read
|
|
131
|
-
// path cannot hand callers a "not ready yet" as an error.
|
|
131
|
+
// path cannot hand callers a "not ready yet" as an error. A site is where the
|
|
132
|
+
// request is BUILT (its requestId, or the historic request), never a trace line
|
|
133
|
+
// that merely names the same logical id (CAP-741's `call.send`), and "inside"
|
|
134
|
+
// means the readiness wait opens between the site's enclosing member and the
|
|
135
|
+
// site, however much tracing the closure carries.
|
|
132
136
|
test('every kernel query request is built inside nmsUntilReplicaReady', () {
|
|
133
137
|
final source = File(
|
|
134
138
|
'lib/src/reception/dart_reception.dart',
|
|
135
139
|
).readAsLinesSync();
|
|
140
|
+
final member = RegExp(r'^ [A-Za-z_]');
|
|
136
141
|
final requestSites = <int>[];
|
|
137
142
|
for (var i = 0; i < source.length; i++) {
|
|
138
143
|
final line = source[i];
|
|
139
|
-
if (line.contains(r":query:$") ||
|
|
144
|
+
if ((line.contains('requestId:') && line.contains(r":query:$")) ||
|
|
140
145
|
line.contains(
|
|
141
146
|
'historicQuery: nomos_protocol.HistoricQueryRequest(')) {
|
|
142
147
|
requestSites.add(i);
|
|
@@ -144,9 +149,14 @@ void main() {
|
|
|
144
149
|
}
|
|
145
150
|
expect(requestSites, hasLength(2), reason: 'query request sites moved');
|
|
146
151
|
for (final site in requestSites) {
|
|
147
|
-
|
|
152
|
+
var start = site;
|
|
153
|
+
while (start > 0 && !member.hasMatch(source[start])) {
|
|
154
|
+
start--;
|
|
155
|
+
}
|
|
148
156
|
expect(
|
|
149
|
-
|
|
157
|
+
source
|
|
158
|
+
.sublist(start, site)
|
|
159
|
+
.any((line) => line.contains('nmsUntilReplicaReady(')),
|
|
150
160
|
isTrue,
|
|
151
161
|
reason: 'dart_reception.dart:${site + 1} sends a kernel query outside '
|
|
152
162
|
'nmsUntilReplicaReady',
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import 'dart:async';
|
|
2
|
+
import 'dart:convert';
|
|
3
|
+
|
|
4
|
+
import 'package:nomos_client/src/generated_call.dart' show NmsGeneratedResult;
|
|
5
|
+
import 'package:nomos_client/src/rows_watch.dart';
|
|
6
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as nomos_protocol;
|
|
7
|
+
import 'package:nomos_types/src/proto/google/protobuf/any.pb.dart'
|
|
8
|
+
as google_protobuf;
|
|
9
|
+
import 'package:test/test.dart';
|
|
10
|
+
|
|
11
|
+
const _type = 'type.nomos.dev/nomos.operation.v1.QueryResult_q';
|
|
12
|
+
|
|
13
|
+
/// A generated row stand-in: value equality like the generated models, so
|
|
14
|
+
/// identity is observable separately.
|
|
15
|
+
final class Row {
|
|
16
|
+
Row(this.id, this.value);
|
|
17
|
+
final String id;
|
|
18
|
+
final int value;
|
|
19
|
+
@override
|
|
20
|
+
bool operator ==(Object other) =>
|
|
21
|
+
other is Row && other.id == id && other.value == value;
|
|
22
|
+
@override
|
|
23
|
+
int get hashCode => Object.hash(id, value);
|
|
24
|
+
@override
|
|
25
|
+
String toString() => '$id=$value';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// The test's "generated decoder": `id=value` pairs separated by `;`.
|
|
29
|
+
List<Row> _decode(NmsGeneratedResult result) {
|
|
30
|
+
expect(result.protobufType, _type);
|
|
31
|
+
final text = utf8.decode(result.protobufPayload);
|
|
32
|
+
if (text.isEmpty) return const [];
|
|
33
|
+
return [
|
|
34
|
+
for (final pair in text.split(';'))
|
|
35
|
+
Row(pair.split('=')[0], int.parse(pair.split('=')[1])),
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
google_protobuf.Any _rows(Map<String, int> rows) => google_protobuf.Any(
|
|
40
|
+
typeUrl: _type,
|
|
41
|
+
value:
|
|
42
|
+
utf8.encode(rows.entries.map((e) => '${e.key}=${e.value}').join(';')),
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
nomos_protocol.WatchChanged _full(String frontier, Map<String, int> rows) =>
|
|
46
|
+
nomos_protocol.WatchChanged(
|
|
47
|
+
subscriptionId: 'w',
|
|
48
|
+
applicationResult: _rows(rows),
|
|
49
|
+
resultFrontier: frontier,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
nomos_protocol.WatchChanged _delta(
|
|
53
|
+
String base,
|
|
54
|
+
String frontier,
|
|
55
|
+
Map<String, int> rows, [
|
|
56
|
+
List<String> removed = const [],
|
|
57
|
+
]) =>
|
|
58
|
+
nomos_protocol.WatchChanged(
|
|
59
|
+
subscriptionId: 'w',
|
|
60
|
+
applicationDelta: nomos_protocol.ApplicationResultDelta(
|
|
61
|
+
baseFrontier: base,
|
|
62
|
+
frontier: frontier,
|
|
63
|
+
changedRows: _rows(rows),
|
|
64
|
+
removedIds: removed,
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
NmsRowsMerge<Row> _merge() =>
|
|
69
|
+
NmsRowsMerge<Row>(decode: _decode, idOf: (row) => row.id);
|
|
70
|
+
|
|
71
|
+
void main() {
|
|
72
|
+
test(
|
|
73
|
+
'a delta patches the complete result in id order; untouched rows keep identity',
|
|
74
|
+
() {
|
|
75
|
+
final merge = _merge();
|
|
76
|
+
final first = merge.apply(_full('f1', {'b': 2, 'a': 1, 'c': 3}))!;
|
|
77
|
+
expect(first.map((row) => row.id), ['a', 'b', 'c']);
|
|
78
|
+
|
|
79
|
+
final second = merge.apply(_delta('f1', 'f2', {'b': 20, 'd': 4}, ['c']))!;
|
|
80
|
+
expect(second, [Row('a', 1), Row('b', 20), Row('d', 4)]);
|
|
81
|
+
expect(identical(second[0], first[0]), isTrue,
|
|
82
|
+
reason: 'row a was not touched');
|
|
83
|
+
|
|
84
|
+
// A changed row that decodes equal keeps the held object and emits nothing.
|
|
85
|
+
expect(merge.apply(_delta('f2', 'f3', {'a': 1})), isNull);
|
|
86
|
+
final third = merge.apply(_delta('f3', 'f4', {'e': 5}))!;
|
|
87
|
+
expect(identical(third[0], first[0]), isTrue);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test(
|
|
91
|
+
'a complete result equal to the held rows emits nothing and keeps identity',
|
|
92
|
+
() {
|
|
93
|
+
final merge = _merge();
|
|
94
|
+
final first = merge.apply(_full('f1', {'a': 1, 'b': 2}))!;
|
|
95
|
+
expect(merge.apply(_full('f2', {'a': 1, 'b': 2})), isNull);
|
|
96
|
+
final next = merge.apply(_full('f3', {'a': 1, 'b': 3}))!;
|
|
97
|
+
expect(identical(next[0], first[0]), isTrue);
|
|
98
|
+
expect(next[1], Row('b', 3));
|
|
99
|
+
// Fewer rows is a change even when every remaining row is equal.
|
|
100
|
+
expect(merge.apply(_full('f4', {'a': 1})), [Row('a', 1)]);
|
|
101
|
+
// The position moved with the complete result.
|
|
102
|
+
expect(merge.apply(_delta('f4', 'f5', {'z': 26})),
|
|
103
|
+
[Row('a', 1), Row('z', 26)]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('ids order by code point, as the kernel orders them', () {
|
|
107
|
+
final merge = _merge();
|
|
108
|
+
final ids = ['\u{1F600}', '~', 'a', 'B'];
|
|
109
|
+
final rows = merge.apply(_full('f1', {for (final id in ids) id: 0}))!;
|
|
110
|
+
expect(rows.map((row) => row.id), ['B', 'a', '~', '\u{1F600}']);
|
|
111
|
+
expect(compareCodePoints('~', '\u{1F600}'), lessThan(0));
|
|
112
|
+
expect('~'.compareTo('\u{1F600}'), greaterThan(0),
|
|
113
|
+
reason: 'UTF-16 order differs, which is why the merge does not use it');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('a delta from another position ends the watch with a typed error',
|
|
117
|
+
() async {
|
|
118
|
+
final source = StreamController<nomos_protocol.WatchChanged>();
|
|
119
|
+
final events = <Object>[];
|
|
120
|
+
final done = Completer<void>();
|
|
121
|
+
nmsMergeRows(source.stream, _merge()).listen(
|
|
122
|
+
events.add,
|
|
123
|
+
onError: events.add,
|
|
124
|
+
onDone: done.complete,
|
|
125
|
+
);
|
|
126
|
+
source.add(_full('f1', {'a': 1}));
|
|
127
|
+
source.add(_delta('elsewhere', 'f2', {'a': 2}));
|
|
128
|
+
await done.future;
|
|
129
|
+
expect(events, hasLength(2));
|
|
130
|
+
final mismatch = events[1] as NomosWatchPositionMismatch;
|
|
131
|
+
expect(mismatch.held, 'f1');
|
|
132
|
+
expect(mismatch.base, 'elsewhere');
|
|
133
|
+
expect(source.hasListener, isFalse,
|
|
134
|
+
reason: 'the peer subscription is released');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test(
|
|
138
|
+
'a delivered failure resets the rows; only a complete result restores them',
|
|
139
|
+
() async {
|
|
140
|
+
final source = StreamController<nomos_protocol.WatchChanged>();
|
|
141
|
+
final events = <Object>[];
|
|
142
|
+
final done = Completer<void>();
|
|
143
|
+
nmsMergeRows(source.stream, _merge()).listen(
|
|
144
|
+
events.add,
|
|
145
|
+
onError: events.add,
|
|
146
|
+
onDone: done.complete,
|
|
147
|
+
);
|
|
148
|
+
source.add(_full('f1', {'a': 1}));
|
|
149
|
+
source.addError(StateError('kernel unavailable'));
|
|
150
|
+
source.add(_full('f2', {'a': 1}));
|
|
151
|
+
source.add(_delta('f2', 'f3', {'b': 2}));
|
|
152
|
+
await source.close();
|
|
153
|
+
await done.future;
|
|
154
|
+
expect(events[0], [Row('a', 1)]);
|
|
155
|
+
expect(events[1], isA<StateError>());
|
|
156
|
+
expect(events[2], [Row('a', 1)],
|
|
157
|
+
reason: 'after a failure the complete rows are delivered again');
|
|
158
|
+
expect(events[3], [Row('a', 1), Row('b', 2)]);
|
|
159
|
+
|
|
160
|
+
// Without a complete result after the failure, a delta has no position.
|
|
161
|
+
final merge = _merge()..apply(_full('f1', {'a': 1}));
|
|
162
|
+
merge.reset();
|
|
163
|
+
expect(() => merge.apply(_delta('f1', 'f2', {})),
|
|
164
|
+
throwsA(isA<NomosWatchPositionMismatch>()));
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('each emission names the rows it changed and removed (CAP-751)', () {
|
|
168
|
+
final merge = _merge();
|
|
169
|
+
final first = merge.apply(_full('f1', {'a': 1, 'b': 2, 'c': 3}))!;
|
|
170
|
+
expect(first.complete, isTrue);
|
|
171
|
+
expect(first.changedIds, {'a', 'b', 'c'});
|
|
172
|
+
expect(first.removedIds, isEmpty);
|
|
173
|
+
|
|
174
|
+
final second = merge.apply(_delta('f1', 'f2', {'b': 20, 'd': 4}, ['c']))!;
|
|
175
|
+
expect(second.complete, isFalse);
|
|
176
|
+
expect(second.changedIds, {'b', 'd'});
|
|
177
|
+
expect(second.removedIds, {'c'});
|
|
178
|
+
for (final row in second) {
|
|
179
|
+
if (!second.changedIds.contains(row.id)) {
|
|
180
|
+
expect(first.contains(row), isTrue);
|
|
181
|
+
expect(identical(row, first.firstWhere((r) => r.id == row.id)), isTrue,
|
|
182
|
+
reason: 'a row outside changedIds is the object emitted before');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Removed and re-added in one delta is a replacement.
|
|
187
|
+
final third = merge.apply(_delta('f2', 'f3', {'a': 10}, ['a']))!;
|
|
188
|
+
expect(third.changedIds, {'a'});
|
|
189
|
+
expect(third.removedIds, isEmpty);
|
|
190
|
+
|
|
191
|
+
// A complete result reports exactly what differs from the held rows.
|
|
192
|
+
final fourth = merge.apply(_full('f4', {'a': 10, 'b': 20, 'e': 5}))!;
|
|
193
|
+
expect(fourth.complete, isTrue);
|
|
194
|
+
expect(fourth.changedIds, {'e'});
|
|
195
|
+
expect(fourth.removedIds, {'d'});
|
|
196
|
+
expect(() => (fourth as List<Row>).add(Row('x', 0)),
|
|
197
|
+
throwsUnsupportedError);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
group('NomosRowProjection', () {
|
|
201
|
+
test('projects only rows it has not seen, keeping earlier projections', () {
|
|
202
|
+
final merge = _merge();
|
|
203
|
+
var calls = 0;
|
|
204
|
+
final projection = NomosRowProjection<Row, String>((row) {
|
|
205
|
+
calls += 1;
|
|
206
|
+
return '${row.id}:${row.value}';
|
|
207
|
+
});
|
|
208
|
+
final first = projection.apply(merge.apply(_full('f1', {
|
|
209
|
+
for (var i = 0; i < 100; i += 1) 'r${i.toString().padLeft(3, '0')}': i,
|
|
210
|
+
}))!);
|
|
211
|
+
expect(calls, 100);
|
|
212
|
+
final second =
|
|
213
|
+
projection.apply(merge.apply(_delta('f1', 'f2', {'r050': -1}))!);
|
|
214
|
+
expect(calls, 101, reason: 'one changed row, one projection');
|
|
215
|
+
expect(second[50], 'r050:-1');
|
|
216
|
+
expect(identical(second[49], first[49]), isTrue);
|
|
217
|
+
expect(second.length, 100);
|
|
218
|
+
// The same rows again: the identical output.
|
|
219
|
+
final rows = merge.apply(_delta('f2', 'f3', {'r999': 9}))!;
|
|
220
|
+
final third = projection.apply(rows);
|
|
221
|
+
expect(identical(projection.apply(rows), third), isTrue);
|
|
222
|
+
expect(calls, 102);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test('an order is kept by merging new projections into the previous order',
|
|
226
|
+
() {
|
|
227
|
+
final merge = _merge();
|
|
228
|
+
var calls = 0;
|
|
229
|
+
int byValueThenId(Row a, Row b) {
|
|
230
|
+
final byValue = a.value.compareTo(b.value);
|
|
231
|
+
return byValue != 0 ? byValue : a.id.compareTo(b.id);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
final projection = NomosRowProjection<Row, Row>((row) {
|
|
235
|
+
calls += 1;
|
|
236
|
+
return row.value < 0 ? null : row; // negative values are left out
|
|
237
|
+
}, order: byValueThenId);
|
|
238
|
+
final values = {for (var i = 0; i < 50; i += 1) 'r$i': (i * 37) % 50};
|
|
239
|
+
final first = projection.apply(merge.apply(_full('f1', values))!);
|
|
240
|
+
expect(first, [...first]..sort(byValueThenId));
|
|
241
|
+
expect(first.length, 50);
|
|
242
|
+
|
|
243
|
+
var frontier = 1;
|
|
244
|
+
List<Row> step(Map<String, int> rows, [List<String> removed = const []]) {
|
|
245
|
+
final emitted =
|
|
246
|
+
merge.apply(_delta('f$frontier', 'f${frontier + 1}', rows, removed))!;
|
|
247
|
+
frontier += 1;
|
|
248
|
+
return projection.apply(emitted);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
var out = step({'r3': 1000, 'r7': -5, 'new': 25}, ['r9']);
|
|
252
|
+
final expected = [
|
|
253
|
+
for (final row in _merge().apply(_full('check', {
|
|
254
|
+
for (final e in values.entries)
|
|
255
|
+
if (e.key != 'r9') e.key: e.value,
|
|
256
|
+
'r3': 1000,
|
|
257
|
+
'r7': -5,
|
|
258
|
+
'new': 25,
|
|
259
|
+
}))!)
|
|
260
|
+
if (row.value >= 0) row,
|
|
261
|
+
]..sort(byValueThenId);
|
|
262
|
+
expect(out, expected);
|
|
263
|
+
expect(calls, 53, reason: 'only the three rows the delta carried');
|
|
264
|
+
|
|
265
|
+
// Leaving a row out anew changes the output; filtering it again does not.
|
|
266
|
+
out = step({'r7': -6});
|
|
267
|
+
expect(out, expected);
|
|
268
|
+
calls = 0;
|
|
269
|
+
projection.invalidate();
|
|
270
|
+
expect(projection.apply(out), expected);
|
|
271
|
+
expect(calls, expected.length);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('projectRows forwards only changed outputs, one projection per listener',
|
|
275
|
+
() async {
|
|
276
|
+
final source = StreamController<List<Row>>.broadcast(sync: true);
|
|
277
|
+
var calls = 0;
|
|
278
|
+
final projected = source.stream.projectRows<String>((row) {
|
|
279
|
+
calls += 1;
|
|
280
|
+
return row.value.isEven ? row.id : null;
|
|
281
|
+
});
|
|
282
|
+
final a = <List<String>>[];
|
|
283
|
+
final b = <List<String>>[];
|
|
284
|
+
final subA = projected.listen(a.add);
|
|
285
|
+
final subB = projected.listen(b.add);
|
|
286
|
+
final r1 = Row('a', 2);
|
|
287
|
+
final r2 = Row('b', 3);
|
|
288
|
+
source.add([r1, r2]);
|
|
289
|
+
source.add([r1, r2]); // identical rows: nothing new to forward
|
|
290
|
+
source.add([r1, Row('b', 4)]);
|
|
291
|
+
await pumpEventQueue();
|
|
292
|
+
expect(a, [
|
|
293
|
+
['a'],
|
|
294
|
+
['a', 'b'],
|
|
295
|
+
]);
|
|
296
|
+
expect(b, a);
|
|
297
|
+
expect(calls, 6, reason: 'two listeners, three distinct rows each');
|
|
298
|
+
await subA.cancel();
|
|
299
|
+
await subB.cancel();
|
|
300
|
+
await source.close();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test('a thousand rows and single-row deltas cost one projection per delta',
|
|
304
|
+
() async {
|
|
305
|
+
final source = StreamController<nomos_protocol.WatchChanged>();
|
|
306
|
+
var calls = 0;
|
|
307
|
+
final outputs = <List<String>>[];
|
|
308
|
+
final sub = nmsMergeRows(source.stream, _merge())
|
|
309
|
+
.projectRows<String>((row) {
|
|
310
|
+
calls += 1;
|
|
311
|
+
return 'p${row.value}';
|
|
312
|
+
}, order: (x, y) => x.compareTo(y)).listen(outputs.add);
|
|
313
|
+
source.add(_full('f0', {
|
|
314
|
+
for (var i = 0; i < 1000; i += 1) 'r${i.toString().padLeft(4, '0')}': i,
|
|
315
|
+
}));
|
|
316
|
+
for (var i = 0; i < 50; i += 1) {
|
|
317
|
+
source.add(_delta('f$i', 'f${i + 1}',
|
|
318
|
+
{'r${(i * 7).toString().padLeft(4, '0')}': 10000 + i}));
|
|
319
|
+
}
|
|
320
|
+
await pumpEventQueue();
|
|
321
|
+
expect(outputs, hasLength(51));
|
|
322
|
+
expect(calls, 1000 + 50);
|
|
323
|
+
final last = outputs.last;
|
|
324
|
+
expect(last, [...last]..sort());
|
|
325
|
+
expect(last.length, 1000);
|
|
326
|
+
await sub.cancel();
|
|
327
|
+
await source.close();
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
@@ -61,6 +61,47 @@ void main() {
|
|
|
61
61
|
expect(receipt.current.phase, NomosOperationPhase.converged);
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
+
test(
|
|
65
|
+
'a birth custody refused as already parented fails its creation receipt, '
|
|
66
|
+
'typed and terminal (CAP-804)', () async {
|
|
67
|
+
final projection = NmsOperationReceiptProjection();
|
|
68
|
+
final birth = projection.register('home-bob', 'intent-birth');
|
|
69
|
+
projection.rememberBirth('notebook-1', birth);
|
|
70
|
+
final other = projection.register('home-bob', 'intent-other');
|
|
71
|
+
projection.advance(
|
|
72
|
+
'home-bob',
|
|
73
|
+
protocol.SyncWorkspaceResponse(
|
|
74
|
+
pushFailed: true,
|
|
75
|
+
admittedIntentIds: const ['intent-other'],
|
|
76
|
+
birthOutcomes: [
|
|
77
|
+
protocol.BirthOutcome(
|
|
78
|
+
workspace: 'notebook-1',
|
|
79
|
+
childWorkspace: 'notebook-1',
|
|
80
|
+
parentWorkspace: 'home-bob',
|
|
81
|
+
status: protocol.BirthOutcomeStatus.BIRTH_OUTCOME_STATUS_REFUSED,
|
|
82
|
+
code: 'birth-child-already-parented',
|
|
83
|
+
refusal: protocol.KernelRefusal(
|
|
84
|
+
terminal: true,
|
|
85
|
+
message: "[birth-child-already-parented] workspace 'notebook-1' "
|
|
86
|
+
'already has a parent',
|
|
87
|
+
),
|
|
88
|
+
),
|
|
89
|
+
],
|
|
90
|
+
),
|
|
91
|
+
);
|
|
92
|
+
await expectLater(
|
|
93
|
+
birth.admitted,
|
|
94
|
+
throwsA(isA<NomosOperationRefused>()
|
|
95
|
+
.having((r) => r.code, 'code', 'birth-child-already-parented')
|
|
96
|
+
.having((r) => r.childWorkspace, 'child', 'notebook-1')),
|
|
97
|
+
);
|
|
98
|
+
await expectLater(birth.converged, throwsA(isA<NomosOperationRefused>()));
|
|
99
|
+
expect(birth.current.isRefused, isTrue);
|
|
100
|
+
expect(birth.current.phase, NomosOperationPhase.locallyAccepted);
|
|
101
|
+
// The parent's other write in the same round is unaffected.
|
|
102
|
+
await other.admitted;
|
|
103
|
+
});
|
|
104
|
+
|
|
64
105
|
test('typed watch registry only correlates runtime events and cancellation',
|
|
65
106
|
() async {
|
|
66
107
|
final registry = NmsTypedWatchRegistry();
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// The canonical test peer's host is DECLARED by NOMOS_TEST_PEER_HOST: unset/node is the Node
|
|
2
|
+
// peer, native is the Apple native host the caller registered. A value naming no host, or native
|
|
3
|
+
// selected without a registered host, is refused loudly — never quietly run on Node.
|
|
4
|
+
@TestOn('vm')
|
|
5
|
+
library;
|
|
6
|
+
|
|
7
|
+
import 'package:nomos_client/src/testing/test_peer_host.dart';
|
|
8
|
+
import 'package:test/test.dart';
|
|
9
|
+
|
|
10
|
+
void main() {
|
|
11
|
+
test('unset or node selects the Node peer', () {
|
|
12
|
+
expect(NmsTestPeerHostKind.selected(const {}), NmsTestPeerHostKind.node);
|
|
13
|
+
expect(
|
|
14
|
+
NmsTestPeerHostKind.selected(const {nmsTestPeerHostVariable: 'node'}),
|
|
15
|
+
NmsTestPeerHostKind.node,
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('native selects the Apple native host', () {
|
|
20
|
+
expect(
|
|
21
|
+
NmsTestPeerHostKind.selected(const {nmsTestPeerHostVariable: 'native'}),
|
|
22
|
+
NmsTestPeerHostKind.native,
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('a value naming no host is refused, not defaulted to Node', () {
|
|
27
|
+
expect(
|
|
28
|
+
() => NmsTestPeerHostKind.selected(
|
|
29
|
+
const {nmsTestPeerHostVariable: 'nativ'}),
|
|
30
|
+
throwsA(isA<StateError>().having(
|
|
31
|
+
(error) => error.message,
|
|
32
|
+
'message',
|
|
33
|
+
contains('names no test peer host'),
|
|
34
|
+
)),
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('native selected without a registered host is refused with the remedy',
|
|
39
|
+
() {
|
|
40
|
+
expect(
|
|
41
|
+
NmsTestPeerHosts.requireNative,
|
|
42
|
+
throwsA(isA<StateError>().having(
|
|
43
|
+
(error) => error.message,
|
|
44
|
+
'message',
|
|
45
|
+
contains('registerNomosNativeTestPeerHost()'),
|
|
46
|
+
)),
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
}
|
package/dart_types/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,42 @@
|
|
|
1
|
+
## 0.14.0
|
|
2
|
+
|
|
3
|
+
- Live stream frames for peer catch-up (CAP-826): `LiveOfferFrame` gains checkpoint, history, ref, pack and
|
|
4
|
+
refusal frames; `PeerStreamEffect`/`PeerStreamReceipt`; the runtime command `serveLiveStream`; and the kernel's
|
|
5
|
+
shallow pack request (`PackDeltaRequest.depth`, `below_head`, `also_heads`).
|
|
6
|
+
|
|
7
|
+
- `NomosDomainRequirement` and `NomosApprovedModel.domainRequirements` (CAP-666): per application domain key,
|
|
8
|
+
the exact law unit (`domain:<key>` of the unit set) and the generated consumer requirement identity a client
|
|
9
|
+
was compiled against. A workspace serving that unit for the key is current for the release whatever else its
|
|
10
|
+
unit set carries. `AdoptApplicationModelRequest` carries them as `domain_requirements` (field 7,
|
|
11
|
+
`DomainUnitRequirement`). Empty on clients generated before they existed: `unitSet` alone then decides.
|
|
12
|
+
- `KeyedBirthName.parent` (field 3) is reserved and unused (CAP-803): a business child is named by namespace and
|
|
13
|
+
key, fixed at birth. CAP-788's parent-scoped naming is withdrawn before release; one parent per node is the
|
|
14
|
+
kernel's `[birth-child-already-parented]` refusal.
|
|
15
|
+
- Carry the current generated kernel Protobuf contract: `FrontierTransitionRequest.candidateUnits`,
|
|
16
|
+
`InterfaceCompatibilityQuery.candidateUnits` and `InterfaceCompatibilityResponse.candidateHash` qualify a law
|
|
17
|
+
release without installing it (CAP-789).
|
|
18
|
+
`ApplyPackRequest.work_budget_bytes` / `continue_pending` and `ApplyPackResponse.import_pending` /
|
|
19
|
+
`work_done_bytes` import a pack in bounded steps (CAP-806). `LawIdentity`, `LawVersion`, `LawVersionClaim` and
|
|
20
|
+
`OfferAdmitted.law_version_warnings` (`LawVersionWarning`, `LawVersionOrder`) report an install that does not
|
|
21
|
+
move its law's claimed version forward (CAP-811). `PeerGitFetchEffect.alongside_refs` (`PeerGitRefsAlongside`)
|
|
22
|
+
and `PeerGitFetchResponse.alongside_refs` (`PeerGitRefsFetched`) carry a head-only mount's settled-letter outcome
|
|
23
|
+
refs beside main.
|
|
24
|
+
|
|
25
|
+
## 0.13.0
|
|
26
|
+
|
|
27
|
+
- `NomosApprovedModel` requires `unitSet`: the law a release approves is its unit set identity (the generated
|
|
28
|
+
`frameworkCalls.lawUnitSet`); `lawSha256` names the package closure. `AdoptApplicationModelRequest` carries
|
|
29
|
+
`unit_set`. `ApplicationBirthContract` is retired: a Home is born under the law its platform serves (CAP-773).
|
|
30
|
+
- Live link frames: `LiveLinkClosure` and `PeerSocketClosure` record how a live connection closed (who ended it,
|
|
31
|
+
code, reason, duration and what it carried) (CAP-683, CAP-684).
|
|
32
|
+
- Rows watches can receive only the rows that changed: `ApplicationResultDelta` and `FullResultReason` (CAP-731).
|
|
33
|
+
`OfferStateChange` names the aggregate types an offer changed, so a local offer re-reads only the watches it
|
|
34
|
+
can change (CAP-760).
|
|
35
|
+
- `CustodianWarming` reports a custodian warming up as its own posture, not an error retry (CAP-769).
|
|
36
|
+
- `ReadAccessPreparation` reports a device enrolled just in time for a granted read (CAP-740).
|
|
37
|
+
- Carry the current generated kernel and Runtime v2 Protobuf contracts, including per-offer phase timings
|
|
38
|
+
(CAP-756) and residency rows (CAP-754).
|
|
39
|
+
|
|
1
40
|
## 0.12.0
|
|
2
41
|
|
|
3
42
|
- Add the `LiveAdmission` live-link frame: the custodian's admission of a live batch, returned over the link (CAP-656).
|