@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
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
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.
|
|
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",
|