@githolon/dsl 0.107.4 → 0.108.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 +19 -0
- package/dart/lib/src/runner/node-entry.mjs +4044 -3831
- package/dart/lib/src/runner/offline-install.mjs +2633 -2537
- package/dart/lib/src/test_harness.dart +249 -239
- package/dart/lib/src/webview_bridge.dart +38 -168
- package/dart/pubspec.lock +17 -1
- package/dart/pubspec.yaml +3 -2
- package/dart_types/CHANGELOG.md +10 -0
- package/dart_types/lib/nomos_kernel_protocol.dart +10 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +16158 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +766 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbjson.dart +6096 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbserver.dart +50 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pb.dart +245 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pbenum.dart +160 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pbjson.dart +178 -0
- package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pb.dart +828 -0
- package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbenum.dart +465 -0
- package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbjson.dart +573 -0
- package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbserver.dart +50 -0
- package/dart_types/pubspec.lock +16 -0
- package/dart_types/pubspec.yaml +3 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +298 -106
- package/src/birth_meter_ensure.ts +2 -0
- package/src/business_birth.ts +2 -0
- package/src/compile_package_main.ts +36 -18
- package/src/framework/application_home.ts +2 -0
- package/src/framework/auth_provider_law.ts +34 -0
- package/src/framework/birth_certificate_law.ts +24 -0
- package/src/framework/home.ts +53 -14
- package/src/framework/owned_custody.ts +6 -1
- package/src/framework/signing_identity.ts +66 -17
- package/src/framework/workspaces.ts +8 -1
- package/src/index.ts +2 -0
- package/src/shape_compat.ts +6 -3
|
@@ -41,13 +41,14 @@ import 'dart:convert';
|
|
|
41
41
|
import 'dart:io';
|
|
42
42
|
|
|
43
43
|
import 'package:crypto/crypto.dart' show sha256;
|
|
44
|
+
import 'package:fixnum/fixnum.dart' show Int64;
|
|
45
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' show BirthCertificate;
|
|
44
46
|
import 'package:package_config/package_config.dart' show loadPackageConfigUri;
|
|
45
47
|
|
|
46
48
|
import 'dev_cloud.dart';
|
|
47
49
|
import 'node_runner_transport.dart';
|
|
48
50
|
import 'session_bound.dart' show DomainNotInstalledForSession;
|
|
49
|
-
import 'webview_bridge.dart'
|
|
50
|
-
show NomosBirthCertificateSpec, NomosBridge, NomosSession;
|
|
51
|
+
import 'webview_bridge.dart' show NomosBridge, NomosSession;
|
|
51
52
|
|
|
52
53
|
enum _KernelRuntimeArtifact {
|
|
53
54
|
executable('holon.wasm'),
|
|
@@ -68,6 +69,30 @@ enum _CandidateKernelBinary {
|
|
|
68
69
|
final String fileName;
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
/// Lower the generated protobuf certificate into the framework law's USDA-authored value shape. This is
|
|
73
|
+
/// deliberately not a second certificate type: callers cannot construct it independently, and every field
|
|
74
|
+
/// is read from the compiler-generated message.
|
|
75
|
+
Map<String, Object?> _birthCertificateLawValue(BirthCertificate certificate) =>
|
|
76
|
+
<String, Object?>{
|
|
77
|
+
'child': certificate.child,
|
|
78
|
+
'founder': certificate.founder,
|
|
79
|
+
'verified': certificate.verified,
|
|
80
|
+
'parentKey': base64Encode(certificate.parentKey),
|
|
81
|
+
'parentId': certificate.parentId,
|
|
82
|
+
'epoch': certificate.epoch.toInt(),
|
|
83
|
+
'signature': base64Encode(certificate.signature),
|
|
84
|
+
if (certificate.hasVerifiedVia()) 'verifiedVia': certificate.verifiedVia,
|
|
85
|
+
if (certificate.hasIdpToken()) 'idpToken': certificate.idpToken,
|
|
86
|
+
if (certificate.hasCapturedClock())
|
|
87
|
+
'capturedClock': certificate.capturedClock.toInt(),
|
|
88
|
+
if (certificate.hasAuthProviderIssuer())
|
|
89
|
+
'authProviderIssuer': certificate.authProviderIssuer,
|
|
90
|
+
if (certificate.hasAuthProviderJwks())
|
|
91
|
+
'authProviderJwks': certificate.authProviderJwks,
|
|
92
|
+
if (certificate.hasAuthProviderAudience())
|
|
93
|
+
'authProviderAudience': certificate.authProviderAudience,
|
|
94
|
+
};
|
|
95
|
+
|
|
71
96
|
/// A source checkout is itself a coordinated release candidate: compiler, framework packages and kernels
|
|
72
97
|
/// move together. Generated scenarios must not combine those source-generated laws with a recently-written
|
|
73
98
|
/// but older global runtime cache. Published packages have no release/ directory and keep the normal
|
|
@@ -104,18 +129,21 @@ Future<String?> _sourceCandidateRuntime(String nodeEntry) async {
|
|
|
104
129
|
if (host == null) {
|
|
105
130
|
throw NomosTestHarnessException('rustc did not report its host target');
|
|
106
131
|
}
|
|
107
|
-
final result = await Process.run(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
132
|
+
final result = await Process.run(
|
|
133
|
+
'cargo',
|
|
134
|
+
[
|
|
135
|
+
'run',
|
|
136
|
+
'--quiet',
|
|
137
|
+
'--target',
|
|
138
|
+
host,
|
|
139
|
+
'--manifest-path',
|
|
140
|
+
'${sourceRoot.path}/tools/kernel-resource/Cargo.toml',
|
|
141
|
+
'--',
|
|
142
|
+
'read',
|
|
143
|
+
'${sourceRoot.path}/release/kernel-candidate.usda',
|
|
144
|
+
field,
|
|
145
|
+
],
|
|
146
|
+
workingDirectory: sourceRoot.path);
|
|
119
147
|
if (result.exitCode != 0) {
|
|
120
148
|
throw NomosTestHarnessException(
|
|
121
149
|
'could not read typed kernel candidate: ${result.stderr}',
|
|
@@ -241,8 +269,8 @@ String? nmsPermanentConvergenceFailure(Map<String, Object?> result) {
|
|
|
241
269
|
final converged = result['converged'];
|
|
242
270
|
final replayDeadLetters =
|
|
243
271
|
converged is Map && converged['deadLettered'] is List
|
|
244
|
-
|
|
245
|
-
|
|
272
|
+
? converged['deadLettered'] as List
|
|
273
|
+
: const [];
|
|
246
274
|
final pushError = result['pushError'];
|
|
247
275
|
final refused = pushError is Map && pushError['refused'] == true;
|
|
248
276
|
if (rejected.isEmpty &&
|
|
@@ -311,10 +339,10 @@ class HarnessPhaseTimeout extends NomosTestHarnessException {
|
|
|
311
339
|
required this.awaiting,
|
|
312
340
|
this.likelyCauses = const [],
|
|
313
341
|
}) : super(
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
342
|
+
'phase "$phase"${workspace != null ? ' (workspace "$workspace")' : ''} '
|
|
343
|
+
'did not complete within ${elapsed.inSeconds}s while awaiting `$awaiting`'
|
|
344
|
+
'${likelyCauses.isEmpty ? '' : ' — likely causes: ${likelyCauses.map((c) => '\n • $c').join()}'}',
|
|
345
|
+
);
|
|
318
346
|
@override
|
|
319
347
|
String toString() => 'HarnessPhaseTimeout: $message';
|
|
320
348
|
}
|
|
@@ -432,16 +460,16 @@ class NomosTestHarness {
|
|
|
432
460
|
_CloudRestartLane? cloudRestartLane,
|
|
433
461
|
this.testCloud,
|
|
434
462
|
this.homeWorkspace,
|
|
435
|
-
})
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
463
|
+
}) : _sessionNames = List.of(
|
|
464
|
+
sessionNames,
|
|
465
|
+
), // GROWABLE — restoreWorkspace may add a fresh session
|
|
466
|
+
_devicePublics = devicePublics,
|
|
467
|
+
_deviceSecrets = deviceSecrets,
|
|
468
|
+
_lawHashes = Map.of(lawHashes),
|
|
469
|
+
_snapshots = Map.of(snapshots),
|
|
470
|
+
_installLane = installLane,
|
|
471
|
+
_phaseTimeout = phaseTimeout,
|
|
472
|
+
_cloudRestartLane = cloudRestartLane;
|
|
445
473
|
|
|
446
474
|
// ── THE "NO SILENT HANGS" MACHINERY — every lifecycle await runs as a named, deadlined PHASE ──
|
|
447
475
|
|
|
@@ -609,11 +637,9 @@ class NomosTestHarness {
|
|
|
609
637
|
'no lawUsdzPaths — pass the build/<name>.law.usdz `npx githolon compile` emits',
|
|
610
638
|
);
|
|
611
639
|
}
|
|
612
|
-
final entry =
|
|
613
|
-
nodeEntry ??
|
|
640
|
+
final entry = nodeEntry ??
|
|
614
641
|
await _resolveEntry(node, resolveFrom ?? Directory.current.path);
|
|
615
|
-
final effectiveRuntimeDir =
|
|
616
|
-
runtimeDir ??
|
|
642
|
+
final effectiveRuntimeDir = runtimeDir ??
|
|
617
643
|
Platform.environment['NOMOS_CANDIDATE_RUNTIME'] ??
|
|
618
644
|
Platform.environment['NOMOS_DART_RUNTIME'] ??
|
|
619
645
|
Platform.environment['NOMOS_OFFLINE_RUNTIME'] ??
|
|
@@ -626,19 +652,17 @@ class NomosTestHarness {
|
|
|
626
652
|
// (default `npx githolon` when the in-repo helper is absent; the test forces this lane by passing
|
|
627
653
|
// e.g. `installHelper: 'node /abs/cli/dist/cli.mjs'`).
|
|
628
654
|
final adjacentInstaller = '${File(entry).parent.path}/offline-install.mjs';
|
|
629
|
-
final resolvedHelper =
|
|
630
|
-
installHelper ??
|
|
655
|
+
final resolvedHelper = installHelper ??
|
|
631
656
|
(File(adjacentInstaller).existsSync()
|
|
632
657
|
? adjacentInstaller
|
|
633
658
|
: '${File(entry).parent.path}/offline-install.ts');
|
|
634
659
|
final useInRepoScript =
|
|
635
660
|
(resolvedHelper.endsWith('.ts') || resolvedHelper.endsWith('.mjs')) &&
|
|
636
|
-
|
|
661
|
+
File(resolvedHelper).existsSync();
|
|
637
662
|
final githolonCmd = installHelper == null ? 'npx githolon' : resolvedHelper;
|
|
638
663
|
|
|
639
664
|
final owner = ownerSubject?.trim();
|
|
640
|
-
final birthsApplicationHome =
|
|
641
|
-
owner != null &&
|
|
665
|
+
final birthsApplicationHome = owner != null &&
|
|
642
666
|
owner.isNotEmpty &&
|
|
643
667
|
workspace == _receivingGroupNameOf(owner);
|
|
644
668
|
// A receiving-group address is not an arbitrary law-install target. It is valid only when a parent
|
|
@@ -662,9 +686,8 @@ class NomosTestHarness {
|
|
|
662
686
|
final info = await _phased(
|
|
663
687
|
phase: '1/4: offline law install (${lawUsdzPaths.length} package(s))',
|
|
664
688
|
workspace: installWorkspace,
|
|
665
|
-
awaiting:
|
|
666
|
-
? 'offline-install.ts'
|
|
667
|
-
: 'githolon harness-install',
|
|
689
|
+
awaiting:
|
|
690
|
+
useInRepoScript ? 'offline-install.ts' : 'githolon harness-install',
|
|
668
691
|
timeout: phaseTimeout ?? _kInstallDeadline,
|
|
669
692
|
onDiagnostic: onDiagnostic,
|
|
670
693
|
likelyCauses: const [
|
|
@@ -781,19 +804,17 @@ class NomosTestHarness {
|
|
|
781
804
|
// scenario carries the value from the same package declaration that compiled the
|
|
782
805
|
// receiving-group plan; the harness does not choose or default it. The ordinary
|
|
783
806
|
// seed offer crosses the kernel author gate before the first birth is attempted.
|
|
784
|
-
run: () => plane
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
},
|
|
796
|
-
),
|
|
807
|
+
run: () => plane.session(installWorkspace).offerOutcome(
|
|
808
|
+
domain: '__nomos_application_home_v1',
|
|
809
|
+
directiveId: 'seedBirthPolicy',
|
|
810
|
+
actor: owner,
|
|
811
|
+
authorSecret: deviceSecret,
|
|
812
|
+
payload: <String, Object?>{
|
|
813
|
+
'maxBirthsPerPrincipal': maxChildWorkspaces,
|
|
814
|
+
'setBy': owner,
|
|
815
|
+
'setAt': bornAt,
|
|
816
|
+
},
|
|
817
|
+
),
|
|
797
818
|
);
|
|
798
819
|
final outcome = await _phased(
|
|
799
820
|
phase: 'application: birth the signed-in application',
|
|
@@ -801,22 +822,20 @@ class NomosTestHarness {
|
|
|
801
822
|
awaiting: 'attested receiving-group birth',
|
|
802
823
|
timeout: phaseTimeout ?? _kOtherDeadline,
|
|
803
824
|
onDiagnostic: onDiagnostic,
|
|
804
|
-
run: () => plane
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
},
|
|
819
|
-
),
|
|
825
|
+
run: () => plane.session(installWorkspace).offerOutcome(
|
|
826
|
+
domain: '__nomos_application_home_v1',
|
|
827
|
+
directiveId: 'nomosInternalBirthReceivingGroup7c1f5e',
|
|
828
|
+
actor: owner,
|
|
829
|
+
authorSecret: deviceSecret,
|
|
830
|
+
payload: <String, Object?>{
|
|
831
|
+
'owner': owner,
|
|
832
|
+
'frameworkHash': framework,
|
|
833
|
+
// Ordered application deployment is reference packages first, deferred child payload last.
|
|
834
|
+
// The parent serves the references; the receiving group is born with only its payload law.
|
|
835
|
+
'lawHash': laws.last,
|
|
836
|
+
'bornAt': bornAt,
|
|
837
|
+
},
|
|
838
|
+
),
|
|
820
839
|
);
|
|
821
840
|
String? bornStatus;
|
|
822
841
|
for (final entry in outcome.birthOutcomes) {
|
|
@@ -870,28 +889,26 @@ class NomosTestHarness {
|
|
|
870
889
|
// harness device through the installed framework law with the same verified provenance
|
|
871
890
|
// that the private test root later attests. Production clients never manufacture this
|
|
872
891
|
// marker; it exists only inside the offline test-mint ceremony.
|
|
873
|
-
run: () => plane
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
},
|
|
886
|
-
),
|
|
892
|
+
run: () => plane.session(workspace).offerOutcome(
|
|
893
|
+
domain: '__nomos_application_home_v1',
|
|
894
|
+
directiveId: 'enrollSigner',
|
|
895
|
+
actor: owner,
|
|
896
|
+
authorSecret: deviceSecret,
|
|
897
|
+
payload: <String, Object?>{
|
|
898
|
+
'keyHash': keyHash,
|
|
899
|
+
'grantedBy': owner,
|
|
900
|
+
'grantedAt': 1,
|
|
901
|
+
'verifiedVia': 'nomos:test-mint',
|
|
902
|
+
},
|
|
903
|
+
),
|
|
887
904
|
);
|
|
888
905
|
} catch (error) {
|
|
889
906
|
// Pre-application-home packages neither expose nor enforce this
|
|
890
907
|
// signer route. Preserve their old harness behaviour, while keeping
|
|
891
908
|
// every refusal from a current package fatal.
|
|
892
909
|
if (!error.toString().contains(
|
|
893
|
-
|
|
894
|
-
|
|
910
|
+
"no active installed law declares domain '__nomos_application_home_v1'",
|
|
911
|
+
)) {
|
|
895
912
|
rethrow;
|
|
896
913
|
}
|
|
897
914
|
}
|
|
@@ -904,36 +921,32 @@ class NomosTestHarness {
|
|
|
904
921
|
timeout: phaseTimeout ?? _kOtherDeadline,
|
|
905
922
|
onDiagnostic: onDiagnostic,
|
|
906
923
|
run: () async {
|
|
907
|
-
await plane
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
'workspaceName': _receivingGroupNameOf(owner),
|
|
924
|
-
'bornAt': bornAt,
|
|
925
|
-
},
|
|
926
|
-
);
|
|
924
|
+
await plane.session(workspace).offerOutcome(
|
|
925
|
+
domain: '__nomos_application_home_v1',
|
|
926
|
+
directiveId: 'nomosInternalSeedReceivingGroupPolicy7c1f5e',
|
|
927
|
+
actor: 'host-birth',
|
|
928
|
+
payload: <String, Object?>{'setAt': bornAt},
|
|
929
|
+
);
|
|
930
|
+
await plane.session(workspace).offerOutcome(
|
|
931
|
+
domain: '__nomos_application_home_v1',
|
|
932
|
+
directiveId: 'nomosInternalSeedReceivingGroup7c1f5e',
|
|
933
|
+
actor: 'host-birth',
|
|
934
|
+
payload: <String, Object?>{
|
|
935
|
+
'principal': owner,
|
|
936
|
+
'workspaceName': _receivingGroupNameOf(owner),
|
|
937
|
+
'bornAt': bornAt,
|
|
938
|
+
},
|
|
939
|
+
);
|
|
927
940
|
},
|
|
928
941
|
);
|
|
929
942
|
final parentCert = await plane.signBirthCert(
|
|
930
|
-
|
|
943
|
+
BirthCertificate(
|
|
931
944
|
child: workspace,
|
|
932
945
|
founder: owner,
|
|
933
946
|
verified: true,
|
|
934
|
-
parentKey: devicePublic,
|
|
947
|
+
parentKey: base64Decode(devicePublic),
|
|
935
948
|
parentId: 'root',
|
|
936
|
-
epoch:
|
|
949
|
+
epoch: Int64.ZERO,
|
|
937
950
|
),
|
|
938
951
|
deviceSecret,
|
|
939
952
|
);
|
|
@@ -944,38 +957,36 @@ class NomosTestHarness {
|
|
|
944
957
|
timeout: phaseTimeout ?? _kOtherDeadline,
|
|
945
958
|
onDiagnostic: onDiagnostic,
|
|
946
959
|
run: () async {
|
|
947
|
-
await plane
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
await plane
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
},
|
|
978
|
-
);
|
|
960
|
+
await plane.session(workspace).offerOutcome(
|
|
961
|
+
domain: '__nomos_application_home_v1',
|
|
962
|
+
directiveId: 'seedBirthAttestation',
|
|
963
|
+
actor: owner,
|
|
964
|
+
authorSecret: deviceSecret,
|
|
965
|
+
payload: <String, Object?>{
|
|
966
|
+
'child': workspace,
|
|
967
|
+
'founder': owner,
|
|
968
|
+
'verified': 'true',
|
|
969
|
+
'parentId': 'root',
|
|
970
|
+
'parentKey': devicePublic,
|
|
971
|
+
'epoch': 0,
|
|
972
|
+
'certChain': jsonEncode(<String, Object?>{
|
|
973
|
+
'links': <Object?>[
|
|
974
|
+
_birthCertificateLawValue(parentCert),
|
|
975
|
+
],
|
|
976
|
+
}),
|
|
977
|
+
},
|
|
978
|
+
);
|
|
979
|
+
await plane.session(workspace).offerOutcome(
|
|
980
|
+
domain: '__nomos_application_home_v1',
|
|
981
|
+
directiveId: 'seedGenesis',
|
|
982
|
+
actor: owner,
|
|
983
|
+
authorSecret: deviceSecret,
|
|
984
|
+
payload: <String, Object?>{
|
|
985
|
+
'principal': owner,
|
|
986
|
+
'workspaceId': workspace,
|
|
987
|
+
'bornAt': '2000-01-01T00:00:00.000Z',
|
|
988
|
+
},
|
|
989
|
+
);
|
|
979
990
|
},
|
|
980
991
|
);
|
|
981
992
|
} on DomainNotInstalledForSession {
|
|
@@ -993,9 +1004,9 @@ class NomosTestHarness {
|
|
|
993
1004
|
final sessionLaw = installedHashes.isEmpty
|
|
994
1005
|
? 'installed'
|
|
995
1006
|
: (birthsApplicationHome
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1007
|
+
? installedHashes.last
|
|
1008
|
+
: installedHashes.first)
|
|
1009
|
+
.substring(0, 12);
|
|
999
1010
|
phase('ready — session "$workspace" connected (law $sessionLaw)');
|
|
1000
1011
|
// THE DECOY SESSION (multi-workspace by default in tests): sole-session inference is how
|
|
1001
1012
|
// single-workspace assumptions ship — with one session connected, an op that forgot its session
|
|
@@ -1125,24 +1136,21 @@ class NomosTestHarness {
|
|
|
1125
1136
|
);
|
|
1126
1137
|
}
|
|
1127
1138
|
}
|
|
1128
|
-
final entry =
|
|
1129
|
-
nodeEntry ??
|
|
1139
|
+
final entry = nodeEntry ??
|
|
1130
1140
|
await _resolveEntry(node, resolveFrom ?? Directory.current.path);
|
|
1131
|
-
final effectiveRuntimeDir =
|
|
1132
|
-
runtimeDir ??
|
|
1141
|
+
final effectiveRuntimeDir = runtimeDir ??
|
|
1133
1142
|
Platform.environment['NOMOS_CANDIDATE_RUNTIME'] ??
|
|
1134
1143
|
Platform.environment['NOMOS_DART_RUNTIME'] ??
|
|
1135
1144
|
Platform.environment['NOMOS_OFFLINE_RUNTIME'] ??
|
|
1136
1145
|
await _sourceCandidateRuntime(entry);
|
|
1137
1146
|
final adjacentInstaller = '${File(entry).parent.path}/offline-install.mjs';
|
|
1138
|
-
final resolvedHelper =
|
|
1139
|
-
installHelper ??
|
|
1147
|
+
final resolvedHelper = installHelper ??
|
|
1140
1148
|
(File(adjacentInstaller).existsSync()
|
|
1141
1149
|
? adjacentInstaller
|
|
1142
1150
|
: '${File(entry).parent.path}/offline-install.ts');
|
|
1143
1151
|
final useInRepoScript =
|
|
1144
1152
|
(resolvedHelper.endsWith('.ts') || resolvedHelper.endsWith('.mjs')) &&
|
|
1145
|
-
|
|
1153
|
+
File(resolvedHelper).existsSync();
|
|
1146
1154
|
final githolonCmd = installHelper == null ? 'npx githolon' : resolvedHelper;
|
|
1147
1155
|
|
|
1148
1156
|
final workdir = await Directory.systemTemp.createTemp('nomos-cluster-');
|
|
@@ -1402,12 +1410,10 @@ class NomosTestHarness {
|
|
|
1402
1410
|
'no lawUsdzPaths — pass the build/<name>.law.usdz `npx githolon compile` emits',
|
|
1403
1411
|
);
|
|
1404
1412
|
}
|
|
1405
|
-
final entry =
|
|
1406
|
-
nodeEntry ??
|
|
1413
|
+
final entry = nodeEntry ??
|
|
1407
1414
|
await _resolveEntry(node, resolveFrom ?? Directory.current.path);
|
|
1408
|
-
final bareUid =
|
|
1409
|
-
? subject.substring(5)
|
|
1410
|
-
: subject;
|
|
1415
|
+
final bareUid =
|
|
1416
|
+
subject.startsWith('user:') ? subject.substring(5) : subject;
|
|
1411
1417
|
final http = HttpClient();
|
|
1412
1418
|
Future<Map<String, Object?>> post(
|
|
1413
1419
|
String path,
|
|
@@ -1481,8 +1487,8 @@ class NomosTestHarness {
|
|
|
1481
1487
|
}
|
|
1482
1488
|
rethrow;
|
|
1483
1489
|
}
|
|
1484
|
-
frameworkHash =
|
|
1485
|
-
?.toString();
|
|
1490
|
+
frameworkHash =
|
|
1491
|
+
((created['controller'] as Map?)?['domainHash'])?.toString();
|
|
1486
1492
|
final existing = await get(
|
|
1487
1493
|
'/v2/workspaces/$platformWorkspace/domains',
|
|
1488
1494
|
);
|
|
@@ -1571,7 +1577,7 @@ class NomosTestHarness {
|
|
|
1571
1577
|
);
|
|
1572
1578
|
final currentLaw =
|
|
1573
1579
|
(parentDomains['currentLaw'] as Map?)?.cast<String, Object?>() ??
|
|
1574
|
-
|
|
1580
|
+
const <String, Object?>{};
|
|
1575
1581
|
final controlHomeLawHash =
|
|
1576
1582
|
currentLaw[applicationHomeDomain]?.toString() ?? '';
|
|
1577
1583
|
if (controlHomeLawHash.isEmpty) {
|
|
@@ -1609,8 +1615,7 @@ class NomosTestHarness {
|
|
|
1609
1615
|
// restart while offline must not fetch any member before it can replay the persisted ledger. Release CI
|
|
1610
1616
|
// supplies the exact candidate bundle. An ordinary local test pins all three artifacts advertised by its
|
|
1611
1617
|
// ephemeral cloud while still online; never combine a candidate kernel with stale framework law.
|
|
1612
|
-
final candidateRuntimeDir =
|
|
1613
|
-
Platform.environment['NOMOS_DART_RUNTIME'] ??
|
|
1618
|
+
final candidateRuntimeDir = Platform.environment['NOMOS_DART_RUNTIME'] ??
|
|
1614
1619
|
Platform.environment['NOMOS_OFFLINE_RUNTIME'];
|
|
1615
1620
|
final localRuntime = await Directory(
|
|
1616
1621
|
'${workdir.path}/client-runtime',
|
|
@@ -1630,8 +1635,8 @@ class NomosTestHarness {
|
|
|
1630
1635
|
_KernelRuntimeArtifact.controllerLaw:
|
|
1631
1636
|
'$candidateRuntimeDir/${_KernelRuntimeArtifact.controllerLaw.fileName}',
|
|
1632
1637
|
};
|
|
1633
|
-
final hasCandidateBundle =
|
|
1634
|
-
|
|
1638
|
+
final hasCandidateBundle = candidateArtifacts.length ==
|
|
1639
|
+
_KernelRuntimeArtifact.values.length &&
|
|
1635
1640
|
candidateArtifacts.values.every((path) => File(path).existsSync());
|
|
1636
1641
|
if (hasCandidateBundle) {
|
|
1637
1642
|
for (final artifact in candidateArtifacts.entries) {
|
|
@@ -1785,7 +1790,12 @@ class NomosTestHarness {
|
|
|
1785
1790
|
'validFromEpoch': 0,
|
|
1786
1791
|
'grantedAt': DateTime.now().toUtc().toIso8601String(),
|
|
1787
1792
|
'idpToken': idpToken,
|
|
1788
|
-
|
|
1793
|
+
// The framework law owns this USDA object shape. Keep the
|
|
1794
|
+
// compiler-generated protobuf certificate structured while
|
|
1795
|
+
// lowering it into that domain value; never resurrect the
|
|
1796
|
+
// retired JSON string envelope (`delegationCert`).
|
|
1797
|
+
'delegationCertificate':
|
|
1798
|
+
_birthCertificateLawValue(delegationCert),
|
|
1789
1799
|
},
|
|
1790
1800
|
);
|
|
1791
1801
|
final enrollmentSync = await plane.sync(
|
|
@@ -1821,36 +1831,34 @@ class NomosTestHarness {
|
|
|
1821
1831
|
// authenticated platform holon mounted. Bind this parent certificate operation to the
|
|
1822
1832
|
// platform session explicitly; relying on the single-session convenience API becomes
|
|
1823
1833
|
// ambiguous as soon as the ceremony correctly opens the target workspace in-kernel.
|
|
1824
|
-
final homeCert =
|
|
1825
|
-
.session('$clientId-platform-author')
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
birthCertificate: homeCert,
|
|
1853
|
-
);
|
|
1834
|
+
final homeCert =
|
|
1835
|
+
await plane.session('$clientId-platform-author').signBirthCert(
|
|
1836
|
+
BirthCertificate(
|
|
1837
|
+
child: homeName,
|
|
1838
|
+
founder: subject,
|
|
1839
|
+
// The cert's `verified` must MATCH the gate verdict (CAS-as-law): the offer carries
|
|
1840
|
+
// the dev-IdP token, so the gate resolves a VERIFIED founder.
|
|
1841
|
+
verified: true,
|
|
1842
|
+
parentKey: base64Decode(device.publicB64),
|
|
1843
|
+
parentId: platformWorkspace,
|
|
1844
|
+
epoch: Int64.ZERO,
|
|
1845
|
+
),
|
|
1846
|
+
device.secret,
|
|
1847
|
+
);
|
|
1848
|
+
final localBirth =
|
|
1849
|
+
await plane.session('$clientId-platform-author').offerOutcome(
|
|
1850
|
+
domain: applicationHomeDomain,
|
|
1851
|
+
directiveId: 'nomosInternalBirthReceivingGroup7c1f5e',
|
|
1852
|
+
actor: subject,
|
|
1853
|
+
authorSecret: device.secret,
|
|
1854
|
+
payload: <String, Object?>{
|
|
1855
|
+
'owner': subject,
|
|
1856
|
+
'frameworkHash': frameworkHash,
|
|
1857
|
+
'lawHash': applicationHomeLawHash,
|
|
1858
|
+
'bornAt': DateTime.now().toUtc().toIso8601String(),
|
|
1859
|
+
},
|
|
1860
|
+
birthCertificate: homeCert,
|
|
1861
|
+
);
|
|
1854
1862
|
if (!localBirth.born.contains(homeName)) {
|
|
1855
1863
|
throw NomosTestHarnessException(
|
|
1856
1864
|
'cloudBound: the local parent kernel admitted the receiving-group offer but did not birth '
|
|
@@ -2006,8 +2014,7 @@ class NomosTestHarness {
|
|
|
2006
2014
|
if (r['pushFailed'] != true &&
|
|
2007
2015
|
r['stillAhead'] != true &&
|
|
2008
2016
|
r['offline'] != true &&
|
|
2009
|
-
r['deferred'] != true)
|
|
2010
|
-
pending.remove(ws);
|
|
2017
|
+
r['deferred'] != true) pending.remove(ws);
|
|
2011
2018
|
} on NomosTestHarnessException {
|
|
2012
2019
|
rethrow;
|
|
2013
2020
|
} catch (e) {
|
|
@@ -2418,14 +2425,14 @@ class NomosTestHarness {
|
|
|
2418
2425
|
// transport first makes its incoming stream wait on consumers that can no longer receive their
|
|
2419
2426
|
// unwatch/done messages—a deterministic teardown deadlock in reactive Flutter tests.
|
|
2420
2427
|
await _plane.dispose().timeout(
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2428
|
+
_phaseTimeout ?? _kOtherDeadline,
|
|
2429
|
+
onTimeout: () => throw HarnessPhaseTimeout(
|
|
2430
|
+
phase: 'dispose: close bridge watches',
|
|
2431
|
+
workspace: workspace,
|
|
2432
|
+
elapsed: _phaseTimeout ?? _kOtherDeadline,
|
|
2433
|
+
awaiting: 'NomosBridge.dispose',
|
|
2434
|
+
),
|
|
2435
|
+
);
|
|
2429
2436
|
} catch (e) {
|
|
2430
2437
|
_diag(
|
|
2431
2438
|
'[nomos-test-harness] bridge dispose error (continuing to runner cleanup): $e',
|
|
@@ -2433,14 +2440,14 @@ class NomosTestHarness {
|
|
|
2433
2440
|
}
|
|
2434
2441
|
try {
|
|
2435
2442
|
await _transport.dispose().timeout(
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2443
|
+
_phaseTimeout ?? _kOtherDeadline,
|
|
2444
|
+
onTimeout: () => throw HarnessPhaseTimeout(
|
|
2445
|
+
phase: 'dispose: stop the node runner',
|
|
2446
|
+
workspace: workspace,
|
|
2447
|
+
elapsed: _phaseTimeout ?? _kOtherDeadline,
|
|
2448
|
+
awaiting: 'NodeRunnerTransport.dispose',
|
|
2449
|
+
),
|
|
2450
|
+
);
|
|
2444
2451
|
} catch (e) {
|
|
2445
2452
|
_diag(
|
|
2446
2453
|
'[nomos-test-harness] transport dispose error (continuing to temp-dir cleanup): $e',
|
|
@@ -2574,10 +2581,13 @@ class NomosTestHarness {
|
|
|
2574
2581
|
'@githolon/client/runner/node-entry.mjs',
|
|
2575
2582
|
'@githolon/client/runner/node-entry.ts',
|
|
2576
2583
|
]) {
|
|
2577
|
-
final r = await Process.run(
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2584
|
+
final r = await Process.run(
|
|
2585
|
+
node,
|
|
2586
|
+
[
|
|
2587
|
+
'-p',
|
|
2588
|
+
"require.resolve('$entry')",
|
|
2589
|
+
],
|
|
2590
|
+
workingDirectory: from);
|
|
2581
2591
|
final out = (r.stdout as String).trim();
|
|
2582
2592
|
if (r.exitCode == 0 && out.isNotEmpty) return out;
|
|
2583
2593
|
}
|
|
@@ -2610,21 +2620,20 @@ class WorkspaceSnapshot {
|
|
|
2610
2620
|
|
|
2611
2621
|
/// JSON round-trip so a staged snapshot persists to disk verbatim (the bytes + provenance).
|
|
2612
2622
|
Map<String, Object?> toJson() => {
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2623
|
+
'workspace': workspace,
|
|
2624
|
+
'snapshotB64': snapshotB64,
|
|
2625
|
+
'head': head,
|
|
2626
|
+
'lawHashes': lawHashes,
|
|
2627
|
+
'capturedAt': capturedAt.toUtc().toIso8601String(),
|
|
2628
|
+
};
|
|
2619
2629
|
|
|
2620
2630
|
factory WorkspaceSnapshot.fromJson(Map<String, Object?> j) =>
|
|
2621
2631
|
WorkspaceSnapshot(
|
|
2622
2632
|
workspace: j['workspace'] as String,
|
|
2623
2633
|
snapshotB64: j['snapshotB64'] as String,
|
|
2624
2634
|
head: (j['head'] ?? '') as String,
|
|
2625
|
-
lawHashes:
|
|
2626
|
-
.map((e) => '$e')
|
|
2627
|
-
.toList(),
|
|
2635
|
+
lawHashes:
|
|
2636
|
+
((j['lawHashes'] as List?) ?? const []).map((e) => '$e').toList(),
|
|
2628
2637
|
capturedAt: DateTime.parse(
|
|
2629
2638
|
(j['capturedAt'] ?? DateTime.now().toUtc().toIso8601String())
|
|
2630
2639
|
as String,
|
|
@@ -2646,19 +2655,20 @@ class PlaneSnapshot {
|
|
|
2646
2655
|
const PlaneSnapshot({required this.workspaces, required this.capturedAt});
|
|
2647
2656
|
|
|
2648
2657
|
Map<String, Object?> toJson() => {
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2658
|
+
'workspaces': workspaces.map((k, v) => MapEntry(k, v.toJson())),
|
|
2659
|
+
'capturedAt': capturedAt.toUtc().toIso8601String(),
|
|
2660
|
+
};
|
|
2652
2661
|
|
|
2653
2662
|
factory PlaneSnapshot.fromJson(Map<String, Object?> j) => PlaneSnapshot(
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2663
|
+
workspaces: ((j['workspaces'] as Map?) ?? const {}).map(
|
|
2664
|
+
(k, v) => MapEntry(
|
|
2665
|
+
'$k',
|
|
2666
|
+
WorkspaceSnapshot.fromJson((v as Map).cast<String, Object?>()),
|
|
2667
|
+
),
|
|
2668
|
+
),
|
|
2669
|
+
capturedAt: DateTime.parse(
|
|
2670
|
+
(j['capturedAt'] ?? DateTime.now().toUtc().toIso8601String())
|
|
2671
|
+
as String,
|
|
2672
|
+
),
|
|
2673
|
+
);
|
|
2664
2674
|
}
|