@githolon/dsl 0.101.6 β 0.102.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/dart/CHANGELOG.md +13 -0
- package/dart/lib/nomos_client.dart +3 -52
- package/dart/lib/src/dev_cloud.dart +5 -0
- package/dart/lib/src/home.dart +10 -15
- package/dart/lib/src/nms_conformance_runtime.dart +12 -0
- package/dart/lib/src/nms_generated_runtime.dart +29 -0
- package/dart/lib/src/nms_test_environment.dart +8 -0
- package/dart/lib/src/node_runner_transport.dart +56 -18
- package/dart/lib/src/provider.dart +5 -1
- package/dart/lib/src/record_origin.dart +55 -0
- package/dart/lib/src/runner/node-entry.mjs +9105 -0
- package/dart/lib/src/runner/offline-install.mjs +4729 -0
- package/dart/lib/src/session_bound.dart +23 -10
- package/dart/lib/src/source_trust.dart +51 -0
- package/dart/lib/src/test_harness.dart +490 -58
- package/dart/lib/src/webview_bridge.dart +167 -58
- package/dart/lib/src/workspace_name.dart +7 -1
- package/dart/pubspec.lock +3 -3
- package/dart/pubspec.yaml +4 -2
- package/dart/test/acquire_session_credentials_test.dart +34 -17
- package/dart/test/acquire_session_test.dart +109 -24
- package/dart/test/adv_realm_interference_test.dart +227 -99
- package/dart/test/bundle_version_pairing_test.dart +1 -1
- package/dart/test/cloud_bound_topology_test.dart +1 -1
- package/dart/test/cluster_attested_test.dart +52 -35
- package/dart/test/cluster_births_test.dart +5 -2
- package/dart/test/cluster_device_keyed_acceptance_test.dart +5 -2
- package/dart/test/cluster_local_autosync_test.dart +5 -2
- package/dart/test/cluster_scale_test.dart +5 -2
- package/dart/test/compat_test.dart +1 -1
- package/dart/test/dlq_session_surface_test.dart +2 -1
- package/dart/test/errors_test.dart +1 -1
- package/dart/test/first_app_gauntlet_test.dart +78 -51
- package/dart/test/four_layer_nested_topology_test.dart +1 -1
- package/dart/test/harness_phase_timeout_test.dart +1 -1
- package/dart/test/home_parity_test.dart +9 -5
- package/dart/test/local_sync_inert_test.dart +22 -14
- package/dart/test/node_runner_transport_test.dart +9 -5
- package/dart/test/null_slot_test.dart +1 -1
- package/dart/test/parent_signer_recovery_test.dart +5 -2
- package/dart/test/schema_validation_test.dart +1 -1
- package/dart/test/sealing_client_test.dart +2 -1
- package/dart/test/session_bound_test.dart +92 -119
- package/dart/test/stale_bundle_test.dart +1 -1
- package/dart/test/test_harness_test.dart +77 -22
- package/dart/test/vo_read_typing_test.dart +32 -14
- package/dart/test/watch_after_offer_stall_test.dart +2 -1
- package/dart/test/webview_bridge_test.dart +17 -4
- package/dart/test/wire_test.dart +1 -1
- package/dart_types/CHANGELOG.md +8 -0
- package/dart_types/lib/nomos_types.dart +7 -6
- package/dart_types/lib/src/activity.dart +23 -13
- package/dart_types/lib/src/foreign_ref.dart +61 -112
- package/dart_types/lib/src/invitation.dart +96 -0
- package/dart_types/lib/src/nomos_ref.dart +97 -59
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/surfaced_test.dart +125 -19
- package/package.json +7 -3
- package/runtime/generated-application-host.js +8851 -0
- package/src/activity.ts +2 -2
- package/src/aggregate.ts +24 -1
- package/src/application_composition.ts +282 -0
- package/src/application_topology.ts +151 -0
- package/src/attested_core.ts +2 -2
- package/src/authoring_context.ts +9 -0
- package/src/birth_meter_ensure.ts +1 -1
- package/src/births.ts +53 -5
- package/src/build_package.ts +79 -71
- package/src/business_birth.ts +93 -10
- package/src/clear_directive.ts +3 -1
- package/src/codegen_dart.ts +369 -420
- package/src/codegen_dart_application.ts +1508 -0
- package/src/codegen_dart_client.ts +156 -95
- package/src/codegen_dart_scenario.ts +299 -0
- package/src/codegen_openapi.ts +8 -3
- package/src/codegen_proof.ts +1113 -1137
- package/src/codegen_provider_dart.ts +12 -1
- package/src/codegen_ts.ts +1176 -496
- package/src/compat_package_main.ts +22 -10
- package/src/compile_package_main.ts +1536 -337
- package/src/contract.ts +10 -4
- package/src/dart_barrel_guard.ts +5 -1
- package/src/describe_model.ts +13 -5
- package/src/directive.ts +244 -5
- package/src/engine_entry.ts +56 -13
- package/src/engine_javascript.ts +31 -0
- package/src/evolve_preview.ts +3 -12
- package/src/framework/application_home.ts +24 -10
- package/src/framework/capability.ts +15 -1
- package/src/framework/domain_lifecycle.ts +8 -0
- package/src/framework/framework_aggregates.ts +2 -2
- package/src/framework/genesis.ts +11 -1
- package/src/framework/home.ts +178 -75
- package/src/framework/impure_capability.ts +24 -7
- package/src/framework/relations.ts +13 -5
- package/src/framework/workspaces.ts +13 -12
- package/src/generated_client_names.ts +42 -3
- package/src/idof_lint.ts +65 -13
- package/src/index.ts +46 -12
- package/src/intent.ts +587 -0
- package/src/law_check.ts +64 -16
- package/src/precondition.ts +16 -4
- package/src/read_projection_namespace.ts +2 -7
- package/src/recipient.ts +33 -0
- package/src/relation_schema.ts +93 -0
- package/src/secondary_ensure.ts +11 -2
- package/src/shape_compat.ts +11 -1
- package/src/usd.ts +30 -8
- package/src/usd_layers.ts +447 -246
- package/src/usd_state.ts +1 -1
- package/src/value_object.ts +49 -13
- package/src/wire_encode.ts +124 -26
- package/src/workspace_sharing.ts +269 -0
- package/src/zod_introspection.ts +84 -0
- package/dart/example/nomos_secure_key_store.dart +0 -82
- package/dart/example/nomos_webview_transport.dart +0 -63
- package/dart/lib/nomos_headless.dart +0 -50
- package/dart/lib/testing.dart +0 -14
- package/dart/test/co2_product_gauntlet_test.dart +0 -244
- package/src/framework/identity.ts +0 -537
|
@@ -16,7 +16,7 @@ library;
|
|
|
16
16
|
|
|
17
17
|
import 'dart:io';
|
|
18
18
|
|
|
19
|
-
import 'package:nomos_client/
|
|
19
|
+
import 'package:nomos_client/src/nms_conformance_runtime.dart';
|
|
20
20
|
import 'package:nomos_types/nomos_types.dart'
|
|
21
21
|
show ParentSignerEnrollmentMissing, NomosRecommendedAction;
|
|
22
22
|
import 'package:test/test.dart';
|
|
@@ -25,7 +25,10 @@ String get _root => '${Directory.current.path}/../..';
|
|
|
25
25
|
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
26
26
|
String get _deploy =>
|
|
27
27
|
'$_root/bench/scale/fixtures/estate-birth-signed/build/estate-birth-signed.deploy.json';
|
|
28
|
-
String get _runtimeDir =>
|
|
28
|
+
String get _runtimeDir =>
|
|
29
|
+
Platform.environment['NOMOS_DART_RUNTIME'] ??
|
|
30
|
+
Platform.environment['NOMOS_OFFLINE_RUNTIME'] ??
|
|
31
|
+
'${Platform.environment['HOME'] ?? ''}/.holon/runtime';
|
|
29
32
|
|
|
30
33
|
const _ws = 'estate-birth-signed';
|
|
31
34
|
const _uid = 'sample-owner';
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// wasm = determinism. No native, no wasmtime, no 2nd artifact, no domain-JS on bare V8.
|
|
6
6
|
// If a file isn't this / hosting this / authoring for this / proving this β it's gone.
|
|
7
7
|
|
|
8
|
-
import 'package:nomos_client/
|
|
8
|
+
import 'package:nomos_client/src/nms_generated_runtime.dart';
|
|
9
9
|
import 'package:test/test.dart';
|
|
10
10
|
|
|
11
11
|
void main() {
|
|
@@ -12,7 +12,7 @@ import 'dart:async';
|
|
|
12
12
|
import 'dart:convert';
|
|
13
13
|
import 'dart:io';
|
|
14
14
|
|
|
15
|
-
import 'package:nomos_client/
|
|
15
|
+
import 'package:nomos_client/src/nms_conformance_runtime.dart';
|
|
16
16
|
import 'package:test/test.dart';
|
|
17
17
|
|
|
18
18
|
String get _root => '${Directory.current.path}/../..';
|
|
@@ -22,6 +22,7 @@ String get _deploy => '$_fixture/build/encrypted-notes.deploy.json';
|
|
|
22
22
|
String get _lockfile => '$_fixture/nomos.stable-ids.json';
|
|
23
23
|
String get _runtimeDir =>
|
|
24
24
|
Platform.environment['NOMOS_DART_RUNTIME'] ??
|
|
25
|
+
Platform.environment['NOMOS_OFFLINE_RUNTIME'] ??
|
|
25
26
|
'${Platform.environment['HOME'] ?? ''}/.holon/runtime';
|
|
26
27
|
|
|
27
28
|
const _domain = 'encrypted_notes';
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
// SESSION-BOUND
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// SESSION-BOUND APPLICATIONS β framework acceptance against the REAL wasm on a local plane.
|
|
2
|
+
// Generated clients are private implementation parts. These tests bind the generated application
|
|
3
|
+
// surface and use raw session calls only where they deliberately exercise nomos_client internals:
|
|
4
4
|
//
|
|
5
|
-
// engine β mount β
|
|
6
|
-
// workspace β engine.follow(ref) β a second
|
|
5
|
+
// engine β mount β generated application bind (preflight) β an internal fixture birth landing in
|
|
6
|
+
// the session workspace β engine.follow(ref) β a second application β a business record operation.
|
|
7
7
|
//
|
|
8
|
-
// Plus the typed failure modes: bind on a deliberately WRONG session throws
|
|
8
|
+
// Plus the typed failure modes: application bind on a deliberately WRONG session throws
|
|
9
9
|
// DomainClientSessionMismatch with every spec field; a dispatch naming an uninstalled domain
|
|
10
10
|
// surfaces as DomainNotInstalledForSession (the context-rich replacement of the kernel's generic
|
|
11
|
-
// "no active installed law declares domain 'x'")
|
|
12
|
-
//
|
|
11
|
+
// "no active installed law declares domain 'x'"). Raw calls here are framework-test setup only;
|
|
12
|
+
// they are not a supported tenant lane and are not exported by nomos_client.
|
|
13
13
|
@TestOn('vm')
|
|
14
14
|
library;
|
|
15
15
|
|
|
@@ -19,15 +19,11 @@ import 'dart:io';
|
|
|
19
19
|
import 'package:crypto/crypto.dart' show sha256;
|
|
20
20
|
|
|
21
21
|
// ignore: deprecated_member_use
|
|
22
|
-
import 'package:nomos_client/
|
|
22
|
+
import 'package:nomos_client/src/nms_conformance_runtime.dart';
|
|
23
23
|
import 'package:test/test.dart';
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// typed surface. ZERO raw strings needed below.
|
|
28
|
-
import '../../../bench/scale/fixtures/estate-birth/build/dart/src/generated/home.client.dart';
|
|
29
|
-
import '../../../bench/scale/fixtures/estate-birth/build/dart/src/generated/estate.client.dart'
|
|
30
|
-
show EstateClient, SeedEstateWorkspacePayload;
|
|
25
|
+
import '../../../bench/scale/fixtures/estate-birth/build/dart/src/generated/home.application.dart';
|
|
26
|
+
import '../../../bench/scale/fixtures/estate-birth/build/dart/src/generated/estate.application.dart';
|
|
31
27
|
|
|
32
28
|
String get _root => '${Directory.current.path}/../..';
|
|
33
29
|
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
@@ -37,13 +33,15 @@ String get _otherDeployJson =>
|
|
|
37
33
|
'$_root/bench/scale/fixtures/surfaced/build/surfaced.deploy.json';
|
|
38
34
|
String get _runtimeDir =>
|
|
39
35
|
Platform.environment['NOMOS_DART_RUNTIME'] ??
|
|
36
|
+
Platform.environment['NOMOS_OFFLINE_RUNTIME'] ??
|
|
40
37
|
'${Platform.environment['HOME'] ?? ''}/.holon/runtime';
|
|
41
38
|
|
|
42
39
|
const _workspace = 'estate-birth';
|
|
43
40
|
const _owner = 'user:session-bound-owner';
|
|
44
41
|
|
|
45
42
|
String? _missingPrereq() {
|
|
46
|
-
if (!File(_repoEntry).existsSync())
|
|
43
|
+
if (!File(_repoEntry).existsSync())
|
|
44
|
+
return 'in-repo runner not found at $_repoEntry';
|
|
47
45
|
if (!File(_deployJson).existsSync() || !File(_otherDeployJson).existsSync()) {
|
|
48
46
|
return 'compile the fixtures: (cd bench/scale/fixtures/estate-birth && node ../../../../dsl/compile_package.mjs); '
|
|
49
47
|
'(cd bench/scale/fixtures/surfaced && node ../../../../dsl/compile_package.mjs)';
|
|
@@ -67,10 +65,31 @@ Future<NomosTestHarness> _harness() => NomosTestHarness.ephemeral(
|
|
|
67
65
|
workspace: _workspace,
|
|
68
66
|
);
|
|
69
67
|
|
|
68
|
+
Future<NomosOfferOutcome> _birthEstate(
|
|
69
|
+
NomosSession session,
|
|
70
|
+
NomosTestHarness harness, {
|
|
71
|
+
String owner = _owner,
|
|
72
|
+
bool enrollOwnerKey = true,
|
|
73
|
+
}) {
|
|
74
|
+
final facts = BirthEstateWorkspacePayload(
|
|
75
|
+
owner: owner,
|
|
76
|
+
frameworkHash: harness.frameworkHash!,
|
|
77
|
+
lawHash: harness.lawHashes(harness.workspace).first,
|
|
78
|
+
bornAt: DateTime.now().toUtc().toIso8601String(),
|
|
79
|
+
ownerKeyHash: enrollOwnerKey ? _ownerKeyHash(harness) : null,
|
|
80
|
+
);
|
|
81
|
+
return session.offerOutcome(
|
|
82
|
+
domain: 'home',
|
|
83
|
+
directiveId: 'birthEstateWorkspace',
|
|
84
|
+
payload: facts.nmsInternalToPayloadJson(),
|
|
85
|
+
actor: owner,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
70
89
|
void main() {
|
|
71
90
|
test(
|
|
72
|
-
'
|
|
73
|
-
'
|
|
91
|
+
'generated applications: mount β bind (preflight) β fixture birth β follow(ref) β '
|
|
92
|
+
'second application β typed record operation', () async {
|
|
74
93
|
final skip = _missingPrereq();
|
|
75
94
|
if (skip != null) {
|
|
76
95
|
markTestSkipped(skip);
|
|
@@ -82,48 +101,50 @@ void main() {
|
|
|
82
101
|
final home = harness.session(harness.workspace);
|
|
83
102
|
|
|
84
103
|
// BIND (ask 2/5): the async preflight β reachable, domain installed, shape-compatible.
|
|
85
|
-
final
|
|
86
|
-
|
|
87
|
-
expect(
|
|
88
|
-
reason: 'inspectable session context (ask 3)');
|
|
104
|
+
final homeApplication =
|
|
105
|
+
await nomosInternalBindHomeApplication(home, _owner);
|
|
106
|
+
expect(homeApplication.activity, isA<HomeActivity>());
|
|
89
107
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
frameworkHash: harness.frameworkHash!,
|
|
95
|
-
lawHash: harness.lawHashes(harness.workspace).first,
|
|
96
|
-
bornAt: DateTime.now().toUtc().toIso8601String(),
|
|
97
|
-
ownerKeyHash: _ownerKeyHash(harness),
|
|
98
|
-
),
|
|
99
|
-
);
|
|
108
|
+
// This fixture predates businessBirth and intentionally keeps its kernel birth
|
|
109
|
+
// primitive out of the generated tenant application. The raw offer is framework
|
|
110
|
+
// setup; the generated application remains the only public business surface.
|
|
111
|
+
final birth = await _birthEstate(home, harness);
|
|
100
112
|
expect(birth.workspace, harness.workspace,
|
|
101
|
-
reason:
|
|
102
|
-
|
|
113
|
+
reason:
|
|
114
|
+
'the birth offer landed in the SESSION workspace, no loose parent');
|
|
115
|
+
expect(birth.gatewayVerdict, isNull,
|
|
116
|
+
reason: 'the local offer lane, not the legacy relay');
|
|
103
117
|
expect(birth.born, hasLength(1));
|
|
104
118
|
expect(birth.birthOutcomes.first['status'], 'born');
|
|
105
119
|
|
|
106
120
|
// FOLLOW THE REF (ask 8): NomosRef in, mounted session out β never a string pair.
|
|
107
|
-
final estate = await harness.bridge.follow(birth.bornRefs.single,
|
|
121
|
+
final estate = await harness.bridge.follow(birth.bornRefs.single,
|
|
122
|
+
authorSecret: harness.deviceSecret(harness.workspace));
|
|
108
123
|
expect(estate.workspace, birth.born.single);
|
|
109
124
|
|
|
110
125
|
// SECOND CLIENT + a typed read AND a directive on it.
|
|
111
|
-
final
|
|
112
|
-
|
|
113
|
-
|
|
126
|
+
final estateApplication =
|
|
127
|
+
await nomosInternalBindEstateApplication(estate, _owner);
|
|
128
|
+
final rows =
|
|
129
|
+
await estateApplication.estateOwners.estatesByOwner(owner: _owner);
|
|
130
|
+
expect(rows, hasLength(1),
|
|
131
|
+
reason: 'the child genesis seed reads back typed');
|
|
114
132
|
final seeded = rows.single;
|
|
115
133
|
// A directive on the second client: re-ensure the singleton with the SAME seeded values
|
|
116
134
|
// (`.ensures` β idempotent; the squat invariant re-accepts its own binding).
|
|
117
|
-
|
|
135
|
+
await estateApplication.estateOwners
|
|
136
|
+
.seedEstateWorkspace(SeedEstateWorkspacePayload(
|
|
118
137
|
owner: seeded.owner!,
|
|
119
138
|
estateName: seeded.estateName!,
|
|
120
139
|
bornAt: seeded.bornAt!,
|
|
121
140
|
));
|
|
122
|
-
|
|
141
|
+
final afterReseed =
|
|
142
|
+
await estateApplication.estateOwners.estatesByOwner(owner: _owner);
|
|
143
|
+
expect(afterReseed, hasLength(1));
|
|
123
144
|
|
|
124
145
|
// ask 9: the harness diagnostic names the installed domains.
|
|
125
146
|
await harness.assertDomainInstalled(
|
|
126
|
-
workspace: estate.workspace, domain:
|
|
147
|
+
workspace: estate.workspace, domain: 'estate');
|
|
127
148
|
} finally {
|
|
128
149
|
await harness.dispose();
|
|
129
150
|
}
|
|
@@ -131,7 +152,8 @@ void main() {
|
|
|
131
152
|
|
|
132
153
|
test(
|
|
133
154
|
'session.watchPendingConflicts() EMITS (the 0.48.0 regression: the session-level stream '
|
|
134
|
-
'registered on an object with no message loop and never emitted)',
|
|
155
|
+
'registered on an object with no message loop and never emitted)',
|
|
156
|
+
() async {
|
|
135
157
|
final skip = _missingPrereq();
|
|
136
158
|
if (skip != null) {
|
|
137
159
|
markTestSkipped(skip);
|
|
@@ -146,32 +168,26 @@ void main() {
|
|
|
146
168
|
// emitted, regardless of ticks. The fix delegates to the parent plane like every other
|
|
147
169
|
// reactive read; the first tick must deliver a snapshot (empty β no Surfaced law here).
|
|
148
170
|
final first = session.watchPendingConflicts().first;
|
|
149
|
-
|
|
150
|
-
await
|
|
151
|
-
payload: BirthEstateWorkspacePayload(
|
|
152
|
-
owner: _owner,
|
|
153
|
-
frameworkHash: harness.frameworkHash!,
|
|
154
|
-
lawHash: harness.lawHashes(harness.workspace).first,
|
|
155
|
-
bornAt: DateTime.now().toUtc().toIso8601String(),
|
|
156
|
-
ownerKeyHash: _ownerKeyHash(harness),
|
|
157
|
-
),
|
|
158
|
-
);
|
|
171
|
+
await nomosInternalBindHomeApplication(session, _owner);
|
|
172
|
+
await _birthEstate(session, harness);
|
|
159
173
|
final rows = await first.timeout(const Duration(seconds: 30));
|
|
160
174
|
expect(rows, isEmpty,
|
|
161
|
-
reason:
|
|
175
|
+
reason:
|
|
176
|
+
'no Surfaced law in this fixture β the point is the stream EMITS at all');
|
|
162
177
|
} finally {
|
|
163
178
|
await harness.dispose();
|
|
164
179
|
}
|
|
165
180
|
}, timeout: const Timeout(Duration(minutes: 4)));
|
|
166
181
|
|
|
167
|
-
test(
|
|
182
|
+
test(
|
|
183
|
+
'bind on a deliberately WRONG session throws DomainClientSessionMismatch with every field',
|
|
168
184
|
() async {
|
|
169
185
|
final skip = _missingPrereq();
|
|
170
186
|
if (skip != null) {
|
|
171
187
|
markTestSkipped(skip);
|
|
172
188
|
return;
|
|
173
189
|
}
|
|
174
|
-
// TWO workspaces, TWO different laws, ONE plane β binding
|
|
190
|
+
// TWO workspaces, TWO different laws, ONE plane β binding HomeApplication to the docs
|
|
175
191
|
// workspace is the deliberate wrong-session case (wrong-package-mounted).
|
|
176
192
|
final harness = await NomosTestHarness.cluster(
|
|
177
193
|
nodeEntry: _repoEntry,
|
|
@@ -185,18 +201,19 @@ void main() {
|
|
|
185
201
|
final wrong = harness.session('docs-ws');
|
|
186
202
|
Object? thrown;
|
|
187
203
|
try {
|
|
188
|
-
await
|
|
204
|
+
await nomosInternalBindHomeApplication(wrong, null);
|
|
189
205
|
} catch (e) {
|
|
190
206
|
thrown = e;
|
|
191
207
|
}
|
|
192
208
|
expect(thrown, isA<DomainClientSessionMismatch>(),
|
|
193
|
-
reason:
|
|
209
|
+
reason:
|
|
210
|
+
'the mismatch fires at CONSTRUCTION, not two clicks into the app');
|
|
194
211
|
final m = thrown! as DomainClientSessionMismatch;
|
|
195
212
|
expect(m.sessionId, 'docs-ws');
|
|
196
213
|
expect(m.sessionWorkspace, 'docs-ws');
|
|
197
|
-
expect(m.expectedDomainKey,
|
|
198
|
-
expect(m.expectedDomainHash,
|
|
199
|
-
expect(m.expectedInterfaceHash,
|
|
214
|
+
expect(m.expectedDomainKey, 'home');
|
|
215
|
+
expect(m.expectedDomainHash, isNotEmpty);
|
|
216
|
+
expect(m.expectedInterfaceHash, isNotEmpty);
|
|
200
217
|
expect(m.activeDomainKeys, isNot(contains(m.expectedDomainKey)));
|
|
201
218
|
expect(m.cause, SessionMismatchCause.wrongPackageMounted,
|
|
202
219
|
reason: 'the docs workspace RUNS law β just not this domain');
|
|
@@ -204,10 +221,12 @@ void main() {
|
|
|
204
221
|
expect(m.toString(), contains(m.expectedDomainKey));
|
|
205
222
|
|
|
206
223
|
// The RIGHT session still binds (the plane is unharmed).
|
|
207
|
-
await
|
|
224
|
+
await nomosInternalBindHomeApplication(
|
|
225
|
+
harness.session(harness.workspace), null);
|
|
208
226
|
|
|
209
|
-
//
|
|
210
|
-
|
|
227
|
+
// The other generated application gets the same typed refusal without exposing
|
|
228
|
+
// its private interface descriptor.
|
|
229
|
+
await expectLater(nomosInternalBindEstateApplication(wrong, null),
|
|
211
230
|
throwsA(isA<DomainClientSessionMismatch>()));
|
|
212
231
|
} finally {
|
|
213
232
|
await harness.dispose();
|
|
@@ -215,8 +234,8 @@ void main() {
|
|
|
215
234
|
}, timeout: const Timeout(Duration(minutes: 4)));
|
|
216
235
|
|
|
217
236
|
test(
|
|
218
|
-
'a dispatch naming an UNINSTALLED domain surfaces as
|
|
219
|
-
'
|
|
237
|
+
'a framework-test dispatch naming an UNINSTALLED domain surfaces as '
|
|
238
|
+
'DomainNotInstalledForSession with context', () async {
|
|
220
239
|
final skip = _missingPrereq();
|
|
221
240
|
if (skip != null) {
|
|
222
241
|
markTestSkipped(skip);
|
|
@@ -228,7 +247,9 @@ void main() {
|
|
|
228
247
|
Object? thrown;
|
|
229
248
|
try {
|
|
230
249
|
await session.offerOutcome(
|
|
231
|
-
domain: 'not_installed_domain',
|
|
250
|
+
domain: 'not_installed_domain',
|
|
251
|
+
directiveId: 'doIt',
|
|
252
|
+
payload: const {});
|
|
232
253
|
} catch (e) {
|
|
233
254
|
thrown = e;
|
|
234
255
|
}
|
|
@@ -237,61 +258,13 @@ void main() {
|
|
|
237
258
|
expect(err.expectedDomainKey, 'not_installed_domain');
|
|
238
259
|
expect(err.sessionWorkspace, harness.workspace);
|
|
239
260
|
expect(err.requestedWorkspace, harness.workspace);
|
|
240
|
-
expect(err.activeDomainKeys, contains(
|
|
261
|
+
expect(err.activeDomainKeys, contains('home'),
|
|
241
262
|
reason: 'the error NAMES what the workspace actually runs');
|
|
242
|
-
expect(err.possibleCauses,
|
|
243
|
-
|
|
263
|
+
expect(err.possibleCauses,
|
|
264
|
+
DomainNotInstalledForSession.defaultPossibleCauses);
|
|
265
|
+
expect(err.underlying.toString(),
|
|
266
|
+
contains('no active installed law declares domain'));
|
|
244
267
|
expect(err.toString(), contains('Possible causes'));
|
|
245
|
-
|
|
246
|
-
// THE DEPRECATED SHIM still functions: the engine-level generic write authors fine.
|
|
247
|
-
// ignore: deprecated_member_use
|
|
248
|
-
final head = await session.offerDirective(
|
|
249
|
-
domain: HomeClient.interface.domain,
|
|
250
|
-
directiveId: 'birthEstateWorkspace',
|
|
251
|
-
payload: {
|
|
252
|
-
'owner': 'user:legacy-lane-owner',
|
|
253
|
-
'frameworkHash': harness.frameworkHash,
|
|
254
|
-
'lawHash': harness.lawHashes(harness.workspace).first,
|
|
255
|
-
'bornAt': DateTime.now().toUtc().toIso8601String(),
|
|
256
|
-
},
|
|
257
|
-
actor: 'user:legacy-lane-owner',
|
|
258
|
-
);
|
|
259
|
-
expect(head, isNotEmpty, reason: 'deprecation, not breakage β the old lane still writes');
|
|
260
|
-
} finally {
|
|
261
|
-
await harness.dispose();
|
|
262
|
-
}
|
|
263
|
-
}, timeout: const Timeout(Duration(minutes: 4)));
|
|
264
|
-
|
|
265
|
-
test('the SYNC constructor lazily preflights on first dispatch (the can\'t-await path)',
|
|
266
|
-
() async {
|
|
267
|
-
final skip = _missingPrereq();
|
|
268
|
-
if (skip != null) {
|
|
269
|
-
markTestSkipped(skip);
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
final harness = await NomosTestHarness.cluster(
|
|
273
|
-
nodeEntry: _repoEntry,
|
|
274
|
-
runtimeDir: _runtimeDir,
|
|
275
|
-
workspaces: {
|
|
276
|
-
_workspace: [_deployJson],
|
|
277
|
-
'docs-ws': [_otherDeployJson],
|
|
278
|
-
},
|
|
279
|
-
);
|
|
280
|
-
try {
|
|
281
|
-
// Constructing synchronously on the wrong session succeeds (no await possible here)β¦
|
|
282
|
-
final client = HomeClient(harness.session('docs-ws'), actor: _owner);
|
|
283
|
-
// β¦but the FIRST dispatch runs the same preflight and throws the same typed mismatch.
|
|
284
|
-
await expectLater(
|
|
285
|
-
client.birthEstateWorkspace(
|
|
286
|
-
payload: BirthEstateWorkspacePayload(
|
|
287
|
-
owner: _owner,
|
|
288
|
-
frameworkHash: harness.frameworkHash!,
|
|
289
|
-
lawHash: harness.lawHashes(harness.workspace).first,
|
|
290
|
-
bornAt: DateTime.now().toUtc().toIso8601String(),
|
|
291
|
-
),
|
|
292
|
-
),
|
|
293
|
-
throwsA(isA<DomainClientSessionMismatch>()),
|
|
294
|
-
);
|
|
295
268
|
} finally {
|
|
296
269
|
await harness.dispose();
|
|
297
270
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// bundle's self-stamped version; the bridge refuses stale/pre-stamp bundles LOUDLY at boot.
|
|
4
4
|
import 'dart:async';
|
|
5
5
|
|
|
6
|
-
import 'package:nomos_client/
|
|
6
|
+
import 'package:nomos_client/src/nms_conformance_runtime.dart';
|
|
7
7
|
import 'package:test/test.dart';
|
|
8
8
|
|
|
9
9
|
class _StampedRunner implements BridgeTransport {
|
|
@@ -11,7 +11,7 @@ library;
|
|
|
11
11
|
|
|
12
12
|
import 'dart:io';
|
|
13
13
|
|
|
14
|
-
import 'package:nomos_client/
|
|
14
|
+
import 'package:nomos_client/src/nms_conformance_runtime.dart';
|
|
15
15
|
import 'package:test/test.dart';
|
|
16
16
|
|
|
17
17
|
String get _repoEntry =>
|
|
@@ -19,26 +19,63 @@ String get _repoEntry =>
|
|
|
19
19
|
String get _guestbookDeployJson =>
|
|
20
20
|
'${Directory.current.path}/../../examples/guestbook/build/guestbook.deploy.json';
|
|
21
21
|
String get _runtimeDir =>
|
|
22
|
+
Platform.environment['NOMOS_DART_RUNTIME'] ??
|
|
23
|
+
Platform.environment['NOMOS_OFFLINE_RUNTIME'] ??
|
|
22
24
|
'${Platform.environment['HOME'] ?? ''}/.holon/runtime';
|
|
23
25
|
// The built CLI bin β drives the `githolon harness-install` SUBCOMMAND lane (the published-harness path).
|
|
24
|
-
String get _cliDist =>
|
|
25
|
-
'${Directory.current.path}/../../cli/dist/cli.mjs';
|
|
26
|
+
String get _cliDist => '${Directory.current.path}/../../cli/dist/cli.mjs';
|
|
26
27
|
|
|
27
28
|
/// The prerequisite that isn't installable in-test (a warm runtime cache + the compiled fixture). Returns a
|
|
28
29
|
/// skip reason, or null when everything the harness needs is present.
|
|
29
30
|
String? _missingPrereq() {
|
|
30
|
-
if (!File(_repoEntry).existsSync())
|
|
31
|
+
if (!File(_repoEntry).existsSync())
|
|
32
|
+
return 'in-repo runner not found at $_repoEntry';
|
|
31
33
|
if (!File(_guestbookDeployJson).existsSync()) {
|
|
32
34
|
return 'compile the fixture first: cd examples/guestbook && npx nomos-compile';
|
|
33
35
|
}
|
|
34
36
|
if (!File('$_runtimeDir/holon-testmint.wasm').existsSync() ||
|
|
35
37
|
!File('$_runtimeDir/packages.json').existsSync()) {
|
|
36
|
-
return '
|
|
38
|
+
return 'provide the coordinated candidate with NOMOS_DART_RUNTIME, or warm ~/.holon/runtime once';
|
|
37
39
|
}
|
|
38
40
|
return null;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
void main() {
|
|
44
|
+
test(
|
|
45
|
+
'convergence never reports rejected or dead-lettered business work as success',
|
|
46
|
+
() {
|
|
47
|
+
expect(
|
|
48
|
+
nmsPermanentConvergenceFailure({
|
|
49
|
+
'stillAhead': false,
|
|
50
|
+
'admission': {
|
|
51
|
+
'ok': true,
|
|
52
|
+
'sessions': [
|
|
53
|
+
{
|
|
54
|
+
'rejected': [
|
|
55
|
+
{'directiveId': 'commissionAsset', 'error': 'event-mismatch'}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
'deadLetters': [
|
|
60
|
+
{'directiveId': 'commissionAsset', 'error': 'event-mismatch'}
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
contains('commissionAsset'));
|
|
65
|
+
expect(
|
|
66
|
+
nmsPermanentConvergenceFailure({
|
|
67
|
+
'stillAhead': false,
|
|
68
|
+
'pushError': {'refused': true, 'error': 'policy-refused'},
|
|
69
|
+
}),
|
|
70
|
+
contains('policy-refused'));
|
|
71
|
+
expect(
|
|
72
|
+
nmsPermanentConvergenceFailure({
|
|
73
|
+
'stillAhead': false,
|
|
74
|
+
'admission': {'ok': true, 'sessions': const []},
|
|
75
|
+
}),
|
|
76
|
+
isNull);
|
|
77
|
+
});
|
|
78
|
+
|
|
42
79
|
test('ephemeral rejects an empty deploy list with a named remedy', () async {
|
|
43
80
|
await expectLater(
|
|
44
81
|
NomosTestHarness.ephemeral(deployJsonPaths: const []),
|
|
@@ -46,7 +83,8 @@ void main() {
|
|
|
46
83
|
);
|
|
47
84
|
});
|
|
48
85
|
|
|
49
|
-
test('OFFLINE: install real law, write one directive, read it back, dispose',
|
|
86
|
+
test('OFFLINE: install real law, write one directive, read it back, dispose',
|
|
87
|
+
() async {
|
|
50
88
|
final skip = _missingPrereq();
|
|
51
89
|
if (skip != null) {
|
|
52
90
|
markTestSkipped(skip);
|
|
@@ -56,7 +94,8 @@ void main() {
|
|
|
56
94
|
deployJsonPaths: [_guestbookDeployJson],
|
|
57
95
|
nodeEntry: _repoEntry,
|
|
58
96
|
runtimeDir: _runtimeDir,
|
|
59
|
-
registerTearDown:
|
|
97
|
+
registerTearDown:
|
|
98
|
+
addTearDown, // deterministic auto-dispose even if an expect() throws
|
|
60
99
|
);
|
|
61
100
|
|
|
62
101
|
// Drive the REAL law: a create directive + a declared query β the same ops a generated client emits.
|
|
@@ -73,16 +112,20 @@ void main() {
|
|
|
73
112
|
expect(outcome.created, hasLength(1));
|
|
74
113
|
expect(outcome.created.single.type, 'guestbook.GuestbookEntry');
|
|
75
114
|
|
|
76
|
-
final rows =
|
|
115
|
+
final rows =
|
|
116
|
+
await harness.bridge.query('entriesByAuthor', {'author': 'harness'});
|
|
77
117
|
expect(rows, hasLength(1));
|
|
78
118
|
expect(rows.first['id'], outcome.created.single.id);
|
|
79
119
|
expect((rows.first['data'] as Map)['message'], 'offline fixture green');
|
|
80
|
-
expect((rows.first['data'] as Map)['moodEmoji'],
|
|
120
|
+
expect((rows.first['data'] as Map)['moodEmoji'],
|
|
121
|
+
'π'); // proves the law's plan ran, not a stub
|
|
81
122
|
|
|
82
123
|
stdout.writeln('offline harness green: ws=${harness.workspace}');
|
|
83
124
|
}, timeout: const Timeout(Duration(minutes: 2)));
|
|
84
125
|
|
|
85
|
-
test(
|
|
126
|
+
test(
|
|
127
|
+
'DECOY: the harness plane is multi-session β an unpinned plane op refuses like production',
|
|
128
|
+
() async {
|
|
86
129
|
final skip = _missingPrereq();
|
|
87
130
|
if (skip != null) {
|
|
88
131
|
markTestSkipped(skip);
|
|
@@ -96,7 +139,8 @@ void main() {
|
|
|
96
139
|
);
|
|
97
140
|
// harness.bridge is a SESSION HANDLE β pinned ops work as beforeβ¦
|
|
98
141
|
expect(harness.bridge, isA<NomosSession>());
|
|
99
|
-
expect(await harness.bridge.query('entriesByAuthor', {'author': 'x'}),
|
|
142
|
+
expect(await harness.bridge.query('entriesByAuthor', {'author': 'x'}),
|
|
143
|
+
isEmpty);
|
|
100
144
|
// β¦but the RAW PLANE holds the decoy too, so an op that forgot its session refuses HERE β
|
|
101
145
|
// exactly the refusal a real multi-workspace app would hit (never a silent sole-session guess).
|
|
102
146
|
final plane = (harness.bridge as NomosSession).parent;
|
|
@@ -106,7 +150,9 @@ void main() {
|
|
|
106
150
|
);
|
|
107
151
|
}, timeout: const Timeout(Duration(minutes: 2)));
|
|
108
152
|
|
|
109
|
-
test(
|
|
153
|
+
test(
|
|
154
|
+
'DX: ephemeral emits phase-progress diagnostics (install β runner β ready β connect)',
|
|
155
|
+
() async {
|
|
110
156
|
final skip = _missingPrereq();
|
|
111
157
|
if (skip != null) {
|
|
112
158
|
markTestSkipped(skip);
|
|
@@ -145,23 +191,28 @@ void main() {
|
|
|
145
191
|
autoTearDown: false, // drive dispose explicitly to assert its determinism
|
|
146
192
|
);
|
|
147
193
|
// A read proves the holon is live before teardown.
|
|
148
|
-
expect(await harness.bridge.query('entriesByAuthor', {'author': 'nobody'}),
|
|
194
|
+
expect(await harness.bridge.query('entriesByAuthor', {'author': 'nobody'}),
|
|
195
|
+
isEmpty);
|
|
149
196
|
|
|
150
197
|
await harness.dispose();
|
|
151
|
-
await harness
|
|
198
|
+
await harness
|
|
199
|
+
.dispose(); // idempotent: the second call is a no-op, never throws
|
|
152
200
|
// The bridge is dead after dispose β a call fails rather than silently reaching a leaked runner.
|
|
153
|
-
await expectLater(
|
|
154
|
-
|
|
201
|
+
await expectLater(harness.bridge.query('entriesByAuthor', {'author': 'x'}),
|
|
202
|
+
throwsA(anything));
|
|
155
203
|
}, timeout: const Timeout(Duration(minutes: 2)));
|
|
156
204
|
|
|
157
|
-
test(
|
|
205
|
+
test(
|
|
206
|
+
'OFFLINE: the `githolon harness-install` SUBCOMMAND lane installs law and drives a read',
|
|
207
|
+
() async {
|
|
158
208
|
final skip = _missingPrereq();
|
|
159
209
|
if (skip != null) {
|
|
160
210
|
markTestSkipped(skip);
|
|
161
211
|
return;
|
|
162
212
|
}
|
|
163
213
|
if (!File(_cliDist).existsSync()) {
|
|
164
|
-
markTestSkipped(
|
|
214
|
+
markTestSkipped(
|
|
215
|
+
'build the CLI first: cd cli && npm run build (needs dist/cli.mjs)');
|
|
165
216
|
return;
|
|
166
217
|
}
|
|
167
218
|
// FORCE the published subcommand lane via the installHelper seam: a githolon command line (here the
|
|
@@ -170,7 +221,8 @@ void main() {
|
|
|
170
221
|
deployJsonPaths: [_guestbookDeployJson],
|
|
171
222
|
nodeEntry: _repoEntry,
|
|
172
223
|
runtimeDir: _runtimeDir,
|
|
173
|
-
installHelper:
|
|
224
|
+
installHelper:
|
|
225
|
+
'node $_cliDist', // NOT an existing .mjs file β the subcommand lane
|
|
174
226
|
registerTearDown: addTearDown,
|
|
175
227
|
);
|
|
176
228
|
|
|
@@ -185,10 +237,13 @@ void main() {
|
|
|
185
237
|
},
|
|
186
238
|
);
|
|
187
239
|
expect(outcome.created, hasLength(1));
|
|
188
|
-
final rows =
|
|
240
|
+
final rows =
|
|
241
|
+
await harness.bridge.query('entriesByAuthor', {'author': 'subcommand'});
|
|
189
242
|
expect(rows, hasLength(1));
|
|
190
|
-
expect((rows.first['data'] as Map)['message'],
|
|
191
|
-
|
|
243
|
+
expect((rows.first['data'] as Map)['message'],
|
|
244
|
+
'installed via githolon harness-install');
|
|
245
|
+
expect((rows.first['data'] as Map)['moodEmoji'],
|
|
246
|
+
'π'); // the law's plan ran through the subcommand-built snapshot
|
|
192
247
|
|
|
193
248
|
stdout.writeln('subcommand harness green: ws=${harness.workspace}');
|
|
194
249
|
}, timeout: const Timeout(Duration(minutes: 2)));
|