@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
package/dart/pubspec.lock CHANGED
@@ -191,7 +191,7 @@ packages:
191
191
  path: "../dart_types"
192
192
  relative: true
193
193
  source: path
194
- version: "0.8.2"
194
+ version: "0.8.3"
195
195
  package_config:
196
196
  dependency: "direct main"
197
197
  description:
package/dart/pubspec.yaml CHANGED
@@ -5,10 +5,9 @@
5
5
  name: nomos_client
6
6
  repository: https://github.com/Captain-App/nomos2
7
7
  description: >-
8
- Nomos 2 Dart frontend types — the hand-written kernel wire types plus the
9
- code-generated per-domain aggregate + directive-payload types. (De)serializes
10
- the EXACT serde JSON the Rust kernel + projection produce/consume.
11
- version: 0.64.4
8
+ Nomos 2 Dart client — generated domain values, intent payloads and application
9
+ lifecycle types over the typed Nomos kernel protocol and USDA custody model.
10
+ version: 0.64.6
12
11
 
13
12
  environment:
14
13
  sdk: ^3.5.0
@@ -17,7 +16,7 @@ dependencies:
17
16
  # The pure-data core (wire types, offer codec, NomosReads, schema validation, error
18
17
  # info). nomos_client depends on and re-exports it, so existing imports are unchanged.
19
18
  # For local monorepo dev, pubspec_overrides.yaml points this at ../dart_types.
20
- nomos_types: ^0.8.2
19
+ nomos_types: ^0.8.3
21
20
  json_schema: ^5.2.2
22
21
  crypto: ^3.0.0
23
22
  package_config: ^2.1.0
@@ -100,7 +100,7 @@ void main() {
100
100
  });
101
101
 
102
102
  test(
103
- 'a plane-resident (parked) workspace opens LOCALLY — no cloud, credentials NOT invoked',
103
+ 'a plane-resident workspace binds retained cloud credentials so background sync owns it',
104
104
  () async {
105
105
  final runner = FakeRunner(resident: ['estate']);
106
106
  final bridge = NomosBridge(runner);
@@ -112,12 +112,12 @@ void main() {
112
112
  asked = true;
113
113
  return const NomosSessionCredentials(cloud: 'https://never.invalid');
114
114
  });
115
- expect(asked, isFalse,
116
- reason: 'a resident workspace never asks for credentials');
115
+ expect(asked, isTrue,
116
+ reason:
117
+ 'a generated resident child must acquire its continuous cloud posture');
117
118
  expect(s.workspace, 'estate');
118
119
  expect(runner.connectCount, 1);
119
- expect(runner.connectArgs(0).containsKey('cloud'), isFalse,
120
- reason: 'a resident open is local-only — no cloud in the connect');
120
+ expect(runner.connectArgs(0)['cloud'], 'https://never.invalid');
121
121
  await bridge.dispose();
122
122
  });
123
123
 
@@ -199,6 +199,63 @@ void main() {
199
199
  await bridge.dispose();
200
200
  });
201
201
 
202
+ test(
203
+ 'generated child inherits typed model decisions and stages opaque USDZ privately',
204
+ () async {
205
+ final runner = FakeRunner(resident: ['child']);
206
+ final bridge = NomosBridge(runner);
207
+ await bridge.ready;
208
+ await bridge.connect(
209
+ workspace: 'home',
210
+ session: 'home',
211
+ authToken: 'header.eyJzdWIiOiJhZGEifQ.signature',
212
+ );
213
+ var handled = false;
214
+ final home = bridge.session('home');
215
+ home.nomosInternalRetainModelChangeHandler((change) async {
216
+ handled = change.expectedDomainHash == 'candidate';
217
+ });
218
+ final child = await home.acquireSession('child');
219
+ await home.nomosInternalEnsureGeneratedModelInChild(
220
+ childWorkspace: 'child',
221
+ domain: 'catalogue',
222
+ candidateHash: 'candidate',
223
+ lawUsdz: const [1, 2, 3],
224
+ );
225
+ await child.nomosInternalHandleModelChange(
226
+ const DomainClientModelAdoptionRequired(
227
+ expectedDomainKey: 'catalogue',
228
+ expectedDomainHash: 'candidate',
229
+ status: 'needs-decision',
230
+ summary: 'review',
231
+ review: DomainModelReview(
232
+ checkedActions: 1,
233
+ candidateActions: 1,
234
+ sameOutcome: true,
235
+ differences: [],
236
+ ),
237
+ ),
238
+ );
239
+ expect(handled, isTrue);
240
+ final stage = runner.sent.singleWhere(
241
+ (message) => message['op'] == 'stageGeneratedModelFromParent',
242
+ );
243
+ expect((stage['args'] as Map)['sourceSession'], 'home');
244
+ expect((stage['args'] as Map)['session'], 'child');
245
+ expect((stage['args'] as Map)['lawUsdz'], 'AQID');
246
+ await home.nomosInternalStageGeneratedModel(
247
+ domain: 'catalogue',
248
+ candidateHash: 'candidate',
249
+ lawUsdz: const [4, 5, 6],
250
+ );
251
+ final rootStage = runner.sent.singleWhere(
252
+ (message) => message['op'] == 'stageGeneratedModel',
253
+ );
254
+ expect((rootStage['args'] as Map)['session'], 'home');
255
+ expect((rootStage['args'] as Map)['lawUsdz'], 'BAUG');
256
+ await bridge.dispose();
257
+ });
258
+
202
259
  test(
203
260
  'a generated application session privately refreshes cloud access for an absent referenced workspace',
204
261
  () async {
@@ -17,7 +17,6 @@
17
17
  import 'dart:convert';
18
18
  import 'dart:io';
19
19
 
20
- import 'package:crypto/crypto.dart' show sha256;
21
20
  import 'package:nomos_client/src/nms_conformance_runtime.dart';
22
21
  import 'package:test/test.dart';
23
22
 
@@ -29,8 +28,10 @@ String get _deploy =>
29
28
  const _subject = 'user:topology-test';
30
29
 
31
30
  String? _missingPrereq() {
32
- if (!File(_repoEntry).existsSync()) return 'in-repo runner not found at $_repoEntry';
33
- if (!File(_devServer).existsSync()) return 'in-repo dev server not found at $_devServer';
31
+ if (!File(_repoEntry).existsSync())
32
+ return 'in-repo runner not found at $_repoEntry';
33
+ if (!File(_devServer).existsSync())
34
+ return 'in-repo dev server not found at $_devServer';
34
35
  if (!File(_deploy).existsSync()) {
35
36
  return 'compile the fixture: cd bench/scale/fixtures/cloud-topology && node ../../../../dsl/compile_package.mjs — missing $_deploy';
36
37
  }
@@ -38,7 +39,8 @@ String? _missingPrereq() {
38
39
  }
39
40
 
40
41
  void main() {
41
- test('the co2 topology: remote home ceremony → local estate birth offline → converge → remote custody',
42
+ test(
43
+ 'the co2 topology: remote home ceremony → local estate birth offline → converge → remote custody',
42
44
  () async {
43
45
  final skip = _missingPrereq();
44
46
  if (skip != null) {
@@ -48,7 +50,12 @@ void main() {
48
50
  final diag = Platform.environment['NOMOS_HARNESS_DIAG'] == '1'
49
51
  ? (String l) => stderr.writeln(l)
50
52
  : null;
51
- final cloud = await NomosTestCloud.start(serverEntry: _devServer, platform: 'topology-platform', lawUsdzPath: _deploy, onDiagnostic: diag);
53
+ final cloud = await NomosTestCloud.start(
54
+ serverEntry: _devServer,
55
+ platform: 'topology-platform',
56
+ lawUsdzPath: _deploy,
57
+ applicationMaxChildWorkspacesPerUser: 8,
58
+ onDiagnostic: diag);
52
59
  NomosTestHarness? harness;
53
60
  try {
54
61
  // 1+2 — the remote ceremony + the local mount, in one constructor (the app's boot path).
@@ -61,60 +68,69 @@ void main() {
61
68
  onDiagnostic: diag,
62
69
  );
63
70
  final home = harness.homeSession;
64
- expect(home.workspace, startsWith('home-'),
65
- reason: 'the remote ceremony born the deterministic home slot');
71
+ expect(home.workspace, isNotEmpty,
72
+ reason:
73
+ 'the generated ceremony resolved the receiving group without exposing its naming scheme');
66
74
  final remoteHome = await cloud.remoteHead(home.workspace);
67
75
  expect(remoteHome.initialized, isTrue,
68
- reason: 'the home was born REMOTELY (custody exists before the app ever mounted it)');
76
+ reason:
77
+ 'the home was born REMOTELY (custody exists before the app ever mounted it)');
69
78
 
70
79
  // 3 — PARTITION.
71
80
  cloud.setOnline(false);
72
81
 
73
- // 4 — the home births the estate LOCALLY (the app lane never needs the network).
74
- // ownerKeyHash: the owner's device (enrolled in the home's genesis) — the estate enrols the SAME
75
- // key at birth so it accepts the owner's signed writes from intent 0 (era-0 signer-ready births).
76
- final ownerKeyHash = sha256
77
- .convert(base64Decode(harness.devicePublicKey(home.workspace)!))
78
- .toString();
82
+ // 4 — the home births the estate LOCALLY (the app lane never needs the network). The business
83
+ // payload contains business facts only; law selection, signer seating and child addressing are
84
+ // private consequences of the compiled birth declaration.
79
85
  final outcome = await harness.bridge.session(home.workspace).offerCreates(
80
- domain: 'home',
81
- directiveId: 'birthEstateWorkspace',
82
- payload: {
83
- 'owner': _subject,
84
- 'frameworkHash': harness.frameworkHash,
85
- 'lawHash': harness.lawHashes(home.workspace).first,
86
- 'bornAt': '2026-07-14T00:00:00Z',
87
- 'ownerKeyHash': ownerKeyHash,
88
- },
89
- actor: _subject,
90
- );
91
- expect(outcome.born, isNotEmpty, reason: 'the local .births() offer spawned the estate');
86
+ domain: 'home',
87
+ directiveId: 'birthEstateWorkspace',
88
+ payload: {
89
+ 'owner': _subject,
90
+ 'establishedAt': '2026-07-14T00:00:00Z',
91
+ 'principal': _subject,
92
+ 'bornAt': '2026-07-14T00:00:00Z',
93
+ },
94
+ actor: _subject,
95
+ );
96
+ expect(outcome.born, isNotEmpty,
97
+ reason: 'the local .births() offer spawned the estate');
92
98
  final estateWs = outcome.born.single;
93
99
  expect(estateWs, startsWith('estate-'));
94
100
 
95
101
  // 5 — the estate opens as a sibling session and accepts a LOCAL WRITE while offline.
96
- final estate = await harness.bridge.acquireSession(estateWs, authorSecret: harness.deviceSecret(home.workspace));
97
- await estate.offerCreates(
102
+ final estate = await home.acquireSession(estateWs,
103
+ authorSecret: harness.deviceSecret(home.workspace));
104
+ final estateWrite = await estate.offerCreates(
98
105
  domain: 'estate',
99
106
  directiveId: 'writeEstateNote',
100
- payload: {'author': _subject, 'body': 'written offline', 'writtenAt': '2026-07-14T00:00:01Z'},
107
+ payload: {
108
+ 'author': _subject,
109
+ 'body': 'written offline',
110
+ 'writtenAt': '2026-07-14T00:00:01Z'
111
+ },
101
112
  actor: _subject,
102
113
  );
103
114
 
104
115
  // 6 — the UI-facing query returns the write IMMEDIATELY (local projection; nothing awaited sync).
105
- final localRows = await estate.query('notesByAuthor', {'author': _subject});
116
+ final localRows =
117
+ await estate.query('notesByAuthor', {'author': _subject});
106
118
  expect(localRows, hasLength(1));
107
119
  expect((localRows.single['data'] as Map)['body'], 'written offline');
108
120
 
109
121
  // …and the estate has NO remote custody yet — the partition is real.
110
122
  final remoteEstateOffline = await cloud.remoteHead(estateWs);
111
123
  expect(remoteEstateOffline.initialized, isFalse,
112
- reason: 'while partitioned, the locally-born estate must have NO remote custody');
124
+ reason:
125
+ 'while partitioned, the locally-born estate must have NO remote custody');
113
126
 
114
- // 7+8 — HEAL, then converge parent-first (home's chain lands; its birth offer-effect creates the
115
- // estate's remote custody; the estate then converges against it).
127
+ // 7+8 — HEAL. The generated runtime owns reconnect and parent/child custody ordering. Await the two
128
+ // exact operation receipts; the test never calls sync(), names an endpoint or infers convergence.
116
129
  cloud.setOnline(true);
117
- await harness.converge(timeout: const Duration(seconds: 180));
130
+ await Future.wait([
131
+ outcome.receipt.converged,
132
+ estateWrite.receipt.converged,
133
+ ]).timeout(const Duration(seconds: 180));
118
134
 
119
135
  // 9 — a FRESH observer: the dev cloud's own remote projection serves the estate write.
120
136
  final http = HttpClient();
@@ -125,8 +141,10 @@ void main() {
125
141
  final body = jsonDecode(await resp.transform(utf8.decoder).join());
126
142
  final rows = ((body as Map)['rows'] as List?) ?? const [];
127
143
  expect(rows, hasLength(1),
128
- reason: 'the remote projection (a fresh observer) serves the offline-written note after converge');
129
- expect(((rows.single as Map)['data'] as Map)['body'], 'written offline');
144
+ reason:
145
+ 'the remote projection (a fresh observer) serves the offline-written note after converge');
146
+ expect(
147
+ ((rows.single as Map)['data'] as Map)['body'], 'written offline');
130
148
  } finally {
131
149
  http.close(force: true);
132
150
  }
@@ -137,10 +155,13 @@ void main() {
137
155
  expect(remoteHome2.initialized, isTrue);
138
156
  expect(remoteHome2.commits, greaterThan(0));
139
157
  expect(remoteEstate2.initialized, isTrue,
140
- reason: 'converge established the estate\'s remote custody (parent birth folded remotely)');
158
+ reason:
159
+ 'converge established the estate\'s remote custody (parent birth folded remotely)');
141
160
  expect(remoteEstate2.commits, greaterThan(0));
142
161
  } finally {
143
- try { await harness?.dispose(); } catch (_) {}
162
+ try {
163
+ await harness?.dispose();
164
+ } catch (_) {}
144
165
  await cloud.stop();
145
166
  }
146
167
  }, timeout: const Timeout(Duration(minutes: 6)));
@@ -13,11 +13,8 @@
13
13
  @TestOn('vm')
14
14
  library;
15
15
 
16
- import 'dart:convert';
17
16
  import 'dart:io';
18
17
 
19
- import 'package:crypto/crypto.dart' show sha256;
20
-
21
18
  // ignore: deprecated_member_use
22
19
  import 'package:nomos_client/src/nms_conformance_runtime.dart';
23
20
  import 'package:test/test.dart';
@@ -54,11 +51,6 @@ String? _missingPrereq() {
54
51
  return null;
55
52
  }
56
53
 
57
- /// The owner-device keyHash (the harness device the install lane minted) — the estate enrols it at birth
58
- /// so the born child accepts the owner's signed writes (era-0 signer-ready births).
59
- String _ownerKeyHash(NomosTestHarness h) =>
60
- sha256.convert(base64Decode(h.devicePublicKey(h.workspace)!)).toString();
61
-
62
54
  Future<NomosTestHarness> _harness() => NomosTestHarness.ephemeral(
63
55
  lawUsdzPaths: [_deployJson],
64
56
  nodeEntry: _repoEntry,
@@ -70,14 +62,12 @@ Future<NomosOfferOutcome> _birthEstate(
70
62
  NomosSession session,
71
63
  NomosTestHarness harness, {
72
64
  String owner = _owner,
73
- bool enrollOwnerKey = true,
74
65
  }) {
75
66
  final facts = BirthEstateWorkspacePayload(
76
67
  owner: owner,
77
68
  frameworkHash: harness.frameworkHash!,
78
69
  lawHash: harness.lawHashes(harness.workspace).first,
79
70
  bornAt: DateTime.now().toUtc().toIso8601String(),
80
- ownerKeyHash: enrollOwnerKey ? _ownerKeyHash(harness) : null,
81
71
  );
82
72
  return session.offerOutcome(
83
73
  domain: 'home',
@@ -88,6 +78,31 @@ Future<NomosOfferOutcome> _birthEstate(
88
78
  }
89
79
 
90
80
  void main() {
81
+ test('an installed domain missing a required member is a newer-client shape',
82
+ () {
83
+ expect(
84
+ nmsInternalClientExpectsNewerInterface(
85
+ domain: 'home',
86
+ activeDomainKeys: const ['home', 'nomos'],
87
+ missing: const ['CatalogueAccessIndexEntry.catalogueWorkspace'],
88
+ incompatible: const [],
89
+ ),
90
+ isTrue,
91
+ reason:
92
+ 'the generated candidate must enter exact-history adoption instead of deployment diagnostics',
93
+ );
94
+ expect(
95
+ nmsInternalClientExpectsNewerInterface(
96
+ domain: 'home',
97
+ activeDomainKeys: const ['catalogue', 'nomos'],
98
+ missing: const ['home'],
99
+ incompatible: const [],
100
+ ),
101
+ isFalse,
102
+ reason: 'a genuinely absent domain keeps the not-deployed diagnosis',
103
+ );
104
+ });
105
+
91
106
  test(
92
107
  'generated applications: mount → bind (preflight) → fixture birth → follow(ref) → '
93
108
  'second application → typed record operation', () async {