@githolon/dsl 0.103.3 → 0.103.5

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 (42) hide show
  1. package/dart/CHANGELOG.md +15 -0
  2. package/dart/lib/src/runner/node-entry.mjs +411 -149
  3. package/dart/lib/src/runner/offline-install.mjs +222 -99
  4. package/dart/lib/src/session_bound.dart +111 -2
  5. package/dart/lib/src/test_harness.dart +117 -66
  6. package/dart/lib/src/webview_bridge.dart +726 -61
  7. package/dart/pubspec.lock +1 -1
  8. package/dart/pubspec.yaml +4 -5
  9. package/dart/test/acquire_session_test.dart +62 -5
  10. package/dart/test/cloud_bound_topology_test.dart +59 -38
  11. package/dart/test/session_bound_test.dart +25 -10
  12. package/dart/test/webview_bridge_test.dart +357 -9
  13. package/dart_types/CHANGELOG.md +5 -0
  14. package/dart_types/lib/nomos_types.dart +1 -0
  15. package/dart_types/lib/src/git_object_id.dart +37 -0
  16. package/dart_types/pubspec.yaml +1 -1
  17. package/package.json +1 -1
  18. package/runtime/generated-application-host.js +425 -145
  19. package/src/births.ts +16 -1
  20. package/src/business_birth.ts +67 -46
  21. package/src/codegen_dart.ts +68 -12
  22. package/src/codegen_dart_application.ts +114 -19
  23. package/src/codegen_dart_client.ts +149 -6
  24. package/src/codegen_proof.ts +16 -9
  25. package/src/codegen_provider_dart.ts +5 -0
  26. package/src/codegen_ts.ts +115 -19
  27. package/src/compile_package_main.ts +142 -40
  28. package/src/fields.ts +43 -0
  29. package/src/framework/attested_receipts.ts +34 -1
  30. package/src/framework/domain_lifecycle.ts +140 -83
  31. package/src/framework/genesis.ts +2 -1
  32. package/src/framework/home.ts +20 -45
  33. package/src/framework/workspaces.ts +62 -29
  34. package/src/idof_lint.ts +65 -51
  35. package/src/index.ts +21 -1
  36. package/src/intent.ts +11 -1
  37. package/src/law_graph.ts +81 -11
  38. package/src/law_graph_usda.ts +45 -54
  39. package/src/relation_schema.ts +18 -0
  40. package/src/shape_compat.ts +3 -0
  41. package/src/stable_ids_for_module.ts +7 -1
  42. package/src/value_object.ts +40 -0
@@ -168,9 +168,30 @@ class FakeRunner implements BridgeTransport {
168
168
  case 'autoSync':
169
169
  _incoming.add(reply({'started': true}));
170
170
  break;
171
+ case 'refreshCredentials':
172
+ _incoming.add(reply({'refreshed': true}));
173
+ break;
171
174
  case 'lawStatus':
172
175
  _incoming.add(reply({'installed': true, 'drifted': false}));
173
176
  break;
177
+ case 'metrics':
178
+ final session = args['session'];
179
+ _incoming.add(reply(session == 'home'
180
+ ? {
181
+ 'ahead': 0,
182
+ 'behind': false,
183
+ 'saved': 2,
184
+ 'onMain': 3,
185
+ 'deadLetters': 0,
186
+ }
187
+ : {
188
+ 'ahead': 4,
189
+ 'behind': true,
190
+ 'saved': 5,
191
+ 'onMain': 6,
192
+ 'deadLetters': 1,
193
+ }));
194
+ break;
174
195
  case 'cryptoOwnedDeviceRequest':
175
196
  _incoming.add(reply({'request': 'opaque-owned-device-request'}));
176
197
  break;
@@ -225,6 +246,15 @@ class FakeRunner implements BridgeTransport {
225
246
  }
226
247
  }));
227
248
  break;
249
+ case 'stageGeneratedModel':
250
+ _incoming.add(reply({
251
+ 'verdict': {
252
+ 'ok': true,
253
+ 'status': 'pending',
254
+ 'candidateHash': 'candidate-law-hash',
255
+ }
256
+ }));
257
+ break;
228
258
  case 'restorePreviousGeneratedModel':
229
259
  _incoming.add(reply({
230
260
  'verdict': {
@@ -248,8 +278,11 @@ class FakeRunner implements BridgeTransport {
248
278
  case 'offerDirective':
249
279
  _incoming.add(reply({
250
280
  'head': 'HEAD-owned-approval',
281
+ 'intentId': 'intent-owned-approval',
251
282
  'created': <Object?>[],
252
- 'births': <Object?>[],
283
+ 'born': args['directiveId'] == 'birthChild'
284
+ ? <Object?>['notes-child']
285
+ : <Object?>[],
253
286
  'workspace': 'notes',
254
287
  }));
255
288
  break;
@@ -300,6 +333,19 @@ class FakeRunner implements BridgeTransport {
300
333
  _incoming.add({'v': kWebviewBridgeVersion, 'event': 'sync', ...event});
301
334
  }
302
335
 
336
+ class ErrorRunner implements BridgeTransport {
337
+ final _incoming = StreamController<Map<String, Object?>>.broadcast();
338
+
339
+ @override
340
+ Stream<Map<String, Object?>> get messages => _incoming.stream;
341
+
342
+ @override
343
+ void send(Map<String, Object?> message) {}
344
+
345
+ void fail(Object error) => _incoming.addError(error);
346
+ Future<void> close() => _incoming.close();
347
+ }
348
+
303
349
  /// A transport that answers EVERY request with one canned `result` (the golden corpus's reply) and records
304
350
  /// the request the bridge sent — so we can assert both halves of the wire contract from the Dart side.
305
351
  class ReplayTransport implements BridgeTransport {
@@ -403,6 +449,20 @@ Object? _expectedTyped(String op, Map<String, Object?> result) {
403
449
  }
404
450
 
405
451
  void main() {
452
+ test('transport failure fails readiness immediately', () async {
453
+ final transport = ErrorRunner();
454
+ final bridge = NomosBridge(transport);
455
+ final failure = StateError('runner exited');
456
+ transport.fail(failure);
457
+ await expectLater(
458
+ bridge.ready,
459
+ throwsA(isA<StateError>()
460
+ .having((e) => e.message, 'message', 'runner exited')),
461
+ );
462
+ await bridge.dispose();
463
+ await transport.close();
464
+ });
465
+
406
466
  // ── THE GOLDEN CORPUS — the cross-language conformance the JS runner replays too ──
407
467
  // For each case: call the typed bridge method with the corpus `args`, then assert (1) the request the bridge
408
468
  // SENT carries exactly those args, and (2) the typed value the bridge parsed from the corpus `result` matches.
@@ -439,6 +499,7 @@ void main() {
439
499
  final now = DateTime.now().millisecondsSinceEpoch;
440
500
  final transport = ReplayTransport({
441
501
  'head': 'H1',
502
+ 'intentId': 'intent-span',
442
503
  'created': <Object?>[],
443
504
  'workspace': 'ws',
444
505
  // the runner's op-pickup + durable-ack instants (additive `timing` field)
@@ -463,8 +524,12 @@ void main() {
463
524
  test(
464
525
  'a runner without timing (older bundle) emits no bridge-leg spans and still answers',
465
526
  () async {
466
- final transport = ReplayTransport(
467
- {'head': 'H1', 'created': <Object?>[], 'workspace': 'ws'});
527
+ final transport = ReplayTransport({
528
+ 'head': 'H1',
529
+ 'intentId': 'intent-old-runner',
530
+ 'created': <Object?>[],
531
+ 'workspace': 'ws'
532
+ });
468
533
  final bridge = NomosBridge(transport);
469
534
  await bridge.ready;
470
535
  final out = await bridge.offerOutcome(
@@ -484,8 +549,12 @@ void main() {
484
549
  // author path signs THIS offer with the intended enrolled key, not the connect-time one). Both offer lanes. ──
485
550
  group('per-offer authorSecret threads to the runner op (#9)', () {
486
551
  test('offerOutcome carries authorSecret to the runner op args', () async {
487
- final transport = ReplayTransport(
488
- {'head': 'H1', 'created': <Object?>[], 'workspace': 'ws'});
552
+ final transport = ReplayTransport({
553
+ 'head': 'H1',
554
+ 'intentId': 'intent-secret',
555
+ 'created': <Object?>[],
556
+ 'workspace': 'ws'
557
+ });
489
558
  final bridge = NomosBridge(transport);
490
559
  await bridge.ready;
491
560
  await bridge.offerOutcome(
@@ -520,8 +589,12 @@ void main() {
520
589
  test(
521
590
  'no authorSecret ⇒ the op omits it (byte-identical to the pre-#9 contract)',
522
591
  () async {
523
- final transport = ReplayTransport(
524
- {'head': 'H1', 'created': <Object?>[], 'workspace': 'ws'});
592
+ final transport = ReplayTransport({
593
+ 'head': 'H1',
594
+ 'intentId': 'intent-no-secret',
595
+ 'created': <Object?>[],
596
+ 'workspace': 'ws'
597
+ });
525
598
  final bridge = NomosBridge(transport);
526
599
  await bridge.ready;
527
600
  await bridge.offerOutcome(
@@ -683,7 +756,8 @@ void main() {
683
756
  await bridge.lawStatus('abc'), {'installed': true, 'drifted': false});
684
757
  });
685
758
 
686
- test('version freshness returns the required sum type without resolving history',
759
+ test(
760
+ 'version freshness returns the required sum type without resolving history',
687
761
  () async {
688
762
  await bridge.ready;
689
763
  final reference = NmsInternalVersionedNomosRefs.create<Object, Object>(
@@ -706,7 +780,8 @@ void main() {
706
780
  final request = runner.sent.last;
707
781
  expect(request['op'], 'checkRefFreshness');
708
782
  final args = (request['args'] as Map).cast<String, Object?>();
709
- final check = ((args['refs'] as List).single as Map).cast<String, Object?>();
783
+ final check =
784
+ ((args['refs'] as List).single as Map).cast<String, Object?>();
710
785
  expect(check['currentField'], 'currentPublication');
711
786
  expect((check['pinned'] as Map)['id'], 'publication:1');
712
787
  expect((check['series'] as Map)['id'], 'series:1');
@@ -879,6 +954,26 @@ void main() {
879
954
  });
880
955
  });
881
956
 
957
+ test(
958
+ 'generated model staging carries opaque USDA through one framework-private operation',
959
+ () async {
960
+ await bridge.ready;
961
+ await bridge.nomosInternalStageGeneratedModel(
962
+ domain: 'evo',
963
+ candidateHash: 'candidate-law-hash',
964
+ lawUsdz: const [1, 2, 3],
965
+ );
966
+
967
+ final request = runner.sent.firstWhere(
968
+ (message) => message['op'] == 'stageGeneratedModel',
969
+ );
970
+ expect((request['args'] as Map).cast<String, Object?>(), {
971
+ 'domain': 'evo',
972
+ 'candidateHash': 'candidate-law-hash',
973
+ 'lawUsdz': 'AQID',
974
+ });
975
+ });
976
+
882
977
  test(
883
978
  'watch is a reactive stream; a runner push reaches the listener; cancel unwatches',
884
979
  () async {
@@ -942,6 +1037,118 @@ void main() {
942
1037
  expect((req['args'] as Map)['signalId'], 'signal:ws:list:main');
943
1038
  });
944
1039
 
1040
+ test(
1041
+ 'an exact write receipt advances from local acceptance to remote convergence',
1042
+ () async {
1043
+ await bridge.ready;
1044
+ final outcome = await bridge.offerOutcome(
1045
+ domain: 'notes',
1046
+ directiveId: 'writeNote',
1047
+ payload: {'text': 'hello'},
1048
+ );
1049
+ expect(outcome.receipt.current.phase, NomosOperationPhase.locallyAccepted);
1050
+ expect(
1051
+ bridge.session('notes').currentStatus.phase,
1052
+ SyncPhase.syncing,
1053
+ reason: 'a locally accepted write cannot remain falsely current',
1054
+ );
1055
+ expect(bridge.session('notes').currentStatus.ahead, 1);
1056
+
1057
+ runner.pushSync({
1058
+ 'session': 'notes',
1059
+ 'reason': 'write',
1060
+ 'result': {
1061
+ 'admittedIntentIds': ['intent-owned-approval'],
1062
+ },
1063
+ });
1064
+ await outcome.receipt.admitted.timeout(const Duration(seconds: 1));
1065
+ expect(outcome.receipt.current.phase,
1066
+ NomosOperationPhase.admittedToRemoteCustody);
1067
+
1068
+ runner.pushSync({
1069
+ 'session': 'notes',
1070
+ 'reason': 'drain-read',
1071
+ 'result': {
1072
+ 'convergedIntentIds': ['intent-owned-approval'],
1073
+ },
1074
+ });
1075
+ await outcome.receipt.converged.timeout(const Duration(seconds: 1));
1076
+ expect(outcome.receipt.current.isConverged, isTrue);
1077
+ expect(bridge.session('notes').currentStatus.phase, SyncPhase.idle);
1078
+ });
1079
+
1080
+ test('offline debt leaves a receipt pending and reconnect settles it',
1081
+ () async {
1082
+ await bridge.ready;
1083
+ final outcome = await bridge.offerOutcome(
1084
+ domain: 'notes',
1085
+ directiveId: 'writeNote',
1086
+ payload: {'text': 'offline'},
1087
+ );
1088
+ runner.pushSync({
1089
+ 'session': 'notes',
1090
+ 'reason': 'write',
1091
+ 'result': {'offline': true, 'stillAhead': true},
1092
+ });
1093
+ await Future<void>.delayed(Duration.zero);
1094
+ expect(outcome.receipt.current.phase, NomosOperationPhase.locallyAccepted);
1095
+
1096
+ runner.pushSync({
1097
+ 'session': 'notes',
1098
+ 'reason': 'reconnect',
1099
+ 'result': {
1100
+ 'admittedIntentIds': ['intent-owned-approval'],
1101
+ 'convergedIntentIds': ['intent-owned-approval'],
1102
+ },
1103
+ });
1104
+ await outcome.receipt.converged.timeout(const Duration(seconds: 1));
1105
+ });
1106
+
1107
+ test('a generated child workspace inherits the parent birth receipt',
1108
+ () async {
1109
+ await bridge.ready;
1110
+ final outcome = await bridge.offerOutcome(
1111
+ domain: 'notes',
1112
+ directiveId: 'birthChild',
1113
+ payload: {'name': 'child'},
1114
+ );
1115
+ final childReceipt =
1116
+ bridge.session('notes-child').nomosInternalWorkspaceCreationReceipt();
1117
+ expect(childReceipt, same(outcome.receipt));
1118
+ });
1119
+
1120
+ test(
1121
+ 'background authentication failure refreshes through the retained application session',
1122
+ () async {
1123
+ String token(String marker) =>
1124
+ '${base64Url.encode(utf8.encode('{"alg":"none"}')).replaceAll('=', '')}.'
1125
+ '${base64Url.encode(utf8.encode('{"sub":"alice","iss":"test","marker":"$marker"}')).replaceAll('=', '')}.x';
1126
+ await bridge.ready;
1127
+ await bridge.connect(
1128
+ cloud: 'https://cloud.example',
1129
+ workspace: 'notes',
1130
+ session: 'notes',
1131
+ authToken: token('initial'),
1132
+ );
1133
+ bridge.session('notes').nomosInternalRetainCloudAccess(
1134
+ cloud: 'https://cloud.example',
1135
+ signIn: () async => token('refreshed'),
1136
+ );
1137
+
1138
+ runner.pushSync({
1139
+ 'session': 'notes',
1140
+ 'reason': 'reconnect',
1141
+ 'result': {
1142
+ 'pushFailed': true,
1143
+ 'pushError': {'status': 401, 'error': 'token expired'},
1144
+ },
1145
+ });
1146
+ await Future<void>.delayed(const Duration(milliseconds: 10));
1147
+ final refresh = runner.sent
1148
+ .lastWhere((message) => message['op'] == 'refreshCredentials');
1149
+ expect((refresh['args'] as Map)['authToken'], token('refreshed'));
1150
+ });
1151
+
945
1152
  test(
946
1153
  'background sync status never calls an offline or failed round successful',
947
1154
  () async {
@@ -979,6 +1186,147 @@ void main() {
979
1186
  expect(bridge.currentStatus.lastError, isNull);
980
1187
  });
981
1188
 
1189
+ test('application sync status scopes and folds every connected workspace',
1190
+ () async {
1191
+ await bridge.ready;
1192
+ await bridge.connect(cloud: 'https://c', workspace: 'home');
1193
+ await bridge.connect(cloud: 'https://c', workspace: 'estate');
1194
+
1195
+ final status = await bridge.syncStatus.firstWhere(
1196
+ (value) => value.ahead == 4 && value.behind == 1,
1197
+ );
1198
+
1199
+ expect(status.saved, 7);
1200
+ expect(status.onMain, 9);
1201
+ expect(status.deadLetters, 1);
1202
+ final metricRequests =
1203
+ runner.sent.where((message) => message['op'] == 'metrics').toList();
1204
+ expect(metricRequests, hasLength(2));
1205
+ expect(
1206
+ metricRequests
1207
+ .map((message) => (message['args'] as Map)['session'])
1208
+ .toSet(),
1209
+ {'home', 'estate'},
1210
+ );
1211
+ });
1212
+
1213
+ test('workspace application status is isolated from sibling workspace debt',
1214
+ () async {
1215
+ await bridge.ready;
1216
+ await bridge.connect(cloud: 'https://c', workspace: 'home');
1217
+ await bridge.connect(cloud: 'https://c', workspace: 'estate');
1218
+
1219
+ expect(
1220
+ bridge.session('home').currentStatus.phase,
1221
+ SyncPhase.syncing,
1222
+ reason: 'an unmeasured session must not claim to be current',
1223
+ );
1224
+
1225
+ final home = await bridge.session('home').syncStatus.firstWhere(
1226
+ (value) => value.saved == 2,
1227
+ );
1228
+ final estate = await bridge.session('estate').syncStatus.firstWhere(
1229
+ (value) => value.saved == 5,
1230
+ );
1231
+
1232
+ expect(home.ahead, 0);
1233
+ expect(home.behind, 0);
1234
+ expect(estate.ahead, 4);
1235
+ expect(estate.behind, 1);
1236
+
1237
+ runner.pushSync({
1238
+ 'session': 'estate',
1239
+ 'reason': 'reconnect',
1240
+ 'ok': false,
1241
+ 'error': 'estate only failed',
1242
+ });
1243
+ await Future<void>.delayed(Duration.zero);
1244
+ expect(bridge.session('home').currentStatus.phase, SyncPhase.idle);
1245
+ expect(bridge.session('estate').currentStatus.phase, SyncPhase.error);
1246
+ expect(bridge.currentStatus.phase, SyncPhase.error);
1247
+ });
1248
+
1249
+ test('custody push refusal is scoped, actionable, and cleared by recovery',
1250
+ () async {
1251
+ await bridge.ready;
1252
+ await bridge.connect(cloud: 'https://c', workspace: 'home');
1253
+ await bridge.connect(cloud: 'https://c', workspace: 'estate');
1254
+
1255
+ runner.pushSync({
1256
+ 'session': 'estate',
1257
+ 'reason': 'local-write',
1258
+ 'result': {
1259
+ 'stillAhead': true,
1260
+ 'pushFailed': true,
1261
+ 'pushError': {
1262
+ 'status': 409,
1263
+ 'refused': true,
1264
+ 'error': 'candidate law is not admitted',
1265
+ },
1266
+ },
1267
+ });
1268
+ await Future<void>.delayed(Duration.zero);
1269
+
1270
+ expect(bridge.session('home').currentStatus.phase, isNot(SyncPhase.error));
1271
+ expect(bridge.session('estate').currentStatus.phase, SyncPhase.error);
1272
+ expect(
1273
+ bridge.session('estate').currentStatus.lastError,
1274
+ 'Shared custody refused this workspace change (HTTP 409): '
1275
+ 'candidate law is not admitted',
1276
+ );
1277
+ expect(bridge.currentStatus.phase, SyncPhase.error);
1278
+
1279
+ runner.pushSync({
1280
+ 'session': 'estate',
1281
+ 'reason': 'retry',
1282
+ 'result': {'stillAhead': false},
1283
+ });
1284
+ await Future<void>.delayed(Duration.zero);
1285
+
1286
+ expect(bridge.session('estate').currentStatus.phase, SyncPhase.idle);
1287
+ expect(bridge.session('estate').currentStatus.lastError, isNull);
1288
+ });
1289
+
1290
+ test('status listener attached before connect never sends ambiguous metrics',
1291
+ () async {
1292
+ await bridge.ready;
1293
+ final statuses = <SyncStatus>[];
1294
+ final subscription = bridge.syncStatus.listen(statuses.add);
1295
+ await Future<void>.delayed(Duration.zero);
1296
+
1297
+ expect(
1298
+ runner.sent.where((message) => message['op'] == 'metrics'),
1299
+ isEmpty,
1300
+ );
1301
+
1302
+ await bridge.connect(cloud: 'https://c', workspace: 'home');
1303
+ await bridge.connect(cloud: 'https://c', workspace: 'estate');
1304
+ for (var attempt = 0;
1305
+ attempt < 20 &&
1306
+ !statuses.any((status) => status.ahead == 4 && status.behind == 1);
1307
+ attempt += 1) {
1308
+ await Future<void>.delayed(const Duration(milliseconds: 10));
1309
+ }
1310
+
1311
+ final metricRequests =
1312
+ runner.sent.where((message) => message['op'] == 'metrics').toList();
1313
+ expect(metricRequests, isNotEmpty);
1314
+ expect(
1315
+ metricRequests.every(
1316
+ (message) =>
1317
+ (message['args'] as Map)['session'] is String &&
1318
+ ((message['args'] as Map)['session'] as String).isNotEmpty,
1319
+ ),
1320
+ isTrue,
1321
+ );
1322
+ expect(
1323
+ statuses.any((status) => status.ahead == 4 && status.behind == 1),
1324
+ isTrue,
1325
+ );
1326
+ expect(statuses.last.lastError, isNull);
1327
+ await subscription.cancel();
1328
+ });
1329
+
982
1330
  test('a law refusal throws NomosBridgeError with the verbatim message',
983
1331
  () async {
984
1332
  await bridge.ready;
@@ -1,3 +1,8 @@
1
+ ## 0.8.3
2
+
3
+ - Add the typed `GitObjectId` value used to identify immutable custody objects without falling back to
4
+ interchangeable strings.
5
+
1
6
  ## 0.8.2
2
7
 
3
8
  - Carry the typed bounded-startup, missing-law and governed child-policy errors used by the USDA-native
@@ -46,6 +46,7 @@ export 'src/invitation.dart' show Identity, User, Recipient, Invitation;
46
46
  // birth methods — actor/authorSecret are distinct types, so a transposition is a compile error.
47
47
  export 'src/principal.dart';
48
48
  export 'src/sha256_digest.dart';
49
+ export 'src/git_object_id.dart';
49
50
  // The CUSTODY-LIFECYCLE types (rebirth_and_custody_lifecycle.md): NomosCustodyBreak / NomosCustodyStatus /
50
51
  // CustodyBreakPolicy — custody incompatibility as a first-class, typed, app-decidable event. Pure data.
51
52
  export 'src/custody.dart';
@@ -0,0 +1,37 @@
1
+ // NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
2
+ // remotes are replaceable custody/transport, not truth.
3
+
4
+ /// A Git object identity in Nomos's current gix custody spelling: exactly 40
5
+ /// lowercase hexadecimal characters.
6
+ ///
7
+ /// This is deliberately not interchangeable with [String] or a semantic
8
+ /// content digest. The wrapper is zero-cost at runtime and validates at the
9
+ /// application boundary before an offer is assembled.
10
+ extension type const GitObjectId._(String value) {
11
+ factory GitObjectId(String value) {
12
+ if (!_isLowercaseGitObjectId(value)) {
13
+ throw FormatException(
14
+ 'expected a 40-character lowercase hexadecimal Git object id',
15
+ value,
16
+ );
17
+ }
18
+ return GitObjectId._(value);
19
+ }
20
+
21
+ static GitObjectId parse(String value) => GitObjectId(value);
22
+
23
+ static GitObjectId? tryParse(String value) {
24
+ if (!_isLowercaseGitObjectId(value)) return null;
25
+ return GitObjectId._(value);
26
+ }
27
+ }
28
+
29
+ bool _isLowercaseGitObjectId(String value) {
30
+ if (value.length != 40) return false;
31
+ for (final codeUnit in value.codeUnits) {
32
+ final digit = codeUnit >= 0x30 && codeUnit <= 0x39;
33
+ final lowercaseHex = codeUnit >= 0x61 && codeUnit <= 0x66;
34
+ if (!digit && !lowercaseHex) return false;
35
+ }
36
+ return true;
37
+ }
@@ -9,7 +9,7 @@ description: >-
9
9
  Pure kernel wire/data types for Nomos Dart frontends — no transport, no bridge,
10
10
  no runtime. The hand-written serde-JSON-compatible types plus the offer codec,
11
11
  reactive read engine, schema validation, and data-only error/trace types.
12
- version: 0.8.2
12
+ version: 0.8.3
13
13
 
14
14
  environment:
15
15
  sdk: ^3.5.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@githolon/dsl",
3
- "version": "0.103.3",
3
+ "version": "0.103.5",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Nomos 2 domain-authoring DSL: aggregates + directives in TS, executed and encoded to the Rust kernel's wire shapes.",