@githolon/dsl 0.101.6 β†’ 0.102.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.
Files changed (120) hide show
  1. package/dart/CHANGELOG.md +13 -0
  2. package/dart/lib/nomos_client.dart +3 -52
  3. package/dart/lib/src/dev_cloud.dart +5 -0
  4. package/dart/lib/src/home.dart +10 -15
  5. package/dart/lib/src/nms_conformance_runtime.dart +12 -0
  6. package/dart/lib/src/nms_generated_runtime.dart +29 -0
  7. package/dart/lib/src/nms_test_environment.dart +8 -0
  8. package/dart/lib/src/node_runner_transport.dart +56 -18
  9. package/dart/lib/src/provider.dart +5 -1
  10. package/dart/lib/src/record_origin.dart +55 -0
  11. package/dart/lib/src/runner/node-entry.mjs +9105 -0
  12. package/dart/lib/src/runner/offline-install.mjs +4729 -0
  13. package/dart/lib/src/session_bound.dart +23 -10
  14. package/dart/lib/src/source_trust.dart +51 -0
  15. package/dart/lib/src/test_harness.dart +490 -58
  16. package/dart/lib/src/webview_bridge.dart +167 -58
  17. package/dart/lib/src/workspace_name.dart +7 -1
  18. package/dart/pubspec.lock +3 -3
  19. package/dart/pubspec.yaml +4 -2
  20. package/dart/test/acquire_session_credentials_test.dart +34 -17
  21. package/dart/test/acquire_session_test.dart +109 -24
  22. package/dart/test/adv_realm_interference_test.dart +227 -99
  23. package/dart/test/bundle_version_pairing_test.dart +1 -1
  24. package/dart/test/cloud_bound_topology_test.dart +1 -1
  25. package/dart/test/cluster_attested_test.dart +52 -35
  26. package/dart/test/cluster_births_test.dart +5 -2
  27. package/dart/test/cluster_device_keyed_acceptance_test.dart +5 -2
  28. package/dart/test/cluster_local_autosync_test.dart +5 -2
  29. package/dart/test/cluster_scale_test.dart +5 -2
  30. package/dart/test/compat_test.dart +1 -1
  31. package/dart/test/dlq_session_surface_test.dart +2 -1
  32. package/dart/test/errors_test.dart +1 -1
  33. package/dart/test/first_app_gauntlet_test.dart +78 -51
  34. package/dart/test/four_layer_nested_topology_test.dart +1 -1
  35. package/dart/test/harness_phase_timeout_test.dart +1 -1
  36. package/dart/test/home_parity_test.dart +9 -5
  37. package/dart/test/local_sync_inert_test.dart +22 -14
  38. package/dart/test/node_runner_transport_test.dart +9 -5
  39. package/dart/test/null_slot_test.dart +1 -1
  40. package/dart/test/parent_signer_recovery_test.dart +5 -2
  41. package/dart/test/schema_validation_test.dart +1 -1
  42. package/dart/test/sealing_client_test.dart +2 -1
  43. package/dart/test/session_bound_test.dart +92 -119
  44. package/dart/test/stale_bundle_test.dart +1 -1
  45. package/dart/test/test_harness_test.dart +77 -22
  46. package/dart/test/vo_read_typing_test.dart +32 -14
  47. package/dart/test/watch_after_offer_stall_test.dart +2 -1
  48. package/dart/test/webview_bridge_test.dart +17 -4
  49. package/dart/test/wire_test.dart +1 -1
  50. package/dart_types/CHANGELOG.md +8 -0
  51. package/dart_types/lib/nomos_types.dart +7 -6
  52. package/dart_types/lib/src/activity.dart +23 -13
  53. package/dart_types/lib/src/foreign_ref.dart +61 -112
  54. package/dart_types/lib/src/invitation.dart +96 -0
  55. package/dart_types/lib/src/nomos_ref.dart +97 -59
  56. package/dart_types/pubspec.yaml +1 -1
  57. package/dart_types/test/surfaced_test.dart +125 -19
  58. package/package.json +7 -3
  59. package/runtime/generated-application-host.js +8851 -0
  60. package/src/activity.ts +2 -2
  61. package/src/aggregate.ts +24 -1
  62. package/src/application_composition.ts +282 -0
  63. package/src/application_topology.ts +151 -0
  64. package/src/attested_core.ts +2 -2
  65. package/src/authoring_context.ts +9 -0
  66. package/src/birth_meter_ensure.ts +1 -1
  67. package/src/births.ts +53 -5
  68. package/src/build_package.ts +79 -71
  69. package/src/business_birth.ts +93 -10
  70. package/src/clear_directive.ts +3 -1
  71. package/src/codegen_dart.ts +369 -420
  72. package/src/codegen_dart_application.ts +1508 -0
  73. package/src/codegen_dart_client.ts +156 -95
  74. package/src/codegen_dart_scenario.ts +299 -0
  75. package/src/codegen_openapi.ts +8 -3
  76. package/src/codegen_proof.ts +1113 -1137
  77. package/src/codegen_provider_dart.ts +12 -1
  78. package/src/codegen_ts.ts +1176 -496
  79. package/src/compat_package_main.ts +22 -10
  80. package/src/compile_package_main.ts +1536 -337
  81. package/src/contract.ts +10 -4
  82. package/src/dart_barrel_guard.ts +5 -1
  83. package/src/describe_model.ts +13 -5
  84. package/src/directive.ts +244 -5
  85. package/src/engine_entry.ts +56 -13
  86. package/src/engine_javascript.ts +31 -0
  87. package/src/evolve_preview.ts +3 -12
  88. package/src/framework/application_home.ts +24 -10
  89. package/src/framework/capability.ts +15 -1
  90. package/src/framework/domain_lifecycle.ts +8 -0
  91. package/src/framework/framework_aggregates.ts +2 -2
  92. package/src/framework/genesis.ts +11 -1
  93. package/src/framework/home.ts +178 -75
  94. package/src/framework/impure_capability.ts +24 -7
  95. package/src/framework/relations.ts +13 -5
  96. package/src/framework/workspaces.ts +13 -12
  97. package/src/generated_client_names.ts +42 -3
  98. package/src/idof_lint.ts +65 -13
  99. package/src/index.ts +46 -12
  100. package/src/intent.ts +587 -0
  101. package/src/law_check.ts +64 -16
  102. package/src/precondition.ts +16 -4
  103. package/src/read_projection_namespace.ts +2 -7
  104. package/src/recipient.ts +33 -0
  105. package/src/relation_schema.ts +93 -0
  106. package/src/secondary_ensure.ts +11 -2
  107. package/src/shape_compat.ts +11 -1
  108. package/src/usd.ts +30 -8
  109. package/src/usd_layers.ts +447 -246
  110. package/src/usd_state.ts +1 -1
  111. package/src/value_object.ts +49 -13
  112. package/src/wire_encode.ts +124 -26
  113. package/src/workspace_sharing.ts +269 -0
  114. package/src/zod_introspection.ts +84 -0
  115. package/dart/example/nomos_secure_key_store.dart +0 -82
  116. package/dart/example/nomos_webview_transport.dart +0 -63
  117. package/dart/lib/nomos_headless.dart +0 -50
  118. package/dart/lib/testing.dart +0 -14
  119. package/dart/test/co2_product_gauntlet_test.dart +0 -244
  120. 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/nomos_headless.dart';
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 => '${Platform.environment['HOME'] ?? ''}/.holon/runtime';
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/nomos_client.dart';
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/nomos_headless.dart';
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 CLIENTS β€” THE ACCEPTANCE (architecture/session_bound_clients.md). The blessed app
2
- // shape end-to-end, against the REAL wasm on a local plane, with ZERO workspace/domain/directive
3
- // strings in the app-side code:
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 β†’ Client.bind(session) (preflight) β†’ a PARENTLESS birth landing in the session
6
- // workspace β†’ engine.follow(ref) β†’ a second client β†’ a directive on it.
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'"); and the deprecated engine-level shims still
12
- // function (compatibility is deprecation, not breakage).
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/nomos_headless.dart';
22
+ import 'package:nomos_client/src/nms_conformance_runtime.dart';
23
23
  import 'package:test/test.dart';
24
24
 
25
- // home.client.dart re-exports home.dart + the nomos_client surface (NomosSession, the typed errors,
26
- // NomosOfferOutcome, …); estate.client.dart adds the child-law client. One import each = the whole
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()) return 'in-repo runner not found at $_repoEntry';
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
- 'THE BLESSED SHAPE: mount β†’ bind (preflight) β†’ parentless birth in the session workspace β†’ '
73
- 'follow(ref) β†’ second client β†’ directive β€” zero routing strings in app code', () async {
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 homeClient = await HomeClient.bind(home, actor: _owner);
86
- expect(homeClient.session, isNotNull);
87
- expect(homeClient.session!.workspace, harness.workspace,
88
- reason: 'inspectable session context (ask 3)');
104
+ final homeApplication =
105
+ await nomosInternalBindHomeApplication(home, _owner);
106
+ expect(homeApplication.activity, isA<HomeActivity>());
89
107
 
90
- // PARENTLESS BIRTH (ask 4): no parent parameter β€” the parent IS the session workspace.
91
- final birth = await homeClient.birthEstateWorkspace(
92
- payload: BirthEstateWorkspacePayload(
93
- owner: _owner,
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: 'the birth offer landed in the SESSION workspace, no loose parent');
102
- expect(birth.gatewayVerdict, isNull, reason: 'the local offer lane, not the legacy relay');
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, authorSecret: harness.deviceSecret(harness.workspace));
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 estateClient = await EstateClient.bind(estate, actor: _owner);
112
- final rows = await estateClient.estatesByOwner(owner: _owner);
113
- expect(rows, hasLength(1), reason: 'the child genesis seed reads back typed');
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
- final reseed = await estateClient.seedEstateWorkspace(SeedEstateWorkspacePayload(
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
- expect(reseed.head, isNotEmpty);
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: EstateClient.interface.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)', () async {
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
- final client = await HomeClient.bind(session, actor: _owner);
150
- await client.birthEstateWorkspace(
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: 'no Surfaced law in this fixture β€” the point is the stream EMITS at all');
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('bind on a deliberately WRONG session throws DomainClientSessionMismatch with every field',
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 HomeClient to the docs
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 HomeClient.bind(wrong);
204
+ await nomosInternalBindHomeApplication(wrong, null);
189
205
  } catch (e) {
190
206
  thrown = e;
191
207
  }
192
208
  expect(thrown, isA<DomainClientSessionMismatch>(),
193
- reason: 'the mismatch fires at CONSTRUCTION, not two clicks into the app');
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, HomeClient.interface.domain);
198
- expect(m.expectedDomainHash, HomeClient.interface.expectedDomainHash);
199
- expect(m.expectedInterfaceHash, HomeClient.interface.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 HomeClient.bind(harness.session(harness.workspace));
224
+ await nomosInternalBindHomeApplication(
225
+ harness.session(harness.workspace), null);
208
226
 
209
- // session.assertCompatible is directly usable from tests (ask 9), same typed refusal.
210
- await expectLater(wrong.assertCompatible(EstateClient.interface),
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 DomainNotInstalledForSession '
219
- '(context-rich, ask 6) β€” and the deprecated engine shims still function (ask 1/7)', () async {
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', directiveId: 'doIt', payload: const {});
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(HomeClient.interface.domain),
261
+ expect(err.activeDomainKeys, contains('home'),
241
262
  reason: 'the error NAMES what the workspace actually runs');
242
- expect(err.possibleCauses, DomainNotInstalledForSession.defaultPossibleCauses);
243
- expect(err.underlying.toString(), contains('no active installed law declares domain'));
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/nomos_headless.dart';
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/nomos_headless.dart';
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()) return 'in-repo runner not found at $_repoEntry';
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 'warm ~/.holon/runtime first (run any `githolon` command once to cache the runtime)';
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', () async {
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: addTearDown, // deterministic auto-dispose even if an expect() throws
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 = await harness.bridge.query('entriesByAuthor', {'author': 'harness'});
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'], '😊'); // proves the law's plan ran, not a stub
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('DECOY: the harness plane is multi-session β€” an unpinned plane op refuses like production', () async {
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'}), isEmpty);
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('DX: ephemeral emits phase-progress diagnostics (install β†’ runner β†’ ready β†’ connect)', () async {
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'}), isEmpty);
194
+ expect(await harness.bridge.query('entriesByAuthor', {'author': 'nobody'}),
195
+ isEmpty);
149
196
 
150
197
  await harness.dispose();
151
- await harness.dispose(); // idempotent: the second call is a no-op, never throws
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
- harness.bridge.query('entriesByAuthor', {'author': 'x'}), throwsA(anything));
201
+ await expectLater(harness.bridge.query('entriesByAuthor', {'author': 'x'}),
202
+ throwsA(anything));
155
203
  }, timeout: const Timeout(Duration(minutes: 2)));
156
204
 
157
- test('OFFLINE: the `githolon harness-install` SUBCOMMAND lane installs law and drives a read', () async {
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('build the CLI first: cd cli && npm run build (needs dist/cli.mjs)');
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: 'node $_cliDist', // NOT an existing .mjs file β‡’ the subcommand lane
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 = await harness.bridge.query('entriesByAuthor', {'author': 'subcommand'});
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'], 'installed via githolon harness-install');
191
- expect((rows.first['data'] as Map)['moodEmoji'], '😊'); // the law's plan ran through the subcommand-built snapshot
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)));