@githolon/dsl 0.1.0 → 0.1.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.
@@ -0,0 +1,43 @@
1
+ // NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
2
+ // remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
3
+ // wasm32-wasip1 artifact {kernel · projection · embedded
4
+ // QuickJS engine} on V8 + WASI-shim, byte-identical everywhere. V8 = portability; the one
5
+ // wasm = determinism. No native, no wasmtime, no 2nd artifact, no domain-JS on bare V8.
6
+ // If a file isn't this / hosting this / authoring for this / proving this — it's gone.
7
+
8
+ /// Nomos 2 Dart frontend types.
9
+ ///
10
+ /// - The hand-written kernel WIRE types (`Value`, `Field`/`WireField`, `Hlc`,
11
+ /// `Op`, `FieldOp`, `Event`, `Schema`/`WireDriver`) — byte-compatible with
12
+ /// serde_json. See `src/wire.dart`, `src/driver.dart`.
13
+ /// - The ONE write verb `dispatch(intent)` (`src/dispatch.dart`) + the typed
14
+ /// reactive read engine `NomosReads` (`src/subscriptions.dart`).
15
+ /// - The framework PRIMITIVES ONLY. The CODE-GENERATED per-domain types
16
+ /// (aggregates + public intent payloads + enums + `watch…` read accessors) NO LONGER
17
+ /// live here — they were extracted into the co-located TENANT package
18
+ /// `co2_nomos_domains` (`ts_packages/co2-nomos-domains/dart/`), emitted there by
19
+ /// `ts_packages/co2-nomos-domains/src/emit_dart.ts`. Tenant domains squatting in
20
+ /// the framework package was the wrong topology; this barrel now exports ONLY the
21
+ /// hand-written framework wire/dispatch/read/provider primitives. App code that
22
+ /// wants the typed domain surface imports `package:co2_nomos_domains/co2_nomos_domains.dart`,
23
+ /// which re-exports this framework barrel PLUS every generated domain.
24
+ library;
25
+
26
+ export 'src/wire.dart';
27
+ export 'src/driver.dart';
28
+ export 'src/dispatch.dart';
29
+ export 'src/subscriptions.dart';
30
+ export 'src/schema_validation.dart';
31
+ // The PROVIDER boundary (server-side capability-provider SDK runtime: TaskOutcome /
32
+ // TaskSuccess / TaskFailure / TaskResult / TaskProvider / HostClock). The symmetric twin
33
+ // of `dispatch.dart`; the generated per-task provider classes build on it.
34
+ export 'src/provider.dart';
35
+
36
+ // ── NO generated domains here ──────────────────────────────────────────────
37
+ // The per-domain generated types moved to the tenant `co2_nomos_domains` package
38
+ // (`ts_packages/co2-nomos-domains/dart/lib/src/generated/`). The tenant barrel
39
+ // (`package:co2_nomos_domains/co2_nomos_domains.dart`) re-exports THIS framework
40
+ // barrel plus every generated domain — so importing it gives the whole typed
41
+ // surface (framework primitives + tenant domains) through one import. The colliding
42
+ // positional enums (`StatusEnum`/`KindEnum`/…) and their `hide` clauses now live on
43
+ // the tenant barrel's exports, not here.
@@ -0,0 +1,87 @@
1
+ // NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
2
+ // remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
3
+ // wasm32-wasip1 artifact {kernel · projection · embedded
4
+ // QuickJS engine} on V8 + WASI-shim, byte-identical everywhere. V8 = portability; the one
5
+ // wasm = determinism. No native, no wasmtime, no 2nd artifact, no domain-JS on bare V8.
6
+ // If a file isn't this / hosting this / authoring for this / proving this — it's gone.
7
+
8
+ /// The ONE write verb: `dispatch(intent)`.
9
+ ///
10
+ /// END-STATE (the "dispatch(intent) purge", Jack 2026-06-01): app code's only
11
+ /// write verb is `dispatch(intent)`. The frontend constructs ONLY a typed
12
+ /// generated intent DTO (a [NomosIntent]) — it never hand-types an engine rule
13
+ /// id, never builds an `Event`, never holds an `Hlc`. `dispatch` ships the
14
+ /// intent payload as DATA to the kernel's FRB `author(domain, directiveId, payloadJson,
15
+ /// actor)`; the sealed engine runs the REAL `.plan()` and is the SOLE lowering
16
+ /// authority (HLC + intent id are kernel-allocated host-side).
17
+ ///
18
+ /// This file is HAND-WRITTEN and domain-AGNOSTIC (declared ONCE, here): the
19
+ /// generated per-domain intent classes `implements NomosIntent`, so a single
20
+ /// generic `dispatch` covers every public intent of every domain — replacing the
21
+ /// ~63-method write façade.
22
+ library;
23
+
24
+ import 'dart:convert';
25
+
26
+ /// A typed, generated public intent. Every generated `…Payload` class implements
27
+ /// this: it knows its [domain] + [intentType] and serialises ONLY its typed
28
+ /// fields to DATA via [toPayloadJson]. It builds NO `Event` and NO `Hlc`.
29
+ abstract interface class NomosIntent {
30
+ /// The policy domain (the kernel `author` `domain` — the engine registry key).
31
+ String get domain;
32
+
33
+ /// The public intent type within [domain].
34
+ String get intentType;
35
+
36
+ /// The intent payload as DATA: the exact JSON shape the domain Zod
37
+ /// schema validates inside the engine. Optional fields are omitted when null.
38
+ Map<String, Object?> toPayloadJson();
39
+ }
40
+
41
+ /// The kernel write seam: the FRB `NomosClient.author` signature, as a function
42
+ /// type. `dispatch` takes this (rather than importing the FRB client) so the
43
+ /// generated `nomos_dsl` package stays free of a `flutter_rust_bridge` dependency
44
+ /// — the app threads `client.author` in. Returns the new HEAD commit oid (hex).
45
+ typedef Authorer = String Function({
46
+ required String domain,
47
+ required String directiveId,
48
+ required String payloadJson,
49
+ required String actor,
50
+ });
51
+
52
+ /// THE ONE WRITE VERB. Ship a typed [intent] payload as DATA to the kernel
53
+ /// [author] seam. The domain + intent type + payload all come from the GENERATED
54
+ /// intent object — nothing is hand-typed. Builds NO `Event` and NO `Hlc`: the
55
+ /// engine lowers. [actor] is the authenticated actor the caller threads from auth.
56
+ /// Returns the new HEAD commit oid (hex).
57
+ String dispatch(NomosIntent intent,
58
+ {required Authorer author, required String actor}) =>
59
+ author(
60
+ domain: intent.domain,
61
+ directiveId: intent.intentType,
62
+ payloadJson: jsonEncode(intent.toPayloadJson()),
63
+ actor: actor,
64
+ );
65
+
66
+ // ── ID-MINT SEAM (the type-safety keystone) ───────────────────────────────────
67
+ //
68
+ // The frontend NEVER mints an aggregate id and NEVER passes a raw string, a seed, or a
69
+ // nonce. A generated `createX(...)` factory (no raw id param) calls the KERNEL mint via
70
+ // this seam to RESERVE a typed, type-tagged `<Type>_<uuidv7>` id, which it ships in the
71
+ // create payload; the kernel's GATE verifies its shape + type-tag prefix on author. Like
72
+ // [Authorer], this is a function type the app threads in (the FRB
73
+ // `NomosClient.mintIdForWorkspace`), so the generated `nomos_dsl` package stays free of a
74
+ // `flutter_rust_bridge` dependency.
75
+ //
76
+ // WHERE THE RANDOMNESS LIVES (the model — the dev touches NONE of it): the KERNEL mints
77
+ // the UUIDv7-shaped body from a HOST-INJECTED random source (native `OsRng`; web
78
+ // `crypto.getRandomValues`), CAPTURES it in the create event, and replay READS it (never
79
+ // re-mints). The UUID body is opaque uniqueness only — NOT time/order; intent HLCs carry
80
+ // ordering. So there is NO Dart-side nonce, NO `freshMintSeed`, NO seed at all — the dev
81
+ // just calls `createX(...)` and gets a guaranteed-unique typed id.
82
+
83
+ /// The kernel id-mint seam: the FRB `NomosClient.mintIdForWorkspace` signature, as a
84
+ /// function type. Returns a kernel-minted `<Type>_<uuidv7>` id (the kernel mints it from
85
+ /// the host-injected rng). The dev supplies ONLY the aggregate type — no workspace, no
86
+ /// author, no seed.
87
+ typedef Minter = String Function({required String aggregateType});
@@ -0,0 +1,122 @@
1
+ // NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
2
+ // remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
3
+ // wasm32-wasip1 artifact {kernel · projection · embedded
4
+ // QuickJS engine} on V8 + WASI-shim, byte-identical everywhere. V8 = portability; the one
5
+ // wasm = determinism. No native, no wasmtime, no 2nd artifact, no domain-JS on bare V8.
6
+ // If a file isn't this / hosting this / authoring for this / proving this — it's gone.
7
+
8
+ /// `Driver` + `Schema` wire shapes, mirroring `nomos2/kernel/src/lib.rs`.
9
+ ///
10
+ /// `Driver = Lww | AddWins | MapOf(Box<Driver>) | Conflict`.
11
+ /// - unit variants are BARE strings: "Lww" / "AddWins" / "Conflict"
12
+ /// - the MapOf newtype variant nests: {"MapOf":"Lww"}
13
+ ///
14
+ /// `Schema = BTreeMap<FieldId, Driver>` — a plain JSON object: field -> Driver.
15
+ library;
16
+
17
+ /// One field's merge driver. Hand-written wire type (the frontend reads a
18
+ /// schema to know how each field merges / renders).
19
+ sealed class WireDriver {
20
+ const WireDriver();
21
+
22
+ factory WireDriver.fromJson(dynamic json) {
23
+ if (json is String) {
24
+ switch (json) {
25
+ case 'Lww':
26
+ return const DriverLww();
27
+ case 'AddWins':
28
+ return const DriverAddWins();
29
+ case 'Conflict':
30
+ return const DriverConflict();
31
+ default:
32
+ throw FormatException('Unknown Driver unit variant: $json');
33
+ }
34
+ }
35
+ if (json is Map<String, dynamic> && json.containsKey('MapOf')) {
36
+ return DriverMapOf(WireDriver.fromJson(json['MapOf']));
37
+ }
38
+ throw FormatException('Unknown Driver shape: $json');
39
+ }
40
+
41
+ /// Returns either a `String` (unit variants) or a `Map` (MapOf).
42
+ dynamic toJson();
43
+ }
44
+
45
+ class DriverLww extends WireDriver {
46
+ const DriverLww();
47
+ @override
48
+ dynamic toJson() => 'Lww';
49
+ @override
50
+ bool operator ==(Object other) => other is DriverLww;
51
+ @override
52
+ int get hashCode => 'Lww'.hashCode;
53
+ @override
54
+ String toString() => 'DriverLww';
55
+ }
56
+
57
+ class DriverAddWins extends WireDriver {
58
+ const DriverAddWins();
59
+ @override
60
+ dynamic toJson() => 'AddWins';
61
+ @override
62
+ bool operator ==(Object other) => other is DriverAddWins;
63
+ @override
64
+ int get hashCode => 'AddWins'.hashCode;
65
+ @override
66
+ String toString() => 'DriverAddWins';
67
+ }
68
+
69
+ class DriverConflict extends WireDriver {
70
+ const DriverConflict();
71
+ @override
72
+ dynamic toJson() => 'Conflict';
73
+ @override
74
+ bool operator ==(Object other) => other is DriverConflict;
75
+ @override
76
+ int get hashCode => 'Conflict'.hashCode;
77
+ @override
78
+ String toString() => 'DriverConflict';
79
+ }
80
+
81
+ class DriverMapOf extends WireDriver {
82
+ final WireDriver inner;
83
+ const DriverMapOf(this.inner);
84
+ @override
85
+ dynamic toJson() => {'MapOf': inner.toJson()};
86
+ @override
87
+ bool operator ==(Object other) => other is DriverMapOf && other.inner == inner;
88
+ @override
89
+ int get hashCode => Object.hash('MapOf', inner);
90
+ @override
91
+ String toString() => 'DriverMapOf($inner)';
92
+ }
93
+
94
+ /// `Schema = BTreeMap<FieldId, Driver>` — field id -> Driver.
95
+ class Schema {
96
+ final Map<String, WireDriver> drivers;
97
+ const Schema(this.drivers);
98
+
99
+ factory Schema.fromJson(Map<String, dynamic> json) => Schema(
100
+ json.map((k, v) => MapEntry(k, WireDriver.fromJson(v))),
101
+ );
102
+
103
+ Map<String, dynamic> toJson() =>
104
+ drivers.map((k, v) => MapEntry(k, v.toJson()));
105
+
106
+ @override
107
+ bool operator ==(Object other) {
108
+ if (other is! Schema) return false;
109
+ if (other.drivers.length != drivers.length) return false;
110
+ for (final e in drivers.entries) {
111
+ if (other.drivers[e.key] != e.value) return false;
112
+ }
113
+ return true;
114
+ }
115
+
116
+ @override
117
+ int get hashCode =>
118
+ Object.hashAll(drivers.entries.map((e) => Object.hash(e.key, e.value)));
119
+
120
+ @override
121
+ String toString() => 'Schema($drivers)';
122
+ }
@@ -0,0 +1,139 @@
1
+ // NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
2
+ // remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
3
+ // wasm32-wasip1 artifact {kernel · projection · embedded
4
+ // QuickJS engine} on V8 + WASI-shim, byte-identical everywhere. V8 = portability; the one
5
+ // wasm = determinism. No native, no wasmtime, no 2nd artifact, no domain-JS on bare V8.
6
+ // If a file isn't this / hosting this / authoring for this / proving this — it's gone.
7
+
8
+ /// The PROVIDER boundary: the typed server-side capability-provider SDK runtime.
9
+ ///
10
+ /// This is the SYMMETRIC TWIN of `dispatch.dart` (the peer write boundary). Where
11
+ /// the peer authors an Order (`createTranscribeVoiceNote(...)` -> `dispatch`), a
12
+ /// PROVIDER (Whisper, a renderer, a multi-target PR transporter) is the
13
+ /// other end of the framework `impureCapability` Order→Receipt pair: it WATCHES `requested`
14
+ /// task rows, performs the impure side-effect OUTSIDE the fold, and authors the RECEIPT
15
+ /// (`complete…`/`fail…`/`block…`/`deadLetter…`) back through the SAME write path. The receipt is an ordinary
16
+ /// intent — the provider is a Nomos author, NOT a bypass (ONE-WRITE-PATH).
17
+ /// Order and Receipt are intent roles, not provider-owned jobs or privileged server
18
+ /// mutation channels.
19
+ ///
20
+ /// PRINCIPLE (Jack 2026-06-04): code is ~free; INTEGRATION POINTS are expensive +
21
+ /// fuckup-guaranteed. So the generated provider SDK is fully TYPED to the boundary —
22
+ /// the dev writes a typed `handler(Order) -> Result | Failure | Blocked | DeadLetter`; the generated
23
+ /// dispatcher DECODES the incoming Order JSON into the typed Order and ENCODES the typed
24
+ /// outcome into the ordinary Receipt intent. NO dynamic / `Map<String, dynamic>` / raw-JSON leaks
25
+ /// into the dev-facing API; the types stop ONLY at the actual JSON serde (`handleOrder`).
26
+ ///
27
+ /// This file is HAND-WRITTEN and domain-AGNOSTIC (declared ONCE, here): the generated
28
+ /// per-task provider classes build on these interfaces, so the round-trip mechanism is
29
+ /// proven once and reused for every capability.
30
+ library;
31
+
32
+ import 'dart:convert';
33
+
34
+ import 'dispatch.dart' show NomosIntent;
35
+
36
+ /// The typed OUTCOME a provider handler returns for one Order: success, failure, blocked,
37
+ /// or dead-lettered.
38
+ /// SEALED so a generated dispatcher's `switch` is EXHAUSTIVE — a new outcome variant is
39
+ /// a compile error at every dispatcher, never a silent fall-through. The mirror of the
40
+ /// framework `TaskOutcome` union (`impure_capability.ts`), but TYPED in the Result.
41
+ sealed class TaskOutcome<R> {
42
+ const TaskOutcome();
43
+ }
44
+
45
+ /// The SUCCESS outcome: the typed [result] the handler produced. The generated
46
+ /// dispatcher maps this → the `complete…` Receipt directive (status:completed +
47
+ /// resultRef + closesOrder).
48
+ final class TaskSuccess<R> extends TaskOutcome<R> {
49
+ /// The typed result the handler produced (e.g. a transcription's `resultRef`).
50
+ final R result;
51
+ const TaskSuccess(this.result);
52
+ }
53
+
54
+ /// The FAILURE outcome: the [failureReason] the handler recorded. The generated
55
+ /// dispatcher maps this → the `fail…` Receipt directive (status:failed + failureReason +
56
+ /// closesOrder). A recorded failure is the terminal state — NOT a retry storm
57
+ /// (conformance X1's "declined is recorded" discipline at the task scale).
58
+ final class TaskFailure<R> extends TaskOutcome<R> {
59
+ /// Why the side-effect failed (recorded onto the task, never re-thrown into the fold).
60
+ final String failureReason;
61
+ const TaskFailure(this.failureReason);
62
+ }
63
+
64
+ /// The BLOCKED outcome: the provider cannot proceed until an external condition changes.
65
+ /// The generated dispatcher maps this -> the `block...` Receipt directive
66
+ /// (status:blocked + blockedReason + nextAction + closesOrder). This is terminal for this
67
+ /// order; recovery is a fresh explicit Order after the next action is satisfied.
68
+ final class TaskBlocked<R> extends TaskOutcome<R> {
69
+ /// Why the provider cannot proceed.
70
+ final String blockedReason;
71
+
72
+ /// What the host/human must do before a fresh order can proceed.
73
+ final String nextAction;
74
+
75
+ const TaskBlocked({
76
+ required this.blockedReason,
77
+ required this.nextAction,
78
+ });
79
+ }
80
+
81
+ /// The DEAD-LETTER outcome: stop retrying this order and park it in the DLQ. The generated
82
+ /// dispatcher maps this -> the `deadLetter...` Receipt directive
83
+ /// (status:deadLettered + deadLetterReason + closesOrder).
84
+ final class TaskDeadLetter<R> extends TaskOutcome<R> {
85
+ /// Why this order was moved to the DLQ.
86
+ final String deadLetterReason;
87
+ const TaskDeadLetter(this.deadLetterReason);
88
+ }
89
+
90
+ /// A typed RESULT a provider handler can encode into a Receipt's content-addressed
91
+ /// `resultRef`. Every generated `…Result` class implements this: it serialises to the
92
+ /// single JSON string the framework `complete…` Receipt records as its `resultRef` (the
93
+ /// authoritative artifact). The framework's `resultRef` is a JSON STRING leaf
94
+ /// (`impure_capability.ts` `completeSchema.resultRef`), so a structured result is JSON-encoded
95
+ /// into it and decodes losslessly on the read side.
96
+ abstract interface class TaskResult {
97
+ /// Encode this result to the Receipt's `resultRef` JSON string.
98
+ String toResultRef();
99
+ }
100
+
101
+ /// The HOST CLOCK seam: the provider stamps the Receipt's terminal time (epoch-ms) from
102
+ /// the host clock — the reactor is a client, so its authoring time comes from a host
103
+ /// port, passed in so the dispatcher stays PURE + unit-testable (the same shape as the
104
+ /// framework `impureCapabilityReceipt(now)`). The app threads its real clock; a test threads a
105
+ /// fixed `now` so the emitted Receipt JSON is deterministic.
106
+ typedef HostClock = int Function();
107
+
108
+ /// The minimal contract a generated per-task provider exposes. Generic in the typed
109
+ /// Order/Result; the generated subtype binds them. `handleOrder` is the ONE seam a host
110
+ /// loop calls per `requested` row.
111
+ abstract interface class TaskProvider<O, R> {
112
+ /// The framework `impureCapability` ids this provider is bound to (provenance — the host
113
+ /// can assert it is draining the right capability). Carried as DATA, never hand-typed.
114
+ String get orderDirectiveId;
115
+ String get completeDirectiveId;
116
+ String get failDirectiveId;
117
+ String get blockDirectiveId;
118
+ String get deadLetterDirectiveId;
119
+
120
+ /// DECODE an incoming Order intent's payload JSON into the typed [O], run the
121
+ /// registered typed handler, and ENCODE the outcome into the ordinary Receipt intent — a
122
+ /// [NomosIntent] ready for the ONE write path (`dispatch`). `now` stamps the
123
+ /// terminal time. Decoding raw JSON happens HERE and only here; the handler the dev
124
+ /// writes sees ONLY the typed [O] and returns ONLY a typed [TaskOutcome].
125
+ NomosIntent handleOrder(String orderPayloadJson, {required HostClock now});
126
+
127
+ /// Convenience: decode an Order payload from an already-parsed map (the host may have
128
+ /// the row's `data` map in hand from a read). Same path as [handleOrder].
129
+ NomosIntent handleOrderMap(
130
+ Map<String, Object?> orderPayload, {
131
+ required HostClock now,
132
+ });
133
+ }
134
+
135
+ /// Decode an Order payload JSON string to a map for a generated `…Order.fromJson`.
136
+ /// Shared so every generated provider decodes Orders identically (the symmetry of the
137
+ /// client's `jsonEncode(toPayloadJson())`).
138
+ Map<String, Object?> decodeOrderPayload(String orderPayloadJson) =>
139
+ (jsonDecode(orderPayloadJson) as Map<String, Object?>);
@@ -0,0 +1,44 @@
1
+ // NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
2
+ // remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
3
+ // wasm32-wasip1 artifact {kernel · projection · embedded
4
+ // QuickJS engine} on V8 + WASI-shim, byte-identical everywhere. V8 = portability; the one
5
+ // wasm = determinism. No native, no wasmtime, no 2nd artifact, no domain-JS on bare V8.
6
+ // If a file isn't this / hosting this / authoring for this / proving this — it's gone.
7
+
8
+ library;
9
+
10
+ import 'package:json_schema/json_schema.dart';
11
+
12
+ /// Thrown when a decoded JSON value fails a compiler-emitted Nomos JSON Schema.
13
+ final class NomosJsonSchemaException implements Exception {
14
+ final String context;
15
+ final ValidationResults results;
16
+
17
+ const NomosJsonSchemaException(this.context, this.results);
18
+
19
+ @override
20
+ String toString() {
21
+ final errors = results.errors.map((e) => e.toString()).join('; ');
22
+ return 'NomosJsonSchemaException($context): $errors';
23
+ }
24
+ }
25
+
26
+ /// Validate [value] against a compiler-emitted JSON Schema map.
27
+ ValidationResults validateNomosJsonSchema(
28
+ Map<String, Object?> schema,
29
+ Object? value, {
30
+ bool? validateFormats,
31
+ }) =>
32
+ JsonSchema.create(schema).validate(value, validateFormats: validateFormats);
33
+
34
+ /// Validate [value] and throw [NomosJsonSchemaException] if it does not conform.
35
+ void requireNomosJsonSchema(
36
+ Map<String, Object?> schema,
37
+ Object? value, {
38
+ String context = 'nomos payload',
39
+ bool? validateFormats,
40
+ }) {
41
+ final results =
42
+ validateNomosJsonSchema(schema, value, validateFormats: validateFormats);
43
+ if (!results.isValid) throw NomosJsonSchemaException(context, results);
44
+ }