@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
@@ -280,6 +280,9 @@ mixin NmsPlatformCredentialProviderHost
280
280
  } on NomosConsoleEnrolmentRefused catch (refusal) {
281
281
  return refused(refusal.status, refusal.message,
282
282
  code: refusal.code, retryable: refusal.retryable);
283
+ } on TimeoutException {
284
+ return failed('the console did not answer within '
285
+ '${nomosConsoleEnrolmentTimeout.inSeconds} s; asking again');
283
286
  } catch (error) {
284
287
  // Never echo the request: it carries the token.
285
288
  return failed('the console could not be reached (${error.runtimeType})');
@@ -126,6 +126,14 @@ enum WorkspaceFollowPhase {
126
126
  rebornRemotely,
127
127
  replacingLocal,
128
128
  localCopyUnreadable,
129
+
130
+ /// This peer's kernel deterministically refused the last round at the same
131
+ /// local head and remote frontier: sync is blocked, not retrying. No timer
132
+ /// re-runs it; it runs again on new local work, an upstream head change, a
133
+ /// socket reopen or a changed kernel/runtime. [WorkspaceFollowStatus.retryAt]
134
+ /// is set only once such an event made it due, and
135
+ /// [WorkspaceFollowStatus.detail] carries the kernel's refusal.
136
+ kernelRefused,
129
137
  unknown,
130
138
  }
131
139
 
@@ -182,6 +190,9 @@ final class WorkspaceFollowStatus {
182
190
  nomos_protocol.FollowObservationPhase
183
191
  .FOLLOW_OBSERVATION_PHASE_LOCAL_COPY_UNREADABLE =>
184
192
  WorkspaceFollowPhase.localCopyUnreadable,
193
+ nomos_protocol.FollowObservationPhase
194
+ .FOLLOW_OBSERVATION_PHASE_KERNEL_REFUSED =>
195
+ WorkspaceFollowPhase.kernelRefused,
185
196
  _ => WorkspaceFollowPhase.unknown,
186
197
  },
187
198
  live: report.live,
package/dart/pubspec.lock CHANGED
@@ -222,7 +222,7 @@ packages:
222
222
  path: "../dart_types"
223
223
  relative: true
224
224
  source: path
225
- version: "0.13.0"
225
+ version: "0.15.0"
226
226
  package_config:
227
227
  dependency: "direct main"
228
228
  description:
package/dart/pubspec.yaml CHANGED
@@ -7,7 +7,7 @@ repository: https://github.com/Captain-App/nomos2
7
7
  description: >-
8
8
  Nomos 2 Dart client — generated domain values, intent payloads and application
9
9
  lifecycle types over the typed Nomos kernel protocol and USDA custody model.
10
- version: 0.68.0
10
+ version: 0.70.1
11
11
 
12
12
  environment:
13
13
  sdk: ^3.5.0
@@ -18,7 +18,7 @@ dependencies:
18
18
  # The pure-data core (wire types, offer codec, NomosReads, schema validation, error
19
19
  # info). nomos_client depends on and re-exports it, so existing imports are unchanged.
20
20
  # For local monorepo dev, pubspec_overrides.yaml points this at ../dart_types.
21
- nomos_types: ^0.13.0
21
+ nomos_types: ^0.15.0
22
22
  fixnum: ^1.1.1
23
23
  json_schema: ^5.2.2
24
24
  crypto: ^3.0.0
@@ -1,9 +1,11 @@
1
+ import 'dart:async';
1
2
  import 'dart:convert';
2
3
  import 'dart:io';
3
4
  import 'dart:typed_data';
4
5
 
5
6
  import 'package:fixnum/fixnum.dart';
6
7
  import 'package:nomos_client/src/console_enrolment.dart';
8
+ import 'package:nomos_client/src/console_http_io.dart';
7
9
  import 'package:nomos_client/src/runtime_v2_transport.dart';
8
10
  import 'package:nomos_peer_host/nomos_peer_host.dart'
9
11
  show NomosDeviceIdentityStore, NomosProviderCredentialStore;
@@ -224,4 +226,49 @@ void main() {
224
226
  final receipt = await noKey.performConsoleEnrolment(effect);
225
227
  expect(receipt.failure.message, contains('no usable identity'));
226
228
  });
229
+
230
+ test(
231
+ 'a console that does not answer is a bounded, retryable transport failure (CAP-807)',
232
+ () async {
233
+ // The default transport gives up on its own deadline instead of holding the write.
234
+ final silent = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
235
+ final held = <Socket>[];
236
+ silent.listen(held.add);
237
+ try {
238
+ final started = DateTime.now();
239
+ await expectLater(
240
+ nomosDefaultConsolePost(
241
+ Uri.parse('http://127.0.0.1:${silent.port}/v1/enrol'), '{}',
242
+ timeout: const Duration(milliseconds: 300)),
243
+ throwsA(isA<TimeoutException>()));
244
+ expect(DateTime.now().difference(started).inSeconds, lessThan(5));
245
+ } finally {
246
+ for (final socket in held) {
247
+ socket.destroy();
248
+ }
249
+ await silent.close();
250
+ }
251
+ // The shell reports it typed, as a failure Peer Office asks again after.
252
+ final seed = Uint8List(32)..[0] = 7;
253
+ final platform = _Platform([])
254
+ ..registerDeviceIdentityStore(_Store({'device': seed}))
255
+ ..registerCredentialProvider(
256
+ capability: 'home',
257
+ cloud: 'x',
258
+ signIn: ({String? deviceChallenge}) async => 'token')
259
+ ..registerConsolePost(
260
+ (url, text) async => throw TimeoutException('slow'));
261
+ final receipt = await platform.performConsoleEnrolment(
262
+ p.PeerConsoleEnrolmentEffect(
263
+ effectId: 'e',
264
+ deviceIdentity: 'device',
265
+ capability: 'home',
266
+ console: 'http://console.test',
267
+ tenant: 't',
268
+ scope: 'user',
269
+ workspaces: ['estate-a']));
270
+ expect(receipt.hasFailure(), isTrue);
271
+ expect(receipt.failure.message,
272
+ 'the console did not answer within 20 s; asking again');
273
+ });
227
274
  }
@@ -0,0 +1,36 @@
1
+ import 'package:fixnum/fixnum.dart' as fixnum;
2
+ import 'package:nomos_client/src/sync_status_projection.dart';
3
+ import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
4
+ import 'package:test/test.dart';
5
+
6
+ void main() {
7
+ test('a follow the kernel refused is blocked, not retrying', () {
8
+ final blocked =
9
+ WorkspaceFollowStatus.fromRuntime(protocol.FollowObservation(
10
+ workspace: 'estate',
11
+ phase: protocol
12
+ .FollowObservationPhase.FOLLOW_OBSERVATION_PHASE_KERNEL_REFUSED,
13
+ detail: 'NMS-KERNEL-CALL: refused',
14
+ attempt: 2,
15
+ ));
16
+ expect(blocked.phase, WorkspaceFollowPhase.kernelRefused);
17
+ expect(blocked.retryAt, isNull,
18
+ reason: 'no timer re-runs a blocked refusal');
19
+ });
20
+
21
+ test('an event made a refused follow due again at its re-run time', () {
22
+ final retryAt = DateTime.utc(2026, 10, 8, 9, 0, 10);
23
+ final status = WorkspaceFollowStatus.fromRuntime(protocol.FollowObservation(
24
+ workspace: 'estate',
25
+ phase: protocol
26
+ .FollowObservationPhase.FOLLOW_OBSERVATION_PHASE_KERNEL_REFUSED,
27
+ detail:
28
+ 'NMS-KERNEL-CALL: invalid or repeated command frontier commit marker',
29
+ retryAtUnixMillis: fixnum.Int64(retryAt.millisecondsSinceEpoch),
30
+ attempt: 2,
31
+ ));
32
+ expect(status.phase, WorkspaceFollowPhase.kernelRefused);
33
+ expect(status.detail, startsWith('NMS-KERNEL-CALL:'));
34
+ expect(status.retryAt, retryAt.toLocal());
35
+ });
36
+ }
@@ -11,6 +11,7 @@ import 'package:nomos_client/src/application_model_adoption.dart';
11
11
  import 'package:nomos_client/src/runtime_v2_transport.dart';
12
12
  import 'package:nomos_client/src/sync_status_projection.dart';
13
13
  import 'package:nomos_types/nomos_kernel_protocol.dart' as p;
14
+ import 'package:nomos_types/nomos_types.dart' show NomosDomainRequirement;
14
15
  import 'package:test/test.dart';
15
16
 
16
17
  const lawSha = '5efef545fdadae764e29ba678833073e0245b1b76e315d3a5fe71c6a65240a45';
@@ -89,6 +90,54 @@ void main() {
89
90
  throwsArgumentError);
90
91
  });
91
92
 
93
+ test('per-domain requirements (CAP-666) are optional, keyed and checked',
94
+ () {
95
+ expect(model().domainRequirements, isEmpty);
96
+ final pinned = NomosApprovedModel(
97
+ unitSet: unitSet,
98
+ lawSha256: lawSha,
99
+ reason: 'r',
100
+ domainKeys: const ['home'],
101
+ loadPackage: () async => Uint8List(0),
102
+ domainRequirements: {
103
+ 'home': NomosDomainRequirement(
104
+ domainKey: 'home',
105
+ unitSha256: '3' * 64,
106
+ requirementSha256: '4' * 64),
107
+ });
108
+ expect(pinned.domainRequirements['home']!.unitSha256, '3' * 64);
109
+ expect(() => pinned.domainRequirements['x'] = pinned.domainRequirements['home']!,
110
+ throwsUnsupportedError);
111
+ expect(
112
+ () => NomosApprovedModel(
113
+ unitSet: unitSet,
114
+ lawSha256: lawSha,
115
+ reason: 'r',
116
+ domainKeys: const ['home'],
117
+ loadPackage: () async => Uint8List(0),
118
+ domainRequirements: {
119
+ 'estate': NomosDomainRequirement(
120
+ domainKey: 'home',
121
+ unitSha256: '3' * 64,
122
+ requirementSha256: '4' * 64),
123
+ }),
124
+ throwsArgumentError);
125
+ expect(
126
+ () => NomosApprovedModel(
127
+ unitSet: unitSet,
128
+ lawSha256: lawSha,
129
+ reason: 'r',
130
+ domainKeys: const ['home'],
131
+ loadPackage: () async => Uint8List(0),
132
+ domainRequirements: {
133
+ 'home': NomosDomainRequirement(
134
+ domainKey: 'home',
135
+ unitSha256: 'nope',
136
+ requirementSha256: '4' * 64),
137
+ }),
138
+ throwsArgumentError);
139
+ });
140
+
92
141
  test('its package source answers only the identity it was released with',
93
142
  () async {
94
143
  final source = NmsApprovedModelLawPackageSource(model());
@@ -1,4 +1,5 @@
1
1
  import 'dart:convert';
2
+ import 'dart:io';
2
3
  import 'dart:typed_data';
3
4
 
4
5
  import 'package:nomos_client/src/nms_generated_runtime.dart';
@@ -46,4 +47,70 @@ def NomosReadResult "Result"
46
47
  'Ship',
47
48
  );
48
49
  });
50
+
51
+ // CAP-862: the shared conformance vectors. Every canonical literal, and every legacy `\uXXXX`
52
+ // spelling an older kernel wrote, reads back as the value it names.
53
+ test('decodes the canonical and legacy USDA string vectors', () {
54
+ final vectors = jsonDecode(
55
+ File('../conformance/usda_string.v1.json').readAsStringSync())
56
+ as Map<String, dynamic>;
57
+ final cases = [
58
+ ...(vectors['encode'] as List).cast<Map<String, dynamic>>(),
59
+ ...(vectors['legacyDecode'] as List).cast<Map<String, dynamic>>(),
60
+ ];
61
+ expect(cases.length, greaterThan(60));
62
+ for (final vector in cases) {
63
+ final result = NmsUsdaResult.parse(
64
+ Uint8List.fromList(
65
+ utf8.encode('''#usda 1.0
66
+ (
67
+ defaultPrim = "Result"
68
+ )
69
+
70
+ def NomosReadResult "Result"
71
+ {
72
+ custom uniform bool nomos:done = true
73
+
74
+ def NomosListValue "Rows"
75
+ {
76
+ def NomosRecordValue "i_00000000"
77
+ {
78
+ custom uniform string nomos:id = "note:1"
79
+ custom rel nomos:type = </Nomos/Domains/d_1/Aggregates/a_note>
80
+
81
+ def NomosStringValue "f_title"
82
+ {
83
+ custom rel nomos:field = </Nomos/Domains/d_1/Aggregates/a_note/Fields/f_title>
84
+ custom uniform string nomos:value = ${vector['usda']}
85
+ }
86
+ }
87
+ }
88
+ }
89
+ '''),
90
+ ),
91
+ );
92
+ expect(
93
+ result.rows.single
94
+ .requiredField('/Nomos/Domains/d_1/Aggregates/a_note/Fields/f_title')
95
+ .stringValue(),
96
+ vector['value'],
97
+ reason: vector['name'] as String,
98
+ );
99
+ }
100
+ });
101
+
102
+ test('framework-call quoting writes every vector canonically', () {
103
+ final vectors = jsonDecode(
104
+ File('../conformance/usda_string.v1.json').readAsStringSync())
105
+ as Map<String, dynamic>;
106
+ for (final vector
107
+ in (vectors['encode'] as List).cast<Map<String, dynamic>>()) {
108
+ expect(NmsUsdaLayer.quote(vector['value'] as String), vector['usda'],
109
+ reason: vector['name'] as String);
110
+ }
111
+ for (final point in (vectors['refusedCodePoints'] as List).cast<int>()) {
112
+ expect(() => NmsUsdaLayer.quote('a${String.fromCharCode(point)}b'),
113
+ throwsArgumentError);
114
+ }
115
+ });
49
116
  }
@@ -61,6 +61,47 @@ void main() {
61
61
  expect(receipt.current.phase, NomosOperationPhase.converged);
62
62
  });
63
63
 
64
+ test(
65
+ 'a birth custody refused as already parented fails its creation receipt, '
66
+ 'typed and terminal (CAP-804)', () async {
67
+ final projection = NmsOperationReceiptProjection();
68
+ final birth = projection.register('home-bob', 'intent-birth');
69
+ projection.rememberBirth('notebook-1', birth);
70
+ final other = projection.register('home-bob', 'intent-other');
71
+ projection.advance(
72
+ 'home-bob',
73
+ protocol.SyncWorkspaceResponse(
74
+ pushFailed: true,
75
+ admittedIntentIds: const ['intent-other'],
76
+ birthOutcomes: [
77
+ protocol.BirthOutcome(
78
+ workspace: 'notebook-1',
79
+ childWorkspace: 'notebook-1',
80
+ parentWorkspace: 'home-bob',
81
+ status: protocol.BirthOutcomeStatus.BIRTH_OUTCOME_STATUS_REFUSED,
82
+ code: 'birth-child-already-parented',
83
+ refusal: protocol.KernelRefusal(
84
+ terminal: true,
85
+ message: "[birth-child-already-parented] workspace 'notebook-1' "
86
+ 'already has a parent',
87
+ ),
88
+ ),
89
+ ],
90
+ ),
91
+ );
92
+ await expectLater(
93
+ birth.admitted,
94
+ throwsA(isA<NomosOperationRefused>()
95
+ .having((r) => r.code, 'code', 'birth-child-already-parented')
96
+ .having((r) => r.childWorkspace, 'child', 'notebook-1')),
97
+ );
98
+ await expectLater(birth.converged, throwsA(isA<NomosOperationRefused>()));
99
+ expect(birth.current.isRefused, isTrue);
100
+ expect(birth.current.phase, NomosOperationPhase.locallyAccepted);
101
+ // The parent's other write in the same round is unaffected.
102
+ await other.admitted;
103
+ });
104
+
64
105
  test('typed watch registry only correlates runtime events and cancellation',
65
106
  () async {
66
107
  final registry = NmsTypedWatchRegistry();
@@ -1,3 +1,33 @@
1
+ ## 0.15.0
2
+
3
+ - `FollowObservationPhase.FOLLOW_OBSERVATION_PHASE_KERNEL_REFUSED`: this peer's kernel deterministically refused a replica follow's round, so the follow's sync is blocked and is not retrying.
4
+
5
+ - Generated value objects compare by the law's canonical value encoding, `nomos.vo-canonical.v1` (RFC 8785 over the value's `toJson()`): `NmsInternalValueIdentity.equals` / `hash` / `canonical` (CAP-853). Member order never matters, `2` and `2.0` are one number, `-0` is `0`, and an omitted optional member differs from `null`.
6
+
7
+ ## 0.14.0
8
+
9
+ - Live stream frames for peer catch-up (CAP-826): `LiveOfferFrame` gains checkpoint, history, ref, pack and
10
+ refusal frames; `PeerStreamEffect`/`PeerStreamReceipt`; the runtime command `serveLiveStream`; and the kernel's
11
+ shallow pack request (`PackDeltaRequest.depth`, `below_head`, `also_heads`).
12
+
13
+ - `NomosDomainRequirement` and `NomosApprovedModel.domainRequirements` (CAP-666): per application domain key,
14
+ the exact law unit (`domain:<key>` of the unit set) and the generated consumer requirement identity a client
15
+ was compiled against. A workspace serving that unit for the key is current for the release whatever else its
16
+ unit set carries. `AdoptApplicationModelRequest` carries them as `domain_requirements` (field 7,
17
+ `DomainUnitRequirement`). Empty on clients generated before they existed: `unitSet` alone then decides.
18
+ - `KeyedBirthName.parent` (field 3) is reserved and unused (CAP-803): a business child is named by namespace and
19
+ key, fixed at birth. CAP-788's parent-scoped naming is withdrawn before release; one parent per node is the
20
+ kernel's `[birth-child-already-parented]` refusal.
21
+ - Carry the current generated kernel Protobuf contract: `FrontierTransitionRequest.candidateUnits`,
22
+ `InterfaceCompatibilityQuery.candidateUnits` and `InterfaceCompatibilityResponse.candidateHash` qualify a law
23
+ release without installing it (CAP-789).
24
+ `ApplyPackRequest.work_budget_bytes` / `continue_pending` and `ApplyPackResponse.import_pending` /
25
+ `work_done_bytes` import a pack in bounded steps (CAP-806). `LawIdentity`, `LawVersion`, `LawVersionClaim` and
26
+ `OfferAdmitted.law_version_warnings` (`LawVersionWarning`, `LawVersionOrder`) report an install that does not
27
+ move its law's claimed version forward (CAP-811). `PeerGitFetchEffect.alongside_refs` (`PeerGitRefsAlongside`)
28
+ and `PeerGitFetchResponse.alongside_refs` (`PeerGitRefsFetched`) carry a head-only mount's settled-letter outcome
29
+ refs beside main.
30
+
1
31
  ## 0.13.0
2
32
 
3
33
  - `NomosApprovedModel` requires `unitSet`: the law a release approves is its unit set identity (the generated
@@ -12,6 +12,55 @@ import 'dart:typed_data';
12
12
 
13
13
  final RegExp _lowerHex64 = RegExp(r'^[0-9a-f]{64}$');
14
14
 
15
+ /// ONE APPLICATION DOMAIN'S REQUIREMENT (CAP-666), as the compiler generated it
16
+ /// beside the unit set: the exact law unit the client was compiled against
17
+ /// (`domain:<key>` in `build/<name>.units`) and the identity of the generated
18
+ /// consumer requirement for that domain (the domain's slice of
19
+ /// `<name>.compatibility.usda`, which the kernel's Compatibility Desk judges).
20
+ ///
21
+ /// A workspace serving exactly [unitSha256] for [domainKey] satisfies this
22
+ /// release for that domain even when its unit set carries other domains the
23
+ /// application never touches. Identities only; the bytes stay in the generated
24
+ /// client. Const-constructible so generated code can bake it; the approved
25
+ /// model validates it.
26
+ final class NomosDomainRequirement {
27
+ const NomosDomainRequirement({
28
+ required this.domainKey,
29
+ required this.unitSha256,
30
+ required this.requirementSha256,
31
+ });
32
+
33
+ /// The application domain key this requirement is about.
34
+ final String domainKey;
35
+
36
+ /// The hash of the domain's law unit (`domain:<key>`), 64 lowercase hex.
37
+ final String unitSha256;
38
+
39
+ /// The sha256 of the domain's generated compatibility requirement, 64
40
+ /// lowercase hex. Observation and qualification name the contract by it.
41
+ final String requirementSha256;
42
+
43
+ /// The wire shape the inventory carries (identities only).
44
+ Map<String, String> toJson() => {
45
+ 'unit': unitSha256,
46
+ 'requirement': requirementSha256,
47
+ };
48
+
49
+ @override
50
+ bool operator ==(Object other) =>
51
+ other is NomosDomainRequirement &&
52
+ other.domainKey == domainKey &&
53
+ other.unitSha256 == unitSha256 &&
54
+ other.requirementSha256 == requirementSha256;
55
+
56
+ @override
57
+ int get hashCode => Object.hash(domainKey, unitSha256, requirementSha256);
58
+
59
+ @override
60
+ String toString() =>
61
+ 'NomosDomainRequirement($domainKey: unit $unitSha256, requirement $requirementSha256)';
62
+ }
63
+
15
64
  /// The law model an application release approves, supplied once at the
16
65
  /// application's Reception. Every field is required: the office refuses to
17
66
  /// adopt without the law (its unit set), the package identity, the domain keys
@@ -24,7 +73,9 @@ final class NomosApprovedModel {
24
73
  required this.reason,
25
74
  required List<String> domainKeys,
26
75
  required this.loadPackage,
27
- }) : domainKeys = List.unmodifiable(domainKeys) {
76
+ Map<String, NomosDomainRequirement> domainRequirements = const {},
77
+ }) : domainKeys = List.unmodifiable(domainKeys),
78
+ domainRequirements = Map.unmodifiable(domainRequirements) {
28
79
  if (!_lowerHex64.hasMatch(unitSet)) {
29
80
  throw ArgumentError.value(
30
81
  unitSet,
@@ -54,6 +105,24 @@ final class NomosApprovedModel {
54
105
  'must name the application domain keys the office observes on each workspace',
55
106
  );
56
107
  }
108
+ for (final entry in this.domainRequirements.entries) {
109
+ final requirement = entry.value;
110
+ if (entry.key.trim().isEmpty || requirement.domainKey != entry.key) {
111
+ throw ArgumentError.value(
112
+ domainRequirements,
113
+ 'domainRequirements',
114
+ 'must be keyed by the domain key each requirement names',
115
+ );
116
+ }
117
+ if (!_lowerHex64.hasMatch(requirement.unitSha256) ||
118
+ !_lowerHex64.hasMatch(requirement.requirementSha256)) {
119
+ throw ArgumentError.value(
120
+ requirement,
121
+ 'domainRequirements',
122
+ "'${entry.key}' must carry the generated domain unit hash and requirement identity (64 lowercase hex each)",
123
+ );
124
+ }
125
+ }
57
126
  }
58
127
 
59
128
  /// THE LAW this release approves (CAP-773): the unit set identity of the
@@ -76,6 +145,14 @@ final class NomosApprovedModel {
76
145
  /// alone; keys it does not serve are ignored.
77
146
  final List<String> domainKeys;
78
147
 
148
+ /// THE PER-DOMAIN REQUIREMENTS this release was generated against (CAP-666):
149
+ /// the generated `frameworkCalls.domainRequirements`, by domain key. A served
150
+ /// key whose workspace serves exactly that domain unit is current for this
151
+ /// release whatever else its unit set carries; the office reports them and
152
+ /// the client inventory records them as this build's contract. Empty on a
153
+ /// client generated before they existed: [unitSet] alone then decides.
154
+ final Map<String, NomosDomainRequirement> domainRequirements;
155
+
79
156
  /// The exact compiled package bytes, read only when a workspace must stage
80
157
  /// them (a bundled asset, a file beside the generated facade).
81
158
  final Future<Uint8List> Function() loadPackage;