@githolon/dsl 0.107.4 → 0.108.1
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 +24 -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 +10634 -6122
- package/dart/lib/src/runner/offline-install.mjs +4187 -2716
- 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 +373 -282
- package/dart/lib/src/webview_bridge.dart +2146 -725
- package/dart/pubspec.lock +17 -1
- package/dart/pubspec.yaml +3 -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 +17 -0
- package/dart_types/lib/nomos_kernel_protocol.dart +11 -0
- 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 +17945 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +933 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pb.dart +245 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pbenum.dart +160 -0
- 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.lock +16 -0
- package/dart_types/pubspec.yaml +3 -1
- package/dart_types/test/surfaced_test.dart +2 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +5760 -1879
- package/src/authoring.ts +3 -2
- package/src/birth_meter_ensure.ts +2 -0
- package/src/births.ts +21 -0
- package/src/build_package.ts +30 -0
- package/src/business_birth.ts +41 -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 +392 -46
- package/src/directive.ts +19 -4
- package/src/engine_entry.ts +45 -4
- package/src/framework/application_home.ts +4 -0
- package/src/framework/auth_provider_law.ts +296 -20
- package/src/framework/birth_certificate_law.ts +24 -0
- package/src/framework/home.ts +83 -40
- package/src/framework/owned_custody.ts +25 -12
- package/src/framework/owned_custody_value.ts +51 -0
- package/src/framework/relations.ts +47 -21
- package/src/framework/signing_identity.ts +100 -17
- package/src/framework/workspaces.ts +39 -10
- 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 +22 -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 +44 -42
- package/src/stable_ids.ts +37 -2
- 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/pubspec.lock
CHANGED
|
@@ -89,6 +89,14 @@ packages:
|
|
|
89
89
|
url: "https://pub.dev"
|
|
90
90
|
source: hosted
|
|
91
91
|
version: "7.0.1"
|
|
92
|
+
fixnum:
|
|
93
|
+
dependency: "direct main"
|
|
94
|
+
description:
|
|
95
|
+
name: fixnum
|
|
96
|
+
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
|
97
|
+
url: "https://pub.dev"
|
|
98
|
+
source: hosted
|
|
99
|
+
version: "1.1.1"
|
|
92
100
|
frontend_server_client:
|
|
93
101
|
dependency: transitive
|
|
94
102
|
description:
|
|
@@ -191,7 +199,7 @@ packages:
|
|
|
191
199
|
path: "../dart_types"
|
|
192
200
|
relative: true
|
|
193
201
|
source: path
|
|
194
|
-
version: "0.
|
|
202
|
+
version: "0.9.0"
|
|
195
203
|
package_config:
|
|
196
204
|
dependency: "direct main"
|
|
197
205
|
description:
|
|
@@ -216,6 +224,14 @@ packages:
|
|
|
216
224
|
url: "https://pub.dev"
|
|
217
225
|
source: hosted
|
|
218
226
|
version: "1.5.2"
|
|
227
|
+
protobuf:
|
|
228
|
+
dependency: transitive
|
|
229
|
+
description:
|
|
230
|
+
name: protobuf
|
|
231
|
+
sha256: "2fcc8a202ca7ec17dab7c97d6b6d91cf03aa07fe6f65f8afbb6dfa52cc5bd902"
|
|
232
|
+
url: "https://pub.dev"
|
|
233
|
+
source: hosted
|
|
234
|
+
version: "5.1.0"
|
|
219
235
|
pub_semver:
|
|
220
236
|
dependency: transitive
|
|
221
237
|
description:
|
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.65.1
|
|
11
11
|
|
|
12
12
|
environment:
|
|
13
13
|
sdk: ^3.5.0
|
|
@@ -16,7 +16,8 @@ dependencies:
|
|
|
16
16
|
# The pure-data core (wire types, offer codec, NomosReads, schema validation, error
|
|
17
17
|
# info). nomos_client depends on and re-exports it, so existing imports are unchanged.
|
|
18
18
|
# For local monorepo dev, pubspec_overrides.yaml points this at ../dart_types.
|
|
19
|
-
nomos_types: ^0.
|
|
19
|
+
nomos_types: ^0.10.0
|
|
20
|
+
fixnum: ^1.1.1
|
|
20
21
|
json_schema: ^5.2.2
|
|
21
22
|
crypto: ^3.0.0
|
|
22
23
|
package_config: ^2.1.0
|
|
@@ -128,7 +128,7 @@ void main() {
|
|
|
128
128
|
reason: 'both concurrent acquirers get the SAME session handle');
|
|
129
129
|
expect(acquired[0].workspace, 'cloud-opened-ws');
|
|
130
130
|
final listed = await plane.listWorkspaces();
|
|
131
|
-
expect(listed.map((
|
|
131
|
+
expect(listed.map((workspace) => workspace.session),
|
|
132
132
|
contains('cloud-opened-ws'),
|
|
133
133
|
reason: 'the acquired workspace is mounted on the plane');
|
|
134
134
|
});
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import 'dart:async';
|
|
7
7
|
|
|
8
8
|
import 'package:nomos_client/src/nms_generated_runtime.dart';
|
|
9
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
|
|
9
10
|
import 'package:test/test.dart';
|
|
10
11
|
|
|
11
12
|
/// A fake runner: answers connect (after a real async gap, so concurrency is observable) and
|
|
@@ -13,6 +14,7 @@ import 'package:test/test.dart';
|
|
|
13
14
|
class FakeRunner implements BridgeTransport {
|
|
14
15
|
final _incoming = StreamController<Map<String, Object?>>.broadcast();
|
|
15
16
|
final List<Map<String, Object?>> sent = [];
|
|
17
|
+
final List<protocol.RuntimeRequest> runtimeRequests = [];
|
|
16
18
|
final List<String>
|
|
17
19
|
resident; // workspaces the plane already holds (realm.list())
|
|
18
20
|
|
|
@@ -25,10 +27,11 @@ class FakeRunner implements BridgeTransport {
|
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
int get connectCount => sent.where((m) => m['op'] == 'connect').length;
|
|
28
|
-
int autoSyncCount(String workspace) =>
|
|
29
|
-
.where((
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
int autoSyncCount(String workspace) => runtimeRequests
|
|
31
|
+
.where((request) =>
|
|
32
|
+
request.whichCommand() ==
|
|
33
|
+
protocol.RuntimeRequest_Command.startAutoSync &&
|
|
34
|
+
request.startAutoSync.session == workspace)
|
|
32
35
|
.length;
|
|
33
36
|
Map<String, Object?> connectArgs(int i) =>
|
|
34
37
|
(sent.where((m) => m['op'] == 'connect').elementAt(i)['args'] as Map)
|
|
@@ -37,6 +40,56 @@ class FakeRunner implements BridgeTransport {
|
|
|
37
40
|
@override
|
|
38
41
|
Stream<Map<String, Object?>> get messages => _incoming.stream;
|
|
39
42
|
|
|
43
|
+
@override
|
|
44
|
+
Future<protocol.RuntimeResponse> invokeRuntime(
|
|
45
|
+
protocol.RuntimeRequest request,
|
|
46
|
+
) async {
|
|
47
|
+
runtimeRequests.add(request.deepCopy());
|
|
48
|
+
final response = protocol.RuntimeResponse(
|
|
49
|
+
protocolVersion: 2,
|
|
50
|
+
requestId: request.requestId,
|
|
51
|
+
);
|
|
52
|
+
switch (request.whichCommand()) {
|
|
53
|
+
case protocol.RuntimeRequest_Command.listWorkspaces:
|
|
54
|
+
final connected = sent
|
|
55
|
+
.where((message) => message['op'] == 'connect')
|
|
56
|
+
.map(
|
|
57
|
+
(message) => ((message['args'] as Map)['workspace']).toString())
|
|
58
|
+
.toSet();
|
|
59
|
+
response.workspaceList = protocol.WorkspaceList(
|
|
60
|
+
workspaces: [
|
|
61
|
+
for (final workspace in {...resident, ...connected})
|
|
62
|
+
protocol.WorkspaceSummary(
|
|
63
|
+
session: workspace,
|
|
64
|
+
workspace: workspace,
|
|
65
|
+
residency: connected.contains(workspace)
|
|
66
|
+
? protocol.WorkspaceResidency.WORKSPACE_RESIDENCY_MOUNTED
|
|
67
|
+
: protocol.WorkspaceResidency.WORKSPACE_RESIDENCY_PARKED,
|
|
68
|
+
syncPosture: protocol
|
|
69
|
+
.WorkspaceSyncPosture.WORKSPACE_SYNC_POSTURE_LOCAL_ONLY,
|
|
70
|
+
),
|
|
71
|
+
],
|
|
72
|
+
);
|
|
73
|
+
break;
|
|
74
|
+
case protocol.RuntimeRequest_Command.startAutoSync:
|
|
75
|
+
response.autoSyncStarted =
|
|
76
|
+
protocol.AutoSyncStarted(session: request.startAutoSync.session);
|
|
77
|
+
break;
|
|
78
|
+
case protocol.RuntimeRequest_Command.stopAutoSync:
|
|
79
|
+
response.autoSyncStopped =
|
|
80
|
+
protocol.AutoSyncStopped(session: request.stopAutoSync.session);
|
|
81
|
+
break;
|
|
82
|
+
case protocol.RuntimeRequest_Command.closeRealm:
|
|
83
|
+
response.realmClosed = protocol.RealmClosed();
|
|
84
|
+
break;
|
|
85
|
+
default:
|
|
86
|
+
throw StateError(
|
|
87
|
+
'FakeRunner does not implement ${request.whichCommand().name}',
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return response;
|
|
91
|
+
}
|
|
92
|
+
|
|
40
93
|
@override
|
|
41
94
|
void send(Map<String, Object?> message) {
|
|
42
95
|
sent.add(message);
|
|
@@ -74,8 +127,7 @@ class FakeRunner implements BridgeTransport {
|
|
|
74
127
|
result = {
|
|
75
128
|
'workspace': args['workspace'],
|
|
76
129
|
'head': 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
77
|
-
if (args['ifHead'] ==
|
|
78
|
-
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
|
|
130
|
+
if (args['ifHead'] == 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
|
|
79
131
|
'unchanged': true
|
|
80
132
|
else
|
|
81
133
|
'snapshot': 'resident-snapshot-b64',
|
|
@@ -390,16 +442,20 @@ void main() {
|
|
|
390
442
|
|
|
391
443
|
// Before any connect: the plane-resident (parked) workspace surfaces with an explicit non-connected state.
|
|
392
444
|
var list = await bridge.listWorkspaces();
|
|
393
|
-
final before =
|
|
394
|
-
|
|
445
|
+
final before =
|
|
446
|
+
list.firstWhere((workspace) => workspace.workspace == 'estate');
|
|
447
|
+
expect(before.residency,
|
|
448
|
+
protocol.WorkspaceResidency.WORKSPACE_RESIDENCY_PARKED,
|
|
395
449
|
reason:
|
|
396
450
|
'parked custody is not connected — the app must acquire it first');
|
|
397
451
|
|
|
398
452
|
// After connecting it, the SAME workspace reports connected.
|
|
399
453
|
await bridge.connect(workspace: 'estate', session: 'estate');
|
|
400
454
|
list = await bridge.listWorkspaces();
|
|
401
|
-
final after =
|
|
402
|
-
|
|
455
|
+
final after =
|
|
456
|
+
list.firstWhere((workspace) => workspace.workspace == 'estate');
|
|
457
|
+
expect(after.residency,
|
|
458
|
+
protocol.WorkspaceResidency.WORKSPACE_RESIDENCY_MOUNTED);
|
|
403
459
|
await bridge.dispose();
|
|
404
460
|
});
|
|
405
461
|
|
|
@@ -410,8 +466,7 @@ void main() {
|
|
|
410
466
|
final bridge = NomosBridge(runner);
|
|
411
467
|
await bridge.ready;
|
|
412
468
|
|
|
413
|
-
final saved =
|
|
414
|
-
await bridge.nomosInternalSnapshotWorkspace('born-child');
|
|
469
|
+
final saved = await bridge.nomosInternalSnapshotWorkspace('born-child');
|
|
415
470
|
expect(saved.workspace, 'born-child');
|
|
416
471
|
expect(saved.head, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
|
|
417
472
|
expect(saved.snapshot, 'resident-snapshot-b64');
|
|
@@ -420,8 +475,7 @@ void main() {
|
|
|
420
475
|
reason:
|
|
421
476
|
'save-all must not acquire an unopened child or invent host authority');
|
|
422
477
|
|
|
423
|
-
final unchanged = await bridge.nomosInternalSnapshotWorkspace(
|
|
424
|
-
'born-child',
|
|
478
|
+
final unchanged = await bridge.nomosInternalSnapshotWorkspace('born-child',
|
|
425
479
|
ifHead: saved.head);
|
|
426
480
|
expect(unchanged.unchanged, isTrue);
|
|
427
481
|
expect(unchanged.snapshot, isNull);
|
|
@@ -21,6 +21,7 @@ import 'dart:convert';
|
|
|
21
21
|
import 'dart:io';
|
|
22
22
|
|
|
23
23
|
import 'package:nomos_client/src/nms_conformance_runtime.dart';
|
|
24
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
|
|
24
25
|
import 'package:nomos_types/src/nomos_ref.dart' show NmsInternalNomosRefs;
|
|
25
26
|
import 'package:test/test.dart';
|
|
26
27
|
|
|
@@ -405,12 +406,13 @@ void main() {
|
|
|
405
406
|
actor: 'adv-owner',
|
|
406
407
|
);
|
|
407
408
|
expect(outcome.born, hasLength(1));
|
|
408
|
-
expect(outcome.birthOutcomes.first
|
|
409
|
+
expect(outcome.birthOutcomes.first.status,
|
|
410
|
+
NomosBirthOutcomeStatus.BIRTH_OUTCOME_STATUS_BORN);
|
|
409
411
|
final child = outcome.born.single;
|
|
410
412
|
|
|
411
413
|
// the child is resident BEFORE any session opens it — listWorkspaces sees it
|
|
412
414
|
final listedPre = await plane.listWorkspaces();
|
|
413
|
-
expect(listedPre.map((
|
|
415
|
+
expect(listedPre.map((workspace) => workspace.workspace), contains(child),
|
|
414
416
|
reason:
|
|
415
417
|
'a born child is resident on the plane before it is connected');
|
|
416
418
|
|
|
@@ -552,8 +554,8 @@ void main() {
|
|
|
552
554
|
restoreFrom: snaps['lru-3'],
|
|
553
555
|
allowUnborn: true);
|
|
554
556
|
final listed = await bridge.listWorkspaces();
|
|
555
|
-
final l1 = listed.firstWhere((
|
|
556
|
-
expect(l1
|
|
557
|
+
final l1 = listed.firstWhere((workspace) => workspace.workspace == 'lru-1');
|
|
558
|
+
expect(l1.residency, protocol.WorkspaceResidency.WORKSPACE_RESIDENCY_PARKED,
|
|
557
559
|
reason: 'lru-1 should be LRU-parked: $listed');
|
|
558
560
|
|
|
559
561
|
// (a) session ops transparently remount from the parked snapshot, state intact
|
|
@@ -98,8 +98,8 @@ void main() {
|
|
|
98
98
|
reason: 'the born workspace is estate-<sha256(owner)>');
|
|
99
99
|
expect(outcome.birthOutcomes, isNotEmpty,
|
|
100
100
|
reason: 'the typed birth verdict rides beside born');
|
|
101
|
-
expect(outcome.birthOutcomes.first
|
|
102
|
-
expect(outcome.birthOutcomes.first
|
|
101
|
+
expect(outcome.birthOutcomes.first.workspace, outcome.born.single);
|
|
102
|
+
expect(outcome.birthOutcomes.first.hasStatus(), isTrue);
|
|
103
103
|
|
|
104
104
|
// The child handle is addressable on the plane (the loop that OPENS + reads it is the dedicated test
|
|
105
105
|
// below — 'born child OPENS as a sibling session ...').
|
|
@@ -121,9 +121,10 @@ void main() {
|
|
|
121
121
|
// child genesis folds clean and the born verdict is `born` — hard-asserted, no skip.
|
|
122
122
|
expect(outcome.born, hasLength(1));
|
|
123
123
|
expect(outcome.birthOutcomes, hasLength(1));
|
|
124
|
-
expect(outcome.birthOutcomes.first
|
|
124
|
+
expect(outcome.birthOutcomes.first.status,
|
|
125
|
+
NomosBirthOutcomeStatus.BIRTH_OUTCOME_STATUS_BORN,
|
|
125
126
|
reason: 'the child genesis (installDomain×2 + seed) folded through the child\'s own gate offline');
|
|
126
|
-
expect(outcome.birthOutcomes.first
|
|
127
|
+
expect(outcome.birthOutcomes.first.workspace, outcome.born.single);
|
|
127
128
|
} finally {
|
|
128
129
|
await harness.dispose();
|
|
129
130
|
}
|
|
@@ -138,7 +139,8 @@ void main() {
|
|
|
138
139
|
}
|
|
139
140
|
final (harness, outcome) = await _birth();
|
|
140
141
|
try {
|
|
141
|
-
expect(outcome.birthOutcomes.first
|
|
142
|
+
expect(outcome.birthOutcomes.first.status,
|
|
143
|
+
NomosBirthOutcomeStatus.BIRTH_OUTCOME_STATUS_BORN,
|
|
142
144
|
reason: 'precondition: the child is born on the local plane');
|
|
143
145
|
final child = outcome.born.single;
|
|
144
146
|
|
|
@@ -159,7 +161,7 @@ void main() {
|
|
|
159
161
|
|
|
160
162
|
// listWorkspaces now enumerates BOTH the parent and the newly-opened born child on the one plane.
|
|
161
163
|
final listed = await harness.bridge.listWorkspaces();
|
|
162
|
-
final sessions = listed.map((
|
|
164
|
+
final sessions = listed.map((workspace) => workspace.session).toSet();
|
|
163
165
|
expect(sessions, contains(child),
|
|
164
166
|
reason: 'listWorkspaces surfaces the born child as a live session');
|
|
165
167
|
expect(sessions, contains('estate-birth'),
|
|
@@ -188,9 +190,10 @@ void main() {
|
|
|
188
190
|
} on BirthIncomplete catch (e) {
|
|
189
191
|
expect(e.failedBirths, isNotEmpty,
|
|
190
192
|
reason: 'a failed birth still reports its typed per-child verdicts');
|
|
191
|
-
expect(e.failedBirths.first
|
|
193
|
+
expect(e.failedBirths.first.status,
|
|
194
|
+
isNot(NomosBirthOutcomeStatus.BIRTH_OUTCOME_STATUS_BORN),
|
|
192
195
|
reason: 'an unresolvable framework hash cannot yield a born child');
|
|
193
|
-
expect(
|
|
196
|
+
expect(e.failedBirths.first.reason, isNotEmpty,
|
|
194
197
|
reason: 'the failure carries a human-legible reason (never a silent drop)');
|
|
195
198
|
} finally {
|
|
196
199
|
await harness?.dispose();
|
|
@@ -80,7 +80,7 @@ void main() {
|
|
|
80
80
|
const Duration(seconds: 10),
|
|
81
81
|
onTimeout: () => throw StateError(
|
|
82
82
|
'listWorkspaces() hung after a local-only autoSync (dispatcher monopolized)'));
|
|
83
|
-
expect(listed.map((
|
|
83
|
+
expect(listed.map((workspace) => workspace.session).toSet(),
|
|
84
84
|
containsAll(names.toSet()));
|
|
85
85
|
final sibling = await harness
|
|
86
86
|
.session('estate-a')
|
|
@@ -12,6 +12,7 @@ library;
|
|
|
12
12
|
import 'dart:io';
|
|
13
13
|
|
|
14
14
|
import 'package:nomos_client/src/nms_conformance_runtime.dart';
|
|
15
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
|
|
15
16
|
import 'package:test/test.dart';
|
|
16
17
|
|
|
17
18
|
String get _root => '${Directory.current.path}/../..';
|
|
@@ -98,9 +99,9 @@ void main() {
|
|
|
98
99
|
|
|
99
100
|
// listWorkspaces enumerates the whole local plane — every one of the N is present + mounted.
|
|
100
101
|
final listed = await harness.bridge.listWorkspaces();
|
|
101
|
-
expect(listed.map((
|
|
102
|
+
expect(listed.map((workspace) => workspace.session).toSet(), names.toSet(),
|
|
102
103
|
reason: 'listWorkspaces surfaces all $_n live workspaces');
|
|
103
|
-
expect(listed.every((
|
|
104
|
+
expect(listed.every((workspace) => workspace.residency == protocol.WorkspaceResidency.WORKSPACE_RESIDENCY_MOUNTED), isTrue,
|
|
104
105
|
reason: 'every listed workspace is mounted');
|
|
105
106
|
|
|
106
107
|
// Wall-clock sanity — dozens of live workspaces must open + drive in a CI-sane budget, not minutes.
|
|
@@ -20,15 +20,18 @@ import 'package:test/test.dart';
|
|
|
20
20
|
|
|
21
21
|
String get _root => '${Directory.current.path}/../..';
|
|
22
22
|
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
23
|
-
String get _geoDeploy =>
|
|
24
|
-
|
|
23
|
+
String get _geoDeploy =>
|
|
24
|
+
'$_root/bench/scale/fixtures/estate-geo/build/estate-geo.law.usdz';
|
|
25
|
+
String get _docsDeploy =>
|
|
26
|
+
'$_root/bench/scale/fixtures/surfaced/build/surfaced.law.usdz';
|
|
25
27
|
String get _runtimeDir =>
|
|
26
28
|
Platform.environment['NOMOS_DART_RUNTIME'] ??
|
|
27
29
|
Platform.environment['NOMOS_OFFLINE_RUNTIME'] ??
|
|
28
30
|
'${Platform.environment['HOME'] ?? ''}/.holon/runtime';
|
|
29
31
|
|
|
30
32
|
String? _missingPrereq() {
|
|
31
|
-
if (!File(_repoEntry).existsSync())
|
|
33
|
+
if (!File(_repoEntry).existsSync())
|
|
34
|
+
return 'in-repo runner not found at $_repoEntry';
|
|
32
35
|
for (final e in {
|
|
33
36
|
_geoDeploy: 'bench/scale/fixtures/estate-geo',
|
|
34
37
|
_docsDeploy: 'bench/scale/fixtures/surfaced',
|
|
@@ -55,12 +58,14 @@ Future<T> _leg<T>(String name, Future<T> Function() body) async {
|
|
|
55
58
|
print('✔ DLQ $name (${sw.elapsedMilliseconds}ms)');
|
|
56
59
|
return r;
|
|
57
60
|
} catch (e, st) {
|
|
58
|
-
throw TestFailure(
|
|
61
|
+
throw TestFailure(
|
|
62
|
+
'DLQ $name FAILED after ${sw.elapsedMilliseconds}ms:\n$e\n$st');
|
|
59
63
|
}
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
void main() {
|
|
63
|
-
test(
|
|
67
|
+
test(
|
|
68
|
+
'DLQ: park → surface → refused retry → law fix → retry admits → discard drains',
|
|
64
69
|
() async {
|
|
65
70
|
final missing = _missingPrereq();
|
|
66
71
|
if (missing != null) {
|
|
@@ -69,7 +74,9 @@ void main() {
|
|
|
69
74
|
}
|
|
70
75
|
final harness = await NomosTestHarness.cluster(
|
|
71
76
|
workspaces: {
|
|
72
|
-
'writer': [
|
|
77
|
+
'writer': [
|
|
78
|
+
_geoDeploy
|
|
79
|
+
], // holds the estate law — where the work is authored
|
|
73
80
|
'app': [_docsDeploy], // the jammed workspace — NO estate law yet
|
|
74
81
|
},
|
|
75
82
|
nodeEntry: _repoEntry,
|
|
@@ -83,7 +90,8 @@ void main() {
|
|
|
83
90
|
|
|
84
91
|
// ── LEG 1: author REAL work under the estate law, and read its full intent bytes back
|
|
85
92
|
// through the production salvage lane. ──
|
|
86
|
-
final work = await _leg(
|
|
93
|
+
final work = await _leg(
|
|
94
|
+
'leg 1: author two writes + salvage their intent bytes', () async {
|
|
87
95
|
Future<void> place(String name) => writer.offerOutcome(
|
|
88
96
|
domain: 'estate',
|
|
89
97
|
directiveId: 'placeSite',
|
|
@@ -106,26 +114,32 @@ void main() {
|
|
|
106
114
|
.where((w) {
|
|
107
115
|
try {
|
|
108
116
|
final doc =
|
|
109
|
-
jsonDecode(utf8.decode(base64Decode(w['changeB64'].toString())))
|
|
117
|
+
jsonDecode(utf8.decode(base64Decode(w['changeB64'].toString())))
|
|
118
|
+
as Map;
|
|
110
119
|
return (doc['payload'] as Map?)?['domain'] == 'estate';
|
|
111
120
|
} catch (_) {
|
|
112
121
|
return false;
|
|
113
122
|
}
|
|
114
123
|
}).toList();
|
|
115
124
|
expect(pending, hasLength(2),
|
|
116
|
-
reason:
|
|
117
|
-
|
|
125
|
+
reason:
|
|
126
|
+
'salvage() returns the authored estate intents with their full bytes');
|
|
127
|
+
expect(pending.every((w) => (w['id'] ?? '').toString().isNotEmpty),
|
|
128
|
+
isTrue);
|
|
118
129
|
return pending;
|
|
119
130
|
});
|
|
120
131
|
|
|
121
132
|
// ── LEG 2: park both on the jammed workspace (the shape a domain rejection parks) and
|
|
122
133
|
// assert deadLetters() surfaces them with full provenance. ──
|
|
123
|
-
await _leg(
|
|
134
|
+
await _leg(
|
|
135
|
+
'leg 2: park as dead letters → deadLetters() surfaces provenance',
|
|
136
|
+
() async {
|
|
124
137
|
final r = await app.absorbSalvage(deadLetters: [
|
|
125
138
|
for (final w in work)
|
|
126
139
|
{
|
|
127
140
|
'id': w['id'],
|
|
128
|
-
'oid': w[
|
|
141
|
+
'oid': w[
|
|
142
|
+
'id'], // provenance slot; the entry is deduped/looked up by id
|
|
129
143
|
'changeB64': w['changeB64'],
|
|
130
144
|
'source': 'local',
|
|
131
145
|
'error': "no active installed law declares domain 'estate'",
|
|
@@ -135,32 +149,32 @@ void main() {
|
|
|
135
149
|
final dead = await app.deadLetters();
|
|
136
150
|
expect(dead, hasLength(2));
|
|
137
151
|
final entry = dead.first;
|
|
138
|
-
expect(entry
|
|
139
|
-
reason:
|
|
140
|
-
|
|
141
|
-
expect(entry
|
|
142
|
-
expect(entry.
|
|
143
|
-
reason: 'deadLetters() is metadata-only; the bytes stay in the durable queue');
|
|
152
|
+
expect(entry.domain, 'estate',
|
|
153
|
+
reason:
|
|
154
|
+
'the DLQ decodes domain/directiveId provenance from the parked bytes');
|
|
155
|
+
expect(entry.directive, 'placeSite');
|
|
156
|
+
expect(entry.source, 'local');
|
|
144
157
|
});
|
|
145
158
|
|
|
146
159
|
// ── LEG 3: retry while the law is still absent — a REFUSAL, not a loss (and, regression:
|
|
147
160
|
// not a "no remote main" throw on this local-only session). ──
|
|
148
161
|
final firstId = work.first['id'].toString();
|
|
149
|
-
await _leg(
|
|
162
|
+
await _leg(
|
|
163
|
+
'leg 3: retry refuses while the law is absent; the entry stays',
|
|
164
|
+
() async {
|
|
150
165
|
final r = await app.retryDeadLetter(firstId);
|
|
151
|
-
expect(r
|
|
166
|
+
expect(r.retried, isFalse, reason: 'the law still cannot merge it: $r');
|
|
152
167
|
final dead = await app.deadLetters();
|
|
153
|
-
expect(dead, hasLength(2),
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
.firstWhere((d) => d['id'] == firstId)['error']
|
|
157
|
-
.toString(),
|
|
168
|
+
expect(dead, hasLength(2),
|
|
169
|
+
reason: 'a refused retry NEVER drops the work');
|
|
170
|
+
expect(dead.firstWhere((d) => d.hasId() && d.id == firstId).message,
|
|
158
171
|
isNotEmpty,
|
|
159
172
|
reason: 'the entry carries the fresh refusal');
|
|
160
173
|
});
|
|
161
174
|
|
|
162
175
|
// ── LEG 4: the domain dev ships the law fix; retry UNJAMS and the work lands, readable. ──
|
|
163
|
-
await _leg(
|
|
176
|
+
await _leg(
|
|
177
|
+
'leg 4: law fix (installDomain) → retry admits → the work is on the ledger',
|
|
164
178
|
() async {
|
|
165
179
|
final usda = (jsonDecode(File(_geoDeploy).readAsStringSync())
|
|
166
180
|
as Map)['packageUsda']
|
|
@@ -178,24 +192,27 @@ void main() {
|
|
|
178
192
|
domainHash: harness.frameworkHash,
|
|
179
193
|
);
|
|
180
194
|
final r = await app.retryDeadLetter(firstId);
|
|
181
|
-
expect(r
|
|
195
|
+
expect(r.retried, isTrue, reason: 'the unjam after the law fix: $r');
|
|
182
196
|
final dead = await app.deadLetters();
|
|
183
|
-
expect(dead, hasLength(1),
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
197
|
+
expect(dead, hasLength(1),
|
|
198
|
+
reason: 'the retried entry resolved out of the queue');
|
|
199
|
+
final rows = await app
|
|
200
|
+
.query('sitesByName', {'name': 'a day of work I refuse to lose'});
|
|
201
|
+
expect(rows, hasLength(1),
|
|
202
|
+
reason: "the user's work landed and reads back");
|
|
187
203
|
});
|
|
188
204
|
|
|
189
205
|
// ── LEG 5: discard is the app's explicit drop; the queue drains; a re-retry of a gone id
|
|
190
206
|
// answers a typed refusal. ──
|
|
191
|
-
await _leg('leg 5: discard drains; retry of a gone id refuses by name',
|
|
207
|
+
await _leg('leg 5: discard drains; retry of a gone id refuses by name',
|
|
208
|
+
() async {
|
|
192
209
|
final secondId = work[1]['id'].toString();
|
|
193
210
|
final d = await app.discardDeadLetter(secondId);
|
|
194
|
-
expect(d
|
|
211
|
+
expect(d.removed, 1);
|
|
195
212
|
expect(await app.deadLetters(), isEmpty);
|
|
196
213
|
final gone = await app.retryDeadLetter(secondId);
|
|
197
|
-
expect(gone
|
|
198
|
-
expect(gone
|
|
214
|
+
expect(gone.retried, isFalse);
|
|
215
|
+
expect(gone.message, contains('no dead letter'));
|
|
199
216
|
});
|
|
200
217
|
} finally {
|
|
201
218
|
await harness.dispose().timeout(const Duration(seconds: 30),
|
|
@@ -251,7 +251,7 @@ void main() {
|
|
|
251
251
|
const Duration(seconds: 10),
|
|
252
252
|
onTimeout: () => throw StateError(
|
|
253
253
|
'listWorkspaces() hung after a local-only autoSync'));
|
|
254
|
-
expect(listed.map((
|
|
254
|
+
expect(listed.map((workspace) => workspace.session).toSet(),
|
|
255
255
|
containsAll({'home', 'docs-ws', _keyed, 'reader'}));
|
|
256
256
|
await home.stopAutoSync().timeout(const Duration(seconds: 10),
|
|
257
257
|
onTimeout: () => throw StateError(
|
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
@TestOn('vm')
|
|
6
6
|
library;
|
|
7
7
|
|
|
8
|
+
import 'dart:convert';
|
|
8
9
|
import 'dart:io';
|
|
9
10
|
|
|
10
11
|
import 'package:nomos_client/src/nms_conformance_runtime.dart';
|
|
12
|
+
import 'package:crypto/crypto.dart' show sha256;
|
|
11
13
|
import 'package:test/test.dart';
|
|
12
14
|
|
|
13
|
-
import '../../../bench/scale/fixtures/estate-birth/build/dart/src/generated/
|
|
15
|
+
import '../../../bench/scale/fixtures/estate-birth/build/dart/src/generated/estate.application.dart';
|
|
14
16
|
|
|
15
17
|
String get _root => '${Directory.current.path}/../..';
|
|
16
18
|
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
@@ -54,30 +56,24 @@ void main() {
|
|
|
54
56
|
);
|
|
55
57
|
try {
|
|
56
58
|
final session = harness.session(_workspace);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// application; it cannot import the private generated client implementation.
|
|
60
|
-
await nomosInternalBindHomeApplication(session, _owner);
|
|
59
|
+
final application =
|
|
60
|
+
await nomosInternalBindEstateApplication(session, _owner);
|
|
61
61
|
|
|
62
|
-
// 1-2.
|
|
63
|
-
|
|
62
|
+
// 1-2. Offer a normal generated business call on the snapshot-opened local
|
|
63
|
+
// session. This test is about local sync, not the separate child-workspace
|
|
64
|
+
// birth ceremony.
|
|
65
|
+
final bornAt = DateTime.now().toUtc().toIso8601String();
|
|
66
|
+
final facts = SeedEstateWorkspacePayload(
|
|
64
67
|
owner: _owner,
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
bornAt:
|
|
68
|
+
estateName:
|
|
69
|
+
'estate-${sha256.convert(utf8.encode(_owner)).toString().substring(0, 40)}',
|
|
70
|
+
bornAt: bornAt,
|
|
68
71
|
);
|
|
69
|
-
|
|
70
|
-
domain: 'home',
|
|
71
|
-
directiveId: 'birthEstateWorkspace',
|
|
72
|
-
payload: facts.nmsInternalToPayloadJson(),
|
|
73
|
-
actor: _owner,
|
|
74
|
-
);
|
|
75
|
-
expect(birth.born, hasLength(1));
|
|
72
|
+
await application.estateOwners.seedEstateWorkspace(facts);
|
|
76
73
|
|
|
77
|
-
// 3.
|
|
78
|
-
// Generated tenant clients deliberately do not expose this framework query.
|
|
74
|
+
// 3. Read through the generated, typed application surface.
|
|
79
75
|
final before =
|
|
80
|
-
await
|
|
76
|
+
await application.estateOwners.estatesByOwner(owner: _owner);
|
|
81
77
|
expect(before, hasLength(1),
|
|
82
78
|
reason: 'the offer must be readable BEFORE sync');
|
|
83
79
|
final statusBefore = await session.custodyStatus();
|
|
@@ -86,7 +82,7 @@ void main() {
|
|
|
86
82
|
|
|
87
83
|
// 4. local-only sync — must be a no-op in EVERY observable respect.
|
|
88
84
|
final summary = await session.sync(admit: true);
|
|
89
|
-
expect(summary
|
|
85
|
+
expect(summary.localOnly, true,
|
|
90
86
|
reason: 'a local:// session syncs as the documented no-op');
|
|
91
87
|
|
|
92
88
|
// 5. THE REGRESSION (co2): custody unchanged, same query returns the SAME row.
|
|
@@ -95,14 +91,15 @@ void main() {
|
|
|
95
91
|
reason:
|
|
96
92
|
'local-only sync must not consume/move pending local intents');
|
|
97
93
|
final after =
|
|
98
|
-
await
|
|
94
|
+
await application.estateOwners.estatesByOwner(owner: _owner);
|
|
99
95
|
expect(after, hasLength(1),
|
|
100
96
|
reason:
|
|
101
97
|
'local-only sync must be observationally inert — the row must still read');
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
expect(
|
|
105
|
-
|
|
98
|
+
expect(after.single.owner, before.single.owner,
|
|
99
|
+
reason: 'the SAME typed business record remains visible');
|
|
100
|
+
expect(after.single.estateName, before.single.estateName);
|
|
101
|
+
expect(after.single.bornAt, before.single.bornAt);
|
|
102
|
+
expect(after.single.ref.toString(), before.single.ref.toString());
|
|
106
103
|
} finally {
|
|
107
104
|
await harness.dispose();
|
|
108
105
|
}
|