@githolon/dsl 0.100.6 → 0.101.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dart/CHANGELOG.md +6 -0
  2. package/dart/lib/src/dev_cloud.dart +46 -15
  3. package/dart/lib/src/test_harness.dart +78 -16
  4. package/dart/lib/src/webview_bridge.dart +640 -126
  5. package/dart/pubspec.lock +1 -1
  6. package/dart/pubspec.yaml +2 -2
  7. package/dart/test/bundle_version_pairing_test.dart +11 -22
  8. package/dart/test/co2_product_gauntlet_test.dart +228 -0
  9. package/dart/test/errors_test.dart +63 -12
  10. package/dart/test/first_app_gauntlet_test.dart +29 -3
  11. package/dart/test/webview_bridge_test.dart +274 -0
  12. package/dart_types/CHANGELOG.md +6 -0
  13. package/dart_types/lib/src/errors.dart +136 -30
  14. package/dart_types/lib/src/surfaced.dart +48 -2
  15. package/dart_types/lib/src/wire.dart +3 -1
  16. package/dart_types/pubspec.yaml +1 -1
  17. package/dart_types/test/surfaced_test.dart +19 -0
  18. package/package.json +1 -1
  19. package/src/activity.ts +43 -4
  20. package/src/aggregate.ts +132 -22
  21. package/src/attested_core.ts +1 -1
  22. package/src/authoring.ts +119 -6
  23. package/src/birth_meter.ts +4 -4
  24. package/src/birth_meter_ensure.ts +9 -1
  25. package/src/births.ts +53 -22
  26. package/src/build_package.ts +55 -14
  27. package/src/business_birth.ts +48 -7
  28. package/src/codegen_dart.ts +267 -37
  29. package/src/codegen_dart_client.ts +921 -82
  30. package/src/codegen_openapi.ts +28 -5
  31. package/src/codegen_proof.ts +107 -11
  32. package/src/codegen_ts.ts +961 -135
  33. package/src/compile_package_main.ts +155 -45
  34. package/src/conflicts.ts +252 -0
  35. package/src/describe_model.ts +448 -0
  36. package/src/directive.ts +5 -38
  37. package/src/drivers.ts +12 -4
  38. package/src/engine_entry.ts +4 -0
  39. package/src/evolve_preview.ts +72 -11
  40. package/src/fields.ts +201 -55
  41. package/src/framework/application_home.ts +103 -0
  42. package/src/framework/application_home_domain.ts +2 -0
  43. package/src/framework/attested_receipts.ts +2 -2
  44. package/src/framework/auth_provider_law.ts +51 -24
  45. package/src/framework/business_corrections.ts +170 -0
  46. package/src/framework/disclosure.ts +1 -1
  47. package/src/framework/domain_lifecycle.ts +250 -16
  48. package/src/framework/encryption_identity.ts +1 -1
  49. package/src/framework/encryption_scope.ts +21 -13
  50. package/src/framework/era_certification.ts +1 -1
  51. package/src/framework/framework_aggregates.ts +2 -0
  52. package/src/framework/genesis.ts +1 -1
  53. package/src/framework/home.ts +22 -9
  54. package/src/framework/identity.ts +5 -5
  55. package/src/framework/impure_capability.ts +2 -2
  56. package/src/framework/owned_custody.ts +561 -0
  57. package/src/framework/platform_admin.ts +1 -1
  58. package/src/framework/relations.ts +9 -1
  59. package/src/framework/repair.ts +1 -1
  60. package/src/framework/signing_identity.ts +2 -2
  61. package/src/framework/source_roster.ts +1 -1
  62. package/src/framework/workspaces.ts +17 -10
  63. package/src/generated_client_names.ts +39 -0
  64. package/src/index.ts +20 -15
  65. package/src/kernel_capabilities.ts +25 -1
  66. package/src/law_check.ts +4 -1
  67. package/src/manifest.ts +356 -63
  68. package/src/read_projection_manifest.ts +8 -0
  69. package/src/read_projection_namespace.ts +4 -0
  70. package/src/relation.ts +1 -1
  71. package/src/shape_compat.ts +47 -2
  72. package/src/stable_ids.ts +38 -6
  73. package/src/stable_ids_types.ts +30 -0
  74. package/src/surfaced_lint.ts +0 -0
  75. package/src/usd.ts +62 -11
  76. package/src/usd_layers.ts +54 -3
  77. package/src/value_object.ts +23 -19
  78. package/src/wire.ts +1 -0
  79. package/src/wire_encode.ts +48 -4
  80. package/src/workspace_routing.ts +57 -0
  81. package/src/workspace_sharding.ts +9 -9
  82. package/src/certified_read.ts +0 -313
package/dart/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.63.0
2
+
3
+ - Generated clients now expose business-first conflict policy, schema-aware merge continuation and recursive
4
+ organisation descriptions. The client privately owns lifecycle discovery, parent/child traversal, signer
5
+ custody and replay; applications follow typed business references rather than framework routes.
6
+
1
7
  ## 0.62.2
2
8
 
3
9
  - Make `NomosTestHarness` own a complete, verifiable local trust lineage. Tests still bind a business actor
@@ -49,10 +49,12 @@ class NomosTestCloud {
49
49
  final Uri _upstream; // the real dev-cloud origin the proxy forwards to
50
50
  final HttpClient _client;
51
51
  final void Function(String line)? _onDiagnostic;
52
+ final String? testKRootPublic;
52
53
  bool _online = true;
53
54
  bool _stopped = false;
54
55
 
55
- NomosTestCloud._(this._server, this._proxy, this._upstream, this._client, this._onDiagnostic);
56
+ NomosTestCloud._(this._server, this._proxy, this._upstream, this._client,
57
+ this._onDiagnostic, this.testKRootPublic);
56
58
 
57
59
  /// The base URL a cloud-BOUND session connects to (`connect(cloud: cloud.origin)`). Points at the PARTITION
58
60
  /// PROXY, so [setOnline] deterministically severs/heals the whole plane's link to custody.
@@ -63,7 +65,8 @@ class NomosTestCloud {
63
65
  /// cloud and peers converge. Deterministic: no timing races, the test decides exactly when the link exists.
64
66
  void setOnline(bool online) {
65
67
  _online = online;
66
- _log('partition: ${online ? "ONLINE (link up)" : "OFFLINE (link severed)"}');
68
+ _log(
69
+ 'partition: ${online ? "ONLINE (link up)" : "OFFLINE (link severed)"}');
67
70
  }
68
71
 
69
72
  bool get online => _online;
@@ -72,8 +75,10 @@ class NomosTestCloud {
72
75
  /// live cloud state DIRECTLY (bypassing the partition proxy) — a test assertion about custody must see the
73
76
  /// truth even while the app's link is partitioned OFFLINE.
74
77
  Future<RemoteWorkspaceState> remoteHead(String workspace) async {
75
- final body = await _getJson(_upstream.replace(path: '/v2/workspaces/$workspace'));
76
- final ledger = (body['ledgerState'] as Map?)?.cast<String, Object?>() ?? const {};
78
+ final body =
79
+ await _getJson(_upstream.replace(path: '/v2/workspaces/$workspace'));
80
+ final ledger =
81
+ (body['ledgerState'] as Map?)?.cast<String, Object?>() ?? const {};
77
82
  return RemoteWorkspaceState(
78
83
  workspace: workspace,
79
84
  initialized: body['initialized'] == true,
@@ -109,7 +114,8 @@ class NomosTestCloud {
109
114
  final chosenPort = port == 0 ? await _freePort() : port;
110
115
  final args = <String>[
111
116
  serverEntry,
112
- '--port', '$chosenPort',
117
+ '--port',
118
+ '$chosenPort',
113
119
  if (ephemeral) '--ephemeral',
114
120
  if (dataDir != null) ...['--data', dataDir],
115
121
  if (platform != null) ...['--platform', platform],
@@ -118,8 +124,14 @@ class NomosTestCloud {
118
124
  void log(String m) => onDiagnostic?.call('[nomos-test-cloud] $m');
119
125
  log('spawning: $node ${args.join(' ')}');
120
126
  final proc = await Process.start(node, args, mode: ProcessStartMode.normal);
121
- proc.stdout.transform(utf8.decoder).transform(const LineSplitter()).listen((l) => log('server: $l'));
122
- proc.stderr.transform(utf8.decoder).transform(const LineSplitter()).listen((l) => log('server!: $l'));
127
+ proc.stdout
128
+ .transform(utf8.decoder)
129
+ .transform(const LineSplitter())
130
+ .listen((l) => log('server: $l'));
131
+ proc.stderr
132
+ .transform(utf8.decoder)
133
+ .transform(const LineSplitter())
134
+ .listen((l) => log('server!: $l'));
123
135
 
124
136
  final upstream = Uri.parse('http://127.0.0.1:$chosenPort');
125
137
  final client = HttpClient();
@@ -128,6 +140,7 @@ class NomosTestCloud {
128
140
  // half-born platform's typed refusals.
129
141
  final deadline = DateTime.now().add(readyTimeout);
130
142
  var ready = false;
143
+ String? testKRootPublic;
131
144
  while (DateTime.now().isBefore(deadline)) {
132
145
  try {
133
146
  final req = await client.getUrl(upstream.replace(path: '/health'));
@@ -136,7 +149,11 @@ class NomosTestCloud {
136
149
  if (resp.statusCode == 200) {
137
150
  final health = jsonDecode(body) as Map<String, dynamic>;
138
151
  final platformReady = health['platformReady'];
139
- if (platform == null || platformReady == true) { ready = true; break; }
152
+ if (platform == null || platformReady == true) {
153
+ testKRootPublic = health['testKRootPublic']?.toString();
154
+ ready = true;
155
+ break;
156
+ }
140
157
  }
141
158
  } catch (_) {/* not up yet */}
142
159
  await Future<void>.delayed(const Duration(milliseconds: 200));
@@ -150,7 +167,8 @@ class NomosTestCloud {
150
167
 
151
168
  // The partition proxy — forwards to the dev cloud when ONLINE, fails fast when OFFLINE.
152
169
  final proxy = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
153
- final cloud = NomosTestCloud._(proc, proxy, upstream, client, onDiagnostic);
170
+ final cloud = NomosTestCloud._(
171
+ proc, proxy, upstream, client, onDiagnostic, testKRootPublic);
154
172
  proxy.listen(cloud._handleProxy);
155
173
  log('partition proxy at ${cloud.origin} → $upstream');
156
174
  return cloud;
@@ -168,16 +186,26 @@ class NomosTestCloud {
168
186
  return;
169
187
  }
170
188
  try {
171
- final target = _upstream.replace(path: req.uri.path, query: req.uri.query.isEmpty ? null : req.uri.query);
189
+ final target = _upstream.replace(
190
+ path: req.uri.path,
191
+ query: req.uri.query.isEmpty ? null : req.uri.query);
172
192
  final fwd = await _client.openUrl(req.method, target);
173
- req.headers.forEach((name, values) { if (name.toLowerCase() != 'host') for (final v in values) fwd.headers.add(name, v); });
193
+ req.headers.forEach((name, values) {
194
+ if (name.toLowerCase() != 'host')
195
+ for (final v in values) fwd.headers.add(name, v);
196
+ });
174
197
  await fwd.addStream(req); // forward the request body
175
198
  final resp = await fwd.close();
176
199
  req.response.statusCode = resp.statusCode;
177
- resp.headers.forEach((name, values) { for (final v in values) req.response.headers.add(name, v); });
200
+ resp.headers.forEach((name, values) {
201
+ for (final v in values) req.response.headers.add(name, v);
202
+ });
178
203
  await resp.pipe(req.response);
179
204
  } catch (e) {
180
- try { req.response.statusCode = 502; await req.response.close(); } catch (_) {}
205
+ try {
206
+ req.response.statusCode = 502;
207
+ await req.response.close();
208
+ } catch (_) {}
181
209
  }
182
210
  }
183
211
 
@@ -186,7 +214,8 @@ class NomosTestCloud {
186
214
  final resp = await req.close();
187
215
  final text = await resp.transform(utf8.decoder).join();
188
216
  if (resp.statusCode != 200) {
189
- throw NomosTestCloudException('GET $url → ${resp.statusCode}: ${text.substring(0, text.length.clamp(0, 200))}');
217
+ throw NomosTestCloudException(
218
+ 'GET $url → ${resp.statusCode}: ${text.substring(0, text.length.clamp(0, 200))}');
190
219
  }
191
220
  return (jsonDecode(text) as Map).cast<String, Object?>();
192
221
  }
@@ -198,7 +227,9 @@ class NomosTestCloud {
198
227
  if (_stopped) return;
199
228
  _stopped = true;
200
229
  _log('stopping');
201
- try { await _proxy.close(force: true); } catch (_) {}
230
+ try {
231
+ await _proxy.close(force: true);
232
+ } catch (_) {}
202
233
  _server.kill(ProcessSignal.sigterm);
203
234
  // Give it a moment, then SIGKILL if still alive.
204
235
  try {
@@ -261,7 +261,7 @@ class NomosTestHarness {
261
261
  /// `packages.json`. The harness owns a throwaway test root; app code never handles its key material.
262
262
  /// [nodeEntry] — path to `node-entry.ts`; when omitted it is resolved from [resolveFrom] via
263
263
  /// `require.resolve('@githolon/client/runner/node-entry.ts')`.
264
- /// [installHelper] — the offline law-install lane. An EXISTING `.mjs` file is run as a node script (the
264
+ /// [installHelper] — the offline law-install lane. An EXISTING `.ts` or `.mjs` file is run as a node script (the
265
265
  /// in-repo `offline-install.ts`, the default when it exists beside [nodeEntry]); anything else is a
266
266
  /// githolon COMMAND LINE whose `harness-install` subcommand is driven (the default `npx githolon` when the
267
267
  /// in-repo helper is absent — a published tenant needs nothing but `githolon`). E.g. force the subcommand
@@ -293,16 +293,20 @@ class NomosTestHarness {
293
293
  final entry = nodeEntry ??
294
294
  await _resolveEntry(node, resolveFrom ?? Directory.current.path);
295
295
  // THE INSTALL LANE. [installHelper] is the one seam (PACKAGING NOTE above): either the in-repo
296
- // `offline-install.ts` node script, OR the published `githolon harness-install` subcommand (the CLI
296
+ // `offline-install.mjs` node script, OR the published `githolon harness-install` subcommand (the CLI
297
297
  // bundles the engine + this exact install sequence, so a published tenant needs nothing but `githolon`).
298
- // • an EXISTING `.mjs` file ⇒ run it as a node script (the in-repo default when present);
298
+ // • an EXISTING `.ts`/`.mjs` file ⇒ run it as a node script (the in-repo default when present);
299
299
  // • anything else ⇒ treat it as a githolon COMMAND LINE and drive `harness-install`
300
300
  // (default `npx githolon` when the in-repo helper is absent; the test forces this lane by passing
301
301
  // e.g. `installHelper: 'node /abs/cli/dist/cli.mjs'`).
302
- final resolvedHelper =
303
- installHelper ?? '${File(entry).parent.path}/offline-install.ts';
302
+ final adjacentInstaller = '${File(entry).parent.path}/offline-install.mjs';
303
+ final resolvedHelper = installHelper ??
304
+ (File(adjacentInstaller).existsSync()
305
+ ? adjacentInstaller
306
+ : '${File(entry).parent.path}/offline-install.ts');
304
307
  final useInRepoScript =
305
- resolvedHelper.endsWith('.mjs') && File(resolvedHelper).existsSync();
308
+ (resolvedHelper.endsWith('.ts') || resolvedHelper.endsWith('.mjs')) &&
309
+ File(resolvedHelper).existsSync();
306
310
  final githolonCmd = installHelper == null ? 'npx githolon' : resolvedHelper;
307
311
 
308
312
  // A FRESH temp dir per harness — the snapshot + any transient runner state live here and nowhere else,
@@ -503,10 +507,14 @@ class NomosTestHarness {
503
507
  }
504
508
  final entry = nodeEntry ??
505
509
  await _resolveEntry(node, resolveFrom ?? Directory.current.path);
506
- final resolvedHelper =
507
- installHelper ?? '${File(entry).parent.path}/offline-install.ts';
510
+ final adjacentInstaller = '${File(entry).parent.path}/offline-install.mjs';
511
+ final resolvedHelper = installHelper ??
512
+ (File(adjacentInstaller).existsSync()
513
+ ? adjacentInstaller
514
+ : '${File(entry).parent.path}/offline-install.ts');
508
515
  final useInRepoScript =
509
- resolvedHelper.endsWith('.mjs') && File(resolvedHelper).existsSync();
516
+ (resolvedHelper.endsWith('.ts') || resolvedHelper.endsWith('.mjs')) &&
517
+ File(resolvedHelper).existsSync();
510
518
  final githolonCmd = installHelper == null ? 'npx githolon' : resolvedHelper;
511
519
 
512
520
  final workdir = await Directory.systemTemp.createTemp('nomos-cluster-');
@@ -780,6 +788,8 @@ class NomosTestHarness {
780
788
  // server's in-process operator lane). Booting a born workspace is idempotent; an unborn one
781
789
  // refuses 409, surfaced here with the fix.
782
790
  String? frameworkHash;
791
+ final frameworkPostRoutes =
792
+ <({String domain, String directive, String path})>[];
783
793
  final lawHashes = <String>[];
784
794
  await _phased(
785
795
  phase: '1/6: boot the born platform + deploy law on the test cloud',
@@ -806,9 +816,32 @@ class NomosTestHarness {
806
816
  for (final p in deployJsonPaths) {
807
817
  final body = await File(p).readAsString();
808
818
  await post('/v2/workspaces/$platformWorkspace/domains', body);
809
- final summary = ((jsonDecode(body) as Map)['summary'] as Map?);
819
+ final deploy = (jsonDecode(body) as Map);
820
+ final summary = (deploy['summary'] as Map?);
810
821
  final h = summary?['domainHash']?.toString();
811
822
  if (h != null && h.isNotEmpty) lawHashes.add(h);
823
+ final openapi = deploy['openapi'] as Map?;
824
+ final pathSets = <Map>[
825
+ (openapi?['paths'] as Map?) ?? const {},
826
+ (((openapi?['x-nomos-framework-routes'] as Map?)?['paths'])
827
+ as Map?) ??
828
+ const {},
829
+ ];
830
+ for (final paths in pathSets) {
831
+ for (final entry in paths.entries) {
832
+ final post = (entry.value as Map?)?['post'] as Map?;
833
+ final route = post?['x-nomos'] as Map?;
834
+ final domain = route?['domain']?.toString() ?? '';
835
+ final directive = route?['directiveId']?.toString() ?? '';
836
+ if (domain.isNotEmpty && directive.isNotEmpty) {
837
+ frameworkPostRoutes.add((
838
+ domain: domain,
839
+ directive: directive,
840
+ path: entry.key.toString(),
841
+ ));
842
+ }
843
+ }
844
+ }
812
845
  }
813
846
  });
814
847
  if (frameworkHash == null || frameworkHash!.isEmpty) {
@@ -819,6 +852,27 @@ class NomosTestHarness {
819
852
  throw NomosTestHarnessException(
820
853
  'cloudBound: no deploy.json carried a summary.domainHash — cannot run the birth ceremony (lawHash unknown)');
821
854
  }
855
+ final birthRoutes = frameworkPostRoutes
856
+ .where((route) => route.directive == 'birthHome')
857
+ .toSet()
858
+ .toList();
859
+ if (birthRoutes.length != 1) {
860
+ throw NomosTestHarnessException(
861
+ 'cloudBound: the deployed package must expose exactly one semantic birthHome route; found ${birthRoutes.length}');
862
+ }
863
+ final applicationHomeDomain = birthRoutes.single.domain;
864
+ final birthHomePath = birthRoutes.single.path;
865
+ final enrollmentRoutes = frameworkPostRoutes
866
+ .where((route) =>
867
+ route.directive == 'enrollFirstDevice' &&
868
+ route.domain == applicationHomeDomain)
869
+ .toSet()
870
+ .toList();
871
+ if (enrollmentRoutes.length != 1) {
872
+ throw NomosTestHarnessException(
873
+ "cloudBound: application/home domain '$applicationHomeDomain' must expose exactly one semantic enrollFirstDevice route; found ${enrollmentRoutes.length}");
874
+ }
875
+ final enrollFirstDevicePath = enrollmentRoutes.single.path;
822
876
  // 2/6 — the LOCAL engine plane, CLOUD-BOUND, spawned BEFORE the birth: the runner's local kernel is
823
877
  // what mints the device key + signs the birth cert (era-0: a birth is ATTESTED or not at all).
824
878
  transport = await _phased(
@@ -830,6 +884,12 @@ class NomosTestHarness {
830
884
  entry: entry,
831
885
  resolveFrom: resolveFrom,
832
886
  node: node,
887
+ environment: cloud.testKRootPublic == null
888
+ ? null
889
+ : {
890
+ ...Platform.environment,
891
+ 'NOMOS_TEST_K_ROOT_PUBLIC': cloud.testKRootPublic!,
892
+ },
833
893
  onDiagnostic: onDiagnostic));
834
894
  final plane = NomosBridge(transport!);
835
895
  await _phased(
@@ -895,7 +955,8 @@ class NomosTestHarness {
895
955
  'capturedClock': DateTime.now().toUtc().millisecondsSinceEpoch,
896
956
  'epoch': 0,
897
957
  }, secret);
898
- await post('/v2/workspaces/$platformWorkspace/enrollFirstDevice', {
958
+ await post(
959
+ '/v2/workspaces/$platformWorkspace$enrollFirstDevicePath', {
899
960
  'keyHash': keyHash,
900
961
  'verifiedVia': issuer,
901
962
  'validFromEpoch': 0,
@@ -904,9 +965,10 @@ class NomosTestHarness {
904
965
  'actor': subject,
905
966
  'authorSecret': secret,
906
967
  'delegationCert': jsonEncode(delegationCert),
907
- }, headers: {
908
- 'x-nomos-auth': idpToken
909
- });
968
+ },
969
+ headers: {
970
+ 'x-nomos-auth': idpToken
971
+ });
910
972
  return (
911
973
  publicB64: publicB64,
912
974
  secret: secret,
@@ -922,7 +984,7 @@ class NomosTestHarness {
922
984
  await _phased(
923
985
  phase: '5/6: remote home-birth ceremony (attested)',
924
986
  workspace: platformWorkspace,
925
- awaiting: 'POST /v2/workspaces/$platformWorkspace/birthHome',
987
+ awaiting: 'POST /v2/workspaces/$platformWorkspace$birthHomePath',
926
988
  timeout: phaseTimeout ?? _kInstallDeadline,
927
989
  onDiagnostic: onDiagnostic,
928
990
  run: () async {
@@ -938,7 +1000,7 @@ class NomosTestHarness {
938
1000
  'epoch': 0,
939
1001
  }, device.secret);
940
1002
  final out =
941
- await post('/v2/workspaces/$platformWorkspace/birthHome', {
1003
+ await post('/v2/workspaces/$platformWorkspace$birthHomePath', {
942
1004
  'owner': subject,
943
1005
  'ownerKeyHash': device.keyHash,
944
1006
  'frameworkHash': frameworkHash,