@githolon/dsl 0.96.0 → 0.97.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 +17 -0
- package/dart/lib/src/dev_cloud.dart +24 -5
- package/dart/lib/src/test_harness.dart +150 -28
- package/dart/lib/src/webview_bridge.dart +31 -10
- package/dart/pubspec.yaml +1 -1
- package/dart/test/cloud_bound_topology_test.dart +9 -2
- package/dart/test/cluster_births_test.dart +15 -8
- package/dart/test/first_app_gauntlet_test.dart +6 -1
- package/dart/test/four_layer_nested_topology_test.dart +8 -4
- package/dart/test/parent_signer_recovery_test.dart +3 -2
- package/dart/test/session_bound_test.dart +11 -1
- package/dart/test/watch_surfaces_test.QUARANTINED.md +17 -0
- package/package.json +1 -1
- package/src/compile_package_main.ts +1 -1
- package/src/framework/genesis.ts +5 -5
- package/src/framework/home.ts +26 -13
- package/src/framework/workspaces.ts +35 -34
- package/dart/test/co2_acceptance_test.dart +0 -101
- package/dart/test/watch_surfaces_test.dart +0 -240
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
// THE REACTIVE PROMISED SURFACES (task #86 gaps 4/5/6): one cluster, three watch lanes the
|
|
2
|
-
// gauntlet build found uncovered from Dart —
|
|
3
|
-
// • watchSpatial — the R*Tree bbox probe as a LIVE stream (typed, via the generated client);
|
|
4
|
-
// • watchCompatibility / onLawChanged — law-change reactivity: subscribe under law v1, install the
|
|
5
|
-
// evolved v2 IN-SESSION (the real `nomos/installDomain` lane), assert both surfaces fire with the
|
|
6
|
-
// right shapes and the evolved directive works immediately;
|
|
7
|
-
// • watchActivity — declared `.activity()` directives stream as activity rows.
|
|
8
|
-
// House style: numbered legs, phase-timeout-bounded, loud failures, graceful skip.
|
|
9
|
-
@TestOn('vm')
|
|
10
|
-
library;
|
|
11
|
-
|
|
12
|
-
import 'dart:async';
|
|
13
|
-
import 'dart:convert';
|
|
14
|
-
import 'dart:io';
|
|
15
|
-
|
|
16
|
-
import 'package:crypto/crypto.dart' show sha256;
|
|
17
|
-
import 'package:nomos_client/nomos_headless.dart';
|
|
18
|
-
import 'package:test/test.dart';
|
|
19
|
-
|
|
20
|
-
import '../../../bench/scale/fixtures/co2-acceptance/build/dart/src/generated/estate.client.dart';
|
|
21
|
-
|
|
22
|
-
String get _root => '${Directory.current.path}/../..';
|
|
23
|
-
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
24
|
-
String get _geoDeploy =>
|
|
25
|
-
'$_root/bench/scale/fixtures/co2-acceptance/build/co2-acceptance.deploy.json';
|
|
26
|
-
String get _actsDeploy =>
|
|
27
|
-
'$_root/bench/scale/fixtures/activity-feed/build/activity_repro.deploy.json';
|
|
28
|
-
String get _evoV1Deploy => '$_root/bench/scale/fixtures/evolution/v1/build/evo.deploy.json';
|
|
29
|
-
String get _evoV2Deploy => '$_root/bench/scale/fixtures/evolution/v2/build/evo.deploy.json';
|
|
30
|
-
String get _evoV2Interface =>
|
|
31
|
-
'$_root/bench/scale/fixtures/evolution/v2/build/evo.interface.json';
|
|
32
|
-
String get _runtimeDir =>
|
|
33
|
-
Platform.environment['NOMOS_DART_RUNTIME'] ??
|
|
34
|
-
'${Platform.environment['HOME'] ?? ''}/.holon/runtime';
|
|
35
|
-
|
|
36
|
-
String? _missingPrereq() {
|
|
37
|
-
if (!File(_repoEntry).existsSync()) return 'in-repo runner not found at $_repoEntry';
|
|
38
|
-
for (final e in {
|
|
39
|
-
_geoDeploy: 'bench/scale/fixtures/co2-acceptance',
|
|
40
|
-
_actsDeploy: 'bench/scale/fixtures/activity-feed',
|
|
41
|
-
_evoV1Deploy: 'bench/scale/fixtures/evolution/v1',
|
|
42
|
-
_evoV2Deploy: 'bench/scale/fixtures/evolution/v2',
|
|
43
|
-
}.entries) {
|
|
44
|
-
if (!File(e.key).existsSync()) {
|
|
45
|
-
return 'missing fixture ${e.key} — compile it: (cd ${e.value} && node ../../../../dsl/compile_package.mjs)';
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
if (!File('$_runtimeDir/holon.wasm').existsSync() ||
|
|
49
|
-
!File('$_runtimeDir/packages.json').existsSync()) {
|
|
50
|
-
return 'warm ~/.holon/runtime first (run any `githolon` command once)';
|
|
51
|
-
}
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
Future<T> _leg<T>(String name, Future<T> Function() body) async {
|
|
56
|
-
final sw = Stopwatch()..start();
|
|
57
|
-
// ignore: avoid_print
|
|
58
|
-
print('▶ WATCH $name …');
|
|
59
|
-
try {
|
|
60
|
-
final r = await body();
|
|
61
|
-
// ignore: avoid_print
|
|
62
|
-
print('✔ WATCH $name (${sw.elapsedMilliseconds}ms)');
|
|
63
|
-
return r;
|
|
64
|
-
} catch (e, st) {
|
|
65
|
-
throw TestFailure('WATCH $name FAILED after ${sw.elapsedMilliseconds}ms:\n$e\n$st');
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/// Await the first stream element satisfying [where], bounded — a watch that never emits is a
|
|
70
|
-
/// loud, named failure, not a hang.
|
|
71
|
-
Future<T> _firstWhere<T>(Stream<T> s, bool Function(T) where, String what,
|
|
72
|
-
[Duration limit = const Duration(seconds: 30)]) =>
|
|
73
|
-
s.firstWhere(where).timeout(limit,
|
|
74
|
-
onTimeout: () => throw StateError('$what never emitted within ${limit.inSeconds}s'));
|
|
75
|
-
|
|
76
|
-
void main() {
|
|
77
|
-
test('reactive surfaces: watchSpatial → watchCompatibility/onLawChanged → watchActivity',
|
|
78
|
-
() async {
|
|
79
|
-
final missing = _missingPrereq();
|
|
80
|
-
if (missing != null) {
|
|
81
|
-
markTestSkipped(missing);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
final harness = await NomosTestHarness.cluster(
|
|
85
|
-
workspaces: {
|
|
86
|
-
'geo': [_geoDeploy],
|
|
87
|
-
'acts': [_actsDeploy],
|
|
88
|
-
'evo': [_evoV1Deploy],
|
|
89
|
-
},
|
|
90
|
-
nodeEntry: _repoEntry,
|
|
91
|
-
runtimeDir: _runtimeDir,
|
|
92
|
-
phaseTimeout: const Duration(seconds: 120),
|
|
93
|
-
);
|
|
94
|
-
try {
|
|
95
|
-
// ── LEG 1: watchSpatial — the typed reactive bbox probe. Write a point, the bbox watch
|
|
96
|
-
// emits it; write a second point inside the box, the watch re-emits with both. ──
|
|
97
|
-
await _leg('leg 1: watchSpatial (typed sitesByBounds bbox stream)', () async {
|
|
98
|
-
final estate = EstateClient(harness.session('geo'), actor: 'user:ada');
|
|
99
|
-
final bbox = NomosBbox(minX: -3, minY: 53, maxX: -2, maxY: 54);
|
|
100
|
-
Future<void> place(String name, List<double> coords) =>
|
|
101
|
-
estate.registerSite(RegisterSitePayload(
|
|
102
|
-
name: name,
|
|
103
|
-
location: SiteLocation(
|
|
104
|
-
point: GeoPoint(coordinates: coords),
|
|
105
|
-
source: SiteLocationSource.addressSearch,
|
|
106
|
-
),
|
|
107
|
-
registeredAt: DateTime.now().toUtc().toIso8601String(),
|
|
108
|
-
));
|
|
109
|
-
await place('inside-1', [-2.24, 53.48]);
|
|
110
|
-
await place('way-outside', [10.0, 10.0]);
|
|
111
|
-
final stream = estate.watchSitesByBounds(bbox: bbox).asBroadcastStream();
|
|
112
|
-
final first = await _firstWhere(
|
|
113
|
-
stream, (rows) => rows.isNotEmpty, 'watchSpatial first snapshot');
|
|
114
|
-
expect(first.map((r) => r.name), ['inside-1'],
|
|
115
|
-
reason: 'the first snapshot holds exactly the in-bbox site (probe, not full scan)');
|
|
116
|
-
final grown = _firstWhere(stream, (rows) => rows.length >= 2, 'watchSpatial re-emission');
|
|
117
|
-
await place('inside-2', [-2.5, 53.6]);
|
|
118
|
-
expect((await grown).map((r) => r.name).toSet(), {'inside-1', 'inside-2'},
|
|
119
|
-
reason: 'a new in-bbox write re-emits through the live spatial watch');
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// ── LEG 2: watchCompatibility + onLawChanged — subscribe under v1, install the evolved v2
|
|
123
|
-
// (rename + added field + added directive) through the REAL nomos/installDomain lane,
|
|
124
|
-
// assert both reactive surfaces fire and the added directive works at once. ──
|
|
125
|
-
await _leg('leg 2: watchCompatibility/onLawChanged across a live law evolution', () async {
|
|
126
|
-
final evo = harness.session('evo');
|
|
127
|
-
final v1Hash = harness.lawHashes('evo').first;
|
|
128
|
-
final v2Deploy =
|
|
129
|
-
(jsonDecode(File(_evoV2Deploy).readAsStringSync()) as Map).cast<String, Object?>();
|
|
130
|
-
final v2Usda = v2Deploy['packageUsda'].toString();
|
|
131
|
-
final v2Hash = sha256.convert(utf8.encode(v2Usda)).toString();
|
|
132
|
-
expect(v2Hash, isNot(v1Hash), reason: 'v2 is a genuinely different law');
|
|
133
|
-
final v2Requirement =
|
|
134
|
-
(jsonDecode(File(_evoV2Interface).readAsStringSync()) as Map).cast<String, Object?>();
|
|
135
|
-
|
|
136
|
-
// Seed a v1 row (written under the OLD field name — the rename's era-1 side).
|
|
137
|
-
await evo.offerOutcome(
|
|
138
|
-
domain: 'evo',
|
|
139
|
-
directiveId: 'registerSite',
|
|
140
|
-
payload: {'name': 'era-1-site', 'geoPos': '51.5,-0.1'},
|
|
141
|
-
domainHash: v1Hash,
|
|
142
|
-
);
|
|
143
|
-
|
|
144
|
-
// Arm the compat superpower AS the v2 client would: verdict against the current (v1) law.
|
|
145
|
-
final before = await evo.checkCompat({'lawHash': v2Hash}, v2Requirement);
|
|
146
|
-
expect(before, isNotNull);
|
|
147
|
-
expect(before!.matches, isFalse, reason: 'the workspace still runs v1');
|
|
148
|
-
expect(before.has('setMood'), isFalse,
|
|
149
|
-
reason: 'the v2-only directive is dark before the law lands');
|
|
150
|
-
|
|
151
|
-
// Subscribe BOTH reactive surfaces before the deploy.
|
|
152
|
-
final compatStream = evo.watchCompatibility(v2Requirement).asBroadcastStream();
|
|
153
|
-
final lawChanged = evo.onLawChanged.asBroadcastStream();
|
|
154
|
-
final firstVerdict = await _firstWhere(
|
|
155
|
-
compatStream, (v) => v.containsKey('ok'), 'watchCompatibility first snapshot');
|
|
156
|
-
expect(firstVerdict['ok'], isFalse,
|
|
157
|
-
reason: 'v2 requirement vs the v1 offer must not verify (setMood missing)');
|
|
158
|
-
expect(firstVerdict['offerKnown'], isTrue);
|
|
159
|
-
|
|
160
|
-
final lawEvent = _firstWhere(
|
|
161
|
-
lawChanged, (c) => c.has('setMood'), 'onLawChanged after the v2 install',
|
|
162
|
-
const Duration(seconds: 60));
|
|
163
|
-
final okVerdict = _firstWhere(
|
|
164
|
-
compatStream, (v) => v['ok'] == true, 'watchCompatibility after the v2 install',
|
|
165
|
-
const Duration(seconds: 60));
|
|
166
|
-
|
|
167
|
-
// THE EVOLVE: install v2 in-session (the same install lane the deploy endpoint drives).
|
|
168
|
-
await evo.offerOutcome(
|
|
169
|
-
domain: 'nomos',
|
|
170
|
-
directiveId: 'installDomain',
|
|
171
|
-
payload: {
|
|
172
|
-
'domainHash': v2Hash,
|
|
173
|
-
'packageUsda': v2Usda,
|
|
174
|
-
'installedBy': 'watch-surfaces-test',
|
|
175
|
-
'dependencies': <Object?>[],
|
|
176
|
-
'finalizers': <Object?>[],
|
|
177
|
-
},
|
|
178
|
-
domainHash: harness.frameworkHash,
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
final changed = await lawEvent;
|
|
182
|
-
expect(changed.has('setMood'), isTrue, reason: 'the dark member lit up');
|
|
183
|
-
expect(changed.verdict, 'compatible');
|
|
184
|
-
expect((await okVerdict)['ok'], isTrue);
|
|
185
|
-
// The evolved law is live: the ADDED directive works on the era-1 row immediately,
|
|
186
|
-
// and the era-1 row reads back under the NEW label lineage.
|
|
187
|
-
final rows = await evo.query('sitesByName', {'name': 'era-1-site'});
|
|
188
|
-
expect(rows, hasLength(1));
|
|
189
|
-
final siteId = (rows.single['id'] ?? '').toString();
|
|
190
|
-
await evo.offerOutcome(
|
|
191
|
-
domain: 'evo',
|
|
192
|
-
directiveId: 'setMood',
|
|
193
|
-
payload: {'siteId': siteId, 'mood': 'thriving'},
|
|
194
|
-
domainHash: v2Hash,
|
|
195
|
-
);
|
|
196
|
-
final after = await evo.queryById(siteId);
|
|
197
|
-
expect(((after.single['data'] as Map)['mood']), 'thriving');
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
// ── LEG 3: watchActivity — `.activity()`-declared directives stream as activity rows. ──
|
|
201
|
-
await _leg('leg 3: watchActivity streams declared activity rows', () async {
|
|
202
|
-
final acts = harness.session('acts');
|
|
203
|
-
final actsHash = harness.lawHashes('acts').first;
|
|
204
|
-
final stream = acts.watchActivity().asBroadcastStream();
|
|
205
|
-
final rowsWithSite = _firstWhere(
|
|
206
|
-
stream,
|
|
207
|
-
(rows) => rows
|
|
208
|
-
.whereType<Map>()
|
|
209
|
-
.any((r) => r['directiveId'] == 'createSite' && r['domain'] == 'estate'),
|
|
210
|
-
'watchActivity emission carrying the createSite row');
|
|
211
|
-
await acts.offerOutcome(
|
|
212
|
-
domain: 'estate',
|
|
213
|
-
directiveId: 'createSite',
|
|
214
|
-
payload: {
|
|
215
|
-
'name': 'Boiler House',
|
|
216
|
-
'createdAt': DateTime.now().toUtc().toIso8601String()
|
|
217
|
-
},
|
|
218
|
-
actor: 'user:ada',
|
|
219
|
-
domainHash: actsHash,
|
|
220
|
-
);
|
|
221
|
-
final rows = await rowsWithSite;
|
|
222
|
-
final row = rows
|
|
223
|
-
.whereType<Map>()
|
|
224
|
-
.firstWhere((r) => r['directiveId'] == 'createSite')
|
|
225
|
-
.cast<String, Object?>();
|
|
226
|
-
expect(row['domain'], 'estate');
|
|
227
|
-
expect(row['intentId'], isNotNull);
|
|
228
|
-
expect(row['occurredAt'], isNotNull);
|
|
229
|
-
expect(((row['payload'] as Map?) ?? const {})['name'], 'Boiler House',
|
|
230
|
-
reason: 'the activity row carries the DECODED directive payload');
|
|
231
|
-
// The pull surface agrees with the stream (activityLog is the same read).
|
|
232
|
-
final log = await acts.activityLog();
|
|
233
|
-
expect(log.any((r) => r['directiveId'] == 'createSite'), isTrue);
|
|
234
|
-
});
|
|
235
|
-
} finally {
|
|
236
|
-
await harness.dispose().timeout(const Duration(seconds: 30),
|
|
237
|
-
onTimeout: () => throw StateError('harness disposal hung'));
|
|
238
|
-
}
|
|
239
|
-
}, timeout: const Timeout(Duration(minutes: 8)));
|
|
240
|
-
}
|