@githolon/dsl 0.57.0 → 0.57.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.
@@ -190,7 +190,13 @@ class NomosTestHarness {
190
190
  allowUnborn: true,
191
191
  );
192
192
  final harness = NomosTestHarness._(
193
- bridge, workspace, transport, workdir, onDiagnostic, [workspace]);
193
+ bridge, workspace, transport, workdir, onDiagnostic, [workspace],
194
+ frameworkHash: info['frameworkHash']?.toString(),
195
+ lawHashes: {
196
+ workspace: ((info['lawHashes'] as List?) ?? const [])
197
+ .map((e) => e.toString())
198
+ .toList()
199
+ });
194
200
  if (autoTearDown && registerTearDown != null) {
195
201
  registerTearDown(harness.dispose);
196
202
  }
@@ -259,9 +265,11 @@ class NomosTestHarness {
259
265
  try {
260
266
  // 1. install each workspace's law offline → one snapshot per workspace (colocated, still separate law).
261
267
  final snapshots = <String, String>{};
268
+ final lawHashesByWs = <String, List<String>>{};
269
+ String? frameworkHash;
262
270
  for (final e in workspaces.entries) {
263
271
  final snap = '${workdir.path}/${e.key}.bin';
264
- await _runInstall(
272
+ final info = await _runInstall(
265
273
  node: node,
266
274
  useInRepoScript: useInRepoScript,
267
275
  inRepoScript: resolvedHelper,
@@ -272,6 +280,10 @@ class NomosTestHarness {
272
280
  runtimeDir: runtimeDir,
273
281
  onDiagnostic: onDiagnostic);
274
282
  snapshots[e.key] = snap;
283
+ lawHashesByWs[e.key] = ((info['lawHashes'] as List?) ?? const [])
284
+ .map((x) => x.toString())
285
+ .toList();
286
+ frameworkHash ??= info['frameworkHash']?.toString();
275
287
  }
276
288
 
277
289
  // 2. ONE runner / ONE bridge for the whole plane — no env restore default; each connect brings its own.
@@ -324,7 +336,9 @@ class NomosTestHarness {
324
336
  final names = workspaces.keys.toList();
325
337
  final harness = NomosTestHarness._(
326
338
  bridge, names.first, transport, workdir, onDiagnostic, names,
327
- devicePublics);
339
+ devicePublics: devicePublics,
340
+ frameworkHash: frameworkHash,
341
+ lawHashes: lawHashesByWs);
328
342
  if (autoTearDown && registerTearDown != null) {
329
343
  registerTearDown(harness.dispose);
330
344
  }
@@ -1127,14 +1127,18 @@ class NomosBridge {
1127
1127
  .whereType<Map>()
1128
1128
  .map((m) => m.cast<String, Object?>())
1129
1129
  .toList();
1130
+ // `born` elements may be plain workspace-name strings OR `{workspace,…}` objects (both shapes ship);
1131
+ // fall back to the birth outcomes' workspace names when no bare `born` list was sent.
1132
+ final bornRaw = (r['born'] as List?);
1130
1133
  final bornNames = <String>[
1131
- ...((r['born'] as List?) ?? const []).map((e) => e.toString()),
1132
- // Fall back to the workspace field of each birth outcome when a bare `born` list wasn't sent.
1133
- if ((r['born'] as List?) == null)
1134
+ if (bornRaw != null)
1135
+ ...bornRaw.map((e) =>
1136
+ (e is Map ? (e['workspace'] ?? '') : e).toString())
1137
+ else
1134
1138
  ...births
1135
1139
  .map((m) => (m['workspace'] ?? '').toString())
1136
1140
  .where((s) => s.isNotEmpty),
1137
- ];
1141
+ ]..removeWhere((s) => s.isEmpty);
1138
1142
  return NomosOfferOutcome(
1139
1143
  head: r['head'].toString(),
1140
1144
  created: ((r['created'] as List?) ?? const [])
@@ -0,0 +1,146 @@
1
+ // LOCAL BIRTHS — DART LEG. An offline `.births()` directive dispatch: the estate-birth `home` law's
2
+ // `birthEstateWorkspace` OFFERS a birth on THIS device's local plane — no cloud. Three things are proven:
3
+ //
4
+ // 1. THE DART API PATH: the offer surfaces the typed birth verdict — `born` (the child workspace name) +
5
+ // `birthOutcomes` (the per-child fate) — off NomosOfferOutcome. A birth is observable from Dart.
6
+ // 2. THE OFFLINE BIRTH SUCCEEDS (status=born): the child genesis (installDomain×2 + seed) folds through the
7
+ // CHILD's own gate, fully offline. This needed a lane fix — the client holon's wasm provider now stages
8
+ // the stage-0 bootstrap package at the preopen root (`/work/domain.package.usda`), the SAME file the
9
+ // adapter engine stages, which `run_birth_effect` reads as the genesis-lane executor for the child's
10
+ // step-0 `bootstrap/installDomain`. Without it the birth failed with "bare JavaScript is not a domain
11
+ // package" (see cloud/web-client/src/kernel_provider_wasm.mjs boot).
12
+ // 3. A FAILED birth carries a TYPED reason to Dart (deliberate bad framework hash) — never a silent drop.
13
+ //
14
+ // THE NAMED GAP (task #42): opening the born child as a SIBLING SESSION (`bridge.session(child)` → typed
15
+ // query) requires the SHARED ENGINE PLANE — today each runner session is its own single-workspace engine, so
16
+ // a sibling session can't see the child custody born in another. We do NOT fake that loop; the child handle
17
+ // is addressable, and we stop there.
18
+ @TestOn('vm')
19
+ library;
20
+
21
+ import 'dart:io';
22
+
23
+ import 'package:nomos_client/nomos_client.dart' show NomosOfferOutcome;
24
+ import 'package:nomos_client/nomos_headless.dart';
25
+ import 'package:test/test.dart';
26
+
27
+ String get _root => '${Directory.current.path}/../..';
28
+ String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.mjs';
29
+ String get _birthDeploy =>
30
+ '$_root/bench/scale/fixtures/estate-birth/build/estate-birth.deploy.json';
31
+ String get _runtimeDir => '${Platform.environment['HOME'] ?? ''}/.holon/runtime';
32
+
33
+ String? _missingPrereq() {
34
+ if (!File(_repoEntry).existsSync()) return 'in-repo runner not found at $_repoEntry';
35
+ if (!File(_birthDeploy).existsSync()) {
36
+ return 'compile the fixture: cd bench/scale/fixtures/estate-birth && node ../../../../dsl/compile_package.mjs — missing $_birthDeploy';
37
+ }
38
+ if (!File('$_runtimeDir/holon.wasm').existsSync() ||
39
+ !File('$_runtimeDir/packages.json').existsSync()) {
40
+ return 'warm ~/.holon/runtime first (run any `githolon` command once)';
41
+ }
42
+ return null;
43
+ }
44
+
45
+ const _owner = 'sample-owner';
46
+
47
+ /// Set up a single-session plane over the estate-birth law + OFFER `birthEstateWorkspace` on it. Returns the
48
+ /// harness (for teardown) + the offer outcome (born / birthOutcomes surfaced). [frameworkHash] overrides the
49
+ /// real framework hash (pass a bogus one to force a genesis failure).
50
+ Future<(NomosTestHarness, NomosOfferOutcome)> _birth({String? frameworkHash}) async {
51
+ final harness = await NomosTestHarness.ephemeral(
52
+ deployJsonPaths: [_birthDeploy],
53
+ nodeEntry: _repoEntry,
54
+ runtimeDir: _runtimeDir,
55
+ workspace: 'estate-birth',
56
+ );
57
+ // The genesis recipe installs the framework + child law BY HASH — the harness surfaces both (frameworkHash
58
+ // is not derivable client-side; lawHash is this package's own domainHash).
59
+ final fw = frameworkHash ?? harness.frameworkHash;
60
+ final lawHash = harness.lawHashes('estate-birth').first;
61
+ final outcome = await harness.bridge.offerCreates(
62
+ domain: 'home',
63
+ directiveId: 'birthEstateWorkspace',
64
+ payload: {
65
+ 'owner': _owner,
66
+ 'frameworkHash': fw,
67
+ 'lawHash': lawHash,
68
+ 'bornAt': '2026-07-05T00:00:00Z',
69
+ },
70
+ actor: _owner,
71
+ );
72
+ return (harness, outcome);
73
+ }
74
+
75
+ void main() {
76
+ test('offline .births() dispatch surfaces born + birthOutcomes TYPED to Dart', () async {
77
+ final skip = _missingPrereq();
78
+ if (skip != null) {
79
+ markTestSkipped(skip);
80
+ return;
81
+ }
82
+ final (harness, outcome) = await _birth();
83
+ try {
84
+ expect(outcome.head, isNotEmpty,
85
+ reason: 'the birth offer admitted on the local plane');
86
+ // THE API PATH (co2's ask): the birth verdict is typed back to Dart.
87
+ expect(outcome.born, isNotEmpty,
88
+ reason: 'the offer-effect allocated the estate child workspace on the local plane');
89
+ expect(outcome.born.single, startsWith('estate-'),
90
+ reason: 'the born workspace is estate-<sha256(owner)>');
91
+ expect(outcome.birthOutcomes, isNotEmpty,
92
+ reason: 'the typed birth verdict rides beside born');
93
+ expect(outcome.birthOutcomes.first['workspace'], outcome.born.single);
94
+ expect(outcome.birthOutcomes.first['status'], isNotNull);
95
+
96
+ // NAMED GAP (#42): the child handle is addressable on the plane, but opening it as its own session
97
+ // across the runner's per-session engines needs the shared engine plane — we stop at the handle.
98
+ expect(harness.bridge.session(outcome.born.single).workspace, outcome.born.single);
99
+ } finally {
100
+ await harness.dispose();
101
+ }
102
+ }, timeout: const Timeout(Duration(minutes: 3)));
103
+
104
+ test('offline birth genesis SUCCEEDS (status=born) — the child is folded through its own gate', () async {
105
+ final skip = _missingPrereq();
106
+ if (skip != null) {
107
+ markTestSkipped(skip);
108
+ return;
109
+ }
110
+ final (harness, outcome) = await _birth();
111
+ try {
112
+ // The lane is fixed (the provider stages the stage-0 bootstrap package at the preopen root), so the
113
+ // child genesis folds clean and the born verdict is `born` — hard-asserted, no skip.
114
+ expect(outcome.born, hasLength(1));
115
+ expect(outcome.birthOutcomes, hasLength(1));
116
+ expect(outcome.birthOutcomes.first['status'], 'born',
117
+ reason: 'the child genesis (installDomain×2 + seed) folded through the child\'s own gate offline');
118
+ expect(outcome.birthOutcomes.first['workspace'], outcome.born.single);
119
+ } finally {
120
+ await harness.dispose();
121
+ }
122
+ }, timeout: const Timeout(Duration(minutes: 3)));
123
+
124
+ test('birth FAILURE carries a typed reason to Dart (deliberate bad framework hash)', () async {
125
+ final skip = _missingPrereq();
126
+ if (skip != null) {
127
+ markTestSkipped(skip);
128
+ return;
129
+ }
130
+ // A well-formed but WRONG framework hash the parent does not fold → the child's step-0 install can't be
131
+ // satisfied → the genesis fold refuses. The offer still admits on the parent (the birth ORDER is valid);
132
+ // the child's fate comes back as a TYPED failure outcome, not a thrown error or a silent drop.
133
+ final badHash = 'deadbeef' * 8; // 64 hex chars, not a package the parent holds
134
+ final (harness, outcome) = await _birth(frameworkHash: badHash);
135
+ try {
136
+ expect(outcome.birthOutcomes, isNotEmpty,
137
+ reason: 'a failed birth still reports its typed verdict to Dart');
138
+ expect(outcome.birthOutcomes.first['status'], isNot('born'),
139
+ reason: 'an unresolvable framework hash cannot yield a born child');
140
+ expect((outcome.birthOutcomes.first['reason'] ?? '').toString(), isNotEmpty,
141
+ reason: 'the failure carries a human-legible reason (never a silent drop)');
142
+ } finally {
143
+ await harness.dispose();
144
+ }
145
+ }, timeout: const Timeout(Duration(minutes: 3)));
146
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@githolon/dsl",
3
- "version": "0.57.0",
3
+ "version": "0.57.1",
4
4
  "type": "module",
5
5
  "description": "Nomos 2 domain-authoring DSL: aggregates + directives in TS, executed and encoded to the Rust kernel's wire shapes.",
6
6
  "license": "SEE LICENSE IN LICENSE.md",