@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.
- package/dart/CHANGELOG.md +15 -0
- package/dart/lib/src/runner/node-entry.mjs +411 -149
- package/dart/lib/src/runner/offline-install.mjs +222 -99
- package/dart/lib/src/session_bound.dart +111 -2
- package/dart/lib/src/test_harness.dart +117 -66
- package/dart/lib/src/webview_bridge.dart +726 -61
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +4 -5
- package/dart/test/acquire_session_test.dart +62 -5
- package/dart/test/cloud_bound_topology_test.dart +59 -38
- package/dart/test/session_bound_test.dart +25 -10
- package/dart/test/webview_bridge_test.dart +357 -9
- package/dart_types/CHANGELOG.md +5 -0
- package/dart_types/lib/nomos_types.dart +1 -0
- package/dart_types/lib/src/git_object_id.dart +37 -0
- package/dart_types/pubspec.yaml +1 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +425 -145
- package/src/births.ts +16 -1
- package/src/business_birth.ts +67 -46
- package/src/codegen_dart.ts +68 -12
- package/src/codegen_dart_application.ts +114 -19
- package/src/codegen_dart_client.ts +149 -6
- package/src/codegen_proof.ts +16 -9
- package/src/codegen_provider_dart.ts +5 -0
- package/src/codegen_ts.ts +115 -19
- package/src/compile_package_main.ts +142 -40
- package/src/fields.ts +43 -0
- package/src/framework/attested_receipts.ts +34 -1
- package/src/framework/domain_lifecycle.ts +140 -83
- package/src/framework/genesis.ts +2 -1
- package/src/framework/home.ts +20 -45
- package/src/framework/workspaces.ts +62 -29
- package/src/idof_lint.ts +65 -51
- package/src/index.ts +21 -1
- package/src/intent.ts +11 -1
- package/src/law_graph.ts +81 -11
- package/src/law_graph_usda.ts +45 -54
- package/src/relation_schema.ts +18 -0
- package/src/shape_compat.ts +3 -0
- package/src/stable_ids_for_module.ts +7 -1
- package/src/value_object.ts +40 -0
package/dart/pubspec.lock
CHANGED
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
|
|
9
|
-
|
|
10
|
-
|
|
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.
|
|
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
|
|
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,
|
|
116
|
-
reason:
|
|
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)
|
|
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())
|
|
33
|
-
|
|
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(
|
|
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(
|
|
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,
|
|
65
|
-
reason:
|
|
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:
|
|
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
|
-
//
|
|
75
|
-
//
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|
97
|
-
|
|
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: {
|
|
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 =
|
|
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:
|
|
124
|
+
reason:
|
|
125
|
+
'while partitioned, the locally-born estate must have NO remote custody');
|
|
113
126
|
|
|
114
|
-
// 7+8 — HEAL
|
|
115
|
-
//
|
|
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
|
|
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:
|
|
129
|
-
|
|
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:
|
|
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 {
|
|
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 {
|