@githolon/dsl 0.102.5 → 0.103.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.
Files changed (131) hide show
  1. package/compile_package.mjs +43 -2
  2. package/compile_proof.d.mts +47 -0
  3. package/compile_proof.mjs +343 -0
  4. package/dart/CHANGELOG.md +7 -0
  5. package/dart/lib/src/dev_cloud.dart +8 -8
  6. package/dart/lib/src/nms_generated_runtime.dart +2 -0
  7. package/dart/lib/src/runner/node-entry.mjs +5504 -1236
  8. package/dart/lib/src/runner/offline-install.mjs +4578 -528
  9. package/dart/lib/src/test_harness.dart +206 -152
  10. package/dart/lib/src/webview_bridge.dart +196 -89
  11. package/dart/pubspec.lock +1 -1
  12. package/dart/pubspec.yaml +2 -2
  13. package/dart/test/acquire_session_credentials_test.dart +4 -3
  14. package/dart/test/adv_realm_interference_test.dart +7 -6
  15. package/dart/test/cloud_bound_topology_test.dart +3 -3
  16. package/dart/test/cluster_births_test.dart +4 -3
  17. package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
  18. package/dart/test/cluster_local_autosync_test.dart +3 -2
  19. package/dart/test/cluster_scale_test.dart +3 -2
  20. package/dart/test/dlq_session_surface_test.dart +7 -6
  21. package/dart/test/first_app_gauntlet_test.dart +14 -21
  22. package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
  23. package/dart/test/four_layer_nested_topology_test.dart +4 -4
  24. package/dart/test/harness_phase_timeout_test.dart +2 -2
  25. package/dart/test/local_sync_inert_test.dart +1 -1
  26. package/dart/test/parent_signer_recovery_test.dart +2 -2
  27. package/dart/test/sealing_client_test.dart +4 -3
  28. package/dart/test/session_bound_test.dart +8 -7
  29. package/dart/test/test_harness_test.dart +13 -12
  30. package/dart/test/vo_read_typing_test.dart +4 -3
  31. package/dart/test/watch_after_offer_stall_test.dart +4 -3
  32. package/dart/test/webview_bridge_test.dart +102 -5
  33. package/dart_types/CHANGELOG.md +5 -0
  34. package/dart_types/lib/nomos_types.dart +9 -0
  35. package/dart_types/lib/src/nomos_ref.dart +20 -0
  36. package/dart_types/lib/src/optional_ref.dart +85 -0
  37. package/dart_types/lib/src/sha256_digest.dart +41 -0
  38. package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
  39. package/dart_types/pubspec.yaml +1 -1
  40. package/dart_types/test/optional_ref_test.dart +44 -0
  41. package/dart_types/test/sha256_digest_test.dart +18 -0
  42. package/package.json +9 -5
  43. package/runtime/generated-application-host.js +5365 -1105
  44. package/src/aggregate.ts +18 -0
  45. package/src/application_composition.ts +6 -32
  46. package/src/attested_core.ts +6 -3
  47. package/src/birth_meter.ts +1 -0
  48. package/src/birth_meter_ensure.ts +28 -2
  49. package/src/births.ts +1 -1
  50. package/src/build_package.ts +162 -538
  51. package/src/business_birth.ts +19 -3
  52. package/src/canonical_json.ts +21 -0
  53. package/src/check_package_main.ts +73 -33
  54. package/src/clear_directive.ts +19 -8
  55. package/src/codegen_dart.ts +305 -22
  56. package/src/codegen_dart_application.ts +844 -292
  57. package/src/codegen_dart_client.ts +1084 -407
  58. package/src/codegen_dart_scenario.ts +12 -12
  59. package/src/codegen_proof.ts +169 -39
  60. package/src/codegen_provider_dart.ts +17 -1
  61. package/src/codegen_ts.ts +298 -106
  62. package/src/compat_manifest.ts +2 -2
  63. package/src/compat_package_main.ts +8 -12
  64. package/src/compile_package_main.ts +1858 -590
  65. package/src/compose.ts +1 -1
  66. package/src/directive.ts +208 -15
  67. package/src/engine_entry.ts +165 -111
  68. package/src/evolve_preview.ts +29 -69
  69. package/src/extremum.ts +1 -1
  70. package/src/fields.ts +183 -4
  71. package/src/flow_gate.ts +75 -2
  72. package/src/framework/application_home.ts +2 -0
  73. package/src/framework/auth_provider_law.ts +2 -2
  74. package/src/framework/birth_meter.ts +7 -19
  75. package/src/framework/bootstrap.ts +0 -2
  76. package/src/framework/business_corrections.ts +9 -5
  77. package/src/framework/domain_lifecycle.ts +15 -29
  78. package/src/framework/encryption_scope.ts +2 -1
  79. package/src/framework/framework_aggregates.ts +2 -0
  80. package/src/framework/genesis.ts +16 -14
  81. package/src/framework/home.ts +20 -14
  82. package/src/framework/owned_custody.ts +9 -3
  83. package/src/framework/platform_admin.ts +3 -3
  84. package/src/framework/relations.ts +21 -13
  85. package/src/framework/signing_identity.ts +45 -40
  86. package/src/framework/workspace_invariant.ts +245 -0
  87. package/src/framework/workspaces.ts +23 -29
  88. package/src/index.ts +36 -16
  89. package/src/intent.ts +178 -6
  90. package/src/law_check.ts +32 -11
  91. package/src/law_graph.ts +2902 -0
  92. package/src/law_graph_usda.ts +1799 -0
  93. package/src/law_identity.ts +435 -0
  94. package/src/law_value_type.ts +230 -0
  95. package/src/lsp_server.ts +58 -29
  96. package/src/ops.ts +7 -0
  97. package/src/package_config.ts +141 -0
  98. package/src/package_dependencies.ts +108 -0
  99. package/src/package_dependency_usage.ts +150 -0
  100. package/src/package_source_imports.ts +71 -0
  101. package/src/plan_source_slice.ts +335 -0
  102. package/src/point_guard.ts +192 -0
  103. package/src/precondition.ts +44 -9
  104. package/src/read_projection_namespace.ts +1 -1
  105. package/src/runtime_zod.ts +44 -0
  106. package/src/secondary_ensure.ts +67 -14
  107. package/src/shape_compat.ts +1 -1
  108. package/src/stable_id_identity_bridge.ts +149 -0
  109. package/src/stable_id_lockfile.ts +140 -6
  110. package/src/stable_ids.ts +1 -1
  111. package/src/stable_ids_for_module.ts +348 -0
  112. package/src/stable_ids_types.ts +1 -1
  113. package/src/sum.ts +1 -1
  114. package/src/usd_state.ts +1 -1
  115. package/src/usda_relationship_integrity.ts +66 -0
  116. package/src/usda_text.ts +18 -0
  117. package/src/usdz.ts +78 -0
  118. package/src/value_object.ts +95 -0
  119. package/src/wire.ts +19 -0
  120. package/src/wire_encode.ts +22 -0
  121. package/src/workspace_sharding.ts +52 -36
  122. package/src/workspace_sharing.ts +28 -4
  123. package/src/zod_introspection.ts +1 -1
  124. package/dart/test/cluster_attested_test.dart +0 -201
  125. package/src/compile_engine.ts +0 -595
  126. package/src/manifest.ts +0 -1476
  127. package/src/read_projection_composition.ts +0 -117
  128. package/src/read_projection_manifest.ts +0 -115
  129. package/src/relation_composition.ts +0 -286
  130. package/src/usd.ts +0 -1967
  131. package/src/usd_layers.ts +0 -1776
@@ -7,7 +7,7 @@
7
7
  //
8
8
  // ```dart
9
9
  // final harness = await NomosTestHarness.ephemeral(
10
- // deployJsonPaths: ['build/guestbook.deploy.json'],
10
+ // lawUsdzPaths: ['build/guestbook.law.usdz'],
11
11
  // registerTearDown: addTearDown, // auto-disposes when the test ends (see below)
12
12
  // );
13
13
  // final app = guestbookClient(harness.bridge); // the generated client — same one the app uses
@@ -85,10 +85,10 @@ Future<String?> _sourceCandidateRuntime(String nodeEntry) async {
85
85
  '${root.path}/cloud/dev-server/kernel-testmint.wasm',
86
86
  );
87
87
  final bootstrap = File(
88
- '${root.path}/cloud/cloudflare-shell/domains/domain_bootstrap.package.usda',
88
+ '${root.path}/cloud/cloudflare-shell/domains/build/bootstrap/domain_bootstrap.law.usdz',
89
89
  );
90
90
  final nomos = File(
91
- '${root.path}/cloud/cloudflare-shell/domains/domain_nomos.package.usda',
91
+ '${root.path}/cloud/cloudflare-shell/domains/build/nomos/nomos.law.usdz',
92
92
  );
93
93
  for (final file in [production, testMint, bootstrap, nomos]) {
94
94
  if (!file.existsSync()) {
@@ -111,14 +111,14 @@ Future<String?> _sourceCandidateRuntime(String nodeEntry) async {
111
111
  '${short(productionActual)}/${short(testMintActual)} — run the kernel candidate preparation before testing',
112
112
  );
113
113
  }
114
- final bootstrapText = await bootstrap.readAsString();
115
- final nomosText = await nomos.readAsString();
114
+ final bootstrapBytes = await bootstrap.readAsBytes();
115
+ final nomosBytes = await nomos.readAsBytes();
116
116
  final bundleIdentity = sha256
117
117
  .convert(
118
118
  utf8.encode(
119
119
  '$productionActual:$testMintActual:'
120
- '${sha256.convert(utf8.encode(bootstrapText))}:'
121
- '${sha256.convert(utf8.encode(nomosText))}',
120
+ '${sha256.convert(bootstrapBytes)}:'
121
+ '${sha256.convert(nomosBytes)}',
122
122
  ),
123
123
  )
124
124
  .toString();
@@ -146,13 +146,8 @@ Future<String?> _sourceCandidateRuntime(String nodeEntry) async {
146
146
 
147
147
  await publish('holon.wasm', productionBytes);
148
148
  await publish('holon-testmint.wasm', testMintBytes);
149
- await publish(
150
- 'packages.json',
151
- utf8.encode(jsonEncode(<String, String>{
152
- 'bootstrap': bootstrapText,
153
- 'nomos': nomosText,
154
- })),
155
- );
149
+ await publish('bootstrap.law.usdz', bootstrapBytes);
150
+ await publish('nomos.law.usdz', nomosBytes);
156
151
  return runtime.path;
157
152
  }
158
153
 
@@ -193,7 +188,7 @@ String? nmsPermanentConvergenceFailure(Map<String, Object?> result) {
193
188
 
194
189
  /// The offline-install lane configuration a harness keeps so the CUSTODY DIAGNOSTIC helpers
195
190
  /// (ask 9 of architecture/session_bound_clients.md) can re-run installs after construction:
196
- /// [seedWorkspaceFromDeployJson] / [resetWorkspaceCustody].
191
+ /// [seedWorkspaceFromLawUsdz] / [resetWorkspaceCustody].
197
192
  class _InstallLane {
198
193
  final String node;
199
194
  final bool useInRepoScript;
@@ -323,7 +318,7 @@ class NomosTestHarness {
323
318
  /// workspace → the base64 restore snapshot it was seeded from (for [resetWorkspaceCustody]).
324
319
  final Map<String, String> _snapshots;
325
320
 
326
- /// The offline-install lane config (for [seedWorkspaceFromDeployJson]); null when unknown.
321
+ /// The offline-install lane config (for [seedWorkspaceFromLawUsdz]); null when unknown.
327
322
  final _InstallLane? _installLane;
328
323
 
329
324
  /// When set, overrides EVERY phase's default deadline (see [HarnessPhaseTimeout]).
@@ -505,13 +500,13 @@ class NomosTestHarness {
505
500
  return bridge.session(workspace);
506
501
  }
507
502
 
508
- /// Install [deployJsonPaths] into a fresh LOCAL workspace and boot a bridge over it — NO cloud contact.
503
+ /// Install [lawUsdzPaths] into a fresh LOCAL workspace and boot a bridge over it — NO cloud contact.
509
504
  ///
510
- /// [deployJsonPaths] — the `build/<name>.deploy.json` files `nomos-compile` emits (installed in order;
505
+ /// [lawUsdzPaths] — the `build/<name>.law.usdz` files `nomos-compile` emits (installed in order;
511
506
  /// framework/platform law before a domain that depends on it).
512
507
  /// [workspace] — the local workspace name (default `test-ws`; law-only, never leaves the machine).
513
508
  /// [runtimeDir] — the runtime cache (default `~/.holon/runtime`); must hold `holon-testmint.wasm` +
514
- /// `packages.json`. The harness owns a throwaway test root; app code never handles its key material.
509
+ /// `bootstrap.law.usdz` + `nomos.law.usdz`. The harness owns a throwaway test root; app code never handles its key material.
515
510
  /// [nodeEntry] — path to `node-entry.ts`; when omitted it is resolved from [resolveFrom] via
516
511
  /// `require.resolve('@githolon/client/runner/node-entry.ts')`.
517
512
  /// [installHelper] — the offline law-install lane. An EXISTING `.ts` or `.mjs` file is run as a node script (the
@@ -523,7 +518,7 @@ class NomosTestHarness {
523
518
  /// (feature-detecting `package:test` from a library is not clean in Dart, so this is explicit and safe).
524
519
  /// [autoTearDown] — when true (default) and [registerTearDown] is given, registers [dispose] as a teardown.
525
520
  static Future<NomosTestHarness> ephemeral({
526
- required List<String> deployJsonPaths,
521
+ required List<String> lawUsdzPaths,
527
522
  String? ownerSubject,
528
523
  String workspace = 'test-ws',
529
524
  String clientId = 'test-runner',
@@ -540,9 +535,9 @@ class NomosTestHarness {
540
535
  Duration? phaseTimeout,
541
536
  bool decoySession = true,
542
537
  }) async {
543
- if (deployJsonPaths.isEmpty) {
538
+ if (lawUsdzPaths.isEmpty) {
544
539
  throw NomosTestHarnessException(
545
- 'no deployJsonPaths — pass the build/<name>.deploy.json `npx githolon compile` emits',
540
+ 'no lawUsdzPaths — pass the build/<name>.law.usdz `npx githolon compile` emits',
546
541
  );
547
542
  }
548
543
  final entry = nodeEntry ??
@@ -592,8 +587,7 @@ class NomosTestHarness {
592
587
  try {
593
588
  final snapshot = '${workdir.path}/snapshot.bin';
594
589
  final info = await _phased(
595
- phase:
596
- '1/4: offline law install (${deployJsonPaths.length} package(s))',
590
+ phase: '1/4: offline law install (${lawUsdzPaths.length} package(s))',
597
591
  workspace: installWorkspace,
598
592
  awaiting:
599
593
  useInRepoScript ? 'offline-install.ts' : 'githolon harness-install',
@@ -608,7 +602,7 @@ class NomosTestHarness {
608
602
  useInRepoScript: useInRepoScript,
609
603
  inRepoScript: resolvedHelper,
610
604
  githolonCmd: githolonCmd,
611
- deployJsonPaths: deployJsonPaths,
605
+ lawUsdzPaths: lawUsdzPaths,
612
606
  workspace: installWorkspace,
613
607
  snapshot: snapshot,
614
608
  runtimeDir: effectiveRuntimeDir,
@@ -647,7 +641,7 @@ class NomosTestHarness {
647
641
  timeout: phaseTimeout ?? readyTimeout,
648
642
  onDiagnostic: onDiagnostic,
649
643
  likelyCauses: const [
650
- 'node is not on PATH, or ~/.holon/runtime is cold (holon-testmint.wasm + packages.json)',
644
+ 'node is not on PATH, or ~/.holon/runtime is cold (holon-testmint.wasm + framework law archives)',
651
645
  'the runner process failed to boot the engine — run with onDiagnostic to see its stderr',
652
646
  ],
653
647
  run: () => plane.ready,
@@ -872,18 +866,16 @@ class NomosTestHarness {
872
866
  // application-ready.
873
867
  }
874
868
  }
875
- final installedHashes =
876
- ((info['lawHashes'] as List?) ?? const [])
877
- .map((hash) => hash.toString())
878
- .where((hash) => hash.isNotEmpty)
879
- .toList();
880
- final sessionLaw =
881
- installedHashes.isEmpty
882
- ? 'installed'
883
- : (birthsApplicationHome
869
+ final installedHashes = ((info['lawHashes'] as List?) ?? const [])
870
+ .map((hash) => hash.toString())
871
+ .where((hash) => hash.isNotEmpty)
872
+ .toList();
873
+ final sessionLaw = installedHashes.isEmpty
874
+ ? 'installed'
875
+ : (birthsApplicationHome
884
876
  ? installedHashes.last
885
877
  : installedHashes.first)
886
- .substring(0, 12);
878
+ .substring(0, 12);
887
879
  phase('ready — session "$workspace" connected (law $sessionLaw)');
888
880
  // THE DECOY SESSION (multi-workspace by default in tests): sole-session inference is how
889
881
  // single-workspace assumptions ship — with one session connected, an op that forgot its session
@@ -978,7 +970,7 @@ class NomosTestHarness {
978
970
  /// the SAME offline lane [ephemeral] uses (one snapshot per workspace), then `connect`ed as its own session;
979
971
  /// the per-connect restore snapshot loads it into the shared plane.
980
972
  ///
981
- /// [workspaces] — `{ workspaceName: [deployJsonPaths] }` (installed in order per workspace). Get a
973
+ /// [workspaces] — `{ workspaceName: [lawUsdzPaths] }` (installed in order per workspace). Get a
982
974
  /// session-scoped view with [session] and bind it to a generated client: `EstateClient(harness.session(
983
975
  /// 'estate'))`. All other params mirror [ephemeral]. [clientId] is suffixed per session.
984
976
  /// [deviceKeyed] names workspaces to open WITH a fresh in-kernel device key as their signing `authorSecret`
@@ -1003,13 +995,13 @@ class NomosTestHarness {
1003
995
  }) async {
1004
996
  if (workspaces.isEmpty) {
1005
997
  throw NomosTestHarnessException(
1006
- 'no workspaces — pass { workspaceName: [build/<name>.deploy.json] } for each colocated workspace',
998
+ 'no workspaces — pass { workspaceName: [build/<name>.law.usdz] } for each colocated workspace',
1007
999
  );
1008
1000
  }
1009
1001
  for (final e in workspaces.entries) {
1010
1002
  if (e.value.isEmpty) {
1011
1003
  throw NomosTestHarnessException(
1012
- 'workspace "${e.key}" has no deployJsonPaths — pass its build/<name>.deploy.json',
1004
+ 'workspace "${e.key}" has no lawUsdzPaths — pass its build/<name>.law.usdz',
1013
1005
  );
1014
1006
  }
1015
1007
  }
@@ -1061,7 +1053,7 @@ class NomosTestHarness {
1061
1053
  useInRepoScript: useInRepoScript,
1062
1054
  inRepoScript: resolvedHelper,
1063
1055
  githolonCmd: githolonCmd,
1064
- deployJsonPaths: e.value,
1056
+ lawUsdzPaths: e.value,
1065
1057
  workspace: e.key,
1066
1058
  snapshot: snap,
1067
1059
  runtimeDir: effectiveRuntimeDir,
@@ -1116,7 +1108,7 @@ class NomosTestHarness {
1116
1108
  timeout: phaseTimeout ?? readyTimeout,
1117
1109
  onDiagnostic: onDiagnostic,
1118
1110
  likelyCauses: const [
1119
- 'node is not on PATH, or ~/.holon/runtime is cold (holon-testmint.wasm + packages.json)',
1111
+ 'node is not on PATH, or ~/.holon/runtime is cold (holon-testmint.wasm + framework law archives)',
1120
1112
  'the runner process failed to boot the engine — run with onDiagnostic to see its stderr',
1121
1113
  ],
1122
1114
  run: () => bridge.ready,
@@ -1255,7 +1247,7 @@ class NomosTestHarness {
1255
1247
  /// ```dart
1256
1248
  /// final cloud = await NomosTestCloud.start(serverEntry: devServer);
1257
1249
  /// final h = await NomosTestHarness.cloudBound(
1258
- /// cloud: cloud, deployJsonPaths: ['build/co2.deploy.json'],
1250
+ /// cloud: cloud, lawUsdzPaths: ['build/co2.law.usdz'],
1259
1251
  /// platformWorkspace: 'co2-platform', subject: 'user:test');
1260
1252
  /// final home = h.homeSession; // the app's entry point
1261
1253
  /// cloud.setOnline(false); // PARTITION
@@ -1266,7 +1258,7 @@ class NomosTestHarness {
1266
1258
  /// ```
1267
1259
  static Future<NomosTestHarness> cloudBound({
1268
1260
  required NomosTestCloud cloud,
1269
- required List<String> deployJsonPaths,
1261
+ required List<String> lawUsdzPaths,
1270
1262
  String platformWorkspace = 'test-platform',
1271
1263
  required String subject,
1272
1264
  String? authToken,
@@ -1282,9 +1274,9 @@ class NomosTestHarness {
1282
1274
  Duration? phaseTimeout,
1283
1275
  bool decoySession = true,
1284
1276
  }) async {
1285
- if (deployJsonPaths.isEmpty) {
1277
+ if (lawUsdzPaths.isEmpty) {
1286
1278
  throw NomosTestHarnessException(
1287
- 'no deployJsonPaths — pass the build/<name>.deploy.json `npx githolon compile` emits',
1279
+ 'no lawUsdzPaths — pass the build/<name>.law.usdz `npx githolon compile` emits',
1288
1280
  );
1289
1281
  }
1290
1282
  final entry = nodeEntry ??
@@ -1333,8 +1325,9 @@ class NomosTestHarness {
1333
1325
  final workdir = await Directory.systemTemp.createTemp('nomos-harness-');
1334
1326
  NodeRunnerTransport? transport;
1335
1327
  try {
1336
- // 1/6 — the remote control plane: BOOT the BORN platform + deploy the law under the test cloud's own
1337
- // trust universe. ERA-0: the host does not create workspaces — the platform must have been stood
1328
+ // 1/6 — BOOT the BORN platform and inspect the law already installed by the test cloud's operator
1329
+ // birth lane. Raw host deployment is retired: the harness never uploads or interprets law.
1330
+ // ERA-0: the host does not create workspaces — the platform must have been stood
1338
1331
  // up BY THE CLOUD at boot (`NomosTestCloud.start(platform: '$platformWorkspace')`, the dev
1339
1332
  // server's in-process operator lane). Booting a born workspace is idempotent; an unborn one
1340
1333
  // refuses 409, surfaced here with the fix.
@@ -1343,9 +1336,9 @@ class NomosTestHarness {
1343
1336
  <({String domain, String directive, String path})>[];
1344
1337
  final lawHashes = <String>[];
1345
1338
  await _phased(
1346
- phase: '1/6: boot the born platform + deploy law on the test cloud',
1339
+ phase: '1/6: boot the born platform + inspect installed law',
1347
1340
  workspace: platformWorkspace,
1348
- awaiting: 'POST /v2/workspaces + /domains',
1341
+ awaiting: 'POST /v2/workspaces + GET /domains + /openapi.json',
1349
1342
  timeout: phaseTimeout ?? _kInstallDeadline,
1350
1343
  onDiagnostic: onDiagnostic,
1351
1344
  run: () async {
@@ -1368,50 +1361,52 @@ class NomosTestHarness {
1368
1361
  final existing = await get(
1369
1362
  '/v2/workspaces/$platformWorkspace/domains',
1370
1363
  );
1371
- final heldHashes = <String>{
1372
- ...((existing['domains'] as List?) ?? const [])
1373
- .map((row) => (row as Map?)?['domainHash']?.toString())
1374
- .whereType<String>()
1375
- .where((hash) => hash.isNotEmpty),
1376
- ...(((existing['currentLaw'] as Map?) ?? const {})
1377
- .values
1378
- .map((hash) => hash?.toString())
1379
- .whereType<String>()
1380
- .where((hash) => hash.isNotEmpty)),
1381
- };
1382
- for (final p in deployJsonPaths) {
1383
- final body = await File(p).readAsString();
1384
- final deploy = (jsonDecode(body) as Map);
1385
- final summary = (deploy['summary'] as Map?);
1386
- final h = summary?['domainHash']?.toString();
1387
- // A composed test application is normally installed atomically by the bootstrap operator.
1388
- // The signed-in scenario participant must not need installDomain authority merely because the
1389
- // harness was also handed those artifacts for routes, hashes and child-law selection.
1390
- if (h == null || h.isEmpty || !heldHashes.contains(h)) {
1391
- await post('/v2/workspaces/$platformWorkspace/domains', body);
1392
- if (h != null && h.isNotEmpty) heldHashes.add(h);
1364
+ for (final path in lawUsdzPaths) {
1365
+ if (!File(path).existsSync()) {
1366
+ throw NomosTestHarnessException(
1367
+ 'cloudBound: compiled law archive is missing: $path',
1368
+ );
1393
1369
  }
1394
- if (h != null && h.isNotEmpty) lawHashes.add(h);
1395
- final openapi = deploy['openapi'] as Map?;
1396
- final pathSets = <Map>[
1397
- (openapi?['paths'] as Map?) ?? const {},
1398
- (((openapi?['x-nomos-framework-routes'] as Map?)?['paths'])
1399
- as Map?) ??
1400
- const {},
1401
- ];
1402
- for (final paths in pathSets) {
1403
- for (final entry in paths.entries) {
1404
- final post = (entry.value as Map?)?['post'] as Map?;
1405
- final route = post?['x-nomos'] as Map?;
1406
- final domain = route?['domain']?.toString() ?? '';
1407
- final directive = route?['directiveId']?.toString() ?? '';
1408
- if (domain.isNotEmpty && directive.isNotEmpty) {
1409
- frameworkPostRoutes.add((
1410
- domain: domain,
1411
- directive: directive,
1412
- path: entry.key.toString(),
1413
- ));
1414
- }
1370
+ }
1371
+ void rememberLaw(Object? value) {
1372
+ final hash = value?.toString() ?? '';
1373
+ if (hash.isNotEmpty &&
1374
+ hash != frameworkHash &&
1375
+ !lawHashes.contains(hash)) {
1376
+ lawHashes.add(hash);
1377
+ }
1378
+ }
1379
+
1380
+ // DomainInstallation rows preserve the operator-authored install order; currentLaw fills
1381
+ // any projection-era gap. These are authenticated kernel reads, not host package metadata.
1382
+ for (final row in ((existing['domains'] as List?) ?? const [])) {
1383
+ rememberLaw((row as Map?)?['domainHash']);
1384
+ }
1385
+ for (final hash
1386
+ in (((existing['currentLaw'] as Map?) ?? const {}).values)) {
1387
+ rememberLaw(hash);
1388
+ }
1389
+ final openapi = await get(
1390
+ '/v2/workspaces/$platformWorkspace/openapi.json',
1391
+ );
1392
+ final pathSets = <Map>[
1393
+ (openapi['paths'] as Map?) ?? const {},
1394
+ (((openapi['x-nomos-framework-routes'] as Map?)?['paths'])
1395
+ as Map?) ??
1396
+ const {},
1397
+ ];
1398
+ for (final paths in pathSets) {
1399
+ for (final entry in paths.entries) {
1400
+ final operation = (entry.value as Map?)?['post'] as Map?;
1401
+ final route = operation?['x-nomos'] as Map?;
1402
+ final domain = route?['domain']?.toString() ?? '';
1403
+ final directive = route?['directiveId']?.toString() ?? '';
1404
+ if (domain.isNotEmpty && directive.isNotEmpty) {
1405
+ frameworkPostRoutes.add((
1406
+ domain: domain,
1407
+ directive: directive,
1408
+ path: entry.key.toString(),
1409
+ ));
1415
1410
  }
1416
1411
  }
1417
1412
  }
@@ -1424,7 +1419,7 @@ class NomosTestHarness {
1424
1419
  }
1425
1420
  if (lawHashes.isEmpty) {
1426
1421
  throw NomosTestHarnessException(
1427
- 'cloudBound: no deploy.json carried a summary.domainHash — cannot run the birth ceremony (lawHash unknown)',
1422
+ 'cloudBound: the born test platform reported no installed application law — cannot run the birth ceremony',
1428
1423
  );
1429
1424
  }
1430
1425
  final birthRoutes = frameworkPostRoutes
@@ -1440,7 +1435,6 @@ class NomosTestHarness {
1440
1435
  );
1441
1436
  }
1442
1437
  final applicationHomeDomain = birthRoutes.single.domain;
1443
- final birthReceivingGroupPath = birthRoutes.single.path;
1444
1438
  // A composed application has two distinct laws at this seam:
1445
1439
  // * the parent's CURRENT control law authors enrollFirstDevice + the birth offer;
1446
1440
  // * the application's final PAYLOAD package is the law installed into the born receiving group.
@@ -1462,7 +1456,7 @@ class NomosTestHarness {
1462
1456
  }
1463
1457
  if (!lawHashes.contains(controlHomeLawHash)) {
1464
1458
  throw NomosTestHarnessException(
1465
- "cloudBound: current application/home control law '$controlHomeLawHash' was not among the packages this scenario deployed",
1459
+ "cloudBound: current application/home control law '$controlHomeLawHash' was not among the platform's installed packages",
1466
1460
  );
1467
1461
  }
1468
1462
  final applicationHomeLawHash = lawHashes.last;
@@ -1479,7 +1473,11 @@ class NomosTestHarness {
1479
1473
  "cloudBound: application/home domain '$applicationHomeDomain' must expose exactly one semantic enrollFirstDevice route; found ${enrollmentRoutes.length}",
1480
1474
  );
1481
1475
  }
1482
- final enrollFirstDevicePath = enrollmentRoutes.single.path;
1476
+ if (enrollmentRoutes.single.path.isEmpty) {
1477
+ throw NomosTestHarnessException(
1478
+ 'cloudBound: enrollFirstDevice generated an empty relay path',
1479
+ );
1480
+ }
1483
1481
  // 2/6 — the LOCAL engine plane, CLOUD-BOUND, spawned BEFORE the birth: the runner's local kernel is
1484
1482
  // what mints the device key + signs the birth cert (era-0: a birth is ATTESTED or not at all).
1485
1483
  // The executable is device custody too. A process restart while offline must not fetch the kernel
@@ -1586,7 +1584,7 @@ class NomosTestHarness {
1586
1584
  final issuer = (minted['issuer'] ?? '').toString();
1587
1585
  if (idpToken.isEmpty || issuer.isEmpty) {
1588
1586
  throw NomosTestHarnessException(
1589
- 'cloudBound: /dev/mint-token returned no token — is the test cloud started with platform:/deployJsonPath: and a law composing setAuthProvider?',
1587
+ 'cloudBound: /dev/mint-token returned no token — is the test cloud started with platform:/lawUsdzPath: and a law composing setAuthProvider?',
1590
1588
  );
1591
1589
  }
1592
1590
  // THE KEYLESS ENROLL-ONCE DELEGATION (links[1] of the home cert chain) — device-self-signed,
@@ -1605,20 +1603,44 @@ class NomosTestHarness {
1605
1603
  'capturedClock': DateTime.now().toUtc().millisecondsSinceEpoch,
1606
1604
  'epoch': 0,
1607
1605
  }, secret);
1608
- await post(
1609
- '/v2/workspaces/$platformWorkspace$enrollFirstDevicePath',
1610
- {
1606
+ // Mount the target workspace in the SAME kernel plane and author through its own
1607
+ // installed law. The HTTP host is neither given raw business fields nor a signing key;
1608
+ // sync carries the already-sealed offer and the receiving kernel replays the one gate.
1609
+ // The enroll route was still resolved above to prove the generated public surface
1610
+ // names this semantic route exactly once, but route strings grant no authority here.
1611
+ final platformAuthorSession = '$clientId-platform-author';
1612
+ await plane.connect(
1613
+ cloud: cloud.origin,
1614
+ workspace: platformWorkspace,
1615
+ session: platformAuthorSession,
1616
+ clientId: '$clientId-platform-author',
1617
+ authToken: idpToken,
1618
+ authorSecret: secret,
1619
+ );
1620
+ await plane.offerDirective(
1621
+ domain: applicationHomeDomain,
1622
+ directiveId: 'enrollFirstDevice',
1623
+ domainHash: controlHomeLawHash,
1624
+ actor: subject,
1625
+ authorSecret: secret,
1626
+ session: platformAuthorSession,
1627
+ payload: {
1611
1628
  'keyHash': keyHash,
1612
1629
  'verifiedVia': issuer,
1613
1630
  'validFromEpoch': 0,
1614
1631
  'grantedAt': DateTime.now().toUtc().toIso8601String(),
1615
1632
  'idpToken': idpToken,
1616
- 'actor': subject,
1617
- 'authorSecret': secret,
1618
1633
  'delegationCert': jsonEncode(delegationCert),
1619
1634
  },
1620
- headers: {'x-nomos-auth': idpToken},
1621
1635
  );
1636
+ final enrollmentSync = await plane.sync(
1637
+ session: platformAuthorSession,
1638
+ );
1639
+ if (enrollmentSync['ok'] == false) {
1640
+ throw NomosTestHarnessException(
1641
+ 'cloudBound: sealed enrollFirstDevice offer did not converge: $enrollmentSync',
1642
+ );
1643
+ }
1622
1644
  return (
1623
1645
  publicB64: publicB64,
1624
1646
  secret: secret,
@@ -1627,21 +1649,26 @@ class NomosTestHarness {
1627
1649
  );
1628
1650
  },
1629
1651
  );
1630
- // 5/6 — THE HOME CEREMONY, remotely: the platform's own gateway offer births the home (the ONE
1631
- // control-plane contact; the app lane below never touches the platform again). The offer carries
1632
- // the device-signed homeCert + the owner's device keyHash (the home's genesis ENROLS it, so the
1633
- // born home is SIGNER-READY — it can birth its own estates/catalogues from intent 0).
1652
+ // 5/6 — THE HOME CEREMONY. Author the birth in the already-mounted PARENT kernel, then sync its sealed
1653
+ // intent to the remote parent where the same offer gate replays it. The HTTP host never receives
1654
+ // business fields or a signing secret and has no birth-authoring lane. The offer carries the
1655
+ // device-signed homeCert + the owner's device keyHash (the home's genesis ENROLS it, so the born
1656
+ // home is SIGNER-READY — it can birth its own estates/catalogues from intent 0).
1634
1657
  String? home;
1635
1658
  await _phased(
1636
1659
  phase: '5/6: remote home-birth ceremony (attested)',
1637
1660
  workspace: platformWorkspace,
1638
- awaiting:
1639
- 'POST /v2/workspaces/$platformWorkspace$birthReceivingGroupPath',
1661
+ awaiting: 'parent-kernel offer → sealed sync replay',
1640
1662
  timeout: phaseTimeout ?? _kInstallDeadline,
1641
1663
  onDiagnostic: onDiagnostic,
1642
1664
  run: () async {
1643
1665
  final homeName = _receivingGroupNameOf(subject);
1644
- final homeCert = await plane.signBirthCert({
1666
+ // The plane deliberately still has both the empty onboarding scratch holon and the
1667
+ // authenticated platform holon mounted. Bind this parent certificate operation to the
1668
+ // platform session explicitly; relying on the single-session convenience API becomes
1669
+ // ambiguous as soon as the ceremony correctly opens the target workspace in-kernel.
1670
+ final homeCert =
1671
+ await plane.session('$clientId-platform-author').signBirthCert({
1645
1672
  'child': homeName,
1646
1673
  'founder': subject,
1647
1674
  // The cert's `verified` must MATCH the gate verdict (CAS-as-law): the offer carries the
@@ -1651,33 +1678,54 @@ class NomosTestHarness {
1651
1678
  'parentId': platformWorkspace,
1652
1679
  'epoch': 0,
1653
1680
  }, device.secret);
1654
- final out = await post(
1655
- '/v2/workspaces/$platformWorkspace$birthReceivingGroupPath',
1656
- {
1681
+ final localBirth =
1682
+ await plane.session('$clientId-platform-author').offerOutcome(
1683
+ domain: applicationHomeDomain,
1684
+ directiveId: 'nomosInternalBirthReceivingGroup7c1f5e',
1685
+ actor: subject,
1686
+ authorSecret: device.secret,
1687
+ payload: <String, Object?>{
1657
1688
  'owner': subject,
1658
1689
  'ownerKeyHash': device.keyHash,
1659
1690
  'frameworkHash': frameworkHash,
1660
1691
  'lawHash': applicationHomeLawHash,
1661
1692
  'bornAt': DateTime.now().toUtc().toIso8601String(),
1662
1693
  'homeCert': jsonEncode(homeCert),
1663
- 'actor': subject,
1664
- 'authorSecret': device.secret,
1665
1694
  },
1666
- headers: {'x-nomos-auth': device.idpToken},
1667
1695
  );
1668
- final born = (out['born'] as List?) ?? const [];
1669
- final bornNames = born
1670
- .map((b) => (b as Map)['workspace']?.toString())
1671
- .whereType<String>()
1672
- .where((w) => w.isNotEmpty)
1673
- .toList();
1674
- home = bornNames.isEmpty ? null : bornNames.first;
1675
- if (home == null) {
1676
- final outcomes = jsonEncode(out['birthOutcomes'] ?? const []);
1696
+ if (!localBirth.born.contains(homeName)) {
1697
+ throw NomosTestHarnessException(
1698
+ 'cloudBound: the local parent kernel admitted the receiving-group offer but did not birth '
1699
+ '"$homeName" (born=${localBirth.born}, birthOutcomes=${localBirth.birthOutcomes})',
1700
+ );
1701
+ }
1702
+ final birthSync = await plane.sync(
1703
+ session: '$clientId-platform-author',
1704
+ );
1705
+ if (birthSync['pushFailed'] == true ||
1706
+ birthSync['diverged'] == true ||
1707
+ birthSync['stillAhead'] == true) {
1708
+ throw NomosTestHarnessException(
1709
+ 'cloudBound: the sealed receiving-group offer did not converge: $birthSync',
1710
+ );
1711
+ }
1712
+ final remoteOutcomes =
1713
+ ((birthSync['birthOutcomes'] as List?) ?? const [])
1714
+ .whereType<Map>()
1715
+ .map((entry) => entry.cast<String, Object?>())
1716
+ .toList();
1717
+ final failedRemoteBirth = remoteOutcomes.where(
1718
+ (entry) =>
1719
+ entry['workspace']?.toString() == homeName &&
1720
+ entry['status']?.toString() != 'born',
1721
+ );
1722
+ if (failedRemoteBirth.isNotEmpty) {
1677
1723
  throw NomosTestHarnessException(
1678
- 'cloudBound: birthHome admitted but spawned no home (birthOutcomes=$outcomes)',
1724
+ 'cloudBound: the remote parent replay refused the receiving-group birth: '
1725
+ '${jsonEncode(failedRemoteBirth.toList())}',
1679
1726
  );
1680
1727
  }
1728
+ home = homeName;
1681
1729
  },
1682
1730
  );
1683
1731
  // 6/6 — mount the born home as a normal cloud-bound session (clone from the test cloud).
@@ -1770,8 +1818,7 @@ class NomosTestHarness {
1770
1818
  // Every LIVE session on the plane (not the harness's construction-time list — a locally-born child
1771
1819
  // acquired after construction must converge too), PARENT-FIRST: the home syncs before its children, so
1772
1820
  // its birth offer-effect creates each child's remote custody BEFORE that child pushes its own writes.
1773
- final live =
1774
- bridge.connectedSessions.where((w) => w != _decoyWorkspace).toList();
1821
+ final live = _applicationConnectedSessions();
1775
1822
  final hw = homeWorkspace;
1776
1823
  if (hw != null && live.remove(hw)) live.insert(0, hw); // the parent leads
1777
1824
  final targets = workspaces ?? live;
@@ -1821,6 +1868,23 @@ class NomosTestHarness {
1821
1868
  return b is NomosSession ? b.parent : b;
1822
1869
  }
1823
1870
 
1871
+ /// Live sessions which belong to the simulated application. The cloud-bound constructor temporarily
1872
+ /// mounts a scratch holon and the application parent to author the receiving-group ceremony. Those are
1873
+ /// deliberately on the same kernel plane, but they are not application workspaces to persist, converge,
1874
+ /// restart or reinstall. In particular, a session label is not necessarily its workspace address:
1875
+ /// replaying `test-runner-platform-author` as an address silently points at a workspace that never existed.
1876
+ List<String> _applicationConnectedSessions() {
1877
+ final lane = _cloudRestartLane;
1878
+ final ceremonySessions = <String>{
1879
+ _decoyWorkspace,
1880
+ if (lane != null) '${lane.clientId}-onboard-scratch',
1881
+ if (lane != null) '${lane.clientId}-platform-author',
1882
+ };
1883
+ return _plane.connectedSessions
1884
+ .where((session) => !ceremonySessions.contains(session))
1885
+ .toList();
1886
+ }
1887
+
1824
1888
  /// RESET a workspace's LOCAL custody to the snapshot it was seeded from — the "stale/corrupt
1825
1889
  /// local custody" repair move in tests. Reconnects the session in place (watches on the old
1826
1890
  /// holon are stopped by the runner's re-connect semantics).
@@ -1848,22 +1912,22 @@ class NomosTestHarness {
1848
1912
  );
1849
1913
  }
1850
1914
 
1851
- /// SEED a NEW workspace on this plane from `build/<name>.deploy.json` law packages — the
1915
+ /// SEED a NEW workspace on this plane from `build/<name>.law.usdz` law packages — the
1852
1916
  /// mid-test twin of [cluster]'s per-workspace install: runs the SAME offline install lane,
1853
1917
  /// then connects the workspace as its own session. Returns its [NomosSession].
1854
- Future<NomosSession> seedWorkspaceFromDeployJson({
1918
+ Future<NomosSession> seedWorkspaceFromLawUsdz({
1855
1919
  required String workspace,
1856
- required List<String> deployJsonPaths,
1920
+ required List<String> lawUsdzPaths,
1857
1921
  }) async {
1858
1922
  final lane = _installLane;
1859
1923
  if (lane == null) {
1860
1924
  throw NomosTestHarnessException(
1861
- 'seedWorkspaceFromDeployJson: this harness holds no install-lane config',
1925
+ 'seedWorkspaceFromLawUsdz: this harness holds no install-lane config',
1862
1926
  );
1863
1927
  }
1864
- if (deployJsonPaths.isEmpty) {
1928
+ if (lawUsdzPaths.isEmpty) {
1865
1929
  throw NomosTestHarnessException(
1866
- 'seedWorkspaceFromDeployJson("$workspace"): pass the build/<name>.deploy.json paths',
1930
+ 'seedWorkspaceFromLawUsdz("$workspace"): pass the build/<name>.law.usdz paths',
1867
1931
  );
1868
1932
  }
1869
1933
  final snap = '${_workdir.path}/$workspace.bin';
@@ -1880,7 +1944,7 @@ class NomosTestHarness {
1880
1944
  useInRepoScript: lane.useInRepoScript,
1881
1945
  inRepoScript: lane.inRepoScript,
1882
1946
  githolonCmd: lane.githolonCmd,
1883
- deployJsonPaths: deployJsonPaths,
1947
+ lawUsdzPaths: lawUsdzPaths,
1884
1948
  workspace: workspace,
1885
1949
  snapshot: snap,
1886
1950
  runtimeDir: lane.runtimeDir,
@@ -1973,9 +2037,7 @@ class NomosTestHarness {
1973
2037
  /// carried per entry). The plane-level twin of [snapshotWorkspace] — migrate a whole staged AppSim plane.
1974
2038
  Future<PlaneSnapshot> snapshotPlane() async {
1975
2039
  final out = <String, WorkspaceSnapshot>{};
1976
- final connected = _plane.connectedSessions.where(
1977
- (ws) => ws != _decoyWorkspace && !ws.endsWith('-onboard-scratch'),
1978
- );
2040
+ final connected = _applicationConnectedSessions();
1979
2041
  for (final ws in connected) {
1980
2042
  out[ws] = await snapshotWorkspace(ws);
1981
2043
  }
@@ -2037,11 +2099,7 @@ class NomosTestHarness {
2037
2099
  }
2038
2100
 
2039
2101
  final oldPlane = _plane;
2040
- final targets = oldPlane.connectedSessions
2041
- .where(
2042
- (ws) => ws != _decoyWorkspace && !ws.endsWith('-onboard-scratch'),
2043
- )
2044
- .toList();
2102
+ final targets = _applicationConnectedSessions();
2045
2103
  if (targets.remove(home)) targets.insert(0, home);
2046
2104
  final snapshots = <String, String>{};
2047
2105
  for (final ws in targets) {
@@ -2139,11 +2197,7 @@ class NomosTestHarness {
2139
2197
  }
2140
2198
 
2141
2199
  final oldPlane = _plane;
2142
- final targets = oldPlane.connectedSessions
2143
- .where(
2144
- (ws) => ws != _decoyWorkspace && !ws.endsWith('-onboard-scratch'),
2145
- )
2146
- .toList();
2200
+ final targets = _applicationConnectedSessions();
2147
2201
  if (targets.remove(home)) targets.insert(0, home);
2148
2202
  for (final ws in targets) {
2149
2203
  await oldPlane.session(ws).stopAutoSync();
@@ -2249,17 +2303,17 @@ class NomosTestHarness {
2249
2303
  required bool useInRepoScript,
2250
2304
  required String inRepoScript,
2251
2305
  required String githolonCmd,
2252
- required List<String> deployJsonPaths,
2306
+ required List<String> lawUsdzPaths,
2253
2307
  required String workspace,
2254
2308
  required String snapshot,
2255
2309
  String? runtimeDir,
2256
2310
  void Function(String line)? onDiagnostic,
2257
2311
  }) async {
2258
- // Both lanes take the SAME flags (--deploy/--workspace/--out/--runtime) and print the SAME one-line JSON
2312
+ // Both lanes take the SAME flags (--law/--workspace/--out/--runtime) and print the SAME one-line JSON
2259
2313
  // contract; only the leading command differs (node <script> vs `<githolon...> harness-install`).
2260
2314
  final common = <String>[
2261
- '--deploy',
2262
- deployJsonPaths.join(','),
2315
+ '--law',
2316
+ lawUsdzPaths.join(','),
2263
2317
  '--workspace',
2264
2318
  workspace,
2265
2319
  '--out',