@githolon/dsl 0.100.4 → 0.100.6

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 CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.62.2
2
+
3
+ - Make `NomosTestHarness` own a complete, verifiable local trust lineage. Tests still bind a business actor
4
+ and call the generated birth method; the harness privately mints its test root, seats the signer and
5
+ delegation, lands the warrant in order, and refuses any child whose full chain does not replay to that root.
6
+
1
7
  ## 0.62.1
2
8
 
3
9
  - Align the public Dart documentation with the one Flutter application entry point: identity comes from the
@@ -24,7 +24,7 @@
24
24
  // snapshot (`restoreFrom`). Every other URL is REFUSED — a stray fetch fails loudly, never reaches the net.
25
25
  //
26
26
  // PREREQUISITES (documented so a failure is legible): `node` on PATH; a resolvable
27
- // `@githolon/client/runner/node-entry.ts`; and a warm `~/.holon/runtime` cache (holon.wasm + packages.json —
27
+ // `@githolon/client/runner/node-entry.ts`; and a warm `~/.holon/runtime` cache (holon-testmint.wasm + packages.json —
28
28
  // one `githolon` run fills it). All local; no network at test time.
29
29
  //
30
30
  // PACKAGING NOTE: `offline-install.ts` imports the adapter engine, which today lives beside the in-repo
@@ -173,8 +173,8 @@ class NomosTestHarness {
173
173
  return session(ws);
174
174
  }
175
175
 
176
- NomosTestHarness._(this.bridge, this.workspace, this._transport, this._workdir,
177
- this._onDiagnostic, List<String> sessionNames,
176
+ NomosTestHarness._(this.bridge, this.workspace, this._transport,
177
+ this._workdir, this._onDiagnostic, List<String> sessionNames,
178
178
  {Map<String, String> devicePublics = const {},
179
179
  Map<String, String> deviceSecrets = const {},
180
180
  this.frameworkHash,
@@ -184,7 +184,8 @@ class NomosTestHarness {
184
184
  Duration? phaseTimeout,
185
185
  this.testCloud,
186
186
  this.homeWorkspace})
187
- : _sessionNames = List.of(sessionNames), // GROWABLE — restoreWorkspace may add a fresh session
187
+ : _sessionNames = List.of(
188
+ sessionNames), // GROWABLE — restoreWorkspace may add a fresh session
188
189
  _devicePublics = devicePublics,
189
190
  _deviceSecrets = deviceSecrets,
190
191
  _lawHashes = Map.of(lawHashes),
@@ -256,7 +257,8 @@ class NomosTestHarness {
256
257
  /// [deployJsonPaths] — the `build/<name>.deploy.json` files `nomos-compile` emits (installed in order;
257
258
  /// framework/platform law before a domain that depends on it).
258
259
  /// [workspace] — the local workspace name (default `test-ws`; law-only, never leaves the machine).
259
- /// [runtimeDir] — the runtime cache (default `~/.holon/runtime`); must hold `holon.wasm` + `packages.json`.
260
+ /// [runtimeDir] — the runtime cache (default `~/.holon/runtime`); must hold `holon-testmint.wasm` +
261
+ /// `packages.json`. The harness owns a throwaway test root; app code never handles its key material.
260
262
  /// [nodeEntry] — path to `node-entry.ts`; when omitted it is resolved from [resolveFrom] via
261
263
  /// `require.resolve('@githolon/client/runner/node-entry.ts')`.
262
264
  /// [installHelper] — the offline law-install lane. An EXISTING `.mjs` file is run as a node script (the
@@ -308,13 +310,18 @@ class NomosTestHarness {
308
310
  final workdir = await Directory.systemTemp.createTemp('nomos-harness-');
309
311
  // Total-ordered teardown even on a mid-setup failure: build the transport lazily and clean up on throw.
310
312
  NodeRunnerTransport? transport;
311
- final phase = (String m) { if (onDiagnostic != null) onDiagnostic('[nomos-test-harness] $m'); };
313
+ final phase = (String m) {
314
+ if (onDiagnostic != null) onDiagnostic('[nomos-test-harness] $m');
315
+ };
312
316
  try {
313
317
  final snapshot = '${workdir.path}/snapshot.bin';
314
318
  final info = await _phased(
315
- phase: '1/4: offline law install (${deployJsonPaths.length} package(s))',
319
+ phase:
320
+ '1/4: offline law install (${deployJsonPaths.length} package(s))',
316
321
  workspace: workspace,
317
- awaiting: useInRepoScript ? 'offline-install.ts' : 'githolon harness-install',
322
+ awaiting: useInRepoScript
323
+ ? 'offline-install.ts'
324
+ : 'githolon harness-install',
318
325
  timeout: phaseTimeout ?? _kInstallDeadline,
319
326
  onDiagnostic: onDiagnostic,
320
327
  likelyCauses: const [
@@ -344,6 +351,11 @@ class NomosTestHarness {
344
351
  environment: {
345
352
  'NOMOS_OFFLINE': '1',
346
353
  'NOMOS_OFFLINE_RESTORE': snapshot,
354
+ 'NOMOS_OFFLINE_WASM': 'holon-testmint.wasm',
355
+ if (info['devicePublic'] != null)
356
+ 'NOMOS_TEST_K_ROOT_PUBLIC': info['devicePublic'].toString(),
357
+ if (info['deviceSecret'] != null)
358
+ 'NOMOS_TEST_ROOT_SECRET': info['deviceSecret'].toString(),
347
359
  if (runtimeDir != null) 'NOMOS_OFFLINE_RUNTIME': runtimeDir,
348
360
  },
349
361
  onDiagnostic: onDiagnostic,
@@ -355,7 +367,7 @@ class NomosTestHarness {
355
367
  timeout: phaseTimeout ?? readyTimeout,
356
368
  onDiagnostic: onDiagnostic,
357
369
  likelyCauses: const [
358
- 'node is not on PATH, or ~/.holon/runtime is cold (holon.wasm + packages.json)',
370
+ 'node is not on PATH, or ~/.holon/runtime is cold (holon-testmint.wasm + packages.json)',
359
371
  'the runner process failed to boot the engine — run with onDiagnostic to see its stderr',
360
372
  ],
361
373
  run: () => plane.ready);
@@ -370,7 +382,8 @@ class NomosTestHarness {
370
382
  'run with onDiagnostic to see where the runner blocked',
371
383
  ],
372
384
  run: () => plane.connect(
373
- cloud: 'https://offline.invalid', // ignored: the offline runner serves everything locally
385
+ cloud:
386
+ 'https://offline.invalid', // ignored: the offline runner serves everything locally
374
387
  workspace: workspace,
375
388
  clientId: clientId,
376
389
  allowUnborn: true,
@@ -379,7 +392,8 @@ class NomosTestHarness {
379
392
  // with the SAME key, so offline `.births()` offers Just Work.
380
393
  authorSecret: info['deviceSecret']?.toString(),
381
394
  ));
382
- phase('ready — session "$workspace" connected (law ${(info['lawHashes'] as List?)?.isNotEmpty == true ? (info['lawHashes'] as List).first.toString().substring(0, 12) : "installed"})');
395
+ phase(
396
+ 'ready — session "$workspace" connected (law ${(info['lawHashes'] as List?)?.isNotEmpty == true ? (info['lawHashes'] as List).first.toString().substring(0, 12) : "installed"})');
383
397
  // THE DECOY SESSION (multi-workspace by default in tests): sole-session inference is how
384
398
  // single-workspace assumptions ship — with one session connected, an op that forgot its session
385
399
  // silently works in tests and only refuses in production when the app connects its second
@@ -403,13 +417,15 @@ class NomosTestHarness {
403
417
  restoreFrom: base64Encode(await File(snapshot).readAsBytes()),
404
418
  ));
405
419
  }
406
- final harness = NomosTestHarness._(
407
- plane.session(workspace), workspace, transport, workdir, onDiagnostic, [workspace],
420
+ final harness = NomosTestHarness._(plane.session(workspace), workspace,
421
+ transport, workdir, onDiagnostic, [workspace],
408
422
  devicePublics: {
409
- if (info['devicePublic'] != null) workspace: info['devicePublic'].toString(),
423
+ if (info['devicePublic'] != null)
424
+ workspace: info['devicePublic'].toString(),
410
425
  },
411
426
  deviceSecrets: {
412
- if (info['deviceSecret'] != null) workspace: info['deviceSecret'].toString(),
427
+ if (info['deviceSecret'] != null)
428
+ workspace: info['deviceSecret'].toString(),
413
429
  },
414
430
  frameworkHash: info['frameworkHash']?.toString(),
415
431
  lawHashes: {
@@ -417,7 +433,9 @@ class NomosTestHarness {
417
433
  .map((e) => e.toString())
418
434
  .toList()
419
435
  },
420
- snapshots: {workspace: base64Encode(await File(snapshot).readAsBytes())},
436
+ snapshots: {
437
+ workspace: base64Encode(await File(snapshot).readAsBytes())
438
+ },
421
439
  installLane: _InstallLane(
422
440
  node: node,
423
441
  useInRepoScript: useInRepoScript,
@@ -499,6 +517,8 @@ class NomosTestHarness {
499
517
  final lawHashesByWs = <String, List<String>>{};
500
518
  final installDevicePublics = <String, String>{};
501
519
  final installDeviceSecrets = <String, String>{};
520
+ String? planeTestRootPublic;
521
+ String? planeTestRootSecret;
502
522
  String? frameworkHash;
503
523
  var _i = 0;
504
524
  for (final e in workspaces.entries) {
@@ -506,7 +526,9 @@ class NomosTestHarness {
506
526
  final info = await _phased(
507
527
  phase: '1: offline law install ${++_i}/${workspaces.length}',
508
528
  workspace: e.key,
509
- awaiting: useInRepoScript ? 'offline-install.ts' : 'githolon harness-install',
529
+ awaiting: useInRepoScript
530
+ ? 'offline-install.ts'
531
+ : 'githolon harness-install',
510
532
  timeout: phaseTimeout ?? _kInstallDeadline,
511
533
  onDiagnostic: onDiagnostic,
512
534
  likelyCauses: const [
@@ -530,12 +552,18 @@ class NomosTestHarness {
530
552
  frameworkHash ??= info['frameworkHash']?.toString();
531
553
  // THE HARNESS DEVICE (era-0): the install lane mints one device key per workspace — sessions sign
532
554
  // with it and the client completes the self-serve birth ceremony (enrol + cert) with the SAME key.
533
- if (info['devicePublic'] != null) installDevicePublics[e.key] = info['devicePublic'].toString();
534
- if (info['deviceSecret'] != null) installDeviceSecrets[e.key] = info['deviceSecret'].toString();
555
+ if (info['devicePublic'] != null)
556
+ installDevicePublics[e.key] = info['devicePublic'].toString();
557
+ if (info['deviceSecret'] != null)
558
+ installDeviceSecrets[e.key] = info['deviceSecret'].toString();
559
+ planeTestRootPublic ??= info['devicePublic']?.toString();
560
+ planeTestRootSecret ??= info['deviceSecret']?.toString();
535
561
  }
536
562
 
537
563
  // 2. ONE runner / ONE bridge for the whole plane — no env restore default; each connect brings its own.
538
- final phase = (String m) { if (onDiagnostic != null) onDiagnostic('[nomos-test-harness] $m'); };
564
+ final phase = (String m) {
565
+ if (onDiagnostic != null) onDiagnostic('[nomos-test-harness] $m');
566
+ };
539
567
  transport = await _phased(
540
568
  phase: '2: spawn the node runner (${workspaces.length} workspace(s))',
541
569
  awaiting: 'Process.start($node)',
@@ -547,6 +575,11 @@ class NomosTestHarness {
547
575
  node: node,
548
576
  environment: {
549
577
  'NOMOS_OFFLINE': '1',
578
+ 'NOMOS_OFFLINE_WASM': 'holon-testmint.wasm',
579
+ if (planeTestRootPublic != null)
580
+ 'NOMOS_TEST_K_ROOT_PUBLIC': planeTestRootPublic,
581
+ if (planeTestRootSecret != null)
582
+ 'NOMOS_TEST_ROOT_SECRET': planeTestRootSecret,
550
583
  if (runtimeDir != null) 'NOMOS_OFFLINE_RUNTIME': runtimeDir,
551
584
  },
552
585
  onDiagnostic: onDiagnostic,
@@ -558,7 +591,7 @@ class NomosTestHarness {
558
591
  timeout: phaseTimeout ?? readyTimeout,
559
592
  onDiagnostic: onDiagnostic,
560
593
  likelyCauses: const [
561
- 'node is not on PATH, or ~/.holon/runtime is cold (holon.wasm + packages.json)',
594
+ 'node is not on PATH, or ~/.holon/runtime is cold (holon-testmint.wasm + packages.json)',
562
595
  'the runner process failed to boot the engine — run with onDiagnostic to see its stderr',
563
596
  ],
564
597
  run: () => bridge.ready);
@@ -568,7 +601,8 @@ class NomosTestHarness {
568
601
  final snapshotB64 = <String, String>{};
569
602
  var _n = 0;
570
603
  for (final e in workspaces.entries) {
571
- snapshotB64[e.key] = base64Encode(await File(snapshots[e.key]!).readAsBytes());
604
+ snapshotB64[e.key] =
605
+ base64Encode(await File(snapshots[e.key]!).readAsBytes());
572
606
  await _phased(
573
607
  phase: '3: connect session ${++_n}/${workspaces.length}',
574
608
  workspace: e.key,
@@ -633,7 +667,8 @@ class NomosTestHarness {
633
667
  ));
634
668
  }
635
669
  if (deviceKeyed.isNotEmpty) {
636
- phase('cluster: ${deviceKeyed.length} device-keyed session(s) re-opened');
670
+ phase(
671
+ 'cluster: ${deviceKeyed.length} device-keyed session(s) re-opened');
637
672
  }
638
673
 
639
674
  final names = workspaces.keys.toList();
@@ -714,7 +749,8 @@ class NomosTestHarness {
714
749
  }
715
750
  final entry = nodeEntry ??
716
751
  await _resolveEntry(node, resolveFrom ?? Directory.current.path);
717
- final bareUid = subject.startsWith('user:') ? subject.substring(5) : subject;
752
+ final bareUid =
753
+ subject.startsWith('user:') ? subject.substring(5) : subject;
718
754
  final http = HttpClient();
719
755
  Future<Map<String, Object?>> post(String path, Object? body,
720
756
  {Map<String, String> headers = const {}}) async {
@@ -723,7 +759,8 @@ class NomosTestHarness {
723
759
  req.headers.set('x-nomos-principal', bareUid);
724
760
  if (authToken != null) req.headers.set('x-nomos-auth', authToken);
725
761
  headers.forEach(req.headers.set);
726
- if (body != null) req.add(utf8.encode(body is String ? body : jsonEncode(body)));
762
+ if (body != null)
763
+ req.add(utf8.encode(body is String ? body : jsonEncode(body)));
727
764
  final resp = await req.close();
728
765
  final text = await resp.transform(utf8.decoder).join();
729
766
  final decoded = (jsonDecode(text) as Map).cast<String, Object?>();
@@ -790,7 +827,10 @@ class NomosTestHarness {
790
827
  timeout: phaseTimeout ?? _kSpawnDeadline,
791
828
  onDiagnostic: onDiagnostic,
792
829
  run: () => NodeRunnerTransport.start(
793
- entry: entry, resolveFrom: resolveFrom, node: node, onDiagnostic: onDiagnostic));
830
+ entry: entry,
831
+ resolveFrom: resolveFrom,
832
+ node: node,
833
+ onDiagnostic: onDiagnostic));
794
834
  final plane = NomosBridge(transport!);
795
835
  await _phased(
796
836
  phase: '3/6: runner ready',
@@ -804,7 +844,8 @@ class NomosTestHarness {
804
844
  // on-chain AuthProvider (enrollFirstDevice — no admin in the loop). The platform is WARRANTED, so
805
845
  // plain unsigned enrolment refuses by design; this is the one first-device door.
806
846
  final device = await _phased(
807
- phase: '4/6: device ceremony (keygen → dev-IdP token → enrollFirstDevice)',
847
+ phase:
848
+ '4/6: device ceremony (keygen → dev-IdP token → enrollFirstDevice)',
808
849
  workspace: platformWorkspace,
809
850
  awaiting: 'authorKeygen + /dev/mint-token + enrollFirstDevice',
810
851
  timeout: phaseTimeout ?? _kInstallDeadline,
@@ -826,14 +867,17 @@ class NomosTestHarness {
826
867
  final publicB64 = (kp['public'] ?? '').toString();
827
868
  final secret = (kp['secret'] ?? '').toString();
828
869
  if (publicB64.isEmpty || secret.isEmpty) {
829
- throw NomosTestHarnessException('cloudBound: authorKeygen returned no device key');
870
+ throw NomosTestHarnessException(
871
+ 'cloudBound: authorKeygen returned no device key');
830
872
  }
831
873
  final keyHash = sha256.convert(base64Decode(publicB64)).toString();
832
- final minted = await post('/dev/mint-token', {'sub': bareUid, 'nonce': keyHash});
874
+ final minted = await post(
875
+ '/dev/mint-token', {'sub': bareUid, 'nonce': keyHash});
833
876
  final idpToken = (minted['token'] ?? '').toString();
834
877
  final issuer = (minted['issuer'] ?? '').toString();
835
878
  if (idpToken.isEmpty || issuer.isEmpty) {
836
- throw NomosTestHarnessException('cloudBound: /dev/mint-token returned no token — is the test cloud started with platform:/deployJsonPath: and a law composing setAuthProvider?');
879
+ throw NomosTestHarnessException(
880
+ 'cloudBound: /dev/mint-token returned no token — is the test cloud started with platform:/deployJsonPath: and a law composing setAuthProvider?');
837
881
  }
838
882
  // THE KEYLESS ENROLL-ONCE DELEGATION (links[1] of the home cert chain) — device-self-signed,
839
883
  // its authority the dev-IdP token. enrollFirstDevice SEEDS it as the user's SignerDelegation, so
@@ -860,8 +904,15 @@ class NomosTestHarness {
860
904
  'actor': subject,
861
905
  'authorSecret': secret,
862
906
  'delegationCert': jsonEncode(delegationCert),
863
- }, headers: {'x-nomos-auth': idpToken});
864
- return (publicB64: publicB64, secret: secret, keyHash: keyHash, idpToken: idpToken);
907
+ }, headers: {
908
+ 'x-nomos-auth': idpToken
909
+ });
910
+ return (
911
+ publicB64: publicB64,
912
+ secret: secret,
913
+ keyHash: keyHash,
914
+ idpToken: idpToken
915
+ );
865
916
  });
866
917
  // 5/6 — THE HOME CEREMONY, remotely: the platform's own gateway offer births the home (the ONE
867
918
  // control-plane contact; the app lane below never touches the platform again). The offer carries
@@ -886,7 +937,8 @@ class NomosTestHarness {
886
937
  'parentId': platformWorkspace,
887
938
  'epoch': 0,
888
939
  }, device.secret);
889
- final out = await post('/v2/workspaces/$platformWorkspace/birthHome', {
940
+ final out =
941
+ await post('/v2/workspaces/$platformWorkspace/birthHome', {
890
942
  'owner': subject,
891
943
  'ownerKeyHash': device.keyHash,
892
944
  'frameworkHash': frameworkHash,
@@ -895,9 +947,15 @@ class NomosTestHarness {
895
947
  'homeCert': jsonEncode(homeCert),
896
948
  'actor': subject,
897
949
  'authorSecret': device.secret,
898
- }, headers: {'x-nomos-auth': device.idpToken});
950
+ }, headers: {
951
+ 'x-nomos-auth': device.idpToken
952
+ });
899
953
  final born = (out['born'] as List?) ?? const [];
900
- final bornNames = born.map((b) => (b as Map)['workspace']?.toString()).whereType<String>().where((w) => w.isNotEmpty).toList();
954
+ final bornNames = born
955
+ .map((b) => (b as Map)['workspace']?.toString())
956
+ .whereType<String>()
957
+ .where((w) => w.isNotEmpty)
958
+ .toList();
901
959
  home = bornNames.isEmpty ? null : bornNames.first;
902
960
  if (home == null) {
903
961
  final outcomes = jsonEncode(out['birthOutcomes'] ?? const []);
@@ -927,7 +985,8 @@ class NomosTestHarness {
927
985
  // NO DECOY on a cloudBound harness: the app is genuinely multi-workspace (the home plus every
928
986
  // locally-born child), so the plane is never sole-session — the decoy the offline harness
929
987
  // needs (to break single-session inference) would just be a spurious local-only workspace here.
930
- final _ = decoySession; // (accepted for signature parity; a cloudBound plane needs no decoy)
988
+ final _ =
989
+ decoySession; // (accepted for signature parity; a cloudBound plane needs no decoy)
931
990
  final harness = NomosTestHarness._(
932
991
  plane.session(home!),
933
992
  home!,
@@ -951,8 +1010,12 @@ class NomosTestHarness {
951
1010
  }
952
1011
  return harness;
953
1012
  } catch (e) {
954
- try { transport?.dispose(); } catch (_) {}
955
- try { await workdir.delete(recursive: true); } catch (_) {}
1013
+ try {
1014
+ transport?.dispose();
1015
+ } catch (_) {}
1016
+ try {
1017
+ await workdir.delete(recursive: true);
1018
+ } catch (_) {}
956
1019
  rethrow;
957
1020
  } finally {
958
1021
  http.close(force: true);
@@ -970,11 +1033,13 @@ class NomosTestHarness {
970
1033
  /// until every session reports neither `pushFailed` nor `stillAhead`, or [timeout] (then throws with the
971
1034
  /// per-workspace last verdicts). Returns the final sync result per workspace.
972
1035
  Future<Map<String, Map<String, Object?>>> converge(
973
- {Duration timeout = const Duration(seconds: 120), List<String>? workspaces}) async {
1036
+ {Duration timeout = const Duration(seconds: 120),
1037
+ List<String>? workspaces}) async {
974
1038
  // Every LIVE session on the plane (not the harness's construction-time list — a locally-born child
975
1039
  // acquired after construction must converge too), PARENT-FIRST: the home syncs before its children, so
976
1040
  // its birth offer-effect creates each child's remote custody BEFORE that child pushes its own writes.
977
- final live = bridge.connectedSessions.where((w) => w != _decoyWorkspace).toList();
1041
+ final live =
1042
+ bridge.connectedSessions.where((w) => w != _decoyWorkspace).toList();
978
1043
  final hw = homeWorkspace;
979
1044
  if (hw != null && live.remove(hw)) live.insert(0, hw); // the parent leads
980
1045
  final targets = workspaces ?? live;
@@ -984,9 +1049,11 @@ class NomosTestHarness {
984
1049
  while (pending.isNotEmpty) {
985
1050
  for (final ws in List.of(pending)) {
986
1051
  try {
987
- final r = (await bridge.session(ws).sync(admit: true)).cast<String, Object?>();
1052
+ final r = (await bridge.session(ws).sync(admit: true))
1053
+ .cast<String, Object?>();
988
1054
  last[ws] = r;
989
- if (r['pushFailed'] != true && r['stillAhead'] != true) pending.remove(ws);
1055
+ if (r['pushFailed'] != true && r['stillAhead'] != true)
1056
+ pending.remove(ws);
990
1057
  } catch (e) {
991
1058
  last[ws] = {'error': e.toString()};
992
1059
  }
@@ -1056,7 +1123,9 @@ class NomosTestHarness {
1056
1123
  final info = await _phased(
1057
1124
  phase: 'seed: offline law install',
1058
1125
  workspace: workspace,
1059
- awaiting: lane.useInRepoScript ? 'offline-install.ts' : 'githolon harness-install',
1126
+ awaiting: lane.useInRepoScript
1127
+ ? 'offline-install.ts'
1128
+ : 'githolon harness-install',
1060
1129
  timeout: _phaseTimeout ?? _kInstallDeadline,
1061
1130
  onDiagnostic: _onDiagnostic,
1062
1131
  run: () => _runInstall(
@@ -1119,7 +1188,8 @@ class NomosTestHarness {
1119
1188
  /// un-synced writes + the DLQ), with its head + law hashes + capture time. The bytes are the SAME
1120
1189
  /// base64 `connect(restoreFrom:)` consumes — [restoreWorkspace] round-trips it. O(1)-ish (a tree serialize).
1121
1190
  Future<WorkspaceSnapshot> snapshotWorkspace(String workspace) async {
1122
- final s = session(workspace); // throws NomosTestHarnessException if not a connected session
1191
+ final s = session(
1192
+ workspace); // throws NomosTestHarnessException if not a connected session
1123
1193
  return _phased(
1124
1194
  phase: 'snapshotWorkspace',
1125
1195
  workspace: workspace,
@@ -1147,13 +1217,15 @@ class NomosTestHarness {
1147
1217
  for (final ws in _sessionNames) {
1148
1218
  out[ws] = await snapshotWorkspace(ws);
1149
1219
  }
1150
- return PlaneSnapshot(workspaces: Map.unmodifiable(out), capturedAt: DateTime.now().toUtc());
1220
+ return PlaneSnapshot(
1221
+ workspaces: Map.unmodifiable(out), capturedAt: DateTime.now().toUtc());
1151
1222
  }
1152
1223
 
1153
1224
  /// Restore a workspace FROM a [WorkspaceSnapshot] — re-open its session on this plane from the snapshot's
1154
1225
  /// LOCAL CUSTODY (no network clone), replacing the resident holon. The workspace need not currently be a
1155
1226
  /// session (a fresh restore is allowed); its name is taken from [snapshot] unless [into] overrides it.
1156
- Future<NomosSession> restoreWorkspace(WorkspaceSnapshot snapshot, {String? into}) async {
1227
+ Future<NomosSession> restoreWorkspace(WorkspaceSnapshot snapshot,
1228
+ {String? into}) async {
1157
1229
  final ws = into ?? snapshot.workspace;
1158
1230
  await _phased(
1159
1231
  phase: 'restoreWorkspace',
@@ -1191,7 +1263,8 @@ class NomosTestHarness {
1191
1263
  if (_disposed) return;
1192
1264
  _disposed = true;
1193
1265
  final f = _onDiagnostic;
1194
- if (f != null) f('[nomos-test-harness] phase dispose (≤${(_phaseTimeout ?? _kOtherDeadline).inSeconds}s) …');
1266
+ if (f != null)
1267
+ f('[nomos-test-harness] phase dispose (≤${(_phaseTimeout ?? _kOtherDeadline).inSeconds}s) …');
1195
1268
  try {
1196
1269
  await _transport.dispose().timeout(_phaseTimeout ?? _kOtherDeadline,
1197
1270
  onTimeout: () => throw HarnessPhaseTimeout(
@@ -1200,7 +1273,8 @@ class NomosTestHarness {
1200
1273
  elapsed: _phaseTimeout ?? _kOtherDeadline,
1201
1274
  awaiting: 'NodeRunnerTransport.dispose'));
1202
1275
  } catch (e) {
1203
- _diag('[nomos-test-harness] transport dispose error (continuing to temp-dir cleanup): $e');
1276
+ _diag(
1277
+ '[nomos-test-harness] transport dispose error (continuing to temp-dir cleanup): $e');
1204
1278
  }
1205
1279
  try {
1206
1280
  if (await _workdir.exists()) await _workdir.delete(recursive: true);
@@ -1235,9 +1309,12 @@ class NomosTestHarness {
1235
1309
  // Both lanes take the SAME flags (--deploy/--workspace/--out/--runtime) and print the SAME one-line JSON
1236
1310
  // contract; only the leading command differs (node <script> vs `<githolon...> harness-install`).
1237
1311
  final common = <String>[
1238
- '--deploy', deployJsonPaths.join(','),
1239
- '--workspace', workspace,
1240
- '--out', snapshot,
1312
+ '--deploy',
1313
+ deployJsonPaths.join(','),
1314
+ '--workspace',
1315
+ workspace,
1316
+ '--out',
1317
+ snapshot,
1241
1318
  if (runtimeDir != null) ...['--runtime', runtimeDir],
1242
1319
  ];
1243
1320
  final String executable;
@@ -1247,8 +1324,11 @@ class NomosTestHarness {
1247
1324
  args = [inRepoScript, ...common];
1248
1325
  } else {
1249
1326
  // `githolonCmd` is a command line (e.g. `npx githolon` or `node /abs/cli.mjs`) — split into tokens.
1250
- final tokens =
1251
- githolonCmd.trim().split(RegExp(r'\s+')).where((t) => t.isNotEmpty).toList();
1327
+ final tokens = githolonCmd
1328
+ .trim()
1329
+ .split(RegExp(r'\s+'))
1330
+ .where((t) => t.isNotEmpty)
1331
+ .toList();
1252
1332
  if (tokens.isEmpty) {
1253
1333
  throw NomosTestHarnessException(
1254
1334
  'no install helper resolved — pass installHelper: (an offline-install.ts path, or a githolon command)');
@@ -1335,12 +1415,15 @@ class WorkspaceSnapshot {
1335
1415
  'capturedAt': capturedAt.toUtc().toIso8601String(),
1336
1416
  };
1337
1417
 
1338
- factory WorkspaceSnapshot.fromJson(Map<String, Object?> j) => WorkspaceSnapshot(
1418
+ factory WorkspaceSnapshot.fromJson(Map<String, Object?> j) =>
1419
+ WorkspaceSnapshot(
1339
1420
  workspace: j['workspace'] as String,
1340
1421
  snapshotB64: j['snapshotB64'] as String,
1341
1422
  head: (j['head'] ?? '') as String,
1342
- lawHashes: ((j['lawHashes'] as List?) ?? const []).map((e) => '$e').toList(),
1343
- capturedAt: DateTime.parse((j['capturedAt'] ?? DateTime.now().toUtc().toIso8601String()) as String),
1423
+ lawHashes:
1424
+ ((j['lawHashes'] as List?) ?? const []).map((e) => '$e').toList(),
1425
+ capturedAt: DateTime.parse((j['capturedAt'] ??
1426
+ DateTime.now().toUtc().toIso8601String()) as String),
1344
1427
  );
1345
1428
 
1346
1429
  @override
@@ -1363,8 +1446,12 @@ class PlaneSnapshot {
1363
1446
  };
1364
1447
 
1365
1448
  factory PlaneSnapshot.fromJson(Map<String, Object?> j) => PlaneSnapshot(
1366
- workspaces: ((j['workspaces'] as Map?) ?? const {}).map(
1367
- (k, v) => MapEntry('$k', WorkspaceSnapshot.fromJson((v as Map).cast<String, Object?>()))),
1368
- capturedAt: DateTime.parse((j['capturedAt'] ?? DateTime.now().toUtc().toIso8601String()) as String),
1449
+ workspaces: ((j['workspaces'] as Map?) ?? const {}).map((k, v) =>
1450
+ MapEntry(
1451
+ '$k',
1452
+ WorkspaceSnapshot.fromJson(
1453
+ (v as Map).cast<String, Object?>()))),
1454
+ capturedAt: DateTime.parse((j['capturedAt'] ??
1455
+ DateTime.now().toUtc().toIso8601String()) as String),
1369
1456
  );
1370
1457
  }
package/dart/pubspec.yaml CHANGED
@@ -11,7 +11,7 @@ description: >-
11
11
  Nomos 2 Dart frontend types — the hand-written kernel wire types plus the
12
12
  code-generated per-domain aggregate + directive-payload types. (De)serializes
13
13
  the EXACT serde JSON the Rust kernel + projection produce/consume.
14
- version: 0.62.1
14
+ version: 0.62.2
15
15
 
16
16
  environment:
17
17
  sdk: ^3.5.0
@@ -29,7 +29,7 @@ String? _missingPrereq() {
29
29
  return 'missing fixture $_docsDeploy — compile it: '
30
30
  '(cd bench/scale/fixtures/surfaced && node ../../../../dsl/compile_package.mjs)';
31
31
  }
32
- if (!File('$_runtimeDir/holon.wasm').existsSync() ||
32
+ if (!File('$_runtimeDir/holon-testmint.wasm').existsSync() ||
33
33
  !File('$_runtimeDir/packages.json').existsSync()) {
34
34
  return 'warm ~/.holon/runtime first (run any `githolon` command once)';
35
35
  }
@@ -37,7 +37,7 @@ String? _missingPrereq() {
37
37
  for (final p in [_repoEntry, _installer, _guestbook, _surfaced, _attributed, _estateBirth]) {
38
38
  if (!File(p).existsSync()) return 'missing $p (compile the fixture / example first)';
39
39
  }
40
- if (!File('$_runtimeDir/holon.wasm').existsSync() ||
40
+ if (!File('$_runtimeDir/holon-testmint.wasm').existsSync() ||
41
41
  !File('$_runtimeDir/packages.json').existsSync()) {
42
42
  return 'warm ~/.holon/runtime first (run any `githolon` command once)';
43
43
  }
@@ -42,7 +42,7 @@ String? _missingPrereq() {
42
42
  '(needs `dart: { support: "package" }` in nomos.package.mjs) — missing $p';
43
43
  }
44
44
  }
45
- if (!File('$_runtimeDir/holon.wasm').existsSync() ||
45
+ if (!File('$_runtimeDir/holon-testmint.wasm').existsSync() ||
46
46
  !File('$_runtimeDir/packages.json').existsSync()) {
47
47
  return 'warm ~/.holon/runtime first (run any `githolon` command once)';
48
48
  }
@@ -37,7 +37,7 @@ String? _missingPrereq() {
37
37
  if (!File(_birthDeploy).existsSync()) {
38
38
  return 'compile the fixture: cd bench/scale/fixtures/estate-birth && node ../../../../dsl/compile_package.mjs — missing $_birthDeploy';
39
39
  }
40
- if (!File('$_runtimeDir/holon.wasm').existsSync() ||
40
+ if (!File('$_runtimeDir/holon-testmint.wasm').existsSync() ||
41
41
  !File('$_runtimeDir/packages.json').existsSync()) {
42
42
  return 'warm ~/.holon/runtime first (run any `githolon` command once)';
43
43
  }
@@ -27,7 +27,7 @@ String? _missingPrereq() {
27
27
  for (final p in [_catDeploy, _estateDeploy]) {
28
28
  if (!File(p).existsSync()) return 'missing fixture $p (compile bench/scale/fixtures first)';
29
29
  }
30
- if (!File('$_runtimeDir/holon.wasm').existsSync() ||
30
+ if (!File('$_runtimeDir/holon-testmint.wasm').existsSync() ||
31
31
  !File('$_runtimeDir/packages.json').existsSync()) {
32
32
  return 'warm ~/.holon/runtime first (run any `githolon` command once)';
33
33
  }
@@ -24,7 +24,7 @@ String? _missingPrereq() {
24
24
  if (!File(_guestbookDeploy).existsSync()) {
25
25
  return 'compile the guestbook example: cd examples/guestbook && npx nomos-compile — missing $_guestbookDeploy';
26
26
  }
27
- if (!File('$_runtimeDir/holon.wasm').existsSync() ||
27
+ if (!File('$_runtimeDir/holon-testmint.wasm').existsSync() ||
28
28
  !File('$_runtimeDir/packages.json').existsSync()) {
29
29
  return 'warm ~/.holon/runtime first (run any `githolon` command once)';
30
30
  }
@@ -28,7 +28,7 @@ String? _missingPrereq() {
28
28
  if (!File(_guestbookDeploy).existsSync()) {
29
29
  return 'compile the guestbook example: cd examples/guestbook && npx nomos-compile — missing $_guestbookDeploy';
30
30
  }
31
- if (!File('$_runtimeDir/holon.wasm').existsSync() ||
31
+ if (!File('$_runtimeDir/holon-testmint.wasm').existsSync() ||
32
32
  !File('$_runtimeDir/packages.json').existsSync()) {
33
33
  return 'warm ~/.holon/runtime first (run any `githolon` command once)';
34
34
  }
@@ -36,7 +36,7 @@ String? _missingPrereq() {
36
36
  return 'missing fixture ${e.key} — compile it: (cd ${e.value} && node ../../../../dsl/compile_package.mjs)';
37
37
  }
38
38
  }
39
- if (!File('$_runtimeDir/holon.wasm').existsSync() ||
39
+ if (!File('$_runtimeDir/holon-testmint.wasm').existsSync() ||
40
40
  !File('$_runtimeDir/packages.json').existsSync()) {
41
41
  return 'warm ~/.holon/runtime first (run any `githolon` command once)';
42
42
  }