@githolon/dsl 0.113.4 → 0.114.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/assets/operation-contract.wasm +0 -0
  2. package/compile_package.mjs +23 -3
  3. package/compile_proof.d.mts +7 -0
  4. package/compile_proof.mjs +38 -3
  5. package/dart/CHANGELOG.md +20 -0
  6. package/dart/lib/src/application_framework_calls.dart +9 -5
  7. package/dart/lib/src/generated_call.dart +14 -3
  8. package/dart/lib/src/nms_generated_runtime.dart +7 -0
  9. package/dart/lib/src/node_runner_transport.dart +77 -8
  10. package/dart/lib/src/nomos_trace.dart +107 -0
  11. package/dart/lib/src/reception/dart_reception.dart +148 -11
  12. package/dart/lib/src/rows_watch.dart +390 -0
  13. package/dart/lib/src/runner/node-entry.mjs +2803 -690
  14. package/dart/lib/src/sync_status_projection.dart +94 -1
  15. package/dart/lib/src/sync_status_reducer.dart +5 -2
  16. package/dart/lib/src/test_harness.dart +107 -41
  17. package/dart/lib/src/testing/test_peer.dart +107 -99
  18. package/dart/lib/src/testing/test_peer_host.dart +305 -0
  19. package/dart/lib/src/typed_watch_registry.dart +26 -3
  20. package/dart/pubspec.lock +1 -1
  21. package/dart/pubspec.yaml +2 -2
  22. package/dart/test/background_model_adoption_test.dart +1 -0
  23. package/dart/test/custodian_warming_test.dart +105 -0
  24. package/dart/test/fixtures/offer_watch_order_peer.dart +74 -0
  25. package/dart/test/kernel_author_after_read_test.dart +6 -3
  26. package/dart/test/kernel_fence_test.dart +4 -1
  27. package/dart/test/model_adoption_test.dart +17 -0
  28. package/dart/test/nomos_trace_test.dart +91 -0
  29. package/dart/test/own_offer_watch_order_test.dart +90 -0
  30. package/dart/test/receiving_group_open_test.dart +4 -8
  31. package/dart/test/replica_readiness_test.dart +14 -4
  32. package/dart/test/rows_watch_test.dart +331 -0
  33. package/dart/test/test_peer_host_selection_test.dart +49 -0
  34. package/dart_types/CHANGELOG.md +15 -0
  35. package/dart_types/lib/src/approved_model.dart +22 -5
  36. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +535 -1
  37. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +65 -0
  38. package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pb.dart +14 -0
  39. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +359 -2
  40. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pbenum.dart +65 -0
  41. package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +20 -2
  42. package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pb.dart +278 -9
  43. package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pb.dart +7 -85
  44. package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pb.dart +35 -0
  45. package/dart_types/pubspec.yaml +1 -1
  46. package/package.json +4 -2
  47. package/src/build_provenance.ts +7 -2
  48. package/src/codegen_dart_client.ts +21 -9
  49. package/src/codegen_dart_facade.ts +1 -0
  50. package/src/codegen_dart_framework_calls.ts +12 -9
  51. package/src/codegen_dart_scenario.ts +7 -0
  52. package/src/codegen_ts.ts +26 -24
  53. package/src/compile_package_main.ts +65 -61
  54. package/src/engine_entry.ts +4 -48
  55. package/src/law_unit_set_identity.ts +44 -0
  56. package/src/query/compile.ts +0 -0
  57. package/src/release_inspection.ts +24 -0
  58. package/src/release_publication.ts +298 -0
@@ -0,0 +1,331 @@
1
+ import 'dart:async';
2
+ import 'dart:convert';
3
+
4
+ import 'package:nomos_client/src/generated_call.dart' show NmsGeneratedResult;
5
+ import 'package:nomos_client/src/rows_watch.dart';
6
+ import 'package:nomos_types/nomos_kernel_protocol.dart' as nomos_protocol;
7
+ import 'package:nomos_types/src/proto/google/protobuf/any.pb.dart'
8
+ as google_protobuf;
9
+ import 'package:test/test.dart';
10
+
11
+ const _type = 'type.nomos.dev/nomos.operation.v1.QueryResult_q';
12
+
13
+ /// A generated row stand-in: value equality like the generated models, so
14
+ /// identity is observable separately.
15
+ final class Row {
16
+ Row(this.id, this.value);
17
+ final String id;
18
+ final int value;
19
+ @override
20
+ bool operator ==(Object other) =>
21
+ other is Row && other.id == id && other.value == value;
22
+ @override
23
+ int get hashCode => Object.hash(id, value);
24
+ @override
25
+ String toString() => '$id=$value';
26
+ }
27
+
28
+ /// The test's "generated decoder": `id=value` pairs separated by `;`.
29
+ List<Row> _decode(NmsGeneratedResult result) {
30
+ expect(result.protobufType, _type);
31
+ final text = utf8.decode(result.protobufPayload);
32
+ if (text.isEmpty) return const [];
33
+ return [
34
+ for (final pair in text.split(';'))
35
+ Row(pair.split('=')[0], int.parse(pair.split('=')[1])),
36
+ ];
37
+ }
38
+
39
+ google_protobuf.Any _rows(Map<String, int> rows) => google_protobuf.Any(
40
+ typeUrl: _type,
41
+ value:
42
+ utf8.encode(rows.entries.map((e) => '${e.key}=${e.value}').join(';')),
43
+ );
44
+
45
+ nomos_protocol.WatchChanged _full(String frontier, Map<String, int> rows) =>
46
+ nomos_protocol.WatchChanged(
47
+ subscriptionId: 'w',
48
+ applicationResult: _rows(rows),
49
+ resultFrontier: frontier,
50
+ );
51
+
52
+ nomos_protocol.WatchChanged _delta(
53
+ String base,
54
+ String frontier,
55
+ Map<String, int> rows, [
56
+ List<String> removed = const [],
57
+ ]) =>
58
+ nomos_protocol.WatchChanged(
59
+ subscriptionId: 'w',
60
+ applicationDelta: nomos_protocol.ApplicationResultDelta(
61
+ baseFrontier: base,
62
+ frontier: frontier,
63
+ changedRows: _rows(rows),
64
+ removedIds: removed,
65
+ ),
66
+ );
67
+
68
+ NmsRowsMerge<Row> _merge() =>
69
+ NmsRowsMerge<Row>(decode: _decode, idOf: (row) => row.id);
70
+
71
+ void main() {
72
+ test(
73
+ 'a delta patches the complete result in id order; untouched rows keep identity',
74
+ () {
75
+ final merge = _merge();
76
+ final first = merge.apply(_full('f1', {'b': 2, 'a': 1, 'c': 3}))!;
77
+ expect(first.map((row) => row.id), ['a', 'b', 'c']);
78
+
79
+ final second = merge.apply(_delta('f1', 'f2', {'b': 20, 'd': 4}, ['c']))!;
80
+ expect(second, [Row('a', 1), Row('b', 20), Row('d', 4)]);
81
+ expect(identical(second[0], first[0]), isTrue,
82
+ reason: 'row a was not touched');
83
+
84
+ // A changed row that decodes equal keeps the held object and emits nothing.
85
+ expect(merge.apply(_delta('f2', 'f3', {'a': 1})), isNull);
86
+ final third = merge.apply(_delta('f3', 'f4', {'e': 5}))!;
87
+ expect(identical(third[0], first[0]), isTrue);
88
+ });
89
+
90
+ test(
91
+ 'a complete result equal to the held rows emits nothing and keeps identity',
92
+ () {
93
+ final merge = _merge();
94
+ final first = merge.apply(_full('f1', {'a': 1, 'b': 2}))!;
95
+ expect(merge.apply(_full('f2', {'a': 1, 'b': 2})), isNull);
96
+ final next = merge.apply(_full('f3', {'a': 1, 'b': 3}))!;
97
+ expect(identical(next[0], first[0]), isTrue);
98
+ expect(next[1], Row('b', 3));
99
+ // Fewer rows is a change even when every remaining row is equal.
100
+ expect(merge.apply(_full('f4', {'a': 1})), [Row('a', 1)]);
101
+ // The position moved with the complete result.
102
+ expect(merge.apply(_delta('f4', 'f5', {'z': 26})),
103
+ [Row('a', 1), Row('z', 26)]);
104
+ });
105
+
106
+ test('ids order by code point, as the kernel orders them', () {
107
+ final merge = _merge();
108
+ final ids = ['\u{1F600}', '~', 'a', 'B'];
109
+ final rows = merge.apply(_full('f1', {for (final id in ids) id: 0}))!;
110
+ expect(rows.map((row) => row.id), ['B', 'a', '~', '\u{1F600}']);
111
+ expect(compareCodePoints('~', '\u{1F600}'), lessThan(0));
112
+ expect('~'.compareTo('\u{1F600}'), greaterThan(0),
113
+ reason: 'UTF-16 order differs, which is why the merge does not use it');
114
+ });
115
+
116
+ test('a delta from another position ends the watch with a typed error',
117
+ () async {
118
+ final source = StreamController<nomos_protocol.WatchChanged>();
119
+ final events = <Object>[];
120
+ final done = Completer<void>();
121
+ nmsMergeRows(source.stream, _merge()).listen(
122
+ events.add,
123
+ onError: events.add,
124
+ onDone: done.complete,
125
+ );
126
+ source.add(_full('f1', {'a': 1}));
127
+ source.add(_delta('elsewhere', 'f2', {'a': 2}));
128
+ await done.future;
129
+ expect(events, hasLength(2));
130
+ final mismatch = events[1] as NomosWatchPositionMismatch;
131
+ expect(mismatch.held, 'f1');
132
+ expect(mismatch.base, 'elsewhere');
133
+ expect(source.hasListener, isFalse,
134
+ reason: 'the peer subscription is released');
135
+ });
136
+
137
+ test(
138
+ 'a delivered failure resets the rows; only a complete result restores them',
139
+ () async {
140
+ final source = StreamController<nomos_protocol.WatchChanged>();
141
+ final events = <Object>[];
142
+ final done = Completer<void>();
143
+ nmsMergeRows(source.stream, _merge()).listen(
144
+ events.add,
145
+ onError: events.add,
146
+ onDone: done.complete,
147
+ );
148
+ source.add(_full('f1', {'a': 1}));
149
+ source.addError(StateError('kernel unavailable'));
150
+ source.add(_full('f2', {'a': 1}));
151
+ source.add(_delta('f2', 'f3', {'b': 2}));
152
+ await source.close();
153
+ await done.future;
154
+ expect(events[0], [Row('a', 1)]);
155
+ expect(events[1], isA<StateError>());
156
+ expect(events[2], [Row('a', 1)],
157
+ reason: 'after a failure the complete rows are delivered again');
158
+ expect(events[3], [Row('a', 1), Row('b', 2)]);
159
+
160
+ // Without a complete result after the failure, a delta has no position.
161
+ final merge = _merge()..apply(_full('f1', {'a': 1}));
162
+ merge.reset();
163
+ expect(() => merge.apply(_delta('f1', 'f2', {})),
164
+ throwsA(isA<NomosWatchPositionMismatch>()));
165
+ });
166
+
167
+ test('each emission names the rows it changed and removed (CAP-751)', () {
168
+ final merge = _merge();
169
+ final first = merge.apply(_full('f1', {'a': 1, 'b': 2, 'c': 3}))!;
170
+ expect(first.complete, isTrue);
171
+ expect(first.changedIds, {'a', 'b', 'c'});
172
+ expect(first.removedIds, isEmpty);
173
+
174
+ final second = merge.apply(_delta('f1', 'f2', {'b': 20, 'd': 4}, ['c']))!;
175
+ expect(second.complete, isFalse);
176
+ expect(second.changedIds, {'b', 'd'});
177
+ expect(second.removedIds, {'c'});
178
+ for (final row in second) {
179
+ if (!second.changedIds.contains(row.id)) {
180
+ expect(first.contains(row), isTrue);
181
+ expect(identical(row, first.firstWhere((r) => r.id == row.id)), isTrue,
182
+ reason: 'a row outside changedIds is the object emitted before');
183
+ }
184
+ }
185
+
186
+ // Removed and re-added in one delta is a replacement.
187
+ final third = merge.apply(_delta('f2', 'f3', {'a': 10}, ['a']))!;
188
+ expect(third.changedIds, {'a'});
189
+ expect(third.removedIds, isEmpty);
190
+
191
+ // A complete result reports exactly what differs from the held rows.
192
+ final fourth = merge.apply(_full('f4', {'a': 10, 'b': 20, 'e': 5}))!;
193
+ expect(fourth.complete, isTrue);
194
+ expect(fourth.changedIds, {'e'});
195
+ expect(fourth.removedIds, {'d'});
196
+ expect(() => (fourth as List<Row>).add(Row('x', 0)),
197
+ throwsUnsupportedError);
198
+ });
199
+
200
+ group('NomosRowProjection', () {
201
+ test('projects only rows it has not seen, keeping earlier projections', () {
202
+ final merge = _merge();
203
+ var calls = 0;
204
+ final projection = NomosRowProjection<Row, String>((row) {
205
+ calls += 1;
206
+ return '${row.id}:${row.value}';
207
+ });
208
+ final first = projection.apply(merge.apply(_full('f1', {
209
+ for (var i = 0; i < 100; i += 1) 'r${i.toString().padLeft(3, '0')}': i,
210
+ }))!);
211
+ expect(calls, 100);
212
+ final second =
213
+ projection.apply(merge.apply(_delta('f1', 'f2', {'r050': -1}))!);
214
+ expect(calls, 101, reason: 'one changed row, one projection');
215
+ expect(second[50], 'r050:-1');
216
+ expect(identical(second[49], first[49]), isTrue);
217
+ expect(second.length, 100);
218
+ // The same rows again: the identical output.
219
+ final rows = merge.apply(_delta('f2', 'f3', {'r999': 9}))!;
220
+ final third = projection.apply(rows);
221
+ expect(identical(projection.apply(rows), third), isTrue);
222
+ expect(calls, 102);
223
+ });
224
+
225
+ test('an order is kept by merging new projections into the previous order',
226
+ () {
227
+ final merge = _merge();
228
+ var calls = 0;
229
+ int byValueThenId(Row a, Row b) {
230
+ final byValue = a.value.compareTo(b.value);
231
+ return byValue != 0 ? byValue : a.id.compareTo(b.id);
232
+ }
233
+
234
+ final projection = NomosRowProjection<Row, Row>((row) {
235
+ calls += 1;
236
+ return row.value < 0 ? null : row; // negative values are left out
237
+ }, order: byValueThenId);
238
+ final values = {for (var i = 0; i < 50; i += 1) 'r$i': (i * 37) % 50};
239
+ final first = projection.apply(merge.apply(_full('f1', values))!);
240
+ expect(first, [...first]..sort(byValueThenId));
241
+ expect(first.length, 50);
242
+
243
+ var frontier = 1;
244
+ List<Row> step(Map<String, int> rows, [List<String> removed = const []]) {
245
+ final emitted =
246
+ merge.apply(_delta('f$frontier', 'f${frontier + 1}', rows, removed))!;
247
+ frontier += 1;
248
+ return projection.apply(emitted);
249
+ }
250
+
251
+ var out = step({'r3': 1000, 'r7': -5, 'new': 25}, ['r9']);
252
+ final expected = [
253
+ for (final row in _merge().apply(_full('check', {
254
+ for (final e in values.entries)
255
+ if (e.key != 'r9') e.key: e.value,
256
+ 'r3': 1000,
257
+ 'r7': -5,
258
+ 'new': 25,
259
+ }))!)
260
+ if (row.value >= 0) row,
261
+ ]..sort(byValueThenId);
262
+ expect(out, expected);
263
+ expect(calls, 53, reason: 'only the three rows the delta carried');
264
+
265
+ // Leaving a row out anew changes the output; filtering it again does not.
266
+ out = step({'r7': -6});
267
+ expect(out, expected);
268
+ calls = 0;
269
+ projection.invalidate();
270
+ expect(projection.apply(out), expected);
271
+ expect(calls, expected.length);
272
+ });
273
+
274
+ test('projectRows forwards only changed outputs, one projection per listener',
275
+ () async {
276
+ final source = StreamController<List<Row>>.broadcast(sync: true);
277
+ var calls = 0;
278
+ final projected = source.stream.projectRows<String>((row) {
279
+ calls += 1;
280
+ return row.value.isEven ? row.id : null;
281
+ });
282
+ final a = <List<String>>[];
283
+ final b = <List<String>>[];
284
+ final subA = projected.listen(a.add);
285
+ final subB = projected.listen(b.add);
286
+ final r1 = Row('a', 2);
287
+ final r2 = Row('b', 3);
288
+ source.add([r1, r2]);
289
+ source.add([r1, r2]); // identical rows: nothing new to forward
290
+ source.add([r1, Row('b', 4)]);
291
+ await pumpEventQueue();
292
+ expect(a, [
293
+ ['a'],
294
+ ['a', 'b'],
295
+ ]);
296
+ expect(b, a);
297
+ expect(calls, 6, reason: 'two listeners, three distinct rows each');
298
+ await subA.cancel();
299
+ await subB.cancel();
300
+ await source.close();
301
+ });
302
+
303
+ test('a thousand rows and single-row deltas cost one projection per delta',
304
+ () async {
305
+ final source = StreamController<nomos_protocol.WatchChanged>();
306
+ var calls = 0;
307
+ final outputs = <List<String>>[];
308
+ final sub = nmsMergeRows(source.stream, _merge())
309
+ .projectRows<String>((row) {
310
+ calls += 1;
311
+ return 'p${row.value}';
312
+ }, order: (x, y) => x.compareTo(y)).listen(outputs.add);
313
+ source.add(_full('f0', {
314
+ for (var i = 0; i < 1000; i += 1) 'r${i.toString().padLeft(4, '0')}': i,
315
+ }));
316
+ for (var i = 0; i < 50; i += 1) {
317
+ source.add(_delta('f$i', 'f${i + 1}',
318
+ {'r${(i * 7).toString().padLeft(4, '0')}': 10000 + i}));
319
+ }
320
+ await pumpEventQueue();
321
+ expect(outputs, hasLength(51));
322
+ expect(calls, 1000 + 50);
323
+ final last = outputs.last;
324
+ expect(last, [...last]..sort());
325
+ expect(last.length, 1000);
326
+ await sub.cancel();
327
+ await source.close();
328
+ });
329
+ });
330
+ }
331
+
@@ -0,0 +1,49 @@
1
+ // The canonical test peer's host is DECLARED by NOMOS_TEST_PEER_HOST: unset/node is the Node
2
+ // peer, native is the Apple native host the caller registered. A value naming no host, or native
3
+ // selected without a registered host, is refused loudly — never quietly run on Node.
4
+ @TestOn('vm')
5
+ library;
6
+
7
+ import 'package:nomos_client/src/testing/test_peer_host.dart';
8
+ import 'package:test/test.dart';
9
+
10
+ void main() {
11
+ test('unset or node selects the Node peer', () {
12
+ expect(NmsTestPeerHostKind.selected(const {}), NmsTestPeerHostKind.node);
13
+ expect(
14
+ NmsTestPeerHostKind.selected(const {nmsTestPeerHostVariable: 'node'}),
15
+ NmsTestPeerHostKind.node,
16
+ );
17
+ });
18
+
19
+ test('native selects the Apple native host', () {
20
+ expect(
21
+ NmsTestPeerHostKind.selected(const {nmsTestPeerHostVariable: 'native'}),
22
+ NmsTestPeerHostKind.native,
23
+ );
24
+ });
25
+
26
+ test('a value naming no host is refused, not defaulted to Node', () {
27
+ expect(
28
+ () => NmsTestPeerHostKind.selected(
29
+ const {nmsTestPeerHostVariable: 'nativ'}),
30
+ throwsA(isA<StateError>().having(
31
+ (error) => error.message,
32
+ 'message',
33
+ contains('names no test peer host'),
34
+ )),
35
+ );
36
+ });
37
+
38
+ test('native selected without a registered host is refused with the remedy',
39
+ () {
40
+ expect(
41
+ NmsTestPeerHosts.requireNative,
42
+ throwsA(isA<StateError>().having(
43
+ (error) => error.message,
44
+ 'message',
45
+ contains('registerNomosNativeTestPeerHost()'),
46
+ )),
47
+ );
48
+ });
49
+ }
@@ -1,3 +1,18 @@
1
+ ## 0.13.0
2
+
3
+ - `NomosApprovedModel` requires `unitSet`: the law a release approves is its unit set identity (the generated
4
+ `frameworkCalls.lawUnitSet`); `lawSha256` names the package closure. `AdoptApplicationModelRequest` carries
5
+ `unit_set`. `ApplicationBirthContract` is retired: a Home is born under the law its platform serves (CAP-773).
6
+ - Live link frames: `LiveLinkClosure` and `PeerSocketClosure` record how a live connection closed (who ended it,
7
+ code, reason, duration and what it carried) (CAP-683, CAP-684).
8
+ - Rows watches can receive only the rows that changed: `ApplicationResultDelta` and `FullResultReason` (CAP-731).
9
+ `OfferStateChange` names the aggregate types an offer changed, so a local offer re-reads only the watches it
10
+ can change (CAP-760).
11
+ - `CustodianWarming` reports a custodian warming up as its own posture, not an error retry (CAP-769).
12
+ - `ReadAccessPreparation` reports a device enrolled just in time for a granted read (CAP-740).
13
+ - Carry the current generated kernel and Runtime v2 Protobuf contracts, including per-offer phase timings
14
+ (CAP-756) and residency rows (CAP-754).
15
+
1
16
  ## 0.12.0
2
17
 
3
18
  - Add the `LiveAdmission` live-link frame: the custodian's admission of a live batch, returned over the link (CAP-656).
@@ -14,21 +14,29 @@ final RegExp _lowerHex64 = RegExp(r'^[0-9a-f]{64}$');
14
14
 
15
15
  /// The law model an application release approves, supplied once at the
16
16
  /// application's Reception. Every field is required: the office refuses to
17
- /// adopt without the package identity, the domain keys it observes, the
18
- /// attributed reason the kernel's transition demands, or the package bytes a
19
- /// child must stage — none of them can be invented later.
17
+ /// adopt without the law (its unit set), the package identity, the domain keys
18
+ /// it observes, the attributed reason the kernel's transition demands, or the
19
+ /// package bytes a child must stage — none of them can be invented later.
20
20
  final class NomosApprovedModel {
21
21
  NomosApprovedModel({
22
+ required this.unitSet,
22
23
  required this.lawSha256,
23
24
  required this.reason,
24
25
  required List<String> domainKeys,
25
26
  required this.loadPackage,
26
27
  }) : domainKeys = List.unmodifiable(domainKeys) {
28
+ if (!_lowerHex64.hasMatch(unitSet)) {
29
+ throw ArgumentError.value(
30
+ unitSet,
31
+ 'unitSet',
32
+ 'must be the unit set identity of the compiled build/<name>.units (64 lowercase hex)',
33
+ );
34
+ }
27
35
  if (!_lowerHex64.hasMatch(lawSha256)) {
28
36
  throw ArgumentError.value(
29
37
  lawSha256,
30
38
  'lawSha256',
31
- 'must be the kernel-derived identity of the compiled law package (64 lowercase hex)',
39
+ 'must be the identity of the compiled package closure (64 lowercase hex)',
32
40
  );
33
41
  }
34
42
  if (reason.trim().isEmpty) {
@@ -48,7 +56,16 @@ final class NomosApprovedModel {
48
56
  }
49
57
  }
50
58
 
51
- /// The kernel-derived identity of the compiled `.law.usdz` closure.
59
+ /// THE LAW this release approves (CAP-773): the unit set identity of the
60
+ /// compiled `build/<name>.units` (the generated `frameworkCalls.lawUnitSet`).
61
+ /// A workspace is on it when the closure it serves is composed from exactly
62
+ /// these units, whatever its kernel's composer.
63
+ final String unitSet;
64
+
65
+ /// The compiled package closure (`.law.usdz`, the generated
66
+ /// `frameworkCalls.lawPackageSha256`): the bytes [loadPackage] supplies when a
67
+ /// package-lane workspace must stage them. Package — not what a unit-lane
68
+ /// workspace serves.
52
69
  final String lawSha256;
53
70
 
54
71
  /// The attributed approval text. The kernel's transition demands it whenever