@githolon/dsl 0.105.23 → 0.105.24
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 +9 -0
- package/dart/lib/src/runner/node-entry.mjs +4461 -267
- package/dart/lib/src/runner/offline-install.mjs +4337 -115
- package/dart/lib/src/test_harness.dart +316 -235
- package/dart/lib/src/webview_bridge.dart +1478 -1087
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart_types/CHANGELOG.md +5 -0
- package/dart_types/lib/src/errors.dart +78 -58
- package/dart_types/pubspec.yaml +1 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +4235 -102
- package/src/codegen_dart_client.ts +7 -10
- package/src/codegen_ts.ts +3 -6
- package/src/compat_manifest.ts +6 -7
- package/src/compile_package_main.ts +9 -11
- package/src/index.ts +9 -11
- package/src/kernel_capabilities.ts +79 -237
|
@@ -51,11 +51,13 @@ import 'webview_bridge.dart'
|
|
|
51
51
|
|
|
52
52
|
enum _KernelRuntimeArtifact {
|
|
53
53
|
executable('holon.wasm'),
|
|
54
|
+
repository('kernel.repository.xml', 'repository.xml'),
|
|
54
55
|
stageZeroLaw('bootstrap.law.usdz'),
|
|
55
56
|
controllerLaw('nomos.law.usdz');
|
|
56
57
|
|
|
57
|
-
const _KernelRuntimeArtifact(this.fileName);
|
|
58
|
+
const _KernelRuntimeArtifact(this.fileName, [this.providerFileName]);
|
|
58
59
|
final String fileName;
|
|
60
|
+
final String? providerFileName;
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
enum _CandidateKernelBinary {
|
|
@@ -74,9 +76,10 @@ Future<String?> _sourceCandidateRuntime(String nodeEntry) async {
|
|
|
74
76
|
var cursor = File(nodeEntry).absolute.parent;
|
|
75
77
|
Directory? root;
|
|
76
78
|
for (var depth = 0; depth < 12; depth++) {
|
|
77
|
-
if (File('${cursor.path}/release/kernel-candidate.
|
|
78
|
-
File(
|
|
79
|
-
|
|
79
|
+
if (File('${cursor.path}/release/kernel-candidate.usda').existsSync() &&
|
|
80
|
+
File(
|
|
81
|
+
'${cursor.path}/cloud/dev-server/kernel-testmint.wasm',
|
|
82
|
+
).existsSync()) {
|
|
80
83
|
root = cursor;
|
|
81
84
|
break;
|
|
82
85
|
}
|
|
@@ -84,31 +87,71 @@ Future<String?> _sourceCandidateRuntime(String nodeEntry) async {
|
|
|
84
87
|
if (parent.path == cursor.path) break;
|
|
85
88
|
cursor = parent;
|
|
86
89
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
90
|
+
final sourceRoot = root;
|
|
91
|
+
if (sourceRoot == null) return null;
|
|
92
|
+
|
|
93
|
+
Future<String> candidateField(String field) async {
|
|
94
|
+
final rust = await Process.run('rustc', ['-vV']);
|
|
95
|
+
if (rust.exitCode != 0) {
|
|
96
|
+
throw NomosTestHarnessException(
|
|
97
|
+
'rustc could not report its host target: ${rust.stderr}',
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
final host = RegExp(
|
|
101
|
+
r'^host: (\S+)$',
|
|
102
|
+
multiLine: true,
|
|
103
|
+
).firstMatch(rust.stdout.toString())?.group(1);
|
|
104
|
+
if (host == null) {
|
|
105
|
+
throw NomosTestHarnessException('rustc did not report its host target');
|
|
106
|
+
}
|
|
107
|
+
final result = await Process.run('cargo', [
|
|
108
|
+
'run',
|
|
109
|
+
'--quiet',
|
|
110
|
+
'--target',
|
|
111
|
+
host,
|
|
112
|
+
'--manifest-path',
|
|
113
|
+
'${sourceRoot.path}/tools/kernel-resource/Cargo.toml',
|
|
114
|
+
'--',
|
|
115
|
+
'read',
|
|
116
|
+
'${sourceRoot.path}/release/kernel-candidate.usda',
|
|
117
|
+
field,
|
|
118
|
+
], workingDirectory: sourceRoot.path);
|
|
119
|
+
if (result.exitCode != 0) {
|
|
120
|
+
throw NomosTestHarnessException(
|
|
121
|
+
'could not read typed kernel candidate: ${result.stderr}',
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return result.stdout.toString().trim();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
final productionExpected = await candidateField('production-sha256');
|
|
128
|
+
final testMintExpected = await candidateField('test-mint-sha256');
|
|
99
129
|
final production = File(
|
|
100
|
-
'${
|
|
130
|
+
'${sourceRoot.path}/cloud/cloudflare-shell/vendor/holon_bytes.wasmbin',
|
|
101
131
|
);
|
|
102
132
|
final testMint = File(
|
|
103
|
-
'${
|
|
133
|
+
'${sourceRoot.path}/cloud/dev-server/kernel-testmint.wasm',
|
|
134
|
+
);
|
|
135
|
+
final productionRepository = File(
|
|
136
|
+
'${sourceRoot.path}/cloud/cloudflare-shell/runtime/kernel.repository.xml',
|
|
137
|
+
);
|
|
138
|
+
final testMintRepository = File(
|
|
139
|
+
'${sourceRoot.path}/cloud/cloudflare-shell/runtime/kernel-testmint.repository.xml',
|
|
104
140
|
);
|
|
105
141
|
final bootstrap = File(
|
|
106
|
-
'${
|
|
142
|
+
'${sourceRoot.path}/cloud/cloudflare-shell/domains/build/bootstrap/domain_bootstrap.law.usdz',
|
|
107
143
|
);
|
|
108
144
|
final nomos = File(
|
|
109
|
-
'${
|
|
145
|
+
'${sourceRoot.path}/cloud/cloudflare-shell/domains/build/nomos/nomos.law.usdz',
|
|
110
146
|
);
|
|
111
|
-
for (final file in [
|
|
147
|
+
for (final file in [
|
|
148
|
+
production,
|
|
149
|
+
testMint,
|
|
150
|
+
productionRepository,
|
|
151
|
+
testMintRepository,
|
|
152
|
+
bootstrap,
|
|
153
|
+
nomos,
|
|
154
|
+
]) {
|
|
112
155
|
if (!file.existsSync()) {
|
|
113
156
|
throw NomosTestHarnessException(
|
|
114
157
|
'source release candidate is incomplete: ${file.path} is missing',
|
|
@@ -117,6 +160,8 @@ Future<String?> _sourceCandidateRuntime(String nodeEntry) async {
|
|
|
117
160
|
}
|
|
118
161
|
final productionBytes = await production.readAsBytes();
|
|
119
162
|
final testMintBytes = await testMint.readAsBytes();
|
|
163
|
+
final productionRepositoryBytes = await productionRepository.readAsBytes();
|
|
164
|
+
final testMintRepositoryBytes = await testMintRepository.readAsBytes();
|
|
120
165
|
final productionActual = sha256.convert(productionBytes).toString();
|
|
121
166
|
final testMintActual = sha256.convert(testMintBytes).toString();
|
|
122
167
|
String short(String value) =>
|
|
@@ -124,7 +169,7 @@ Future<String?> _sourceCandidateRuntime(String nodeEntry) async {
|
|
|
124
169
|
if (productionActual != productionExpected ||
|
|
125
170
|
testMintActual != testMintExpected) {
|
|
126
171
|
throw NomosTestHarnessException(
|
|
127
|
-
'source release candidate kernel mismatch: release/kernel-candidate.
|
|
172
|
+
'source release candidate kernel mismatch: release/kernel-candidate.usda '
|
|
128
173
|
'expects ${short(productionExpected)}/${short(testMintExpected)}, but the staged artifacts are '
|
|
129
174
|
'${short(productionActual)}/${short(testMintActual)} — run the kernel candidate preparation before testing',
|
|
130
175
|
);
|
|
@@ -135,6 +180,8 @@ Future<String?> _sourceCandidateRuntime(String nodeEntry) async {
|
|
|
135
180
|
.convert(
|
|
136
181
|
utf8.encode(
|
|
137
182
|
'$productionActual:$testMintActual:'
|
|
183
|
+
'${sha256.convert(productionRepositoryBytes)}:'
|
|
184
|
+
'${sha256.convert(testMintRepositoryBytes)}:'
|
|
138
185
|
'${sha256.convert(bootstrapBytes)}:'
|
|
139
186
|
'${sha256.convert(nomosBytes)}',
|
|
140
187
|
),
|
|
@@ -148,7 +195,8 @@ Future<String?> _sourceCandidateRuntime(String nodeEntry) async {
|
|
|
148
195
|
final target = File('${runtime.path}/$name');
|
|
149
196
|
if (target.existsSync() && target.lengthSync() == bytes.length) return;
|
|
150
197
|
final candidate = File(
|
|
151
|
-
|
|
198
|
+
'${target.path}.tmp-${pid}-${DateTime.now().microsecondsSinceEpoch}',
|
|
199
|
+
);
|
|
152
200
|
await candidate.writeAsBytes(bytes, flush: true);
|
|
153
201
|
try {
|
|
154
202
|
await candidate.rename(target.path);
|
|
@@ -164,6 +212,8 @@ Future<String?> _sourceCandidateRuntime(String nodeEntry) async {
|
|
|
164
212
|
|
|
165
213
|
await publish('holon.wasm', productionBytes);
|
|
166
214
|
await publish('holon-testmint.wasm', testMintBytes);
|
|
215
|
+
await publish('kernel.repository.xml', productionRepositoryBytes);
|
|
216
|
+
await publish('kernel-testmint.repository.xml', testMintRepositoryBytes);
|
|
167
217
|
await publish('bootstrap.law.usdz', bootstrapBytes);
|
|
168
218
|
await publish('nomos.law.usdz', nomosBytes);
|
|
169
219
|
return runtime.path;
|
|
@@ -191,8 +241,8 @@ String? nmsPermanentConvergenceFailure(Map<String, Object?> result) {
|
|
|
191
241
|
final converged = result['converged'];
|
|
192
242
|
final replayDeadLetters =
|
|
193
243
|
converged is Map && converged['deadLettered'] is List
|
|
194
|
-
|
|
195
|
-
|
|
244
|
+
? converged['deadLettered'] as List
|
|
245
|
+
: const [];
|
|
196
246
|
final pushError = result['pushError'];
|
|
197
247
|
final refused = pushError is Map && pushError['refused'] == true;
|
|
198
248
|
if (rejected.isEmpty &&
|
|
@@ -261,10 +311,10 @@ class HarnessPhaseTimeout extends NomosTestHarnessException {
|
|
|
261
311
|
required this.awaiting,
|
|
262
312
|
this.likelyCauses = const [],
|
|
263
313
|
}) : super(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
314
|
+
'phase "$phase"${workspace != null ? ' (workspace "$workspace")' : ''} '
|
|
315
|
+
'did not complete within ${elapsed.inSeconds}s while awaiting `$awaiting`'
|
|
316
|
+
'${likelyCauses.isEmpty ? '' : ' — likely causes: ${likelyCauses.map((c) => '\n • $c').join()}'}',
|
|
317
|
+
);
|
|
268
318
|
@override
|
|
269
319
|
String toString() => 'HarnessPhaseTimeout: $message';
|
|
270
320
|
}
|
|
@@ -382,16 +432,16 @@ class NomosTestHarness {
|
|
|
382
432
|
_CloudRestartLane? cloudRestartLane,
|
|
383
433
|
this.testCloud,
|
|
384
434
|
this.homeWorkspace,
|
|
385
|
-
})
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
435
|
+
}) : _sessionNames = List.of(
|
|
436
|
+
sessionNames,
|
|
437
|
+
), // GROWABLE — restoreWorkspace may add a fresh session
|
|
438
|
+
_devicePublics = devicePublics,
|
|
439
|
+
_deviceSecrets = deviceSecrets,
|
|
440
|
+
_lawHashes = Map.of(lawHashes),
|
|
441
|
+
_snapshots = Map.of(snapshots),
|
|
442
|
+
_installLane = installLane,
|
|
443
|
+
_phaseTimeout = phaseTimeout,
|
|
444
|
+
_cloudRestartLane = cloudRestartLane;
|
|
395
445
|
|
|
396
446
|
// ── THE "NO SILENT HANGS" MACHINERY — every lifecycle await runs as a named, deadlined PHASE ──
|
|
397
447
|
|
|
@@ -559,9 +609,11 @@ class NomosTestHarness {
|
|
|
559
609
|
'no lawUsdzPaths — pass the build/<name>.law.usdz `npx githolon compile` emits',
|
|
560
610
|
);
|
|
561
611
|
}
|
|
562
|
-
final entry =
|
|
612
|
+
final entry =
|
|
613
|
+
nodeEntry ??
|
|
563
614
|
await _resolveEntry(node, resolveFrom ?? Directory.current.path);
|
|
564
|
-
final effectiveRuntimeDir =
|
|
615
|
+
final effectiveRuntimeDir =
|
|
616
|
+
runtimeDir ??
|
|
565
617
|
Platform.environment['NOMOS_CANDIDATE_RUNTIME'] ??
|
|
566
618
|
Platform.environment['NOMOS_DART_RUNTIME'] ??
|
|
567
619
|
Platform.environment['NOMOS_OFFLINE_RUNTIME'] ??
|
|
@@ -574,17 +626,19 @@ class NomosTestHarness {
|
|
|
574
626
|
// (default `npx githolon` when the in-repo helper is absent; the test forces this lane by passing
|
|
575
627
|
// e.g. `installHelper: 'node /abs/cli/dist/cli.mjs'`).
|
|
576
628
|
final adjacentInstaller = '${File(entry).parent.path}/offline-install.mjs';
|
|
577
|
-
final resolvedHelper =
|
|
629
|
+
final resolvedHelper =
|
|
630
|
+
installHelper ??
|
|
578
631
|
(File(adjacentInstaller).existsSync()
|
|
579
632
|
? adjacentInstaller
|
|
580
633
|
: '${File(entry).parent.path}/offline-install.ts');
|
|
581
634
|
final useInRepoScript =
|
|
582
635
|
(resolvedHelper.endsWith('.ts') || resolvedHelper.endsWith('.mjs')) &&
|
|
583
|
-
|
|
636
|
+
File(resolvedHelper).existsSync();
|
|
584
637
|
final githolonCmd = installHelper == null ? 'npx githolon' : resolvedHelper;
|
|
585
638
|
|
|
586
639
|
final owner = ownerSubject?.trim();
|
|
587
|
-
final birthsApplicationHome =
|
|
640
|
+
final birthsApplicationHome =
|
|
641
|
+
owner != null &&
|
|
588
642
|
owner.isNotEmpty &&
|
|
589
643
|
workspace == _receivingGroupNameOf(owner);
|
|
590
644
|
// A receiving-group address is not an arbitrary law-install target. It is valid only when a parent
|
|
@@ -608,8 +662,9 @@ class NomosTestHarness {
|
|
|
608
662
|
final info = await _phased(
|
|
609
663
|
phase: '1/4: offline law install (${lawUsdzPaths.length} package(s))',
|
|
610
664
|
workspace: installWorkspace,
|
|
611
|
-
awaiting:
|
|
612
|
-
|
|
665
|
+
awaiting: useInRepoScript
|
|
666
|
+
? 'offline-install.ts'
|
|
667
|
+
: 'githolon harness-install',
|
|
613
668
|
timeout: phaseTimeout ?? _kInstallDeadline,
|
|
614
669
|
onDiagnostic: onDiagnostic,
|
|
615
670
|
likelyCauses: const [
|
|
@@ -726,17 +781,19 @@ class NomosTestHarness {
|
|
|
726
781
|
// scenario carries the value from the same package declaration that compiled the
|
|
727
782
|
// receiving-group plan; the harness does not choose or default it. The ordinary
|
|
728
783
|
// seed offer crosses the kernel author gate before the first birth is attempted.
|
|
729
|
-
run: () => plane
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
784
|
+
run: () => plane
|
|
785
|
+
.session(installWorkspace)
|
|
786
|
+
.offerOutcome(
|
|
787
|
+
domain: '__nomos_application_home_v1',
|
|
788
|
+
directiveId: 'seedBirthPolicy',
|
|
789
|
+
actor: owner,
|
|
790
|
+
authorSecret: deviceSecret,
|
|
791
|
+
payload: <String, Object?>{
|
|
792
|
+
'maxBirthsPerPrincipal': maxChildWorkspaces,
|
|
793
|
+
'setBy': owner,
|
|
794
|
+
'setAt': bornAt,
|
|
795
|
+
},
|
|
796
|
+
),
|
|
740
797
|
);
|
|
741
798
|
final outcome = await _phased(
|
|
742
799
|
phase: 'application: birth the signed-in application',
|
|
@@ -744,20 +801,22 @@ class NomosTestHarness {
|
|
|
744
801
|
awaiting: 'attested receiving-group birth',
|
|
745
802
|
timeout: phaseTimeout ?? _kOtherDeadline,
|
|
746
803
|
onDiagnostic: onDiagnostic,
|
|
747
|
-
run: () => plane
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
804
|
+
run: () => plane
|
|
805
|
+
.session(installWorkspace)
|
|
806
|
+
.offerOutcome(
|
|
807
|
+
domain: '__nomos_application_home_v1',
|
|
808
|
+
directiveId: 'nomosInternalBirthReceivingGroup7c1f5e',
|
|
809
|
+
actor: owner,
|
|
810
|
+
authorSecret: deviceSecret,
|
|
811
|
+
payload: <String, Object?>{
|
|
812
|
+
'owner': owner,
|
|
813
|
+
'frameworkHash': framework,
|
|
814
|
+
// Ordered application deployment is reference packages first, deferred child payload last.
|
|
815
|
+
// The parent serves the references; the receiving group is born with only its payload law.
|
|
816
|
+
'lawHash': laws.last,
|
|
817
|
+
'bornAt': bornAt,
|
|
818
|
+
},
|
|
819
|
+
),
|
|
761
820
|
);
|
|
762
821
|
String? bornStatus;
|
|
763
822
|
for (final entry in outcome.birthOutcomes) {
|
|
@@ -811,26 +870,28 @@ class NomosTestHarness {
|
|
|
811
870
|
// harness device through the installed framework law with the same verified provenance
|
|
812
871
|
// that the private test root later attests. Production clients never manufacture this
|
|
813
872
|
// marker; it exists only inside the offline test-mint ceremony.
|
|
814
|
-
run: () => plane
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
873
|
+
run: () => plane
|
|
874
|
+
.session(workspace)
|
|
875
|
+
.offerOutcome(
|
|
876
|
+
domain: '__nomos_application_home_v1',
|
|
877
|
+
directiveId: 'enrollSigner',
|
|
878
|
+
actor: owner,
|
|
879
|
+
authorSecret: deviceSecret,
|
|
880
|
+
payload: <String, Object?>{
|
|
881
|
+
'keyHash': keyHash,
|
|
882
|
+
'grantedBy': owner,
|
|
883
|
+
'grantedAt': 1,
|
|
884
|
+
'verifiedVia': 'nomos:test-mint',
|
|
885
|
+
},
|
|
886
|
+
),
|
|
826
887
|
);
|
|
827
888
|
} catch (error) {
|
|
828
889
|
// Pre-application-home packages neither expose nor enforce this
|
|
829
890
|
// signer route. Preserve their old harness behaviour, while keeping
|
|
830
891
|
// every refusal from a current package fatal.
|
|
831
892
|
if (!error.toString().contains(
|
|
832
|
-
|
|
833
|
-
|
|
893
|
+
"no active installed law declares domain '__nomos_application_home_v1'",
|
|
894
|
+
)) {
|
|
834
895
|
rethrow;
|
|
835
896
|
}
|
|
836
897
|
}
|
|
@@ -843,34 +904,39 @@ class NomosTestHarness {
|
|
|
843
904
|
timeout: phaseTimeout ?? _kOtherDeadline,
|
|
844
905
|
onDiagnostic: onDiagnostic,
|
|
845
906
|
run: () async {
|
|
846
|
-
await plane
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
907
|
+
await plane
|
|
908
|
+
.session(workspace)
|
|
909
|
+
.offerOutcome(
|
|
910
|
+
domain: '__nomos_application_home_v1',
|
|
911
|
+
directiveId: 'nomosInternalSeedReceivingGroupPolicy7c1f5e',
|
|
912
|
+
actor: 'host-birth',
|
|
913
|
+
payload: <String, Object?>{'setAt': bornAt},
|
|
914
|
+
);
|
|
915
|
+
await plane
|
|
916
|
+
.session(workspace)
|
|
917
|
+
.offerOutcome(
|
|
918
|
+
domain: '__nomos_application_home_v1',
|
|
919
|
+
directiveId: 'nomosInternalSeedReceivingGroup7c1f5e',
|
|
920
|
+
actor: 'host-birth',
|
|
921
|
+
payload: <String, Object?>{
|
|
922
|
+
'principal': owner,
|
|
923
|
+
'workspaceName': _receivingGroupNameOf(owner),
|
|
924
|
+
'bornAt': bornAt,
|
|
925
|
+
},
|
|
926
|
+
);
|
|
862
927
|
},
|
|
863
928
|
);
|
|
864
929
|
final parentCert = await plane.signBirthCert(
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
930
|
+
NomosBirthCertificateSpec(
|
|
931
|
+
child: workspace,
|
|
932
|
+
founder: owner,
|
|
933
|
+
verified: true,
|
|
934
|
+
parentKey: devicePublic,
|
|
935
|
+
parentId: 'root',
|
|
936
|
+
epoch: 0,
|
|
937
|
+
),
|
|
938
|
+
deviceSecret,
|
|
939
|
+
);
|
|
874
940
|
await _phased(
|
|
875
941
|
phase: 'owner: establish the verified test application root',
|
|
876
942
|
workspace: workspace,
|
|
@@ -878,34 +944,38 @@ class NomosTestHarness {
|
|
|
878
944
|
timeout: phaseTimeout ?? _kOtherDeadline,
|
|
879
945
|
onDiagnostic: onDiagnostic,
|
|
880
946
|
run: () async {
|
|
881
|
-
await plane
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
947
|
+
await plane
|
|
948
|
+
.session(workspace)
|
|
949
|
+
.offerOutcome(
|
|
950
|
+
domain: '__nomos_application_home_v1',
|
|
951
|
+
directiveId: 'seedBirthAttestation',
|
|
952
|
+
actor: owner,
|
|
953
|
+
authorSecret: deviceSecret,
|
|
954
|
+
payload: <String, Object?>{
|
|
955
|
+
'child': workspace,
|
|
956
|
+
'founder': owner,
|
|
957
|
+
'verified': 'true',
|
|
958
|
+
'parentId': 'root',
|
|
959
|
+
'parentKey': devicePublic,
|
|
960
|
+
'epoch': 0,
|
|
961
|
+
'certChain': jsonEncode(<String, Object?>{
|
|
962
|
+
'links': <Object?>[parentCert.toWire()],
|
|
963
|
+
}),
|
|
964
|
+
},
|
|
965
|
+
);
|
|
966
|
+
await plane
|
|
967
|
+
.session(workspace)
|
|
968
|
+
.offerOutcome(
|
|
969
|
+
domain: '__nomos_application_home_v1',
|
|
970
|
+
directiveId: 'seedGenesis',
|
|
971
|
+
actor: owner,
|
|
972
|
+
authorSecret: deviceSecret,
|
|
973
|
+
payload: <String, Object?>{
|
|
974
|
+
'principal': owner,
|
|
975
|
+
'workspaceId': workspace,
|
|
976
|
+
'bornAt': '2000-01-01T00:00:00.000Z',
|
|
977
|
+
},
|
|
978
|
+
);
|
|
909
979
|
},
|
|
910
980
|
);
|
|
911
981
|
} on DomainNotInstalledForSession {
|
|
@@ -923,9 +993,9 @@ class NomosTestHarness {
|
|
|
923
993
|
final sessionLaw = installedHashes.isEmpty
|
|
924
994
|
? 'installed'
|
|
925
995
|
: (birthsApplicationHome
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
996
|
+
? installedHashes.last
|
|
997
|
+
: installedHashes.first)
|
|
998
|
+
.substring(0, 12);
|
|
929
999
|
phase('ready — session "$workspace" connected (law $sessionLaw)');
|
|
930
1000
|
// THE DECOY SESSION (multi-workspace by default in tests): sole-session inference is how
|
|
931
1001
|
// single-workspace assumptions ship — with one session connected, an op that forgot its session
|
|
@@ -1055,21 +1125,24 @@ class NomosTestHarness {
|
|
|
1055
1125
|
);
|
|
1056
1126
|
}
|
|
1057
1127
|
}
|
|
1058
|
-
final entry =
|
|
1128
|
+
final entry =
|
|
1129
|
+
nodeEntry ??
|
|
1059
1130
|
await _resolveEntry(node, resolveFrom ?? Directory.current.path);
|
|
1060
|
-
final effectiveRuntimeDir =
|
|
1131
|
+
final effectiveRuntimeDir =
|
|
1132
|
+
runtimeDir ??
|
|
1061
1133
|
Platform.environment['NOMOS_CANDIDATE_RUNTIME'] ??
|
|
1062
1134
|
Platform.environment['NOMOS_DART_RUNTIME'] ??
|
|
1063
1135
|
Platform.environment['NOMOS_OFFLINE_RUNTIME'] ??
|
|
1064
1136
|
await _sourceCandidateRuntime(entry);
|
|
1065
1137
|
final adjacentInstaller = '${File(entry).parent.path}/offline-install.mjs';
|
|
1066
|
-
final resolvedHelper =
|
|
1138
|
+
final resolvedHelper =
|
|
1139
|
+
installHelper ??
|
|
1067
1140
|
(File(adjacentInstaller).existsSync()
|
|
1068
1141
|
? adjacentInstaller
|
|
1069
1142
|
: '${File(entry).parent.path}/offline-install.ts');
|
|
1070
1143
|
final useInRepoScript =
|
|
1071
1144
|
(resolvedHelper.endsWith('.ts') || resolvedHelper.endsWith('.mjs')) &&
|
|
1072
|
-
|
|
1145
|
+
File(resolvedHelper).existsSync();
|
|
1073
1146
|
final githolonCmd = installHelper == null ? 'npx githolon' : resolvedHelper;
|
|
1074
1147
|
|
|
1075
1148
|
final workdir = await Directory.systemTemp.createTemp('nomos-cluster-');
|
|
@@ -1329,10 +1402,12 @@ class NomosTestHarness {
|
|
|
1329
1402
|
'no lawUsdzPaths — pass the build/<name>.law.usdz `npx githolon compile` emits',
|
|
1330
1403
|
);
|
|
1331
1404
|
}
|
|
1332
|
-
final entry =
|
|
1405
|
+
final entry =
|
|
1406
|
+
nodeEntry ??
|
|
1333
1407
|
await _resolveEntry(node, resolveFrom ?? Directory.current.path);
|
|
1334
|
-
final bareUid =
|
|
1335
|
-
|
|
1408
|
+
final bareUid = subject.startsWith('user:')
|
|
1409
|
+
? subject.substring(5)
|
|
1410
|
+
: subject;
|
|
1336
1411
|
final http = HttpClient();
|
|
1337
1412
|
Future<Map<String, Object?>> post(
|
|
1338
1413
|
String path,
|
|
@@ -1406,8 +1481,8 @@ class NomosTestHarness {
|
|
|
1406
1481
|
}
|
|
1407
1482
|
rethrow;
|
|
1408
1483
|
}
|
|
1409
|
-
frameworkHash =
|
|
1410
|
-
|
|
1484
|
+
frameworkHash = ((created['controller'] as Map?)?['domainHash'])
|
|
1485
|
+
?.toString();
|
|
1411
1486
|
final existing = await get(
|
|
1412
1487
|
'/v2/workspaces/$platformWorkspace/domains',
|
|
1413
1488
|
);
|
|
@@ -1496,7 +1571,7 @@ class NomosTestHarness {
|
|
|
1496
1571
|
);
|
|
1497
1572
|
final currentLaw =
|
|
1498
1573
|
(parentDomains['currentLaw'] as Map?)?.cast<String, Object?>() ??
|
|
1499
|
-
|
|
1574
|
+
const <String, Object?>{};
|
|
1500
1575
|
final controlHomeLawHash =
|
|
1501
1576
|
currentLaw[applicationHomeDomain]?.toString() ?? '';
|
|
1502
1577
|
if (controlHomeLawHash.isEmpty) {
|
|
@@ -1534,10 +1609,12 @@ class NomosTestHarness {
|
|
|
1534
1609
|
// restart while offline must not fetch any member before it can replay the persisted ledger. Release CI
|
|
1535
1610
|
// supplies the exact candidate bundle. An ordinary local test pins all three artifacts advertised by its
|
|
1536
1611
|
// ephemeral cloud while still online; never combine a candidate kernel with stale framework law.
|
|
1537
|
-
final candidateRuntimeDir =
|
|
1612
|
+
final candidateRuntimeDir =
|
|
1613
|
+
Platform.environment['NOMOS_DART_RUNTIME'] ??
|
|
1538
1614
|
Platform.environment['NOMOS_OFFLINE_RUNTIME'];
|
|
1539
|
-
final localRuntime = await Directory(
|
|
1540
|
-
|
|
1615
|
+
final localRuntime = await Directory(
|
|
1616
|
+
'${workdir.path}/client-runtime',
|
|
1617
|
+
).create(recursive: true);
|
|
1541
1618
|
final selectedKernel = cloud.testKRootPublic == null
|
|
1542
1619
|
? _CandidateKernelBinary.production
|
|
1543
1620
|
: _CandidateKernelBinary.testMint;
|
|
@@ -1546,18 +1623,21 @@ class NomosTestHarness {
|
|
|
1546
1623
|
: <_KernelRuntimeArtifact, String>{
|
|
1547
1624
|
_KernelRuntimeArtifact.executable:
|
|
1548
1625
|
'$candidateRuntimeDir/${selectedKernel.fileName}',
|
|
1626
|
+
_KernelRuntimeArtifact.repository:
|
|
1627
|
+
'$candidateRuntimeDir/${selectedKernel == _CandidateKernelBinary.production ? 'kernel.repository.xml' : 'kernel-testmint.repository.xml'}',
|
|
1549
1628
|
_KernelRuntimeArtifact.stageZeroLaw:
|
|
1550
1629
|
'$candidateRuntimeDir/${_KernelRuntimeArtifact.stageZeroLaw.fileName}',
|
|
1551
1630
|
_KernelRuntimeArtifact.controllerLaw:
|
|
1552
1631
|
'$candidateRuntimeDir/${_KernelRuntimeArtifact.controllerLaw.fileName}',
|
|
1553
1632
|
};
|
|
1554
|
-
final hasCandidateBundle =
|
|
1555
|
-
|
|
1633
|
+
final hasCandidateBundle =
|
|
1634
|
+
candidateArtifacts.length == _KernelRuntimeArtifact.values.length &&
|
|
1556
1635
|
candidateArtifacts.values.every((path) => File(path).existsSync());
|
|
1557
1636
|
if (hasCandidateBundle) {
|
|
1558
1637
|
for (final artifact in candidateArtifacts.entries) {
|
|
1559
|
-
await File(
|
|
1560
|
-
|
|
1638
|
+
await File(
|
|
1639
|
+
artifact.value,
|
|
1640
|
+
).copy('${localRuntime.path}/${artifact.key.fileName}');
|
|
1561
1641
|
}
|
|
1562
1642
|
} else {
|
|
1563
1643
|
await _phased(
|
|
@@ -1569,7 +1649,7 @@ class NomosTestHarness {
|
|
|
1569
1649
|
for (final artifact in _KernelRuntimeArtifact.values) {
|
|
1570
1650
|
final request = await http.getUrl(
|
|
1571
1651
|
Uri.parse(
|
|
1572
|
-
'${cloud.origin}/v1/runtime/${artifact.fileName}',
|
|
1652
|
+
'${cloud.origin}/v1/runtime/${artifact.providerFileName ?? artifact.fileName}',
|
|
1573
1653
|
),
|
|
1574
1654
|
);
|
|
1575
1655
|
final response = await request.close();
|
|
@@ -1583,8 +1663,9 @@ class NomosTestHarness {
|
|
|
1583
1663
|
'(status ${response.statusCode}, ${bytes.length} bytes)',
|
|
1584
1664
|
);
|
|
1585
1665
|
}
|
|
1586
|
-
await File(
|
|
1587
|
-
|
|
1666
|
+
await File(
|
|
1667
|
+
'${localRuntime.path}/${artifact.fileName}',
|
|
1668
|
+
).writeAsBytes(bytes, flush: true);
|
|
1588
1669
|
}
|
|
1589
1670
|
},
|
|
1590
1671
|
);
|
|
@@ -1740,34 +1821,36 @@ class NomosTestHarness {
|
|
|
1740
1821
|
// authenticated platform holon mounted. Bind this parent certificate operation to the
|
|
1741
1822
|
// platform session explicitly; relying on the single-session convenience API becomes
|
|
1742
1823
|
// ambiguous as soon as the ceremony correctly opens the target workspace in-kernel.
|
|
1743
|
-
final homeCert =
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1824
|
+
final homeCert = await plane
|
|
1825
|
+
.session('$clientId-platform-author')
|
|
1826
|
+
.signBirthCert(
|
|
1827
|
+
NomosBirthCertificateSpec(
|
|
1828
|
+
child: homeName,
|
|
1829
|
+
founder: subject,
|
|
1830
|
+
// The cert's `verified` must MATCH the gate verdict (CAS-as-law): the offer carries
|
|
1831
|
+
// the dev-IdP token, so the gate resolves a VERIFIED founder.
|
|
1832
|
+
verified: true,
|
|
1833
|
+
parentKey: device.publicB64,
|
|
1834
|
+
parentId: platformWorkspace,
|
|
1835
|
+
epoch: 0,
|
|
1836
|
+
),
|
|
1837
|
+
device.secret,
|
|
1838
|
+
);
|
|
1839
|
+
final localBirth = await plane
|
|
1840
|
+
.session('$clientId-platform-author')
|
|
1841
|
+
.offerOutcome(
|
|
1842
|
+
domain: applicationHomeDomain,
|
|
1843
|
+
directiveId: 'nomosInternalBirthReceivingGroup7c1f5e',
|
|
1844
|
+
actor: subject,
|
|
1845
|
+
authorSecret: device.secret,
|
|
1846
|
+
payload: <String, Object?>{
|
|
1847
|
+
'owner': subject,
|
|
1848
|
+
'frameworkHash': frameworkHash,
|
|
1849
|
+
'lawHash': applicationHomeLawHash,
|
|
1850
|
+
'bornAt': DateTime.now().toUtc().toIso8601String(),
|
|
1851
|
+
},
|
|
1852
|
+
birthCertificate: homeCert,
|
|
1853
|
+
);
|
|
1771
1854
|
if (!localBirth.born.contains(homeName)) {
|
|
1772
1855
|
throw NomosTestHarnessException(
|
|
1773
1856
|
'cloudBound: the local parent kernel admitted the receiving-group offer but did not birth '
|
|
@@ -1923,7 +2006,8 @@ class NomosTestHarness {
|
|
|
1923
2006
|
if (r['pushFailed'] != true &&
|
|
1924
2007
|
r['stillAhead'] != true &&
|
|
1925
2008
|
r['offline'] != true &&
|
|
1926
|
-
r['deferred'] != true)
|
|
2009
|
+
r['deferred'] != true)
|
|
2010
|
+
pending.remove(ws);
|
|
1927
2011
|
} on NomosTestHarnessException {
|
|
1928
2012
|
rethrow;
|
|
1929
2013
|
} catch (e) {
|
|
@@ -2334,14 +2418,14 @@ class NomosTestHarness {
|
|
|
2334
2418
|
// transport first makes its incoming stream wait on consumers that can no longer receive their
|
|
2335
2419
|
// unwatch/done messages—a deterministic teardown deadlock in reactive Flutter tests.
|
|
2336
2420
|
await _plane.dispose().timeout(
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2421
|
+
_phaseTimeout ?? _kOtherDeadline,
|
|
2422
|
+
onTimeout: () => throw HarnessPhaseTimeout(
|
|
2423
|
+
phase: 'dispose: close bridge watches',
|
|
2424
|
+
workspace: workspace,
|
|
2425
|
+
elapsed: _phaseTimeout ?? _kOtherDeadline,
|
|
2426
|
+
awaiting: 'NomosBridge.dispose',
|
|
2427
|
+
),
|
|
2428
|
+
);
|
|
2345
2429
|
} catch (e) {
|
|
2346
2430
|
_diag(
|
|
2347
2431
|
'[nomos-test-harness] bridge dispose error (continuing to runner cleanup): $e',
|
|
@@ -2349,14 +2433,14 @@ class NomosTestHarness {
|
|
|
2349
2433
|
}
|
|
2350
2434
|
try {
|
|
2351
2435
|
await _transport.dispose().timeout(
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2436
|
+
_phaseTimeout ?? _kOtherDeadline,
|
|
2437
|
+
onTimeout: () => throw HarnessPhaseTimeout(
|
|
2438
|
+
phase: 'dispose: stop the node runner',
|
|
2439
|
+
workspace: workspace,
|
|
2440
|
+
elapsed: _phaseTimeout ?? _kOtherDeadline,
|
|
2441
|
+
awaiting: 'NodeRunnerTransport.dispose',
|
|
2442
|
+
),
|
|
2443
|
+
);
|
|
2360
2444
|
} catch (e) {
|
|
2361
2445
|
_diag(
|
|
2362
2446
|
'[nomos-test-harness] transport dispose error (continuing to temp-dir cleanup): $e',
|
|
@@ -2490,13 +2574,10 @@ class NomosTestHarness {
|
|
|
2490
2574
|
'@githolon/client/runner/node-entry.mjs',
|
|
2491
2575
|
'@githolon/client/runner/node-entry.ts',
|
|
2492
2576
|
]) {
|
|
2493
|
-
final r = await Process.run(
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
"require.resolve('$entry')",
|
|
2498
|
-
],
|
|
2499
|
-
workingDirectory: from);
|
|
2577
|
+
final r = await Process.run(node, [
|
|
2578
|
+
'-p',
|
|
2579
|
+
"require.resolve('$entry')",
|
|
2580
|
+
], workingDirectory: from);
|
|
2500
2581
|
final out = (r.stdout as String).trim();
|
|
2501
2582
|
if (r.exitCode == 0 && out.isNotEmpty) return out;
|
|
2502
2583
|
}
|
|
@@ -2529,20 +2610,21 @@ class WorkspaceSnapshot {
|
|
|
2529
2610
|
|
|
2530
2611
|
/// JSON round-trip so a staged snapshot persists to disk verbatim (the bytes + provenance).
|
|
2531
2612
|
Map<String, Object?> toJson() => {
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2613
|
+
'workspace': workspace,
|
|
2614
|
+
'snapshotB64': snapshotB64,
|
|
2615
|
+
'head': head,
|
|
2616
|
+
'lawHashes': lawHashes,
|
|
2617
|
+
'capturedAt': capturedAt.toUtc().toIso8601String(),
|
|
2618
|
+
};
|
|
2538
2619
|
|
|
2539
2620
|
factory WorkspaceSnapshot.fromJson(Map<String, Object?> j) =>
|
|
2540
2621
|
WorkspaceSnapshot(
|
|
2541
2622
|
workspace: j['workspace'] as String,
|
|
2542
2623
|
snapshotB64: j['snapshotB64'] as String,
|
|
2543
2624
|
head: (j['head'] ?? '') as String,
|
|
2544
|
-
lawHashes:
|
|
2545
|
-
|
|
2625
|
+
lawHashes: ((j['lawHashes'] as List?) ?? const [])
|
|
2626
|
+
.map((e) => '$e')
|
|
2627
|
+
.toList(),
|
|
2546
2628
|
capturedAt: DateTime.parse(
|
|
2547
2629
|
(j['capturedAt'] ?? DateTime.now().toUtc().toIso8601String())
|
|
2548
2630
|
as String,
|
|
@@ -2564,20 +2646,19 @@ class PlaneSnapshot {
|
|
|
2564
2646
|
const PlaneSnapshot({required this.workspaces, required this.capturedAt});
|
|
2565
2647
|
|
|
2566
2648
|
Map<String, Object?> toJson() => {
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2649
|
+
'workspaces': workspaces.map((k, v) => MapEntry(k, v.toJson())),
|
|
2650
|
+
'capturedAt': capturedAt.toUtc().toIso8601String(),
|
|
2651
|
+
};
|
|
2570
2652
|
|
|
2571
2653
|
factory PlaneSnapshot.fromJson(Map<String, Object?> j) => PlaneSnapshot(
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
);
|
|
2654
|
+
workspaces: ((j['workspaces'] as Map?) ?? const {}).map(
|
|
2655
|
+
(k, v) => MapEntry(
|
|
2656
|
+
'$k',
|
|
2657
|
+
WorkspaceSnapshot.fromJson((v as Map).cast<String, Object?>()),
|
|
2658
|
+
),
|
|
2659
|
+
),
|
|
2660
|
+
capturedAt: DateTime.parse(
|
|
2661
|
+
(j['capturedAt'] ?? DateTime.now().toUtc().toIso8601String()) as String,
|
|
2662
|
+
),
|
|
2663
|
+
);
|
|
2583
2664
|
}
|