@githolon/dsl 0.108.0 → 0.108.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dart/CHANGELOG.md +5 -0
- package/dart/lib/src/application_framework_calls.dart +299 -0
- package/dart/lib/src/generated_call.dart +96 -0
- package/dart/lib/src/nms_generated_runtime.dart +29 -0
- package/dart/lib/src/node_runner_transport.dart +77 -1
- package/dart/lib/src/result_usda.dart +487 -0
- package/dart/lib/src/runner/node-entry.mjs +6401 -2102
- package/dart/lib/src/runner/offline-install.mjs +1538 -163
- package/dart/lib/src/runtime_v2_gateway.dart +54 -0
- package/dart/lib/src/runtime_v2_transport.dart +117 -0
- package/dart/lib/src/session_bound.dart +7 -2
- package/dart/lib/src/test_harness.dart +193 -112
- package/dart/lib/src/webview_bridge.dart +2120 -569
- package/dart/pubspec.yaml +2 -2
- package/dart/test/acquire_session_credentials_test.dart +1 -1
- package/dart/test/acquire_session_test.dart +68 -14
- package/dart/test/adv_realm_interference_test.dart +6 -4
- package/dart/test/cluster_births_test.dart +11 -8
- package/dart/test/cluster_local_autosync_test.dart +1 -1
- package/dart/test/cluster_scale_test.dart +3 -2
- package/dart/test/dlq_session_surface_test.dart +52 -35
- package/dart/test/first_app_gauntlet_test.dart +1 -1
- package/dart/test/local_sync_inert_test.dart +23 -26
- package/dart/test/offer_authentication_proof_test.dart +193 -0
- package/dart/test/result_usda_test.dart +49 -0
- package/dart/test/session_bound_test.dart +8 -11
- package/dart/test/stale_bundle_test.dart +18 -3
- package/dart/test/test_harness_test.dart +14 -27
- package/dart/test/webview_bridge_test.dart +504 -83
- package/dart_types/CHANGELOG.md +7 -0
- package/dart_types/lib/nomos_kernel_protocol.dart +3 -2
- package/dart_types/lib/src/custody.dart +64 -19
- package/dart_types/lib/src/local_work_rejected.dart +30 -45
- package/dart_types/lib/src/proto/nomos/custody/v1/snapshot.pb.dart +169 -0
- package/dart_types/lib/src/proto/nomos/custody/v1/snapshot.pbenum.dart +11 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +2021 -234
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +175 -8
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +8664 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pbenum.dart +369 -0
- package/dart_types/lib/src/surfaced.dart +30 -25
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/surfaced_test.dart +2 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +5736 -2047
- package/src/authoring.ts +3 -2
- package/src/births.ts +21 -0
- package/src/build_package.ts +30 -0
- package/src/business_birth.ts +39 -35
- package/src/codegen_call_usda.ts +1111 -0
- package/src/codegen_dart.ts +207 -282
- package/src/codegen_dart_application.ts +195 -252
- package/src/codegen_dart_client.ts +1978 -593
- package/src/codegen_dart_framework_calls.ts +291 -0
- package/src/codegen_dart_scenario.ts +7 -0
- package/src/codegen_proof.ts +3 -4
- package/src/codegen_provider_dart.ts +21 -7
- package/src/codegen_query_usda.ts +214 -0
- package/src/codegen_result_usda.ts +685 -0
- package/src/codegen_ts.ts +3945 -1111
- package/src/compile_package_main.ts +360 -32
- package/src/directive.ts +19 -4
- package/src/engine_entry.ts +45 -4
- package/src/framework/application_home.ts +2 -0
- package/src/framework/auth_provider_law.ts +262 -20
- package/src/framework/home.ts +32 -28
- package/src/framework/owned_custody.ts +19 -11
- package/src/framework/owned_custody_value.ts +51 -0
- package/src/framework/relations.ts +57 -21
- package/src/framework/signing_identity.ts +34 -0
- package/src/framework/workspaces.ts +31 -9
- package/src/framework_call_definitions.ts +53 -0
- package/src/framework_read_definitions.ts +45 -0
- package/src/identity_frontier.ts +58 -0
- package/src/index.ts +20 -1
- package/src/intent.ts +216 -16
- package/src/law_check.ts +19 -1
- package/src/law_graph.ts +57 -8
- package/src/law_graph_usda.ts +73 -5
- package/src/law_value_type.ts +106 -19
- package/src/read.ts +47 -0
- package/src/read_projection_namespace.ts +7 -0
- package/src/shape_compat.ts +38 -39
- package/src/stable_ids.ts +37 -2
- package/src/stable_ids_for_module.ts +64 -4
- package/src/test_call_usda.ts +83 -0
- package/src/wire.ts +36 -2
- package/src/wire_encode.ts +45 -1
- package/src/workspace_sharing.ts +27 -10
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbjson.dart +0 -6096
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbserver.dart +0 -50
- package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pbjson.dart +0 -178
- package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pb.dart +0 -828
- package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbenum.dart +0 -465
- package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbjson.dart +0 -573
- package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbserver.dart +0 -50
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
@internal
|
|
2
|
+
library;
|
|
3
|
+
|
|
4
|
+
import 'package:fixnum/fixnum.dart' as fixnum;
|
|
5
|
+
import 'package:meta/meta.dart' show internal;
|
|
6
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as nomos_protocol;
|
|
7
|
+
import 'package:nomos_types/nomos_types.dart'
|
|
8
|
+
show NomosErrorInfo, nomosErrorFromInfo;
|
|
9
|
+
|
|
10
|
+
import 'runtime_v2_transport.dart';
|
|
11
|
+
|
|
12
|
+
/// Correlates generated Runtime v2 frames and translates only the generated
|
|
13
|
+
/// failure arm. It knows nothing about sessions, domains, lifecycle policy, or
|
|
14
|
+
/// application state; those remain with the caller that owns each command.
|
|
15
|
+
final class NmsRuntimeV2Gateway {
|
|
16
|
+
NmsRuntimeV2Gateway(this._transport);
|
|
17
|
+
|
|
18
|
+
final NomosRuntimeV2Transport _transport;
|
|
19
|
+
int _sequence = 0;
|
|
20
|
+
|
|
21
|
+
Future<nomos_protocol.RuntimeResponse> invoke(
|
|
22
|
+
nomos_protocol.RuntimeRequest request,
|
|
23
|
+
) async {
|
|
24
|
+
final requestId = fixnum.Int64(++_sequence);
|
|
25
|
+
request
|
|
26
|
+
..protocolVersion = 2
|
|
27
|
+
..requestId = requestId;
|
|
28
|
+
final response = await _transport.invokeRuntime(request);
|
|
29
|
+
if (response.protocolVersion != 2) {
|
|
30
|
+
throw StateError(
|
|
31
|
+
'Nomos host returned Runtime protocol ${response.protocolVersion}',
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
if (response.requestId != requestId) {
|
|
35
|
+
throw StateError(
|
|
36
|
+
'Runtime v2 response ${response.requestId} belongs to another request',
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
if (response.hasFailure()) {
|
|
40
|
+
final failure = response.failure;
|
|
41
|
+
throw nomosErrorFromInfo(
|
|
42
|
+
NomosErrorInfo(
|
|
43
|
+
code: failure.code,
|
|
44
|
+
message: failure.message,
|
|
45
|
+
component: failure.component,
|
|
46
|
+
workspace: failure.hasWorkspace() ? failure.workspace : null,
|
|
47
|
+
domain: failure.hasDomain() ? failure.domain : null,
|
|
48
|
+
directiveId: failure.hasDirective() ? failure.directive : null,
|
|
49
|
+
),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return response;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
@internal
|
|
2
|
+
library;
|
|
3
|
+
|
|
4
|
+
import 'dart:async';
|
|
5
|
+
|
|
6
|
+
import 'package:fixnum/fixnum.dart' as fixnum;
|
|
7
|
+
import 'package:meta/meta.dart' show internal;
|
|
8
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as nomos_protocol;
|
|
9
|
+
|
|
10
|
+
/// Binary generated-message path from an application host to the kernel.
|
|
11
|
+
abstract interface class NomosKernelCallTransport {
|
|
12
|
+
Future<nomos_protocol.CallResponse> callKernel({
|
|
13
|
+
required String session,
|
|
14
|
+
required nomos_protocol.CallRequest request,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/// Exact Runtime v2 frame exchange. Platform transports carry bytes; they do
|
|
19
|
+
/// not acquire command names, argument bags, or domain result semantics.
|
|
20
|
+
abstract interface class NomosRuntimeV2Transport {
|
|
21
|
+
Future<nomos_protocol.RuntimeResponse> invokeRuntime(
|
|
22
|
+
nomos_protocol.RuntimeRequest request,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Generated asynchronous runtime event lane. It is separate from invocation
|
|
27
|
+
/// correlation because a watch/sync event has no request future to complete.
|
|
28
|
+
abstract interface class NomosRuntimeV2EventTransport {
|
|
29
|
+
Stream<nomos_protocol.RuntimeEvent> get runtimeEvents;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// A level-triggered generated event stream. Runtime readiness can arrive while
|
|
33
|
+
/// a WebView script is loading, before [NomosBridge] exists; a broadcast stream
|
|
34
|
+
/// would silently discard it and turn a successful boot into an infinite wait.
|
|
35
|
+
/// Buffering ends when the first bridge subscribes, after which events remain a
|
|
36
|
+
/// normal broadcast stream.
|
|
37
|
+
final class NmsBufferedRuntimeEvents {
|
|
38
|
+
NmsBufferedRuntimeEvents() {
|
|
39
|
+
_controller = StreamController<nomos_protocol.RuntimeEvent>.broadcast(
|
|
40
|
+
onListen: _attach,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
late final StreamController<nomos_protocol.RuntimeEvent> _controller;
|
|
45
|
+
final List<nomos_protocol.RuntimeEvent> _pending = [];
|
|
46
|
+
bool _attached = false;
|
|
47
|
+
|
|
48
|
+
Stream<nomos_protocol.RuntimeEvent> get stream => _controller.stream;
|
|
49
|
+
bool get isClosed => _controller.isClosed;
|
|
50
|
+
|
|
51
|
+
void _attach() {
|
|
52
|
+
if (_attached) return;
|
|
53
|
+
_attached = true;
|
|
54
|
+
final pending = List<nomos_protocol.RuntimeEvent>.of(_pending);
|
|
55
|
+
_pending.clear();
|
|
56
|
+
for (final event in pending) {
|
|
57
|
+
if (!_controller.isClosed) _controller.add(event);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
void add(nomos_protocol.RuntimeEvent event) {
|
|
62
|
+
if (_controller.isClosed) return;
|
|
63
|
+
if (_attached) {
|
|
64
|
+
_controller.add(event);
|
|
65
|
+
} else {
|
|
66
|
+
_pending.add(event);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
void addError(Object error, [StackTrace? stackTrace]) {
|
|
71
|
+
if (_controller.isClosed) return;
|
|
72
|
+
_controller.addError(error, stackTrace);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
Future<void> close() {
|
|
76
|
+
_pending.clear();
|
|
77
|
+
return _controller.close();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// Shared typed kernel-call lowering for every Runtime v2 transport.
|
|
82
|
+
mixin NomosRuntimeV2KernelCalls
|
|
83
|
+
implements NomosRuntimeV2Transport, NomosKernelCallTransport {
|
|
84
|
+
int _nmsRuntimeRequestSequence = 0;
|
|
85
|
+
|
|
86
|
+
@override
|
|
87
|
+
Future<nomos_protocol.CallResponse> callKernel({
|
|
88
|
+
required String session,
|
|
89
|
+
required nomos_protocol.CallRequest request,
|
|
90
|
+
}) async {
|
|
91
|
+
final requestId = fixnum.Int64(++_nmsRuntimeRequestSequence);
|
|
92
|
+
final response = await invokeRuntime(
|
|
93
|
+
nomos_protocol.RuntimeRequest(
|
|
94
|
+
protocolVersion: 2,
|
|
95
|
+
requestId: requestId,
|
|
96
|
+
kernelCall: nomos_protocol.KernelCallRequest(
|
|
97
|
+
session: session,
|
|
98
|
+
request: request,
|
|
99
|
+
),
|
|
100
|
+
),
|
|
101
|
+
);
|
|
102
|
+
if (response.requestId != requestId) {
|
|
103
|
+
throw StateError(
|
|
104
|
+
'Runtime v2 response ${response.requestId} belongs to another request',
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
if (response.hasFailure()) {
|
|
108
|
+
throw StateError('${response.failure.code}: ${response.failure.message}');
|
|
109
|
+
}
|
|
110
|
+
if (!response.hasKernelCall()) {
|
|
111
|
+
throw StateError(
|
|
112
|
+
'Runtime v2 returned ${response.whichOutcome().name} for a kernel call',
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return response.kernelCall;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -28,6 +28,8 @@ import 'package:meta/meta.dart' show internal;
|
|
|
28
28
|
|
|
29
29
|
import 'dart:convert';
|
|
30
30
|
|
|
31
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as nomos_protocol;
|
|
32
|
+
|
|
31
33
|
import 'webview_bridge.dart';
|
|
32
34
|
|
|
33
35
|
/// The STATIC INTERFACE DESCRIPTOR a generated client exposes as `XClient.interface` — what the
|
|
@@ -474,8 +476,11 @@ extension NomosSessionCompat on NomosSession {
|
|
|
474
476
|
final m = await metrics();
|
|
475
477
|
behind = ((m['behind'] as num?) ?? 0).toInt();
|
|
476
478
|
final listed = await listWorkspaces();
|
|
477
|
-
cloudBound = listed.any((
|
|
478
|
-
|
|
479
|
+
cloudBound = listed.any((candidate) =>
|
|
480
|
+
candidate.workspace == workspace &&
|
|
481
|
+
candidate.syncPosture ==
|
|
482
|
+
nomos_protocol
|
|
483
|
+
.WorkspaceSyncPosture.WORKSPACE_SYNC_POSTURE_CLOUD_BOUND);
|
|
479
484
|
} catch (_) {/* best-effort */}
|
|
480
485
|
if (cloudBound && behind > 0) {
|
|
481
486
|
cause = SessionMismatchCause.liveCloudHasLawButLocalStale;
|
|
@@ -42,10 +42,13 @@ import 'dart:io';
|
|
|
42
42
|
|
|
43
43
|
import 'package:crypto/crypto.dart' show sha256;
|
|
44
44
|
import 'package:fixnum/fixnum.dart' show Int64;
|
|
45
|
-
import 'package:nomos_types/nomos_kernel_protocol.dart'
|
|
45
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart'
|
|
46
|
+
show BirthCertificate, BirthOutcomeStatus, SyncWorkspaceResponse;
|
|
47
|
+
import 'package:nomos_types/nomos_types.dart' show Sha256Digest;
|
|
46
48
|
import 'package:package_config/package_config.dart' show loadPackageConfigUri;
|
|
47
49
|
|
|
48
50
|
import 'dev_cloud.dart';
|
|
51
|
+
import 'application_framework_calls.dart';
|
|
49
52
|
import 'node_runner_transport.dart';
|
|
50
53
|
import 'session_bound.dart' show DomainNotInstalledForSession;
|
|
51
54
|
import 'webview_bridge.dart' show NomosBridge, NomosSession;
|
|
@@ -250,36 +253,13 @@ Future<String?> _sourceCandidateRuntime(String nodeEntry) async {
|
|
|
250
253
|
/// Framework-internal classifier for a sync result that can never become converged by retrying. Generated
|
|
251
254
|
/// scenarios use it through [NomosTestHarness.converge]; it is public only inside `package:nomos_client/src`
|
|
252
255
|
/// so the false-green contract has a cheap regression test without booting a cloud.
|
|
253
|
-
String? nmsPermanentConvergenceFailure(
|
|
254
|
-
final
|
|
255
|
-
|
|
256
|
-
? admission['sessions'] as List
|
|
257
|
-
: const [];
|
|
258
|
-
final rejected = sessions
|
|
259
|
-
.whereType<Map>()
|
|
260
|
-
.expand(
|
|
261
|
-
(session) => session['rejected'] is List
|
|
262
|
-
? session['rejected'] as List
|
|
263
|
-
: const [],
|
|
264
|
-
)
|
|
265
|
-
.toList();
|
|
266
|
-
final deadLetters = admission is Map && admission['deadLetters'] is List
|
|
267
|
-
? admission['deadLetters'] as List
|
|
268
|
-
: const [];
|
|
269
|
-
final converged = result['converged'];
|
|
270
|
-
final replayDeadLetters =
|
|
271
|
-
converged is Map && converged['deadLettered'] is List
|
|
272
|
-
? converged['deadLettered'] as List
|
|
273
|
-
: const [];
|
|
274
|
-
final pushError = result['pushError'];
|
|
275
|
-
final refused = pushError is Map && pushError['refused'] == true;
|
|
276
|
-
if (rejected.isEmpty &&
|
|
277
|
-
deadLetters.isEmpty &&
|
|
278
|
-
replayDeadLetters.isEmpty &&
|
|
279
|
-
!refused) {
|
|
256
|
+
String? nmsPermanentConvergenceFailure(SyncWorkspaceResponse result) {
|
|
257
|
+
final transportRefused = result.hasPushError() && result.pushError.refused;
|
|
258
|
+
if (result.refused == 0 && !transportRefused) {
|
|
280
259
|
return null;
|
|
281
260
|
}
|
|
282
|
-
return
|
|
261
|
+
return 'refused=${result.refused}, '
|
|
262
|
+
'push=${result.hasPushError() ? result.pushError.message : "none"}';
|
|
283
263
|
}
|
|
284
264
|
|
|
285
265
|
/// The offline-install lane configuration a harness keeps so the CUSTODY DIAGNOSTIC helpers
|
|
@@ -615,6 +595,7 @@ class NomosTestHarness {
|
|
|
615
595
|
/// [autoTearDown] — when true (default) and [registerTearDown] is given, registers [dispose] as a teardown.
|
|
616
596
|
static Future<NomosTestHarness> ephemeral({
|
|
617
597
|
required List<String> lawUsdzPaths,
|
|
598
|
+
NmsApplicationFrameworkCalls? applicationFrameworkCalls,
|
|
618
599
|
int? applicationMaxChildWorkspacesPerUser,
|
|
619
600
|
String? ownerSubject,
|
|
620
601
|
String workspace = 'test-ws',
|
|
@@ -665,6 +646,12 @@ class NomosTestHarness {
|
|
|
665
646
|
final birthsApplicationHome = owner != null &&
|
|
666
647
|
owner.isNotEmpty &&
|
|
667
648
|
workspace == _receivingGroupNameOf(owner);
|
|
649
|
+
if (birthsApplicationHome && applicationFrameworkCalls == null) {
|
|
650
|
+
throw NomosTestHarnessException(
|
|
651
|
+
'a generated application scenario must pass its compiler-generated framework calls; '
|
|
652
|
+
'the harness cannot construct domain payloads',
|
|
653
|
+
);
|
|
654
|
+
}
|
|
668
655
|
// A receiving-group address is not an arbitrary law-install target. It is valid only when a parent
|
|
669
656
|
// births it with the attestation, signer seating and warrant ordered into genesis. Generated scenarios
|
|
670
657
|
// therefore install their package on a private throwaway application parent and exercise the same local
|
|
@@ -804,49 +791,151 @@ class NomosTestHarness {
|
|
|
804
791
|
// scenario carries the value from the same package declaration that compiled the
|
|
805
792
|
// receiving-group plan; the harness does not choose or default it. The ordinary
|
|
806
793
|
// seed offer crosses the kernel author gate before the first birth is attempted.
|
|
807
|
-
run: () => plane.session(installWorkspace).
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
794
|
+
run: () => plane.session(installWorkspace).offerCallUsdaOutcome(
|
|
795
|
+
call: applicationFrameworkCalls!.seedBirthPolicy(
|
|
796
|
+
domain: '__nomos_application_home_v1',
|
|
797
|
+
workspace: installWorkspace,
|
|
798
|
+
maxBirthsPerPrincipal: maxChildWorkspaces,
|
|
799
|
+
setBy: owner,
|
|
800
|
+
setAt: bornAt,
|
|
801
|
+
),
|
|
802
|
+
actor: owner,
|
|
803
|
+
),
|
|
804
|
+
);
|
|
805
|
+
final keyHash = sha256.convert(base64Decode(devicePublic)).toString();
|
|
806
|
+
await _phased(
|
|
807
|
+
phase: 'application: seat the verified parent signer',
|
|
808
|
+
workspace: installWorkspace,
|
|
809
|
+
awaiting: 'law-declared enrollSigner offer',
|
|
810
|
+
timeout: phaseTimeout ?? _kOtherDeadline,
|
|
811
|
+
onDiagnostic: onDiagnostic,
|
|
812
|
+
run: () => plane.session(installWorkspace).offerCallUsdaOutcome(
|
|
813
|
+
call: applicationFrameworkCalls!.enrollSigner(
|
|
814
|
+
domain: '__nomos_application_home_v1',
|
|
815
|
+
workspace: installWorkspace,
|
|
816
|
+
keyHash: keyHash,
|
|
817
|
+
grantedBy: owner,
|
|
818
|
+
grantedAt: 1,
|
|
819
|
+
verifiedVia: 'nomos:test-mint',
|
|
820
|
+
),
|
|
821
|
+
actor: owner,
|
|
822
|
+
),
|
|
818
823
|
);
|
|
824
|
+
final delegationCert =
|
|
825
|
+
await plane.session(installWorkspace).signBirthCert(
|
|
826
|
+
BirthCertificate(
|
|
827
|
+
child: workspace,
|
|
828
|
+
founder: owner,
|
|
829
|
+
verified: true,
|
|
830
|
+
parentKey: base64Decode(devicePublic),
|
|
831
|
+
parentId: 'root',
|
|
832
|
+
epoch: Int64.ZERO,
|
|
833
|
+
),
|
|
834
|
+
deviceSecret,
|
|
835
|
+
);
|
|
836
|
+
await _phased(
|
|
837
|
+
phase: 'application: record the parent-signed device delegation',
|
|
838
|
+
workspace: installWorkspace,
|
|
839
|
+
awaiting: 'law-declared enrollDelegation offer',
|
|
840
|
+
timeout: phaseTimeout ?? _kOtherDeadline,
|
|
841
|
+
onDiagnostic: onDiagnostic,
|
|
842
|
+
run: () => plane.session(installWorkspace).offerCallUsdaOutcome(
|
|
843
|
+
call: applicationFrameworkCalls!.enrollDelegation(
|
|
844
|
+
domain: '__nomos_application_home_v1',
|
|
845
|
+
workspace: installWorkspace,
|
|
846
|
+
user: owner,
|
|
847
|
+
deviceKey: devicePublic,
|
|
848
|
+
certificate: delegationCert,
|
|
849
|
+
enrolledAt: '1970-01-01T00:00:00.000Z',
|
|
850
|
+
),
|
|
851
|
+
actor: owner,
|
|
852
|
+
),
|
|
853
|
+
);
|
|
854
|
+
final parentCert = await plane.session(installWorkspace).signBirthCert(
|
|
855
|
+
BirthCertificate(
|
|
856
|
+
child: installWorkspace,
|
|
857
|
+
founder: owner,
|
|
858
|
+
verified: true,
|
|
859
|
+
parentKey: base64Decode(devicePublic),
|
|
860
|
+
parentId: 'root',
|
|
861
|
+
epoch: Int64.ZERO,
|
|
862
|
+
),
|
|
863
|
+
deviceSecret,
|
|
864
|
+
);
|
|
865
|
+
await _phased(
|
|
866
|
+
phase: 'application: attest and warrant the application parent',
|
|
867
|
+
workspace: installWorkspace,
|
|
868
|
+
awaiting: 'typed birth attestation and genesis offers',
|
|
869
|
+
timeout: phaseTimeout ?? _kOtherDeadline,
|
|
870
|
+
onDiagnostic: onDiagnostic,
|
|
871
|
+
run: () async {
|
|
872
|
+
await plane.session(installWorkspace).offerCallUsdaOutcome(
|
|
873
|
+
call: applicationFrameworkCalls!.seedBirthAttestation(
|
|
874
|
+
domain: '__nomos_application_home_v1',
|
|
875
|
+
workspace: installWorkspace,
|
|
876
|
+
child: installWorkspace,
|
|
877
|
+
founder: owner,
|
|
878
|
+
verified: 'true',
|
|
879
|
+
parentId: 'root',
|
|
880
|
+
parentKey: devicePublic,
|
|
881
|
+
epoch: 0,
|
|
882
|
+
links: <BirthCertificate>[parentCert],
|
|
883
|
+
),
|
|
884
|
+
actor: owner,
|
|
885
|
+
);
|
|
886
|
+
await plane.session(installWorkspace).offerCallUsdaOutcome(
|
|
887
|
+
call: applicationFrameworkCalls.seedGenesis(
|
|
888
|
+
domain: '__nomos_application_home_v1',
|
|
889
|
+
workspace: installWorkspace,
|
|
890
|
+
principal: owner,
|
|
891
|
+
workspaceId: installWorkspace,
|
|
892
|
+
bornAt: '1970-01-01T00:00:00.000Z',
|
|
893
|
+
),
|
|
894
|
+
actor: owner,
|
|
895
|
+
);
|
|
896
|
+
},
|
|
897
|
+
);
|
|
898
|
+
final homeCert = await plane.session(installWorkspace).signBirthCert(
|
|
899
|
+
BirthCertificate(
|
|
900
|
+
child: workspace,
|
|
901
|
+
founder: owner,
|
|
902
|
+
verified: true,
|
|
903
|
+
parentKey: base64Decode(devicePublic),
|
|
904
|
+
parentId: installWorkspace,
|
|
905
|
+
epoch: Int64.ZERO,
|
|
906
|
+
),
|
|
907
|
+
deviceSecret,
|
|
908
|
+
);
|
|
819
909
|
final outcome = await _phased(
|
|
820
910
|
phase: 'application: birth the signed-in application',
|
|
821
911
|
workspace: workspace,
|
|
822
912
|
awaiting: 'attested receiving-group birth',
|
|
823
913
|
timeout: phaseTimeout ?? _kOtherDeadline,
|
|
824
914
|
onDiagnostic: onDiagnostic,
|
|
825
|
-
run: () => plane.session(installWorkspace).
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
),
|
|
915
|
+
run: () => plane.session(installWorkspace).offerCallUsdaOutcome(
|
|
916
|
+
call: applicationFrameworkCalls!.birthReceivingGroup(
|
|
917
|
+
domain: '__nomos_application_home_v1',
|
|
918
|
+
workspace: installWorkspace,
|
|
919
|
+
owner: owner,
|
|
920
|
+
frameworkHash: Sha256Digest(framework),
|
|
921
|
+
lawHash: Sha256Digest(laws.last),
|
|
922
|
+
bornAt: bornAt,
|
|
923
|
+
),
|
|
924
|
+
actor: owner,
|
|
925
|
+
authorSecret: deviceSecret,
|
|
926
|
+
birthCertificate: homeCert,
|
|
927
|
+
),
|
|
839
928
|
);
|
|
840
929
|
String? bornStatus;
|
|
841
930
|
for (final entry in outcome.birthOutcomes) {
|
|
842
|
-
if (entry
|
|
843
|
-
bornStatus = entry
|
|
931
|
+
if (entry.workspace == workspace) {
|
|
932
|
+
bornStatus = entry.status.name;
|
|
844
933
|
break;
|
|
845
934
|
}
|
|
846
935
|
}
|
|
847
936
|
if (outcome.born.length != 1 ||
|
|
848
937
|
outcome.born.single != workspace ||
|
|
849
|
-
bornStatus != '
|
|
938
|
+
bornStatus != 'BIRTH_OUTCOME_STATUS_BORN') {
|
|
850
939
|
throw NomosTestHarnessException(
|
|
851
940
|
'the offline application parent did not birth "$workspace" cleanly '
|
|
852
941
|
'(born=${outcome.born}, birthOutcomes=${outcome.birthOutcomes})',
|
|
@@ -889,18 +978,17 @@ class NomosTestHarness {
|
|
|
889
978
|
// harness device through the installed framework law with the same verified provenance
|
|
890
979
|
// that the private test root later attests. Production clients never manufacture this
|
|
891
980
|
// marker; it exists only inside the offline test-mint ceremony.
|
|
892
|
-
run: () => plane.session(workspace).
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
),
|
|
981
|
+
run: () => plane.session(workspace).offerCallUsdaOutcome(
|
|
982
|
+
call: applicationFrameworkCalls!.enrollSigner(
|
|
983
|
+
domain: '__nomos_application_home_v1',
|
|
984
|
+
workspace: workspace,
|
|
985
|
+
keyHash: keyHash,
|
|
986
|
+
grantedBy: owner,
|
|
987
|
+
grantedAt: 1,
|
|
988
|
+
verifiedVia: 'nomos:test-mint',
|
|
989
|
+
),
|
|
990
|
+
actor: owner,
|
|
991
|
+
),
|
|
904
992
|
);
|
|
905
993
|
} catch (error) {
|
|
906
994
|
// Pre-application-home packages neither expose nor enforce this
|
|
@@ -921,22 +1009,24 @@ class NomosTestHarness {
|
|
|
921
1009
|
timeout: phaseTimeout ?? _kOtherDeadline,
|
|
922
1010
|
onDiagnostic: onDiagnostic,
|
|
923
1011
|
run: () async {
|
|
924
|
-
await plane.session(workspace).
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1012
|
+
await plane.session(workspace).offerCallUsdaOutcome(
|
|
1013
|
+
call: applicationFrameworkCalls!.seedReceivingGroupPolicy(
|
|
1014
|
+
domain: '__nomos_application_home_v1',
|
|
1015
|
+
workspace: workspace,
|
|
1016
|
+
setAt: bornAt,
|
|
1017
|
+
),
|
|
1018
|
+
actor: 'host-birth',
|
|
1019
|
+
);
|
|
1020
|
+
await plane.session(workspace).offerCallUsdaOutcome(
|
|
1021
|
+
call: applicationFrameworkCalls.seedReceivingGroup(
|
|
1022
|
+
domain: '__nomos_application_home_v1',
|
|
1023
|
+
workspace: workspace,
|
|
1024
|
+
principal: owner,
|
|
1025
|
+
workspaceName: _receivingGroupNameOf(owner),
|
|
1026
|
+
bornAt: bornAt,
|
|
1027
|
+
),
|
|
1028
|
+
actor: 'host-birth',
|
|
1029
|
+
);
|
|
940
1030
|
},
|
|
941
1031
|
);
|
|
942
1032
|
final parentCert = await plane.signBirthCert(
|
|
@@ -1798,14 +1888,9 @@ class NomosTestHarness {
|
|
|
1798
1888
|
_birthCertificateLawValue(delegationCert),
|
|
1799
1889
|
},
|
|
1800
1890
|
);
|
|
1801
|
-
|
|
1891
|
+
await plane.sync(
|
|
1802
1892
|
session: platformAuthorSession,
|
|
1803
1893
|
);
|
|
1804
|
-
if (enrollmentSync['ok'] == false) {
|
|
1805
|
-
throw NomosTestHarnessException(
|
|
1806
|
-
'cloudBound: sealed enrollFirstDevice offer did not converge: $enrollmentSync',
|
|
1807
|
-
);
|
|
1808
|
-
}
|
|
1809
1894
|
return (
|
|
1810
1895
|
publicB64: publicB64,
|
|
1811
1896
|
secret: secret,
|
|
@@ -1868,27 +1953,24 @@ class NomosTestHarness {
|
|
|
1868
1953
|
final birthSync = await plane.sync(
|
|
1869
1954
|
session: '$clientId-platform-author',
|
|
1870
1955
|
);
|
|
1871
|
-
if (birthSync
|
|
1872
|
-
birthSync
|
|
1873
|
-
birthSync
|
|
1956
|
+
if (birthSync.pushFailed ||
|
|
1957
|
+
birthSync.diverged ||
|
|
1958
|
+
birthSync.stillAhead) {
|
|
1874
1959
|
throw NomosTestHarnessException(
|
|
1875
1960
|
'cloudBound: the sealed receiving-group offer did not converge: $birthSync',
|
|
1876
1961
|
);
|
|
1877
1962
|
}
|
|
1878
|
-
final remoteOutcomes =
|
|
1879
|
-
((birthSync['birthOutcomes'] as List?) ?? const [])
|
|
1880
|
-
.whereType<Map>()
|
|
1881
|
-
.map((entry) => entry.cast<String, Object?>())
|
|
1882
|
-
.toList();
|
|
1963
|
+
final remoteOutcomes = birthSync.birthOutcomes;
|
|
1883
1964
|
final failedRemoteBirth = remoteOutcomes.where(
|
|
1884
1965
|
(entry) =>
|
|
1885
|
-
entry
|
|
1886
|
-
entry
|
|
1966
|
+
entry.hasWorkspace() &&
|
|
1967
|
+
entry.workspace == homeName &&
|
|
1968
|
+
entry.status != BirthOutcomeStatus.BIRTH_OUTCOME_STATUS_BORN,
|
|
1887
1969
|
);
|
|
1888
1970
|
if (failedRemoteBirth.isNotEmpty) {
|
|
1889
1971
|
throw NomosTestHarnessException(
|
|
1890
1972
|
'cloudBound: the remote parent replay refused the receiving-group birth: '
|
|
1891
|
-
'${
|
|
1973
|
+
'${failedRemoteBirth.toList()}',
|
|
1892
1974
|
);
|
|
1893
1975
|
}
|
|
1894
1976
|
home = homeName;
|
|
@@ -1985,7 +2067,7 @@ class NomosTestHarness {
|
|
|
1985
2067
|
/// each locally-born child's remote custody BEFORE that child's own sync converges against it. Repeats
|
|
1986
2068
|
/// until every session reports neither `pushFailed` nor `stillAhead`, or [timeout] (then throws with the
|
|
1987
2069
|
/// per-workspace last verdicts). Returns the final sync result per workspace.
|
|
1988
|
-
Future<Map<String,
|
|
2070
|
+
Future<Map<String, SyncWorkspaceResponse>> converge({
|
|
1989
2071
|
Duration timeout = const Duration(seconds: 120),
|
|
1990
2072
|
List<String>? workspaces,
|
|
1991
2073
|
}) async {
|
|
@@ -1996,36 +2078,35 @@ class NomosTestHarness {
|
|
|
1996
2078
|
final hw = homeWorkspace;
|
|
1997
2079
|
if (hw != null && live.remove(hw)) live.insert(0, hw); // the parent leads
|
|
1998
2080
|
final targets = workspaces ?? live;
|
|
1999
|
-
final last = <String,
|
|
2081
|
+
final last = <String, SyncWorkspaceResponse>{};
|
|
2082
|
+
final lastErrors = <String, String>{};
|
|
2000
2083
|
final pending = List.of(targets);
|
|
2001
2084
|
final deadline = DateTime.now().add(timeout);
|
|
2002
2085
|
while (pending.isNotEmpty) {
|
|
2003
2086
|
for (final ws in List.of(pending)) {
|
|
2004
2087
|
try {
|
|
2005
|
-
final r =
|
|
2006
|
-
.cast<String, Object?>();
|
|
2088
|
+
final r = await bridge.session(ws).sync(admit: true);
|
|
2007
2089
|
last[ws] = r;
|
|
2090
|
+
lastErrors.remove(ws);
|
|
2008
2091
|
final permanentFailure = nmsPermanentConvergenceFailure(r);
|
|
2009
2092
|
if (permanentFailure != null) {
|
|
2010
2093
|
throw NomosTestHarnessException(
|
|
2011
2094
|
'converge: $ws rejected or dead-lettered business work — $permanentFailure',
|
|
2012
2095
|
);
|
|
2013
2096
|
}
|
|
2014
|
-
if (r
|
|
2015
|
-
|
|
2016
|
-
r['offline'] != true &&
|
|
2017
|
-
r['deferred'] != true) pending.remove(ws);
|
|
2097
|
+
if (!r.pushFailed && !r.stillAhead && !r.offline && r.deferred == 0)
|
|
2098
|
+
pending.remove(ws);
|
|
2018
2099
|
} on NomosTestHarnessException {
|
|
2019
2100
|
rethrow;
|
|
2020
2101
|
} catch (e) {
|
|
2021
|
-
|
|
2102
|
+
lastErrors[ws] = e.toString();
|
|
2022
2103
|
}
|
|
2023
2104
|
}
|
|
2024
2105
|
if (pending.isEmpty) break;
|
|
2025
2106
|
if (DateTime.now().isAfter(deadline)) {
|
|
2026
2107
|
throw NomosTestHarnessException(
|
|
2027
2108
|
'converge: ${pending.length} workspace(s) did not converge within ${timeout.inSeconds}s — '
|
|
2028
|
-
'${pending.map((w) => '$w: ${
|
|
2109
|
+
'${pending.map((w) => '$w: ${lastErrors[w] ?? last[w]?.toString() ?? "no result"}').join('; ')}',
|
|
2029
2110
|
);
|
|
2030
2111
|
}
|
|
2031
2112
|
await Future<void>.delayed(const Duration(milliseconds: 500));
|