@githolon/dsl 0.1.0 → 0.1.1

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.
@@ -0,0 +1,485 @@
1
+ {
2
+ "roots": [
3
+ "nomos_dsl"
4
+ ],
5
+ "packages": [
6
+ {
7
+ "name": "nomos_dsl",
8
+ "version": "0.0.0",
9
+ "dependencies": [
10
+ "json_schema"
11
+ ],
12
+ "devDependencies": [
13
+ "test"
14
+ ]
15
+ },
16
+ {
17
+ "name": "test",
18
+ "version": "1.31.1",
19
+ "dependencies": [
20
+ "analyzer",
21
+ "async",
22
+ "boolean_selector",
23
+ "collection",
24
+ "coverage",
25
+ "http_multi_server",
26
+ "io",
27
+ "matcher",
28
+ "node_preamble",
29
+ "package_config",
30
+ "path",
31
+ "pool",
32
+ "shelf",
33
+ "shelf_packages_handler",
34
+ "shelf_static",
35
+ "shelf_web_socket",
36
+ "source_span",
37
+ "stack_trace",
38
+ "stream_channel",
39
+ "test_api",
40
+ "test_core",
41
+ "typed_data",
42
+ "web_socket_channel",
43
+ "webkit_inspection_protocol",
44
+ "yaml"
45
+ ]
46
+ },
47
+ {
48
+ "name": "json_schema",
49
+ "version": "5.2.2",
50
+ "dependencies": [
51
+ "collection",
52
+ "http",
53
+ "logging",
54
+ "rfc_6901",
55
+ "uri"
56
+ ]
57
+ },
58
+ {
59
+ "name": "yaml",
60
+ "version": "3.1.3",
61
+ "dependencies": [
62
+ "collection",
63
+ "source_span",
64
+ "string_scanner"
65
+ ]
66
+ },
67
+ {
68
+ "name": "webkit_inspection_protocol",
69
+ "version": "1.2.1",
70
+ "dependencies": [
71
+ "logging"
72
+ ]
73
+ },
74
+ {
75
+ "name": "web_socket_channel",
76
+ "version": "3.0.3",
77
+ "dependencies": [
78
+ "async",
79
+ "crypto",
80
+ "stream_channel",
81
+ "web",
82
+ "web_socket"
83
+ ]
84
+ },
85
+ {
86
+ "name": "typed_data",
87
+ "version": "1.4.0",
88
+ "dependencies": [
89
+ "collection"
90
+ ]
91
+ },
92
+ {
93
+ "name": "test_core",
94
+ "version": "0.6.18",
95
+ "dependencies": [
96
+ "analyzer",
97
+ "args",
98
+ "async",
99
+ "boolean_selector",
100
+ "collection",
101
+ "coverage",
102
+ "frontend_server_client",
103
+ "glob",
104
+ "io",
105
+ "meta",
106
+ "package_config",
107
+ "path",
108
+ "pool",
109
+ "source_map_stack_trace",
110
+ "source_maps",
111
+ "source_span",
112
+ "stack_trace",
113
+ "stream_channel",
114
+ "test_api",
115
+ "vm_service",
116
+ "yaml"
117
+ ]
118
+ },
119
+ {
120
+ "name": "test_api",
121
+ "version": "0.7.12",
122
+ "dependencies": [
123
+ "async",
124
+ "boolean_selector",
125
+ "collection",
126
+ "meta",
127
+ "source_span",
128
+ "stack_trace",
129
+ "stream_channel",
130
+ "string_scanner",
131
+ "term_glyph"
132
+ ]
133
+ },
134
+ {
135
+ "name": "stream_channel",
136
+ "version": "2.1.4",
137
+ "dependencies": [
138
+ "async"
139
+ ]
140
+ },
141
+ {
142
+ "name": "stack_trace",
143
+ "version": "1.12.1",
144
+ "dependencies": [
145
+ "path"
146
+ ]
147
+ },
148
+ {
149
+ "name": "source_span",
150
+ "version": "1.10.2",
151
+ "dependencies": [
152
+ "collection",
153
+ "path",
154
+ "term_glyph"
155
+ ]
156
+ },
157
+ {
158
+ "name": "shelf_web_socket",
159
+ "version": "3.0.0",
160
+ "dependencies": [
161
+ "shelf",
162
+ "stream_channel",
163
+ "web_socket_channel"
164
+ ]
165
+ },
166
+ {
167
+ "name": "shelf_static",
168
+ "version": "1.1.3",
169
+ "dependencies": [
170
+ "convert",
171
+ "http_parser",
172
+ "mime",
173
+ "path",
174
+ "shelf"
175
+ ]
176
+ },
177
+ {
178
+ "name": "shelf_packages_handler",
179
+ "version": "3.0.2",
180
+ "dependencies": [
181
+ "path",
182
+ "shelf",
183
+ "shelf_static"
184
+ ]
185
+ },
186
+ {
187
+ "name": "shelf",
188
+ "version": "1.4.2",
189
+ "dependencies": [
190
+ "async",
191
+ "collection",
192
+ "http_parser",
193
+ "path",
194
+ "stack_trace",
195
+ "stream_channel"
196
+ ]
197
+ },
198
+ {
199
+ "name": "pool",
200
+ "version": "1.5.2",
201
+ "dependencies": [
202
+ "async",
203
+ "stack_trace"
204
+ ]
205
+ },
206
+ {
207
+ "name": "path",
208
+ "version": "1.9.1",
209
+ "dependencies": []
210
+ },
211
+ {
212
+ "name": "package_config",
213
+ "version": "2.2.0",
214
+ "dependencies": [
215
+ "path"
216
+ ]
217
+ },
218
+ {
219
+ "name": "node_preamble",
220
+ "version": "2.0.2",
221
+ "dependencies": []
222
+ },
223
+ {
224
+ "name": "matcher",
225
+ "version": "0.12.20",
226
+ "dependencies": [
227
+ "async",
228
+ "meta",
229
+ "stack_trace",
230
+ "term_glyph",
231
+ "test_api"
232
+ ]
233
+ },
234
+ {
235
+ "name": "io",
236
+ "version": "1.0.5",
237
+ "dependencies": [
238
+ "meta",
239
+ "path",
240
+ "string_scanner"
241
+ ]
242
+ },
243
+ {
244
+ "name": "http_multi_server",
245
+ "version": "3.2.2",
246
+ "dependencies": [
247
+ "async"
248
+ ]
249
+ },
250
+ {
251
+ "name": "coverage",
252
+ "version": "1.15.0",
253
+ "dependencies": [
254
+ "args",
255
+ "cli_config",
256
+ "glob",
257
+ "logging",
258
+ "meta",
259
+ "package_config",
260
+ "path",
261
+ "source_maps",
262
+ "stack_trace",
263
+ "vm_service",
264
+ "yaml"
265
+ ]
266
+ },
267
+ {
268
+ "name": "collection",
269
+ "version": "1.19.1",
270
+ "dependencies": []
271
+ },
272
+ {
273
+ "name": "boolean_selector",
274
+ "version": "2.1.2",
275
+ "dependencies": [
276
+ "source_span",
277
+ "string_scanner"
278
+ ]
279
+ },
280
+ {
281
+ "name": "async",
282
+ "version": "2.13.1",
283
+ "dependencies": [
284
+ "collection",
285
+ "meta"
286
+ ]
287
+ },
288
+ {
289
+ "name": "analyzer",
290
+ "version": "13.0.0",
291
+ "dependencies": [
292
+ "_fe_analyzer_shared",
293
+ "collection",
294
+ "convert",
295
+ "crypto",
296
+ "glob",
297
+ "meta",
298
+ "package_config",
299
+ "path",
300
+ "pub_semver",
301
+ "source_span",
302
+ "watcher",
303
+ "yaml"
304
+ ]
305
+ },
306
+ {
307
+ "name": "uri",
308
+ "version": "1.0.0",
309
+ "dependencies": [
310
+ "matcher",
311
+ "quiver"
312
+ ]
313
+ },
314
+ {
315
+ "name": "rfc_6901",
316
+ "version": "0.2.1",
317
+ "dependencies": []
318
+ },
319
+ {
320
+ "name": "logging",
321
+ "version": "1.3.0",
322
+ "dependencies": []
323
+ },
324
+ {
325
+ "name": "http",
326
+ "version": "1.6.0",
327
+ "dependencies": [
328
+ "async",
329
+ "http_parser",
330
+ "meta",
331
+ "web"
332
+ ]
333
+ },
334
+ {
335
+ "name": "string_scanner",
336
+ "version": "1.4.1",
337
+ "dependencies": [
338
+ "source_span"
339
+ ]
340
+ },
341
+ {
342
+ "name": "web_socket",
343
+ "version": "1.0.1",
344
+ "dependencies": [
345
+ "web"
346
+ ]
347
+ },
348
+ {
349
+ "name": "web",
350
+ "version": "1.1.1",
351
+ "dependencies": []
352
+ },
353
+ {
354
+ "name": "crypto",
355
+ "version": "3.0.7",
356
+ "dependencies": [
357
+ "typed_data"
358
+ ]
359
+ },
360
+ {
361
+ "name": "vm_service",
362
+ "version": "15.2.0",
363
+ "dependencies": []
364
+ },
365
+ {
366
+ "name": "source_maps",
367
+ "version": "0.10.13",
368
+ "dependencies": [
369
+ "source_span"
370
+ ]
371
+ },
372
+ {
373
+ "name": "source_map_stack_trace",
374
+ "version": "2.1.2",
375
+ "dependencies": [
376
+ "path",
377
+ "source_maps",
378
+ "stack_trace"
379
+ ]
380
+ },
381
+ {
382
+ "name": "meta",
383
+ "version": "1.18.2",
384
+ "dependencies": []
385
+ },
386
+ {
387
+ "name": "glob",
388
+ "version": "2.1.3",
389
+ "dependencies": [
390
+ "async",
391
+ "collection",
392
+ "file",
393
+ "path",
394
+ "string_scanner"
395
+ ]
396
+ },
397
+ {
398
+ "name": "frontend_server_client",
399
+ "version": "4.0.0",
400
+ "dependencies": [
401
+ "async",
402
+ "path"
403
+ ]
404
+ },
405
+ {
406
+ "name": "args",
407
+ "version": "2.7.0",
408
+ "dependencies": []
409
+ },
410
+ {
411
+ "name": "term_glyph",
412
+ "version": "1.2.2",
413
+ "dependencies": []
414
+ },
415
+ {
416
+ "name": "mime",
417
+ "version": "2.0.0",
418
+ "dependencies": []
419
+ },
420
+ {
421
+ "name": "http_parser",
422
+ "version": "4.1.2",
423
+ "dependencies": [
424
+ "collection",
425
+ "source_span",
426
+ "string_scanner",
427
+ "typed_data"
428
+ ]
429
+ },
430
+ {
431
+ "name": "convert",
432
+ "version": "3.1.2",
433
+ "dependencies": [
434
+ "typed_data"
435
+ ]
436
+ },
437
+ {
438
+ "name": "cli_config",
439
+ "version": "0.2.0",
440
+ "dependencies": [
441
+ "args",
442
+ "yaml"
443
+ ]
444
+ },
445
+ {
446
+ "name": "watcher",
447
+ "version": "1.2.1",
448
+ "dependencies": [
449
+ "async",
450
+ "path"
451
+ ]
452
+ },
453
+ {
454
+ "name": "pub_semver",
455
+ "version": "2.2.0",
456
+ "dependencies": [
457
+ "collection"
458
+ ]
459
+ },
460
+ {
461
+ "name": "_fe_analyzer_shared",
462
+ "version": "100.0.0",
463
+ "dependencies": [
464
+ "meta",
465
+ "source_span"
466
+ ]
467
+ },
468
+ {
469
+ "name": "quiver",
470
+ "version": "3.2.2",
471
+ "dependencies": [
472
+ "matcher"
473
+ ]
474
+ },
475
+ {
476
+ "name": "file",
477
+ "version": "7.0.1",
478
+ "dependencies": [
479
+ "meta",
480
+ "path"
481
+ ]
482
+ }
483
+ ],
484
+ "configVersion": 1
485
+ }
@@ -0,0 +1 @@
1
+ 3.41.9
@@ -0,0 +1,147 @@
1
+ <!--
2
+ NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
3
+ remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
4
+ wasm32-wasip1 artifact {kernel · projection · embedded
5
+ QuickJS engine} on V8 + WASI-shim, byte-identical everywhere. V8 = portability; the one
6
+ wasm = determinism. No native, no wasmtime, no 2nd artifact, no domain-JS on bare V8.
7
+ If a file isn't this / hosting this / authoring for this / proving this — it's gone.
8
+ -->
9
+
10
+ # Nomos 2 Dart frontend codegen — findings
11
+
12
+ The Dart types the Flutter frontend needs, (de)serializing the **exact** serde
13
+ JSON the Rust kernel + projection produce/consume. This is decision-6 of the DSL
14
+ design: the schema is the single source of truth → TS (done), proto (later),
15
+ **Dart frontend types (this slice)**.
16
+
17
+ ## What was built
18
+
19
+ `nomos2/dsl/dart/` — a Dart package (`nomos_dsl`):
20
+
21
+ | Path | Role | Hand-written / generated |
22
+ |---|---|---|
23
+ | `lib/src/wire.dart` | Fixed kernel wire types: `Hlc`, `Value` (sealed: `ValueStr`/`ValueInt`/`ValueSet`/`ValueMap`), `WireField`, `Op` (sealed: `OpSet`/`OpAddToSet`/`OpSetEntry`), `FieldOp`, `Event` (`{aggregate, ops}`, #56), `Intent` (`{hlc, events}`, #56). | hand-written |
24
+ | `lib/src/driver.dart` | `WireDriver` (sealed: `DriverLww`/`DriverAddWins`/`DriverConflict`/`DriverMapOf`) + `Schema`. | hand-written |
25
+ | `lib/src/generated/trackable_asset.dart` | The `trackable_asset` domain: `TrackableAsset` aggregate, `StatusEnum`, and `CreateAssetPayload`/`MoveAssetPayload`/`TagAssetPayload`/`SetCustomFieldPayload`. | **generated** |
26
+ | `lib/nomos_dsl.dart` | Public barrel (exports the three above). | hand-written |
27
+ | `test/wire_test.dart` | Round-trip tests for every wire type + Driver/Schema. | — |
28
+ | `test/trackable_asset_test.dart` | Read-model aggregate deserialization + directive-payload → Event JSON. | — |
29
+
30
+ The **codegen** lives in `nomos2/dsl/` (TS, alongside the existing TS DSL):
31
+
32
+ | Path | Role |
33
+ |---|---|
34
+ | `src/codegen_dart.ts` | The generator: introspects aggregates + directives, emits the Dart per-domain source. |
35
+ | `src/emit_dart.ts` | The driver: wires the `trackable_asset` domain into the generator, writes `dart/lib/src/generated/`. Run `npx tsx src/emit_dart.ts` (or `npm run emit:dart`). |
36
+
37
+ Per the brief: #1 (fixed wire types) is hand-written; **#2 (per-domain types) is
38
+ generated**, so it stays reproducible from the domain definitions, not drifting.
39
+
40
+ ## The exact serde JSON shape matched
41
+
42
+ Verified against `nomos2/kernel/src/lib.rs` and the existing emitted fixtures
43
+ (`nomos2/dsl/fixtures/*.json`, the TS side's real-kernel round-trip output):
44
+
45
+ ```
46
+ Hlc {"physical":1,"logical":0,"replica":7} replica is a BARE int (newtype-struct transparency), NOT {"ReplicaId":7}
47
+ Value::Str {"Str":"red"}
48
+ Value::Int {"Int":10} i64 in Rust -> Dart int
49
+ Value::Set {"Set":["a","b"]} BTreeSet<String> -> JSON array
50
+ Value::Map {"Map":{"k":{"value":<Value>,"stamp":<Hlc>}}}
51
+ Op::Set {"Set":{"Int":10}} / {"Set":{"Str":"A"}}
52
+ Op::AddToSet {"AddToSet":["x","y"]}
53
+ Op::SetEntry {"SetEntry":{"key":"color","value":{"Str":"red"}}}
54
+ FieldOp {"field":"pos","op":<Op>}
55
+ Event {"aggregate":"asset-1","ops":[<FieldOp>...]} (#56: per-aggregate, the *where*)
56
+ Intent {"hlc":<Hlc>,"events":[<Event>...]} (#56: one commit = intent.json)
57
+ Driver unit "Lww" / "AddWins" / "Conflict" bare strings
58
+ Driver::MapOf {"MapOf":"Lww"} nests
59
+ Schema {"label":"Lww",...,"customFields":{"MapOf":"Lww"}} plain object (BTreeMap)
60
+ ```
61
+
62
+ Read-model aggregate shape (what `TrackableAsset.fromJson` consumes — REALIGNED
63
+ 2026-06-01 to the dispatch(intent) read half): the FLAT, already-decoded `data`
64
+ map of a `{type,id,data}` row that the FRB `query()`/`query_by_id()`/`view()`
65
+ emit — e.g. `{ "label":"Pump 2", "pos":10, "status":"active", "tags":["x","y"],
66
+ "customFields":{"size":"L"} }`. NOT the projection-internal `{field:{value,stamp}}`
67
+ `WireField` shape (the Rust `decode_field` has already peeled the kernel `Value`
68
+ back to bare JSON). The generated `fromJson` reads each field via a tolerant
69
+ flat-projection reader in `src/subscriptions.dart` (`readStr`/`readInt`/`readSet`/
70
+ `readStrMap`/`readEnum`/`readJsonStr`).
71
+
72
+ The generated `TrackableAsset` maps each field to a frontend-pleasant Dart type:
73
+ `label: String`, `pos: int`, `status: StatusEnum`, `tags: Set<String>`,
74
+ `customFields: Map<String, String>`. The app subscribes through the generated
75
+ typed read accessors (`watch…`/`read…` over `query()`/`query_by_id()`, threading a
76
+ `NomosReads`) — the READ half of the peer boundary, symmetric with `dispatch`.
77
+
78
+ ## How the codegen derives directive ops (no second source of truth)
79
+
80
+ A directive's `plan(payload, ctx)` is arbitrary TS, so the op structure can't be
81
+ read off statically. Instead the generator runs the **same** `plan` with a Proxy
82
+ payload whose property reads return tracking tokens (`{__payloadKey}`). The
83
+ resulting `PlannedOp[]` then tells us, per op, which payload key feeds the value
84
+ / items / entry-key. That op template is emitted into the Dart payload's
85
+ `toEvent()` — which lowers exactly as the TS `runDirective`/`lowerValue` does
86
+ (int→`ValueInt`, string/enum→`ValueStr`, string[]→`ValueSet`) and threads the
87
+ directive's target aggregate id into the `Event{aggregate, ops}` (#56);
88
+ `toIntent(hlc)` wraps it in a one-event `Intent{hlc, events}`. So the Dart wire
89
+ output is driven by the identical authored plan, not a re-typed copy.
90
+
91
+ ## The proof (green via `dart test`)
92
+
93
+ `cd nomos2/dsl/dart && dart test` — **28 tests pass**, `dart analyze` clean:
94
+
95
+ - **Round-trip** (`wire_test.dart`): kernel JSON for `Hlc`, all four `Value`
96
+ variants, all three `Op` variants, an `Event{aggregate, ops}` (Set/AddToSet/
97
+ SetEntry), a multi-aggregate `Intent{hlc, events}` (#56), all `Driver` variants,
98
+ and the lowered `Schema` → `fromJson` → `toJson` structurally equals the input.
99
+ Explicit assertions that `replica` is a bare int and `MapOf` nests.
100
+ - **Read-model aggregate** (`trackable_asset_test.dart`): a `{field:{value,stamp}}`
101
+ JSON (incl. a `Value::Map` for `customFields`) deserializes into the generated
102
+ `TrackableAsset`; enum string → `StatusEnum`; missing field throws.
103
+ - **Directive payload → kernel Intent**: `CreateAssetPayload(...).toIntent(hlc)`
104
+ emits a one-event `Intent` whose event threads `aggregate: 'TrackableAsset'`
105
+ (#56); `toEvent()` exposes the per-aggregate `Event`; `moveAsset`/`tagAsset`/
106
+ `setCustomField` each lower the expected `Set`/`AddToSet`/`SetEntry`; the produced
107
+ `Intent` round-trips back through `Intent.fromJson`.
108
+
109
+ The codegen is **reproducible**: re-running `npx tsx src/emit_dart.ts` produces a
110
+ byte-identical `trackable_asset.dart` (verified). The TS side is unaffected:
111
+ `npx tsc --noEmit` is clean and the existing 13 vitest tests stay green.
112
+
113
+ ## Friction / notes (not fixed, by instruction)
114
+
115
+ 1. **Aggregate identity (#56) — DONE.** The kernel `Event` is now
116
+ `{aggregate, ops}` and a commit is an `Intent{hlc, events}` (`intent.json`).
117
+ `Event`/`Intent` in `wire.dart` match; each generated payload threads its
118
+ `aggregateId` into `toEvent()` and `toIntent(hlc)` wraps a one-event `Intent`.
119
+ (The generated domain directives are single-aggregate, so each payload's intent
120
+ carries exactly one event; a future multi-aggregate generated directive would
121
+ emit several — same grouping the TS `runDirective` already does.)
122
+ 2. **`replica` unsigned.** Rust `ReplicaId(u64)`; Dart has no unsigned int, so
123
+ `WireReplicaId = int`. Fine for realistic replica ids; a far-future >2^63 id
124
+ would need `BigInt` (noted, not handled — out of scope).
125
+ 3. **`Value::Set` ordering.** Kernel `BTreeSet` is sorted; Dart `List<String>`
126
+ preserves wire order on round-trip (we don't re-sort), so `toJson` is
127
+ byte-stable against the kernel's already-sorted input. The aggregate field is
128
+ surfaced as a `Set<String>` (order-insensitive) for the frontend.
129
+ 4. **Generator scope.** `codegen_dart.ts` handles the field/payload kinds the
130
+ `trackable_asset` domain uses (string/int/enum/set/map, and `json`/`ref` →
131
+ `String` on the aggregate side). Unsupported Zod payload kinds throw loudly
132
+ rather than mis-emit — same fail-closed posture as the TS `lowerDriver`.
133
+
134
+ ## How to run
135
+
136
+ ```
137
+ cd nomos2/dsl
138
+ npm install
139
+ npx tsx src/emit_dart.ts # (re)generate dart/lib/src/generated/
140
+ npx tsc --noEmit # TS still typechecks
141
+ npx vitest run # existing 13 TS tests
142
+
143
+ cd dart
144
+ dart pub get
145
+ dart analyze # clean
146
+ dart test # 28 tests green
147
+ ```
@@ -0,0 +1 @@
1
+ {"format-version":[1,0,0],"native-assets":{}}
@@ -0,0 +1 @@
1
+ {"format-version":[1,0,0],"native-assets":{}}