@githolon/dsl 0.114.0 → 0.116.2

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 (86) hide show
  1. package/assets/operation-contract.wasm +0 -0
  2. package/dart/CHANGELOG.md +55 -0
  3. package/dart/lib/src/application_framework_calls.dart +7 -1
  4. package/dart/lib/src/console_enrolment.dart +7 -0
  5. package/dart/lib/src/console_http_io.dart +22 -15
  6. package/dart/lib/src/dev_cloud.dart +62 -0
  7. package/dart/lib/src/nms_generated_runtime.dart +1 -0
  8. package/dart/lib/src/operation_receipts.dart +74 -7
  9. package/dart/lib/src/reception/dart_reception.dart +26 -6
  10. package/dart/lib/src/result_usda.dart +64 -27
  11. package/dart/lib/src/runner/node-entry.mjs +4733 -1534
  12. package/dart/lib/src/runtime_v2_transport.dart +3 -0
  13. package/dart/lib/src/sync_status_projection.dart +11 -0
  14. package/dart/pubspec.lock +1 -1
  15. package/dart/pubspec.yaml +2 -2
  16. package/dart/test/console_enrolment_test.dart +47 -0
  17. package/dart/test/kernel_refused_follow_test.dart +36 -0
  18. package/dart/test/model_adoption_test.dart +49 -0
  19. package/dart/test/result_usda_test.dart +67 -0
  20. package/dart/test/runtime_projections_test.dart +41 -0
  21. package/dart_types/CHANGELOG.md +30 -0
  22. package/dart_types/lib/src/approved_model.dart +78 -1
  23. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +528 -2
  24. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +24 -0
  25. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +1897 -112
  26. package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +98 -0
  27. package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pbenum.dart +11 -1
  28. package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pb.dart +16 -0
  29. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +43 -2
  30. package/dart_types/lib/src/vo_canonical.dart +185 -0
  31. package/dart_types/pubspec.lock +1 -1
  32. package/dart_types/pubspec.yaml +3 -1
  33. package/dart_types/test/vo_canonical_test.dart +91 -0
  34. package/package.json +4 -2
  35. package/src/aggregate.ts +95 -0
  36. package/src/anonymous_value_interning.ts +205 -0
  37. package/src/baseline.ts +27 -0
  38. package/src/baseline_export.ts +432 -0
  39. package/src/baseline_import.ts +312 -0
  40. package/src/baseline_usda.ts +185 -0
  41. package/src/birth_key_rule.ts +472 -0
  42. package/src/births.ts +207 -24
  43. package/src/build_package.ts +47 -7
  44. package/src/build_provenance.ts +10 -0
  45. package/src/business_birth.ts +50 -4
  46. package/src/check_package_main.ts +50 -6
  47. package/src/codegen_call_proto.ts +5 -18
  48. package/src/codegen_call_usda.ts +6 -3
  49. package/src/codegen_dart.ts +114 -60
  50. package/src/codegen_dart_client.ts +52 -6
  51. package/src/codegen_dart_facade.ts +57 -35
  52. package/src/codegen_dart_framework_calls.ts +20 -1
  53. package/src/codegen_proof.ts +5 -2
  54. package/src/codegen_result_proto.ts +8 -6
  55. package/src/codegen_result_usda.ts +12 -4
  56. package/src/codegen_ts.ts +109 -10
  57. package/src/compatibility_graph.ts +41 -4
  58. package/src/compatibility_usda.ts +11 -1
  59. package/src/compile_package_main.ts +222 -52
  60. package/src/composed_wire_identity.ts +24 -0
  61. package/src/directive.ts +6 -0
  62. package/src/domain_requirements.ts +75 -0
  63. package/src/engine_entry.ts +12 -4
  64. package/src/engine_javascript.ts +12 -6
  65. package/src/fields.ts +109 -0
  66. package/src/identity_usda.ts +80 -103
  67. package/src/index.ts +35 -1
  68. package/src/intent.ts +33 -3
  69. package/src/law_check.ts +34 -0
  70. package/src/law_graph.ts +142 -1
  71. package/src/law_graph_usda.ts +27 -144
  72. package/src/law_layer_prims.ts +174 -0
  73. package/src/law_unit_selection.ts +45 -0
  74. package/src/law_units.ts +7 -6
  75. package/src/law_version.ts +108 -0
  76. package/src/package_config.ts +44 -0
  77. package/src/package_dependencies.ts +8 -0
  78. package/src/point_guard.ts +5 -1
  79. package/src/release_inspection.ts +6 -4
  80. package/src/release_publication.ts +6 -5
  81. package/src/usda_text.ts +78 -3
  82. package/src/value_identity.ts +355 -0
  83. package/src/value_object.ts +35 -4
  84. package/src/wire.ts +2 -0
  85. package/src/wire_paths.ts +17 -0
  86. package/src/workspace_routing.ts +1 -1
Binary file
package/dart/CHANGELOG.md CHANGED
@@ -1,3 +1,58 @@
1
+ ## 0.70.1
2
+
3
+ - Carries the 2026.10.2 runtime in the bundled Node peer (CAP-870, CAP-799, CAP-689): a copy whose saved bookmark is stale first asks its workspace's stream to level from its own head, so sync never re-lists history the custodian already holds; a complete custodian answers `off_main` at once for a head it does not hold, so a device with local work offers within seconds instead of a minute; kernel selection streams the download and times fetch, verify and store separately.
4
+
5
+ ## 0.70.0
6
+
7
+ - `WorkspaceFollowPhase.kernelRefused`: the kernel refused the follow's last round at the same local head and remote frontier, so its sync is blocked. No timer re-runs it. It runs again on new local work, an upstream head change, a socket reopen, or a changed kernel or runtime. `retryAt` is set only once such an event has made the follow due, and `detail` carries the kernel's refusal. If you switch exhaustively over `WorkspaceFollowPhase`, add this case.
8
+
9
+ - The bundled Node peer (`--discovery-provider`) keeps its last verified kernel selection under `<custody>/runtime/boot`. A restart while the channel is unreachable boots from it, with the attestation chain checked again (CAP-839).
10
+
11
+ - Read results decode USDA strings with OpenUSD's escape rules (CAP-862): `\a \b \f \n \r \t \v`, `\xHH`, octal `\NNN`, and any other escaped character as itself. The legacy `\uXXXX` that older kernels wrote still decodes. A raw U+2028 or U+2029 inside a string value no longer fails the parse.
12
+ - Generated Dart call encoders write controls as `\xHH` (U+0000–U+001F except the named escapes, and U+007F) and write C1 characters raw. They throw `ArgumentError` on a lone surrogate. Before, a lone surrogate was silently replaced in the UTF-8 bytes.
13
+
14
+ - The generated runtime surface exports `NmsInternalValueIdentity`, which generated value-object `==` and `hashCode` now use (CAP-853).
15
+
16
+ ## 0.69.0
17
+
18
+ - The test cloud's partition proxy (`NomosTestCloud`) carries live sockets: each workspace's catch-up
19
+ stream and live offers pass through it while ONLINE, and going OFFLINE severs them too (CAP-826).
20
+
21
+ Uses nomos_types 0.14.0; regenerate generated clients with this compiler.
22
+
23
+ - Generated framework calls expose `domainRequirements` (CAP-666): for each application domain, the law unit and
24
+ the generated requirement identity this client was compiled against. `NomosApprovedModel` carries them to the
25
+ model-adoption desk, and the client inventory records them as the build's contract, so an application pinned
26
+ to one domain no longer goes stale when a rollout moves a domain it never touches.
27
+ - One parent per node (CAP-788): the kernel refuses, with `[birth-child-already-parented]`, a business birth whose
28
+ child already records a different parent. Names are fixed at birth (CAP-803): `keyedBirthWorkspace` and the
29
+ generated `open` / `fetch` / `watchOpenProgress(key)` address a business child by
30
+ `workspaceNameForKey(namespace, key)`, exactly as 0.68.0 did, and send no parent. The parent-scoped naming
31
+ proposed with CAP-788 is withdrawn before release.
32
+ - Birth keys are minted by default (CAP-804). A business birth with no `key` names its child by an id Nomos
33
+ mints inside the birth; generated applications open it with `openReference` / `fetchReference` / `mine()`
34
+ instead of `open(key)`. An encrypted `.owned()` birth reserves its minted key first (`session.mintId`). An
35
+ explicit key, `key: "<field>"`, must name a typed-id field and names children exactly as before. When a duplicate birth is refused on the losing peer
36
+ (`[birth-child-already-parented]`), the child's creation receipt completes with a `NomosOperationRefused`
37
+ error (new export; `NomosOperationStatus.refusal` / `isRefused`).
38
+ - Birth keys are typed ids (CAP-804 follow-up): a generated client's branded-id fields are extension types in read
39
+ models and query keys, and `open(key)` / `fetch(key)` / `watchOpenProgress(key)` take the key's branded type. A
40
+ write that changes a birth key is refused (`birth-key-write-once`).
41
+ - One console enrolment exchange is bounded at 20 s (`nomosConsoleEnrolmentTimeout`); a timeout is a retryable
42
+ transport failure, so a slow console no longer holds a person's write (CAP-807).
43
+ - Nested record members, map values, list elements and union variants carry their protobuf wire allocation in the
44
+ generated compatibility requirement, and the kernel judges them recursively (CAP-681). A client whose nested
45
+ allocation differs from the installed law is refused with the member named, never decoded wrongly.
46
+ - A reloaded app no longer sees an empty child list (CAP-824): `nomosFrameworkChildWorkspaces` (behind the
47
+ generated `mine()` / `available()`) waits out a retryable not-ready answer while the device is still preparing
48
+ its copy, as every other one-shot read does, so the caller gets the children or a real error. A write that meets
49
+ unfinished preparation is held and reported as `NomosWritePhase.preparing`, then completes; no client change.
50
+ - Bundles the 0.115.0 runtime: the kernel-side release qualification (CAP-789), the per-domain adoption record
51
+ (CAP-666), one parent per node with names fixed at birth (CAP-788, CAP-803), minted birth keys and the losing
52
+ peer's typed birth refusal (CAP-804), the pack indexed in place (CAP-802), pack imports in bounded steps
53
+ that never hold up a person's work (CAP-806), one law version per identity named by its unit set (CAP-809), the
54
+ v2 command frontier (CAP-810) and claimed law versions (CAP-811).
55
+
1
56
  ## 0.68.0
2
57
 
3
58
  Breaking: regenerate generated Dart clients with this compiler.
@@ -3,7 +3,7 @@
3
3
  import 'dart:convert' show utf8;
4
4
  import 'dart:typed_data' show Uint8List;
5
5
  import 'package:meta/meta.dart' show internal;
6
- import 'package:nomos_types/nomos_types.dart' show Sha256Digest;
6
+ import 'package:nomos_types/nomos_types.dart' show NomosDomainRequirement, Sha256Digest;
7
7
  import 'package:nomos_types/nomos_kernel_protocol.dart' show BirthCertificate;
8
8
 
9
9
  import 'generated_call.dart';
@@ -244,6 +244,12 @@ abstract interface class NmsApplicationFrameworkCalls {
244
244
  /// model-adoption desk on a package-lane workspace. Package — not what a unit-lane workspace serves.
245
245
  String get lawPackageSha256;
246
246
 
247
+ /// THE PER-DOMAIN REQUIREMENTS (CAP-666), by application domain key: the exact law unit
248
+ /// (`domain:<key>` of the unit set) and the generated consumer requirement identity this
249
+ /// client was compiled against. An application pins its release by these beside [lawUnitSet];
250
+ /// the model-adoption desk and the client inventory carry them as this build's contract.
251
+ Map<String, NomosDomainRequirement> get domainRequirements;
252
+
247
253
  NmsGeneratedCall enrollFirstDevice({
248
254
  required String domain,
249
255
  required String workspace,
@@ -192,6 +192,13 @@ final class NomosConsoleEnrolmentRefused implements Exception {
192
192
  String toString() => 'console enrolment refused (HTTP $status): $message';
193
193
  }
194
194
 
195
+ /// How long one console enrolment exchange may take before the shell reports
196
+ /// it as a retryable transport failure. The console answers once the
197
+ /// workspaces the device asked for admitted it (its fan-out follows in the
198
+ /// background), so a healthy answer takes a few seconds; Peer Office asks
199
+ /// again with a fresh token rather than holding a person's write.
200
+ const Duration nomosConsoleEnrolmentTimeout = Duration(seconds: 20);
201
+
195
202
  /// One console exchange: status and raw body.
196
203
  final class NomosConsoleResponse {
197
204
  const NomosConsoleResponse({required this.status, required this.body});
@@ -1,25 +1,32 @@
1
+ import 'dart:async';
1
2
  import 'dart:convert';
2
3
  import 'dart:io';
3
4
 
4
- import 'console_enrolment.dart' show NomosConsoleResponse;
5
+ import 'console_enrolment.dart'
6
+ show NomosConsoleResponse, nomosConsoleEnrolmentTimeout;
5
7
 
6
8
  /// The native shells' console transport: one JSON POST, the answer's status
7
9
  /// and body. No retry and no interpretation: Peer Office owns both.
8
- Future<NomosConsoleResponse> nomosDefaultConsolePost(
9
- Uri url, String body) async {
10
- final client = HttpClient()..connectionTimeout = const Duration(seconds: 30);
10
+ ///
11
+ /// The whole exchange is bounded by [timeout] ([nomosConsoleEnrolmentTimeout]) (CAP-807:
12
+ /// a slow console held a person's write for 53 s before this failed). A
13
+ /// [TimeoutException] is a retryable transport failure: the console may still
14
+ /// finish the enrolment, and the next attempt (a fresh token) finds it done.
15
+ Future<NomosConsoleResponse> nomosDefaultConsolePost(Uri url, String body,
16
+ {Duration timeout = nomosConsoleEnrolmentTimeout}) async {
17
+ final client = HttpClient()..connectionTimeout = timeout;
11
18
  try {
12
- final request = await client.postUrl(url);
13
- request.headers
14
- ..contentType = ContentType.json
15
- ..set(HttpHeaders.acceptHeader, 'application/json');
16
- request.add(utf8.encode(body));
17
- final response = await request.close().timeout(const Duration(seconds: 60));
18
- final text = await response
19
- .transform(utf8.decoder)
20
- .join()
21
- .timeout(const Duration(seconds: 60));
22
- return NomosConsoleResponse(status: response.statusCode, body: text);
19
+ return await () async {
20
+ final request = await client.postUrl(url);
21
+ request.headers
22
+ ..contentType = ContentType.json
23
+ ..set(HttpHeaders.acceptHeader, 'application/json');
24
+ request.add(utf8.encode(body));
25
+ final response = await request.close();
26
+ final text = await response.transform(utf8.decoder).join();
27
+ return NomosConsoleResponse(status: response.statusCode, body: text);
28
+ }()
29
+ .timeout(timeout);
23
30
  } finally {
24
31
  client.close(force: true);
25
32
  }
@@ -79,6 +79,10 @@ class NomosTestCloud {
79
79
  bool _online = true;
80
80
  bool _stopped = false;
81
81
 
82
+ /// Live sockets the proxy carries (each workspace's catch-up stream and live offers,
83
+ /// architecture/peer_catchup.md). Going OFFLINE severs them too, as a real link loss would.
84
+ final Set<WebSocket> _liveSockets = {};
85
+
82
86
  NomosTestCloud._(
83
87
  this._server,
84
88
  this._proxy,
@@ -98,6 +102,12 @@ class NomosTestCloud {
98
102
  /// cloud and peers converge. Deterministic: no timing races, the test decides exactly when the link exists.
99
103
  void setOnline(bool online) {
100
104
  _online = online;
105
+ if (!online) {
106
+ for (final socket in _liveSockets.toList()) {
107
+ socket.close(WebSocketStatus.goingAway, 'partitioned');
108
+ }
109
+ _liveSockets.clear();
110
+ }
101
111
  _log(
102
112
  'partition: ${online ? "ONLINE (link up)" : "OFFLINE (link severed)"}',
103
113
  );
@@ -350,6 +360,10 @@ class NomosTestCloud {
350
360
  } catch (_) {}
351
361
  return;
352
362
  }
363
+ if (WebSocketTransformer.isUpgradeRequest(req)) {
364
+ await _proxyLiveSocket(req);
365
+ return;
366
+ }
353
367
  try {
354
368
  final target = _upstream.replace(
355
369
  path: req.uri.path,
@@ -375,6 +389,50 @@ class NomosTestCloud {
375
389
  }
376
390
  }
377
391
 
392
+ /// A WebSocket through the partition: both ends piped frame for frame, both closed together.
393
+ Future<void> _proxyLiveSocket(HttpRequest req) async {
394
+ final WebSocket upstream;
395
+ try {
396
+ upstream = await WebSocket.connect(_upstream
397
+ .replace(
398
+ scheme: _upstream.scheme == 'https' ? 'wss' : 'ws',
399
+ path: req.uri.path,
400
+ query: req.uri.query.isEmpty ? null : req.uri.query,
401
+ )
402
+ .toString());
403
+ } catch (_) {
404
+ try {
405
+ req.response.statusCode = 502;
406
+ await req.response.close();
407
+ } catch (_) {}
408
+ return;
409
+ }
410
+ final WebSocket client;
411
+ try {
412
+ client = await WebSocketTransformer.upgrade(req);
413
+ } catch (_) {
414
+ await upstream.close();
415
+ return;
416
+ }
417
+ _liveSockets
418
+ ..add(client)
419
+ ..add(upstream);
420
+ void closeBoth() {
421
+ _liveSockets
422
+ ..remove(client)
423
+ ..remove(upstream);
424
+ client.close();
425
+ upstream.close();
426
+ }
427
+
428
+ upstream.listen((frame) {
429
+ if (_online) client.add(frame);
430
+ }, onDone: closeBoth, onError: (_) => closeBoth());
431
+ client.listen((frame) {
432
+ if (_online) upstream.add(frame);
433
+ }, onDone: closeBoth, onError: (_) => closeBoth());
434
+ }
435
+
378
436
  Future<Map<String, Object?>> _getJson(Uri url) async {
379
437
  final req = await _client.getUrl(url);
380
438
  final resp = await req.close();
@@ -394,6 +452,10 @@ class NomosTestCloud {
394
452
  if (_stopped) return;
395
453
  _stopped = true;
396
454
  _log('stopping');
455
+ for (final socket in _liveSockets.toList()) {
456
+ socket.close();
457
+ }
458
+ _liveSockets.clear();
397
459
  try {
398
460
  await _proxy.close(force: true);
399
461
  } catch (_) {}
@@ -87,3 +87,4 @@ export 'runtime_v2_transport.dart'
87
87
  export 'reception/peer_office_connection.dart' show PeerOfficeConnection;
88
88
 
89
89
  export 'admission_decision.dart';
90
+ export 'package:nomos_types/src/vo_canonical.dart' show NmsInternalValueIdentity;
@@ -8,15 +8,43 @@ enum NomosOperationPhase {
8
8
  converged,
9
9
  }
10
10
 
11
+ /// Custody refused a write this device accepted locally: a terminal verdict, never retried.
12
+ ///
13
+ /// Today the one such refusal Nomos reports is a duplicate birth: a workspace name is global to its
14
+ /// namespace and fixed at birth, and another parent already owns this child's name, so the cloud
15
+ /// refused this birth (`[birth-child-already-parented]`). The child's local copy is quarantined on this
16
+ /// device — kept for inspection, never synced. The message names only this device's own workspaces.
17
+ final class NomosOperationRefused implements Exception {
18
+ const NomosOperationRefused({
19
+ required this.code,
20
+ required this.message,
21
+ this.childWorkspace,
22
+ });
23
+
24
+ /// The typed refusal tag, e.g. `birth-child-already-parented`.
25
+ final String code;
26
+ final String message;
27
+
28
+ /// The quarantined local child, for a refused birth.
29
+ final String? childWorkspace;
30
+
31
+ @override
32
+ String toString() => 'NomosOperationRefused[$code]: $message';
33
+ }
34
+
11
35
  /// Product-facing progress for one accepted write.
12
36
  final class NomosOperationStatus {
13
- const NomosOperationStatus(this.phase, {this.updatedAt});
37
+ const NomosOperationStatus(this.phase, {this.updatedAt, this.refusal});
14
38
 
15
39
  final NomosOperationPhase phase;
16
40
  final DateTime? updatedAt;
17
41
 
42
+ /// Set when custody refused this write: terminal; [phase] stays [NomosOperationPhase.locallyAccepted].
43
+ final NomosOperationRefused? refusal;
44
+
18
45
  bool get isAdmitted => phase != NomosOperationPhase.locallyAccepted;
19
46
  bool get isConverged => phase == NomosOperationPhase.converged;
47
+ bool get isRefused => refusal != null;
20
48
  }
21
49
 
22
50
  /// Awaitable projection of typed peer-runtime receipts for one stable intent.
@@ -36,13 +64,36 @@ final class NomosOperationReceipt {
36
64
  yield* _changes.stream;
37
65
  }
38
66
 
39
- Future<void> get admitted => _status.isAdmitted
40
- ? Future<void>.value()
41
- : watch().firstWhere((status) => status.isAdmitted).then((_) {});
67
+ /// Completes when remote custody admits this write; completes with a
68
+ /// [NomosOperationRefused] error when custody refuses it.
69
+ Future<void> get admitted => _settled((status) => status.isAdmitted);
70
+
71
+ /// Completes when this write has converged; completes with a
72
+ /// [NomosOperationRefused] error when custody refuses it.
73
+ Future<void> get converged => _settled((status) => status.isConverged);
74
+
75
+ Future<void> _settled(bool Function(NomosOperationStatus) reached) {
76
+ final refusal = _status.refusal;
77
+ if (refusal != null) return Future<void>.error(refusal);
78
+ if (reached(_status)) return Future<void>.value();
79
+ return watch()
80
+ .firstWhere((status) => status.isRefused || reached(status))
81
+ .then((status) {
82
+ final refused = status.refusal;
83
+ if (refused != null) throw refused;
84
+ });
85
+ }
42
86
 
43
- Future<void> get converged => _status.isConverged
44
- ? Future<void>.value()
45
- : watch().firstWhere((status) => status.isConverged).then((_) {});
87
+ void _refuse(NomosOperationRefused refusal) {
88
+ if (_status.isRefused || _status.isAdmitted) return;
89
+ _status = NomosOperationStatus(
90
+ _status.phase,
91
+ updatedAt: DateTime.now(),
92
+ refusal: refusal,
93
+ );
94
+ if (!_changes.isClosed) _changes.add(_status);
95
+ unawaited(_changes.close());
96
+ }
46
97
 
47
98
  void _advance(NomosOperationPhase phase) {
48
99
  if (phase.index <= _status.phase.index) return;
@@ -81,6 +132,22 @@ final class NmsOperationReceiptProjection {
81
132
  NomosOperationReceipt? birthFor(String workspace) => _births[workspace];
82
133
 
83
134
  void advance(String workspace, nomos_protocol.SyncWorkspaceResponse result) {
135
+ // Custody refused one of this workspace's births (the child's name already has another parent):
136
+ // the creation receipt of that child reports the typed, terminal refusal (CAP-804).
137
+ for (final birth in result.birthOutcomes) {
138
+ if (birth.status !=
139
+ nomos_protocol.BirthOutcomeStatus.BIRTH_OUTCOME_STATUS_REFUSED ||
140
+ birth.code != 'birth-child-already-parented') {
141
+ continue;
142
+ }
143
+ final child =
144
+ birth.childWorkspace.isNotEmpty ? birth.childWorkspace : birth.workspace;
145
+ _births[child]?._refuse(NomosOperationRefused(
146
+ code: birth.code,
147
+ message: birth.hasRefusal() ? birth.refusal.message : birth.reason,
148
+ childWorkspace: child,
149
+ ));
150
+ }
84
151
  final pending = _pending[workspace];
85
152
  if (pending == null || pending.isEmpty) return;
86
153
 
@@ -94,7 +94,11 @@ export '../sync_status_projection.dart'
94
94
  WorkspaceFollowStatus,
95
95
  WorkspaceFollowPhase;
96
96
  export '../operation_receipts.dart'
97
- show NomosOperationPhase, NomosOperationReceipt, NomosOperationStatus;
97
+ show
98
+ NomosOperationPhase,
99
+ NomosOperationReceipt,
100
+ NomosOperationRefused,
101
+ NomosOperationStatus;
98
102
  export 'progress_desk.dart'
99
103
  show
100
104
  KernelLawHydrationStep,
@@ -2128,15 +2132,23 @@ class DartReception {
2128
2132
 
2129
2133
  /// Framework-private organisational read used by generated `describe()`. The runner joins the parent
2130
2134
  /// ledger's verified birth witnesses and lifecycle facts; tenant code only receives safe child links.
2135
+ ///
2136
+ /// On a cold mount the office answers "not ready yet" while it prepares the parent's birth
2137
+ /// records (CAP-824); this waits that out like every other one-shot read, so a caller sees the
2138
+ /// children or a real error, never an empty list that fills in by itself a moment later.
2131
2139
  Future<List<nomos_protocol.ChildWorkspace>> nomosFrameworkChildWorkspaces(
2132
2140
  {String? namespace}) async {
2133
- final response = await _invokeRuntimeV2(
2134
- nomos_protocol.RuntimeRequest(
2135
- listChildWorkspaces: nomos_protocol.ListChildWorkspacesRequest(
2136
- session: _runtimeSession(),
2137
- namespace: namespace,
2141
+ final session = _runtimeSession();
2142
+ final response = await nmsUntilReplicaReady(
2143
+ () => _invokeRuntimeV2(
2144
+ nomos_protocol.RuntimeRequest(
2145
+ listChildWorkspaces: nomos_protocol.ListChildWorkspacesRequest(
2146
+ session: session,
2147
+ namespace: namespace,
2148
+ ),
2138
2149
  ),
2139
2150
  ),
2151
+ synced: _eventPlane._events.workspacesSynced,
2140
2152
  );
2141
2153
  if (!response.hasChildWorkspaces()) {
2142
2154
  throw StateError(
@@ -4300,6 +4312,14 @@ class NomosSession extends DartReception {
4300
4312
  domainKeys: model.domainKeys,
4301
4313
  acceptanceReason: model.reason,
4302
4314
  dryRun: dryRun,
4315
+ domainRequirements: [
4316
+ for (final requirement in model.domainRequirements.values)
4317
+ nomos_protocol.DomainUnitRequirement(
4318
+ domainKey: requirement.domainKey,
4319
+ unitSha256: requirement.unitSha256,
4320
+ requirementSha256: requirement.requirementSha256,
4321
+ ),
4322
+ ],
4303
4323
  ),
4304
4324
  ),
4305
4325
  );
@@ -331,9 +331,13 @@ final class NmsUsdaResult {
331
331
  abstract final class NmsUsdaLayer {
332
332
  static NmsUsdaPrim parse(Uint8List bytes) => _parseLayer(bytes);
333
333
 
334
+ /// The canonical USDA string literal (CAP-862), byte-identical with the kernel's
335
+ /// `usda_string.rs` and the generated call encoders: `\"` `\\` `\n` `\r` `\t` `\b` `\f`; any
336
+ /// other U+0000–U+001F and U+007F as `\xHH`; C1 and everything else raw. A lone surrogate is
337
+ /// refused rather than silently replaced in the UTF-8 bytes.
334
338
  static String quote(String value) {
335
339
  final out = StringBuffer('"');
336
- for (final code in value.codeUnits) {
340
+ for (final code in value.runes) {
337
341
  switch (code) {
338
342
  case 0x22:
339
343
  out.write(r'\"');
@@ -350,9 +354,12 @@ abstract final class NmsUsdaLayer {
350
354
  case 0x0c:
351
355
  out.write(r'\f');
352
356
  default:
353
- if (code < 0x20) {
354
- out.write(r'\u');
355
- out.write(code.toRadixString(16).padLeft(4, '0'));
357
+ if (code < 0x20 || code == 0x7f) {
358
+ out.write(r'\x');
359
+ out.write(code.toRadixString(16).padLeft(2, '0'));
360
+ } else if (code >= 0xd800 && code <= 0xdfff) {
361
+ throw ArgumentError.value(
362
+ value, 'value', 'Nomos USDA strings cannot carry a lone surrogate');
356
363
  } else {
357
364
  out.writeCharCode(code);
358
365
  }
@@ -361,6 +368,7 @@ abstract final class NmsUsdaLayer {
361
368
  out.write('"');
362
369
  return out.toString();
363
370
  }
371
+
364
372
  }
365
373
 
366
374
  NmsUsdaPrim _parseLayer(Uint8List bytes) {
@@ -406,6 +414,8 @@ NmsUsdaPrim _parseLayer(Uint8List bytes) {
406
414
  }
407
415
  final property = RegExp(
408
416
  r'^custom(?:\s+uniform)?\s+(?:rel|[A-Za-z0-9_\[\]]+)\s+([A-Za-z0-9_:]+)\s*=\s*(.+)$',
417
+ // A raw U+2028/U+2029 inside a string literal is value, not a line break.
418
+ dotAll: true,
409
419
  ).firstMatch(line);
410
420
  if (property != null) {
411
421
  if (stack.isEmpty)
@@ -439,48 +449,75 @@ Object _atom(String source) {
439
449
  throw FormatException('Unsupported USDA value $value');
440
450
  }
441
451
 
452
+ /// Decodes one quoted USDA string with OpenUSD's escape rules (`TfEscapeString`): `\a \b \f \n \r
453
+ /// \t \v`, `\x` with up to two hex digits, `\NNN` with up to three octal digits, and any other
454
+ /// escaped character as itself. A LEGACY `\uXXXX` (written by pre-CAP-862 kernels; OpenUSD has no
455
+ /// `\u`) reads as the scalar it names, as the kernel normalises it.
442
456
  String _unquote(String source) {
443
457
  if (source.length < 2 || !source.startsWith('"') || !source.endsWith('"')) {
444
458
  throw const FormatException('Invalid USDA string');
445
459
  }
460
+ final body = source.substring(1, source.length - 1);
446
461
  final out = StringBuffer();
447
- for (var index = 1; index < source.length - 1; index++) {
448
- final character = source[index];
462
+ int digitsAt(int start, int max, int radix) {
463
+ var count = 0;
464
+ while (count < max &&
465
+ start + count < body.length &&
466
+ int.tryParse(body[start + count], radix: radix) != null) {
467
+ count++;
468
+ }
469
+ return count;
470
+ }
471
+
472
+ for (var index = 0; index < body.length; index++) {
473
+ final character = body[index];
449
474
  if (character != r'\') {
450
475
  out.write(character);
451
476
  continue;
452
477
  }
453
- if (++index >= source.length - 1) {
454
- throw const FormatException('Truncated USDA escape');
455
- }
456
- final escaped = source[index];
478
+ if (++index >= body.length) break;
479
+ final escaped = body[index];
457
480
  switch (escaped) {
481
+ case 'a':
482
+ out.writeCharCode(0x07);
483
+ case 'b':
484
+ out.write('\b');
485
+ case 'f':
486
+ out.write('\f');
458
487
  case 'n':
459
488
  out.write('\n');
460
489
  case 'r':
461
490
  out.write('\r');
462
491
  case 't':
463
492
  out.write('\t');
464
- case 'b':
465
- out.write('\b');
466
- case 'f':
467
- out.write('\f');
468
- case '"':
469
- out.write('"');
470
- case r'\':
471
- out.write(r'\');
493
+ case 'v':
494
+ out.writeCharCode(0x0b);
495
+ case 'x':
496
+ final count = digitsAt(index + 1, 2, 16);
497
+ out.writeCharCode(count == 0
498
+ ? 0
499
+ : int.parse(body.substring(index + 1, index + 1 + count), radix: 16));
500
+ index += count;
472
501
  case 'u':
473
- if (index + 4 >= source.length) {
474
- throw const FormatException('Truncated USDA unicode escape');
502
+ final count = digitsAt(index + 1, 4, 16);
503
+ final code = count == 4
504
+ ? int.parse(body.substring(index + 1, index + 5), radix: 16)
505
+ : null;
506
+ if (code != null && (code < 0xd800 || code > 0xdfff)) {
507
+ out.writeCharCode(code);
508
+ index += 4;
509
+ } else {
510
+ out.write('u');
475
511
  }
476
- final digits = source.substring(index + 1, index + 5);
477
- final code = int.tryParse(digits, radix: 16);
478
- if (code == null)
479
- throw const FormatException('Invalid USDA unicode escape');
480
- out.writeCharCode(code);
481
- index += 4;
482
512
  default:
483
- throw FormatException('Unsupported USDA escape $escaped');
513
+ if (escaped.codeUnitAt(0) >= 0x30 && escaped.codeUnitAt(0) <= 0x37) {
514
+ final count = digitsAt(index, 3, 8);
515
+ out.writeCharCode(
516
+ int.parse(body.substring(index, index + count), radix: 8));
517
+ index += count - 1;
518
+ } else {
519
+ out.write(escaped);
520
+ }
484
521
  }
485
522
  }
486
523
  return out.toString();