@githolon/dsl 0.113.4 → 0.115.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/assets/operation-contract.wasm +0 -0
  2. package/compile_package.mjs +23 -3
  3. package/compile_proof.d.mts +7 -0
  4. package/compile_proof.mjs +38 -3
  5. package/dart/CHANGELOG.md +60 -0
  6. package/dart/lib/src/application_framework_calls.dart +16 -6
  7. package/dart/lib/src/console_enrolment.dart +7 -0
  8. package/dart/lib/src/console_http_io.dart +22 -15
  9. package/dart/lib/src/dev_cloud.dart +62 -0
  10. package/dart/lib/src/generated_call.dart +14 -3
  11. package/dart/lib/src/nms_generated_runtime.dart +7 -0
  12. package/dart/lib/src/node_runner_transport.dart +77 -8
  13. package/dart/lib/src/nomos_trace.dart +107 -0
  14. package/dart/lib/src/operation_receipts.dart +74 -7
  15. package/dart/lib/src/reception/dart_reception.dart +174 -17
  16. package/dart/lib/src/rows_watch.dart +390 -0
  17. package/dart/lib/src/runner/node-entry.mjs +7678 -2860
  18. package/dart/lib/src/runtime_v2_transport.dart +3 -0
  19. package/dart/lib/src/sync_status_projection.dart +94 -1
  20. package/dart/lib/src/sync_status_reducer.dart +5 -2
  21. package/dart/lib/src/test_harness.dart +107 -41
  22. package/dart/lib/src/testing/test_peer.dart +107 -99
  23. package/dart/lib/src/testing/test_peer_host.dart +305 -0
  24. package/dart/lib/src/typed_watch_registry.dart +26 -3
  25. package/dart/pubspec.lock +1 -1
  26. package/dart/pubspec.yaml +2 -2
  27. package/dart/test/background_model_adoption_test.dart +1 -0
  28. package/dart/test/console_enrolment_test.dart +47 -0
  29. package/dart/test/custodian_warming_test.dart +105 -0
  30. package/dart/test/fixtures/offer_watch_order_peer.dart +74 -0
  31. package/dart/test/kernel_author_after_read_test.dart +6 -3
  32. package/dart/test/kernel_fence_test.dart +4 -1
  33. package/dart/test/model_adoption_test.dart +66 -0
  34. package/dart/test/nomos_trace_test.dart +91 -0
  35. package/dart/test/own_offer_watch_order_test.dart +90 -0
  36. package/dart/test/receiving_group_open_test.dart +4 -8
  37. package/dart/test/replica_readiness_test.dart +14 -4
  38. package/dart/test/rows_watch_test.dart +331 -0
  39. package/dart/test/runtime_projections_test.dart +41 -0
  40. package/dart/test/test_peer_host_selection_test.dart +49 -0
  41. package/dart_types/CHANGELOG.md +39 -0
  42. package/dart_types/lib/src/approved_model.dart +100 -6
  43. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +1063 -3
  44. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +89 -0
  45. package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pb.dart +14 -0
  46. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +2272 -130
  47. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pbenum.dart +65 -0
  48. package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +118 -2
  49. package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pb.dart +278 -9
  50. package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pb.dart +7 -85
  51. package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pb.dart +16 -0
  52. package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pb.dart +35 -0
  53. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +43 -2
  54. package/dart_types/pubspec.yaml +1 -1
  55. package/package.json +6 -2
  56. package/src/aggregate.ts +93 -0
  57. package/src/anonymous_value_interning.ts +205 -0
  58. package/src/baseline.ts +27 -0
  59. package/src/baseline_export.ts +432 -0
  60. package/src/baseline_import.ts +312 -0
  61. package/src/baseline_usda.ts +186 -0
  62. package/src/birth_key_rule.ts +472 -0
  63. package/src/births.ts +207 -24
  64. package/src/build_provenance.ts +17 -2
  65. package/src/business_birth.ts +50 -4
  66. package/src/check_package_main.ts +50 -6
  67. package/src/codegen_call_proto.ts +5 -18
  68. package/src/codegen_dart.ts +95 -3
  69. package/src/codegen_dart_client.ts +73 -15
  70. package/src/codegen_dart_facade.ts +58 -35
  71. package/src/codegen_dart_framework_calls.ts +32 -10
  72. package/src/codegen_dart_scenario.ts +7 -0
  73. package/src/codegen_proof.ts +5 -2
  74. package/src/codegen_result_proto.ts +8 -6
  75. package/src/codegen_result_usda.ts +4 -0
  76. package/src/codegen_ts.ts +135 -34
  77. package/src/compatibility_graph.ts +41 -4
  78. package/src/compatibility_usda.ts +11 -1
  79. package/src/compile_package_main.ts +272 -105
  80. package/src/composed_wire_identity.ts +24 -0
  81. package/src/directive.ts +6 -0
  82. package/src/domain_requirements.ts +75 -0
  83. package/src/engine_entry.ts +4 -48
  84. package/src/engine_javascript.ts +12 -6
  85. package/src/fields.ts +109 -0
  86. package/src/identity_usda.ts +77 -51
  87. package/src/index.ts +25 -1
  88. package/src/intent.ts +5 -0
  89. package/src/law_graph.ts +142 -1
  90. package/src/law_graph_usda.ts +25 -141
  91. package/src/law_layer_prims.ts +199 -0
  92. package/src/law_unit_selection.ts +45 -0
  93. package/src/law_unit_set_identity.ts +44 -0
  94. package/src/law_version.ts +107 -0
  95. package/src/package_config.ts +44 -0
  96. package/src/package_dependencies.ts +8 -0
  97. package/src/query/compile.ts +0 -0
  98. package/src/release_inspection.ts +24 -0
  99. package/src/release_publication.ts +298 -0
  100. package/src/value_object.ts +6 -3
  101. package/src/wire.ts +2 -0
  102. package/src/wire_paths.ts +17 -0
  103. package/src/workspace_routing.ts +1 -1
@@ -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: contract, principal, session, no seed',
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.contract.lawSha256, _lawSha256);
113
- expect(paper.contract.toProto3Json(), {'lawSha256': _lawSha256},
110
+ expect((paper.toProto3Json()! as Map).keys, isNot(contains('contract')),
114
111
  reason:
115
- 'the contract is law-only: the platform controller is observed by the desk');
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
- contract: protocol.ApplicationBirthContract(lawSha256: _lawSha256),
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
- final window = source.sublist(site - 16 < 0 ? 0 : site - 16, site);
152
+ var start = site;
153
+ while (start > 0 && !member.hasMatch(source[start])) {
154
+ start--;
155
+ }
148
156
  expect(
149
- window.any((line) => line.contains('nmsUntilReplicaReady(')),
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
+ }
@@ -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).