@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
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.
|
|
10
|
+
version: 0.69.0
|
|
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.
|
|
21
|
+
nomos_types: ^0.14.0
|
|
22
22
|
fixnum: ^1.1.1
|
|
23
23
|
json_schema: ^5.2.2
|
|
24
24
|
crypto: ^3.0.0
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import 'dart:async';
|
|
1
2
|
import 'dart:convert';
|
|
2
3
|
import 'dart:io';
|
|
3
4
|
import 'dart:typed_data';
|
|
4
5
|
|
|
5
6
|
import 'package:fixnum/fixnum.dart';
|
|
6
7
|
import 'package:nomos_client/src/console_enrolment.dart';
|
|
8
|
+
import 'package:nomos_client/src/console_http_io.dart';
|
|
7
9
|
import 'package:nomos_client/src/runtime_v2_transport.dart';
|
|
8
10
|
import 'package:nomos_peer_host/nomos_peer_host.dart'
|
|
9
11
|
show NomosDeviceIdentityStore, NomosProviderCredentialStore;
|
|
@@ -224,4 +226,49 @@ void main() {
|
|
|
224
226
|
final receipt = await noKey.performConsoleEnrolment(effect);
|
|
225
227
|
expect(receipt.failure.message, contains('no usable identity'));
|
|
226
228
|
});
|
|
229
|
+
|
|
230
|
+
test(
|
|
231
|
+
'a console that does not answer is a bounded, retryable transport failure (CAP-807)',
|
|
232
|
+
() async {
|
|
233
|
+
// The default transport gives up on its own deadline instead of holding the write.
|
|
234
|
+
final silent = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
|
|
235
|
+
final held = <Socket>[];
|
|
236
|
+
silent.listen(held.add);
|
|
237
|
+
try {
|
|
238
|
+
final started = DateTime.now();
|
|
239
|
+
await expectLater(
|
|
240
|
+
nomosDefaultConsolePost(
|
|
241
|
+
Uri.parse('http://127.0.0.1:${silent.port}/v1/enrol'), '{}',
|
|
242
|
+
timeout: const Duration(milliseconds: 300)),
|
|
243
|
+
throwsA(isA<TimeoutException>()));
|
|
244
|
+
expect(DateTime.now().difference(started).inSeconds, lessThan(5));
|
|
245
|
+
} finally {
|
|
246
|
+
for (final socket in held) {
|
|
247
|
+
socket.destroy();
|
|
248
|
+
}
|
|
249
|
+
await silent.close();
|
|
250
|
+
}
|
|
251
|
+
// The shell reports it typed, as a failure Peer Office asks again after.
|
|
252
|
+
final seed = Uint8List(32)..[0] = 7;
|
|
253
|
+
final platform = _Platform([])
|
|
254
|
+
..registerDeviceIdentityStore(_Store({'device': seed}))
|
|
255
|
+
..registerCredentialProvider(
|
|
256
|
+
capability: 'home',
|
|
257
|
+
cloud: 'x',
|
|
258
|
+
signIn: ({String? deviceChallenge}) async => 'token')
|
|
259
|
+
..registerConsolePost(
|
|
260
|
+
(url, text) async => throw TimeoutException('slow'));
|
|
261
|
+
final receipt = await platform.performConsoleEnrolment(
|
|
262
|
+
p.PeerConsoleEnrolmentEffect(
|
|
263
|
+
effectId: 'e',
|
|
264
|
+
deviceIdentity: 'device',
|
|
265
|
+
capability: 'home',
|
|
266
|
+
console: 'http://console.test',
|
|
267
|
+
tenant: 't',
|
|
268
|
+
scope: 'user',
|
|
269
|
+
workspaces: ['estate-a']));
|
|
270
|
+
expect(receipt.hasFailure(), isTrue);
|
|
271
|
+
expect(receipt.failure.message,
|
|
272
|
+
'the console did not answer within 20 s; asking again');
|
|
273
|
+
});
|
|
227
274
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import 'package:fixnum/fixnum.dart' as fixnum;
|
|
2
|
+
import 'package:nomos_client/src/sync_status_projection.dart';
|
|
3
|
+
import 'package:nomos_client/src/sync_status_reducer.dart';
|
|
4
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
|
|
5
|
+
import 'package:test/test.dart';
|
|
6
|
+
|
|
7
|
+
void main() {
|
|
8
|
+
final since = DateTime.utc(2026, 10, 4, 9);
|
|
9
|
+
protocol.FollowObservation backoff({protocol.CustodianWarming? warming}) =>
|
|
10
|
+
protocol.FollowObservation(
|
|
11
|
+
workspace: 'estate',
|
|
12
|
+
phase: protocol.FollowObservationPhase.FOLLOW_OBSERVATION_PHASE_BACKOFF,
|
|
13
|
+
detail: 'the custodian is opening this workspace from verified custody',
|
|
14
|
+
retryAtUnixMillis: fixnum.Int64(
|
|
15
|
+
since.add(const Duration(seconds: 3)).millisecondsSinceEpoch),
|
|
16
|
+
attempt: 1,
|
|
17
|
+
custodianWarming: warming,
|
|
18
|
+
);
|
|
19
|
+
protocol.CustodianWarming warming({bool stalled = false}) =>
|
|
20
|
+
protocol.CustodianWarming(
|
|
21
|
+
sinceUnixMillis: fixnum.Int64(since.millisecondsSinceEpoch),
|
|
22
|
+
expectedReadyUnixMillis: fixnum.Int64(
|
|
23
|
+
since.add(const Duration(seconds: 3)).millisecondsSinceEpoch),
|
|
24
|
+
stalledAfterUnixMillis: fixnum.Int64(
|
|
25
|
+
since.add(const Duration(seconds: 15)).millisecondsSinceEpoch),
|
|
26
|
+
stalled: stalled,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
test('an opening answer is a warming posture with its expected-ready time',
|
|
30
|
+
() {
|
|
31
|
+
final status =
|
|
32
|
+
WorkspaceFollowStatus.fromRuntime(backoff(warming: warming()));
|
|
33
|
+
final posture = status.custodianWarming!;
|
|
34
|
+
expect(posture.since, since.toLocal());
|
|
35
|
+
expect(posture.expectedReadyAt,
|
|
36
|
+
since.add(const Duration(seconds: 3)).toLocal());
|
|
37
|
+
final now = since.add(const Duration(seconds: 1));
|
|
38
|
+
expect(posture.remaining(now), const Duration(seconds: 2));
|
|
39
|
+
expect(posture.elapsed(now), const Duration(seconds: 1));
|
|
40
|
+
expect(status.isWarming(now), isTrue);
|
|
41
|
+
expect(status.isWarmingStalled(now), isFalse);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('an ordinary error retry carries no warming posture', () {
|
|
45
|
+
final status = WorkspaceFollowStatus.fromRuntime(backoff());
|
|
46
|
+
expect(status.phase, WorkspaceFollowPhase.backoff);
|
|
47
|
+
expect(status.custodianWarming, isNull);
|
|
48
|
+
expect(status.isWarming(since), isFalse);
|
|
49
|
+
expect(status.isWarmingStalled(since), isFalse);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('warming past its bound is stalled, by the peer report or by the clock',
|
|
53
|
+
() {
|
|
54
|
+
final reported = WorkspaceFollowStatus.fromRuntime(
|
|
55
|
+
backoff(warming: warming(stalled: true)));
|
|
56
|
+
expect(reported.isWarmingStalled(since), isTrue);
|
|
57
|
+
expect(reported.isWarming(since), isFalse);
|
|
58
|
+
|
|
59
|
+
final quiet =
|
|
60
|
+
WorkspaceFollowStatus.fromRuntime(backoff(warming: warming()));
|
|
61
|
+
final late = since.add(const Duration(seconds: 16));
|
|
62
|
+
expect(quiet.isWarmingStalled(late), isTrue,
|
|
63
|
+
reason: 'a quiet report still escalates once the bound has passed');
|
|
64
|
+
expect(quiet.isWarming(late), isFalse);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('a changed warming posture is a changed follow report', () {
|
|
68
|
+
final projection = NmsSyncStatusProjection();
|
|
69
|
+
final seen = <SyncStatus>[];
|
|
70
|
+
final subscription = projection.applicationStream.listen(seen.add);
|
|
71
|
+
addTearDown(subscription.cancel);
|
|
72
|
+
final plain = WorkspaceFollowStatus.fromRuntime(backoff());
|
|
73
|
+
final warm = WorkspaceFollowStatus.fromRuntime(backoff(warming: warming()));
|
|
74
|
+
projection.updateFollows(1, 0, statuses: [plain]);
|
|
75
|
+
projection.updateFollows(1, 0, statuses: [warm]);
|
|
76
|
+
final status = projection.applicationStatus;
|
|
77
|
+
expect(status.followStatuses.single.custodianWarming, isNotNull);
|
|
78
|
+
final now = since.add(const Duration(seconds: 1));
|
|
79
|
+
expect(status.warmingFollows(now).map((f) => f.workspace), ['estate']);
|
|
80
|
+
expect(status.stalledWarmingFollows(now), isEmpty);
|
|
81
|
+
expect(status.stalledWarmingFollows(since.add(const Duration(minutes: 1))),
|
|
82
|
+
hasLength(1));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('a warming sync round is still owed: neither settled nor an error', () {
|
|
86
|
+
final at = DateTime.utc(2026, 10, 4);
|
|
87
|
+
for (final offering in [true, false]) {
|
|
88
|
+
final status = reduceSyncCompletion(
|
|
89
|
+
const SyncStatus(),
|
|
90
|
+
protocol.SyncCompleted(
|
|
91
|
+
result: protocol.SyncWorkspaceResponse(
|
|
92
|
+
workspace: 'estate',
|
|
93
|
+
currentHead: 'head',
|
|
94
|
+
directOfferDeferred: offering,
|
|
95
|
+
custodianWarmingRetryAfterMillis: 3000,
|
|
96
|
+
),
|
|
97
|
+
),
|
|
98
|
+
at,
|
|
99
|
+
);
|
|
100
|
+
expect(status.phase, SyncPhase.syncing);
|
|
101
|
+
expect(status.lastError, isNull);
|
|
102
|
+
expect(status.lastSyncedAt, isNull);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// A stand-in Node runner for own_offer_watch_order_test.dart. It speaks the runner's stdio framing
|
|
2
|
+
// (`runtime-v2:` requests in, `runtime-v2-event:` events and `runtime-v2:` replies out) and answers a
|
|
3
|
+
// local offer exactly as the Peer Office does since CAP-755: the change of the watch the offer
|
|
4
|
+
// affected first, then the offer's reply, both in ONE stdout write so they share a chunk.
|
|
5
|
+
import 'dart:convert';
|
|
6
|
+
import 'dart:io';
|
|
7
|
+
|
|
8
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as p;
|
|
9
|
+
import 'package:nomos_types/src/proto/google/protobuf/any.pb.dart' as protobuf;
|
|
10
|
+
|
|
11
|
+
const _rows = 'type.nomos.dev/estate.CanvasRowsResult';
|
|
12
|
+
|
|
13
|
+
void main() {
|
|
14
|
+
final subscriptions = <String>[];
|
|
15
|
+
var version = 0;
|
|
16
|
+
String position() => 'f$version';
|
|
17
|
+
stdin
|
|
18
|
+
.transform(utf8.decoder)
|
|
19
|
+
.transform(const LineSplitter())
|
|
20
|
+
.listen((line) {
|
|
21
|
+
if (!line.startsWith('runtime-v2:')) return;
|
|
22
|
+
final request = p.RuntimeRequest.fromBuffer(
|
|
23
|
+
base64Decode(line.substring('runtime-v2:'.length)));
|
|
24
|
+
final response =
|
|
25
|
+
p.RuntimeResponse(protocolVersion: 2, requestId: request.requestId);
|
|
26
|
+
final out = StringBuffer();
|
|
27
|
+
void event(p.WatchChanged changed) => out.writeln('runtime-v2-event:'
|
|
28
|
+
'${base64Encode(p.RuntimeEvent(watchChanged: changed).writeToBuffer())}');
|
|
29
|
+
if (request.hasWatch()) {
|
|
30
|
+
final id = request.watch.subscriptionId;
|
|
31
|
+
subscriptions.add(id);
|
|
32
|
+
event(p.WatchChanged(
|
|
33
|
+
subscriptionId: id,
|
|
34
|
+
applicationResult:
|
|
35
|
+
protobuf.Any(typeUrl: _rows, value: utf8.encode('row=v$version')),
|
|
36
|
+
resultFrontier: position(),
|
|
37
|
+
));
|
|
38
|
+
response.watchStarted = p.WatchStarted(subscriptionId: id);
|
|
39
|
+
} else if (request.hasUnwatch()) {
|
|
40
|
+
subscriptions.remove(request.unwatch.subscriptionId);
|
|
41
|
+
response.watchStopped =
|
|
42
|
+
p.WatchStopped(subscriptionId: request.unwatch.subscriptionId);
|
|
43
|
+
} else if (request.hasCloseRealm()) {
|
|
44
|
+
response.realmClosed = p.RealmClosed();
|
|
45
|
+
} else if (request.hasKernelCall() &&
|
|
46
|
+
request.kernelCall.request.hasOffer()) {
|
|
47
|
+
final base = position();
|
|
48
|
+
version += 1;
|
|
49
|
+
for (final id in subscriptions) {
|
|
50
|
+
event(p.WatchChanged(
|
|
51
|
+
subscriptionId: id,
|
|
52
|
+
applicationDelta: p.ApplicationResultDelta(
|
|
53
|
+
baseFrontier: base,
|
|
54
|
+
frontier: position(),
|
|
55
|
+
changedRows: protobuf.Any(
|
|
56
|
+
typeUrl: _rows, value: utf8.encode('row=v$version')),
|
|
57
|
+
),
|
|
58
|
+
));
|
|
59
|
+
}
|
|
60
|
+
response.kernelCall = p.CallResponse(
|
|
61
|
+
protocolVersion: 1,
|
|
62
|
+
requestId: request.kernelCall.request.requestId,
|
|
63
|
+
offer: p.OfferResponse(
|
|
64
|
+
admitted: p.OfferAdmitted(head: 'head-$version')),
|
|
65
|
+
);
|
|
66
|
+
} else {
|
|
67
|
+
response.failure = p.RuntimeFailure(
|
|
68
|
+
code: 'test.unexpected',
|
|
69
|
+
message: 'unexpected ${request.whichCommand().name}');
|
|
70
|
+
}
|
|
71
|
+
out.writeln('runtime-v2:${base64Encode(response.writeToBuffer())}');
|
|
72
|
+
stdout.write(out.toString());
|
|
73
|
+
});
|
|
74
|
+
}
|
|
@@ -23,6 +23,8 @@ import 'dart:math';
|
|
|
23
23
|
import 'package:fixnum/fixnum.dart';
|
|
24
24
|
|
|
25
25
|
import 'package:nomos_client/src/testing/test_peer.dart';
|
|
26
|
+
import 'package:nomos_client/src/testing/test_peer_host.dart'
|
|
27
|
+
show NmsTestPeerHostKind;
|
|
26
28
|
import 'package:nomos_types/nomos_kernel_protocol.dart' as nomos_protocol;
|
|
27
29
|
import 'package:nomos_types/src/proto/google/protobuf/any.pb.dart'
|
|
28
30
|
as google_protobuf;
|
|
@@ -62,6 +64,7 @@ void main() {
|
|
|
62
64
|
fail('NOMOS_CANDIDATE_RUNTIME must point at a staged runtime');
|
|
63
65
|
}
|
|
64
66
|
final peer = await NmsTestPeer.start(
|
|
67
|
+
kind: NmsTestPeerHostKind.node,
|
|
65
68
|
entry: await _testEntry(),
|
|
66
69
|
runtimeDirectory: runtime,
|
|
67
70
|
resolveFrom: Directory.current.path,
|
|
@@ -69,7 +72,7 @@ void main() {
|
|
|
69
72
|
);
|
|
70
73
|
addTearDown(peer.dispose);
|
|
71
74
|
|
|
72
|
-
final custodyDir = Directory('${peer.
|
|
75
|
+
final custodyDir = Directory('${peer.custodyDirectory.path}/custody');
|
|
73
76
|
await custodyDir.create(recursive: true);
|
|
74
77
|
for (final ws in [_workspace, _home]) {
|
|
75
78
|
if (!Directory('$custodyRoot/holon-$ws.git').existsSync()) {
|
|
@@ -92,7 +95,7 @@ void main() {
|
|
|
92
95
|
|
|
93
96
|
var sequence = 0;
|
|
94
97
|
Future<nomos_protocol.CallResponse> describe(String session) =>
|
|
95
|
-
peer.
|
|
98
|
+
peer.host.kernelCalls.callKernel(
|
|
96
99
|
session: session,
|
|
97
100
|
request: nomos_protocol.CallRequest(
|
|
98
101
|
protocolVersion: 1,
|
|
@@ -115,7 +118,7 @@ void main() {
|
|
|
115
118
|
String typeUrl) async {
|
|
116
119
|
final nomos_protocol.CallResponse offered;
|
|
117
120
|
try {
|
|
118
|
-
offered = await peer.
|
|
121
|
+
offered = await peer.host.kernelCalls.callKernel(
|
|
119
122
|
session: session,
|
|
120
123
|
request: nomos_protocol.CallRequest(
|
|
121
124
|
protocolVersion: 1,
|
|
@@ -20,6 +20,8 @@ import 'dart:io';
|
|
|
20
20
|
import 'dart:isolate';
|
|
21
21
|
|
|
22
22
|
import 'package:nomos_client/src/testing/test_peer.dart';
|
|
23
|
+
import 'package:nomos_client/src/testing/test_peer_host.dart'
|
|
24
|
+
show NmsTestPeerHostKind;
|
|
23
25
|
import 'package:test/test.dart';
|
|
24
26
|
|
|
25
27
|
Future<String> _testEntry() async {
|
|
@@ -68,6 +70,7 @@ void main() {
|
|
|
68
70
|
.first;
|
|
69
71
|
|
|
70
72
|
final peer = await NmsTestPeer.start(
|
|
73
|
+
kind: NmsTestPeerHostKind.node,
|
|
71
74
|
entry: await _testEntry(),
|
|
72
75
|
runtimeDirectory: runtime,
|
|
73
76
|
resolveFrom: Directory.current.path,
|
|
@@ -75,7 +78,7 @@ void main() {
|
|
|
75
78
|
);
|
|
76
79
|
addTearDown(peer.dispose);
|
|
77
80
|
|
|
78
|
-
final custodyDir = Directory('${peer.
|
|
81
|
+
final custodyDir = Directory('${peer.custodyDirectory.path}/custody');
|
|
79
82
|
await custodyDir.create(recursive: true);
|
|
80
83
|
final answers = <String, Object?>{};
|
|
81
84
|
for (final workspace in workspaces) {
|
|
@@ -11,9 +11,13 @@ import 'package:nomos_client/src/application_model_adoption.dart';
|
|
|
11
11
|
import 'package:nomos_client/src/runtime_v2_transport.dart';
|
|
12
12
|
import 'package:nomos_client/src/sync_status_projection.dart';
|
|
13
13
|
import 'package:nomos_types/nomos_kernel_protocol.dart' as p;
|
|
14
|
+
import 'package:nomos_types/nomos_types.dart' show NomosDomainRequirement;
|
|
14
15
|
import 'package:test/test.dart';
|
|
15
16
|
|
|
16
17
|
const lawSha = '5efef545fdadae764e29ba678833073e0245b1b76e315d3a5fe71c6a65240a45';
|
|
18
|
+
/// The approved law (CAP-773): the unit set identity of the compiled units.
|
|
19
|
+
const unitSet =
|
|
20
|
+
'a40efb425bc4de94dacaadfdff69633d300c8fb25a2701e78d592f9fab5e75cb';
|
|
17
21
|
const otherSha =
|
|
18
22
|
'48f3b1810000000000000000000000000000000000000000000000000000abcd';
|
|
19
23
|
|
|
@@ -30,6 +34,7 @@ class _Platform
|
|
|
30
34
|
|
|
31
35
|
NomosApprovedModel model({List<String> keys = const ['home']}) =>
|
|
32
36
|
NomosApprovedModel(
|
|
37
|
+
unitSet: unitSet,
|
|
33
38
|
lawSha256: lawSha,
|
|
34
39
|
reason: '2026-09-11: retire the value fields; decided by Jack',
|
|
35
40
|
domainKeys: keys,
|
|
@@ -45,6 +50,15 @@ void main() {
|
|
|
45
50
|
expect(model().domainKeys, ['home']);
|
|
46
51
|
expect(
|
|
47
52
|
() => NomosApprovedModel(
|
|
53
|
+
unitSet: 'not-a-hash',
|
|
54
|
+
lawSha256: lawSha,
|
|
55
|
+
reason: 'r',
|
|
56
|
+
domainKeys: const ['home'],
|
|
57
|
+
loadPackage: () async => Uint8List(0)),
|
|
58
|
+
throwsArgumentError);
|
|
59
|
+
expect(
|
|
60
|
+
() => NomosApprovedModel(
|
|
61
|
+
unitSet: unitSet,
|
|
48
62
|
lawSha256: 'not-a-hash',
|
|
49
63
|
reason: 'r',
|
|
50
64
|
domainKeys: const ['home'],
|
|
@@ -52,6 +66,7 @@ void main() {
|
|
|
52
66
|
throwsArgumentError);
|
|
53
67
|
expect(
|
|
54
68
|
() => NomosApprovedModel(
|
|
69
|
+
unitSet: unitSet,
|
|
55
70
|
lawSha256: lawSha,
|
|
56
71
|
reason: ' ',
|
|
57
72
|
domainKeys: const ['home'],
|
|
@@ -59,6 +74,7 @@ void main() {
|
|
|
59
74
|
throwsArgumentError);
|
|
60
75
|
expect(
|
|
61
76
|
() => NomosApprovedModel(
|
|
77
|
+
unitSet: unitSet,
|
|
62
78
|
lawSha256: lawSha,
|
|
63
79
|
reason: 'r',
|
|
64
80
|
domainKeys: const [],
|
|
@@ -66,6 +82,7 @@ void main() {
|
|
|
66
82
|
throwsArgumentError);
|
|
67
83
|
expect(
|
|
68
84
|
() => NomosApprovedModel(
|
|
85
|
+
unitSet: unitSet,
|
|
69
86
|
lawSha256: lawSha,
|
|
70
87
|
reason: 'r',
|
|
71
88
|
domainKeys: const ['home', ' '],
|
|
@@ -73,6 +90,54 @@ void main() {
|
|
|
73
90
|
throwsArgumentError);
|
|
74
91
|
});
|
|
75
92
|
|
|
93
|
+
test('per-domain requirements (CAP-666) are optional, keyed and checked',
|
|
94
|
+
() {
|
|
95
|
+
expect(model().domainRequirements, isEmpty);
|
|
96
|
+
final pinned = NomosApprovedModel(
|
|
97
|
+
unitSet: unitSet,
|
|
98
|
+
lawSha256: lawSha,
|
|
99
|
+
reason: 'r',
|
|
100
|
+
domainKeys: const ['home'],
|
|
101
|
+
loadPackage: () async => Uint8List(0),
|
|
102
|
+
domainRequirements: {
|
|
103
|
+
'home': NomosDomainRequirement(
|
|
104
|
+
domainKey: 'home',
|
|
105
|
+
unitSha256: '3' * 64,
|
|
106
|
+
requirementSha256: '4' * 64),
|
|
107
|
+
});
|
|
108
|
+
expect(pinned.domainRequirements['home']!.unitSha256, '3' * 64);
|
|
109
|
+
expect(() => pinned.domainRequirements['x'] = pinned.domainRequirements['home']!,
|
|
110
|
+
throwsUnsupportedError);
|
|
111
|
+
expect(
|
|
112
|
+
() => NomosApprovedModel(
|
|
113
|
+
unitSet: unitSet,
|
|
114
|
+
lawSha256: lawSha,
|
|
115
|
+
reason: 'r',
|
|
116
|
+
domainKeys: const ['home'],
|
|
117
|
+
loadPackage: () async => Uint8List(0),
|
|
118
|
+
domainRequirements: {
|
|
119
|
+
'estate': NomosDomainRequirement(
|
|
120
|
+
domainKey: 'home',
|
|
121
|
+
unitSha256: '3' * 64,
|
|
122
|
+
requirementSha256: '4' * 64),
|
|
123
|
+
}),
|
|
124
|
+
throwsArgumentError);
|
|
125
|
+
expect(
|
|
126
|
+
() => NomosApprovedModel(
|
|
127
|
+
unitSet: unitSet,
|
|
128
|
+
lawSha256: lawSha,
|
|
129
|
+
reason: 'r',
|
|
130
|
+
domainKeys: const ['home'],
|
|
131
|
+
loadPackage: () async => Uint8List(0),
|
|
132
|
+
domainRequirements: {
|
|
133
|
+
'home': NomosDomainRequirement(
|
|
134
|
+
domainKey: 'home',
|
|
135
|
+
unitSha256: 'nope',
|
|
136
|
+
requirementSha256: '4' * 64),
|
|
137
|
+
}),
|
|
138
|
+
throwsArgumentError);
|
|
139
|
+
});
|
|
140
|
+
|
|
76
141
|
test('its package source answers only the identity it was released with',
|
|
77
142
|
() async {
|
|
78
143
|
final source = NmsApprovedModelLawPackageSource(model());
|
|
@@ -109,6 +174,7 @@ void main() {
|
|
|
109
174
|
final broken = _Platform()
|
|
110
175
|
..registerLawPackageSource(NmsApprovedModelLawPackageSource(
|
|
111
176
|
NomosApprovedModel(
|
|
177
|
+
unitSet: unitSet,
|
|
112
178
|
lawSha256: lawSha,
|
|
113
179
|
reason: 'r',
|
|
114
180
|
domainKeys: const ['home'],
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import 'dart:async';
|
|
2
|
+
|
|
3
|
+
import 'package:fixnum/fixnum.dart' as fixnum;
|
|
4
|
+
import 'package:nomos_client/src/nomos_trace.dart';
|
|
5
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as nomos_protocol;
|
|
6
|
+
import 'package:nomos_types/src/proto/google/protobuf/any.pb.dart'
|
|
7
|
+
as google_protobuf;
|
|
8
|
+
import 'package:test/test.dart';
|
|
9
|
+
|
|
10
|
+
void main() {
|
|
11
|
+
test('the timeline is off unless NOMOS_TRACE is defined', () {
|
|
12
|
+
expect(NomosTrace.enabled, isFalse);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('requests are described by operation kind, never by argument values',
|
|
16
|
+
() {
|
|
17
|
+
final offer = nomos_protocol.RuntimeRequest(
|
|
18
|
+
protocolVersion: 2,
|
|
19
|
+
requestId: fixnum.Int64(7),
|
|
20
|
+
kernelCall: nomos_protocol.KernelCallRequest(
|
|
21
|
+
session: 'estate-1',
|
|
22
|
+
request: nomos_protocol.CallRequest(
|
|
23
|
+
requestId: 'estate-1:offer:3',
|
|
24
|
+
offer: nomos_protocol.OfferRequest(
|
|
25
|
+
call: nomos_protocol.DirectiveCall(
|
|
26
|
+
domain: 'estate',
|
|
27
|
+
directive: 'placeAssetOnDrawing',
|
|
28
|
+
operation: google_protobuf.Any(
|
|
29
|
+
typeUrl: 'type.nomos.dev/secret',
|
|
30
|
+
value: [1, 2, 3],
|
|
31
|
+
),
|
|
32
|
+
),
|
|
33
|
+
),
|
|
34
|
+
),
|
|
35
|
+
),
|
|
36
|
+
);
|
|
37
|
+
expect(NomosTrace.describeRequest(offer),
|
|
38
|
+
'kernelCall/offer:estate/placeAssetOnDrawing');
|
|
39
|
+
final watch = nomos_protocol.RuntimeRequest(
|
|
40
|
+
watch: nomos_protocol.WatchRequest(
|
|
41
|
+
query: nomos_protocol.QueryRequest(
|
|
42
|
+
rows: nomos_protocol.RowsQuery(id: 'assetsByAssetId'),
|
|
43
|
+
),
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
expect(
|
|
47
|
+
NomosTrace.describeRequest(watch), 'watch/query:rows:assetsByAssetId');
|
|
48
|
+
expect(
|
|
49
|
+
NomosTrace.describeCall(nomos_protocol.CallRequest(
|
|
50
|
+
query: nomos_protocol.QueryRequest(
|
|
51
|
+
byType: nomos_protocol.ByTypeQuery(aggregateType: 'estate.Asset'),
|
|
52
|
+
),
|
|
53
|
+
)),
|
|
54
|
+
'query:byType:estate.Asset',
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('a sampled context carries no ids, so span ids stay request-derived',
|
|
59
|
+
() {
|
|
60
|
+
final context = NomosTrace.sampledContext();
|
|
61
|
+
expect(context.sampled, isTrue);
|
|
62
|
+
expect(context.traceId, isEmpty);
|
|
63
|
+
expect(context.spanId, isEmpty);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('listener timing leaves the stream untouched while tracing is off',
|
|
67
|
+
() async {
|
|
68
|
+
final source = Stream<int>.fromIterable([1, 2, 3]);
|
|
69
|
+
expect(identical(NomosTrace.timedListener(source, 'q=x'), source), isTrue);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('listener timing forwards every event and error when on', () async {
|
|
73
|
+
NomosTrace.enabled = true;
|
|
74
|
+
addTearDown(() => NomosTrace.enabled = false);
|
|
75
|
+
final controller = StreamController<int>();
|
|
76
|
+
final seen = <Object>[];
|
|
77
|
+
final done = Completer<void>();
|
|
78
|
+
NomosTrace.timedListener(controller.stream, 'q=x').listen(
|
|
79
|
+
seen.add,
|
|
80
|
+
onError: (Object error) => seen.add('error:$error'),
|
|
81
|
+
onDone: done.complete,
|
|
82
|
+
);
|
|
83
|
+
controller
|
|
84
|
+
..add(1)
|
|
85
|
+
..addError('boom')
|
|
86
|
+
..add(2);
|
|
87
|
+
await controller.close();
|
|
88
|
+
await done.future;
|
|
89
|
+
expect(seen, [1, 'error:boom', 2]);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// CAP-755: a local offer's call returns only after its affected watches have delivered.
|
|
2
|
+
//
|
|
3
|
+
// The Peer Office queues the change of every watch a local offer affected before the offer's reply,
|
|
4
|
+
// and the runner writes both to stdout in order, often in one chunk. Their Dart listeners run as
|
|
5
|
+
// microtasks, so without care the awaiting caller could resume first and see its own offer missing
|
|
6
|
+
// from the watch. This drives the real NodeRunnerTransport and DartReception rows watch against a
|
|
7
|
+
// stand-in runner that writes the change and the reply in one stdout write.
|
|
8
|
+
@TestOn('vm')
|
|
9
|
+
library;
|
|
10
|
+
|
|
11
|
+
import 'dart:async';
|
|
12
|
+
import 'dart:convert';
|
|
13
|
+
import 'dart:io';
|
|
14
|
+
import 'dart:isolate';
|
|
15
|
+
import 'dart:typed_data';
|
|
16
|
+
|
|
17
|
+
import 'package:nomos_client/src/nms_generated_runtime.dart';
|
|
18
|
+
import 'package:nomos_client/src/node_runner_transport.dart';
|
|
19
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as p;
|
|
20
|
+
import 'package:test/test.dart';
|
|
21
|
+
|
|
22
|
+
final _query = NmsInternalGeneratedQueries.create(
|
|
23
|
+
domain: 'estate',
|
|
24
|
+
query: 'canvasRows',
|
|
25
|
+
protobufType: 'type.nomos.dev/estate.CanvasRowsArgs',
|
|
26
|
+
protobufPayload: Uint8List.fromList([8, 1]),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
Future<String> _fixture() async {
|
|
30
|
+
final library = await Isolate.resolvePackageUri(
|
|
31
|
+
Uri.parse('package:nomos_client/src/node_runner_transport.dart'));
|
|
32
|
+
// dsl/dart/lib/src/node_runner_transport.dart -> dsl/dart/test/fixtures/...
|
|
33
|
+
return library!
|
|
34
|
+
.resolve('../../test/fixtures/offer_watch_order_peer.dart')
|
|
35
|
+
.toFilePath();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
void main() {
|
|
39
|
+
test('each local offer\'s watch change reaches its listener before the offer\'s call returns',
|
|
40
|
+
() async {
|
|
41
|
+
final transport = await NodeRunnerTransport.start(
|
|
42
|
+
entry: await _fixture(),
|
|
43
|
+
node: Platform.resolvedExecutable,
|
|
44
|
+
);
|
|
45
|
+
final reception = DartReception(transport);
|
|
46
|
+
addTearDown(() async {
|
|
47
|
+
await reception.dispose();
|
|
48
|
+
await transport.dispose();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
final timeline = <String>[];
|
|
52
|
+
final first = Completer<void>();
|
|
53
|
+
final watch = reception
|
|
54
|
+
.session('estate')
|
|
55
|
+
.nmsWatchRows<String>(
|
|
56
|
+
_query,
|
|
57
|
+
decode: (result) => [utf8.decode(result.protobufPayload)],
|
|
58
|
+
idOf: (row) => row.split('=').first,
|
|
59
|
+
)
|
|
60
|
+
.listen((rows) {
|
|
61
|
+
timeline.add('watch ${rows.single}');
|
|
62
|
+
if (!first.isCompleted) first.complete();
|
|
63
|
+
});
|
|
64
|
+
addTearDown(watch.cancel);
|
|
65
|
+
await first.future.timeout(const Duration(seconds: 30));
|
|
66
|
+
|
|
67
|
+
// A group of sequential offers, as an application issues them: each awaits the last.
|
|
68
|
+
for (var offer = 1; offer <= 20; offer += 1) {
|
|
69
|
+
final reply = await transport.callKernel(
|
|
70
|
+
session: 'estate',
|
|
71
|
+
request: p.CallRequest(
|
|
72
|
+
protocolVersion: 1,
|
|
73
|
+
requestId: 'offer-$offer',
|
|
74
|
+
offer: p.OfferRequest(),
|
|
75
|
+
),
|
|
76
|
+
credentialUse: p.CredentialUse.CREDENTIAL_USE_SESSION,
|
|
77
|
+
);
|
|
78
|
+
expect(reply.offer.hasAdmitted(), isTrue);
|
|
79
|
+
timeline.add('returned $offer');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
expect(timeline, [
|
|
83
|
+
'watch row=v0',
|
|
84
|
+
for (var offer = 1; offer <= 20; offer += 1) ...[
|
|
85
|
+
'watch row=v$offer',
|
|
86
|
+
'returned $offer',
|
|
87
|
+
],
|
|
88
|
+
], reason: 'every offer\'s own change is delivered once, before its call returns');
|
|
89
|
+
}, timeout: const Timeout(Duration(minutes: 2)));
|
|
90
|
+
}
|