@githolon/dsl 0.13.0 → 0.15.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.
@@ -19,7 +19,8 @@ class FakeRunner implements BridgeTransport {
19
19
 
20
20
  FakeRunner() {
21
21
  // the runner announces readiness at construction.
22
- scheduleMicrotask(() => _incoming.add({'v': kWebviewBridgeVersion, 'event': 'ready'}));
22
+ scheduleMicrotask(
23
+ () => _incoming.add({'v': kWebviewBridgeVersion, 'event': 'ready'}));
23
24
  }
24
25
 
25
26
  @override
@@ -32,34 +33,97 @@ class FakeRunner implements BridgeTransport {
32
33
  final op = message['op'] as String;
33
34
  final args = (message['args'] as Map).cast<String, Object?>();
34
35
  // reply exactly as the runner's ops do (the contract both ends share).
35
- Map<String, Object?> reply(Object? result) => {'v': kWebviewBridgeVersion, 'id': id, 'ok': true, 'result': result};
36
+ Map<String, Object?> reply(Object? result) =>
37
+ {'v': kWebviewBridgeVersion, 'id': id, 'ok': true, 'result': result};
36
38
  scheduleMicrotask(() {
37
39
  switch (op) {
38
- case 'connect': _incoming.add(reply({'workspace': args['workspace']})); break;
39
- case 'dispatch':
40
+ case 'connect':
41
+ _incoming.add(reply({'workspace': args['workspace']}));
42
+ break;
43
+ case 'offer':
40
44
  // a directive the law refuses answers ok:false (the runner frames the gate's verdict).
41
- if (args['directiveId'] == 'refuseMe') {
42
- _incoming.add({'v': kWebviewBridgeVersion, 'id': id, 'ok': false, 'error': 'the law refused this intent'});
45
+ final intentJson =
46
+ utf8.decode(base64Decode(args['intentBytes'] as String));
47
+ if (intentJson.contains('"directiveId":"refuseMe"')) {
48
+ _incoming.add({
49
+ 'v': kWebviewBridgeVersion,
50
+ 'id': id,
51
+ 'ok': false,
52
+ 'error': 'the law refused this intent'
53
+ });
43
54
  } else {
44
- _incoming.add(reply({'head': 'HEAD-${args['directiveId']}'}));
55
+ _incoming.add(reply({'head': 'HEAD-offer'}));
45
56
  }
46
57
  break;
47
- case 'deliverShare': _incoming.add(reply({'head': 'HEAD-share'})); break;
48
- case 'query': _incoming.add(reply({'rows': [{'id': 'a', 'data': {'q': args['queryId']}}]})); break;
49
- case 'queryById': _incoming.add(reply({'rows': [{'id': args['id'], 'data': {'ok': true}}]})); break;
50
- case 'count': _incoming.add(reply({'count': 7})); break;
51
- case 'sync': _incoming.add(reply({'pushed': true})); break;
52
- case 'lawStatus': _incoming.add(reply({'installed': true, 'drifted': false})); break;
53
- case 'badop': _incoming.add({'v': kWebviewBridgeVersion, 'id': id, 'ok': false, 'error': 'the law refused this intent'}); break;
54
- case 'watch': lastWatchSubId = args['subId'] as String; _incoming.add(reply({'subId': args['subId']})); break;
55
- case 'unwatch': _incoming.add(reply({'subId': args['subId']})); break;
56
- default: _incoming.add({'v': kWebviewBridgeVersion, 'id': id, 'ok': false, 'error': "unknown op '$op'"});
58
+ case 'query':
59
+ _incoming.add(reply({
60
+ 'rows': [
61
+ {
62
+ 'id': 'a',
63
+ 'data': {'q': args['queryId']}
64
+ }
65
+ ]
66
+ }));
67
+ break;
68
+ case 'queryById':
69
+ _incoming.add(reply({
70
+ 'rows': [
71
+ {
72
+ 'id': args['id'],
73
+ 'data': {'ok': true}
74
+ }
75
+ ]
76
+ }));
77
+ break;
78
+ case 'count':
79
+ _incoming.add(reply({'count': 7}));
80
+ break;
81
+ case 'sync':
82
+ _incoming.add(reply({'pushed': true}));
83
+ break;
84
+ case 'autoSync':
85
+ _incoming.add(reply({'started': true}));
86
+ break;
87
+ case 'lawStatus':
88
+ _incoming.add(reply({'installed': true, 'drifted': false}));
89
+ break;
90
+ case 'badop':
91
+ _incoming.add({
92
+ 'v': kWebviewBridgeVersion,
93
+ 'id': id,
94
+ 'ok': false,
95
+ 'error': 'the law refused this intent'
96
+ });
97
+ break;
98
+ case 'watch':
99
+ lastWatchSubId = args['subId'] as String;
100
+ _incoming.add(reply({'subId': args['subId']}));
101
+ break;
102
+ case 'unwatch':
103
+ _incoming.add(reply({'subId': args['subId']}));
104
+ break;
105
+ default:
106
+ _incoming.add({
107
+ 'v': kWebviewBridgeVersion,
108
+ 'id': id,
109
+ 'ok': false,
110
+ 'error': "unknown op '$op'"
111
+ });
57
112
  }
58
113
  });
59
114
  }
60
115
 
61
116
  /// Simulate the holon firing a reactive change to an active watch.
62
- void pushWatch(String subId, List<dynamic> rows) => _incoming.add({'v': kWebviewBridgeVersion, 'event': 'watch', 'subId': subId, 'rows': rows});
117
+ void pushWatch(String subId, List<dynamic> rows) => _incoming.add({
118
+ 'v': kWebviewBridgeVersion,
119
+ 'event': 'watch',
120
+ 'subId': subId,
121
+ 'rows': rows
122
+ });
123
+
124
+ /// Simulate the runner forwarding a signal holon projection.
125
+ void pushSignal(Map<String, Object?> signal) => _incoming
126
+ .add({'v': kWebviewBridgeVersion, 'event': 'signal', 'signal': signal});
63
127
  }
64
128
 
65
129
  /// A transport that answers EVERY request with one canned `result` (the golden corpus's reply) and records
@@ -69,7 +133,8 @@ class ReplayTransport implements BridgeTransport {
69
133
  final _incoming = StreamController<Map<String, Object?>>.broadcast();
70
134
  Map<String, Object?>? lastRequest;
71
135
  ReplayTransport(this.result) {
72
- scheduleMicrotask(() => _incoming.add({'v': kWebviewBridgeVersion, 'event': 'ready'}));
136
+ scheduleMicrotask(
137
+ () => _incoming.add({'v': kWebviewBridgeVersion, 'event': 'ready'}));
73
138
  }
74
139
  @override
75
140
  Stream<Map<String, Object?>> get messages => _incoming.stream;
@@ -77,7 +142,8 @@ class ReplayTransport implements BridgeTransport {
77
142
  void send(Map<String, Object?> message) {
78
143
  lastRequest = message;
79
144
  final id = message['id'];
80
- scheduleMicrotask(() => _incoming.add({'v': kWebviewBridgeVersion, 'id': id, 'ok': true, 'result': result}));
145
+ scheduleMicrotask(() => _incoming.add(
146
+ {'v': kWebviewBridgeVersion, 'id': id, 'ok': true, 'result': result}));
81
147
  }
82
148
  }
83
149
 
@@ -91,24 +157,29 @@ Map<String, Object?> _loadCorpus() {
91
157
  ];
92
158
  for (final p in candidates) {
93
159
  final f = File(p);
94
- if (f.existsSync()) return (jsonDecode(f.readAsStringSync()) as Map).cast<String, Object?>();
160
+ if (f.existsSync())
161
+ return (jsonDecode(f.readAsStringSync()) as Map).cast<String, Object?>();
95
162
  }
96
- fail('golden corpus not found from ${Directory.current.path}; tried $candidates');
163
+ fail(
164
+ 'golden corpus not found from ${Directory.current.path}; tried $candidates');
97
165
  }
98
166
 
99
167
  /// Drive one corpus op through the typed bridge method and return its typed result (so we can compare it to
100
168
  /// what the corpus says the unwrapped value should be).
101
- Future<Object?> _invoke(NomosBridge b, String op, Map<String, Object?> a) async {
169
+ Future<Object?> _invoke(
170
+ NomosBridge b, String op, Map<String, Object?> a) async {
102
171
  switch (op) {
103
172
  case 'connect':
104
- await b.connect(cloud: a['cloud'] as String, workspace: a['workspace'] as String, clientId: a['clientId'] as String?);
173
+ await b.connect(
174
+ cloud: a['cloud'] as String,
175
+ workspace: a['workspace'] as String,
176
+ clientId: a['clientId'] as String?);
105
177
  return null;
106
- case 'dispatch':
107
- return b.dispatch(domain: a['domain'] as String, directiveId: a['directiveId'] as String, payload: (a['payload'] as Map).cast<String, Object?>(), domainHash: a['domainHash'] as String);
108
- case 'deliverShare':
109
- return b.deliverShare(quote: (a['quote'] as Map).cast<String, Object?>(), domainHash: a['domainHash'] as String);
178
+ case 'offer':
179
+ return b.offer(intentBytes: a['intentBytes'] as String);
110
180
  case 'query':
111
- return b.query(a['queryId'] as String, (a['params'] as Map).cast<String, Object?>());
181
+ return b.query(
182
+ a['queryId'] as String, (a['params'] as Map).cast<String, Object?>());
112
183
  case 'queryById':
113
184
  return b.queryById(a['id'] as String);
114
185
  case 'count':
@@ -127,8 +198,9 @@ Future<Object?> _invoke(NomosBridge b, String op, Map<String, Object?> a) async
127
198
  /// What the corpus says the typed (unwrapped) return value should be, given the reply's `result`.
128
199
  Object? _expectedTyped(String op, Map<String, Object?> result) {
129
200
  switch (op) {
130
- case 'dispatch':
131
- case 'deliverShare':
201
+ case 'offer':
202
+ return result['head'];
203
+ case 'offer':
132
204
  return result['head'];
133
205
  case 'query':
134
206
  case 'queryById':
@@ -153,7 +225,8 @@ void main() {
153
225
  group('golden corpus (shared with the JS runner)', () {
154
226
  final corpus = _loadCorpus();
155
227
  final cases = (corpus['cases'] as List).cast<Map<String, Object?>>();
156
- test('version pins the bridge version', () => expect(corpus['version'], kWebviewBridgeVersion));
228
+ test('version pins the bridge version',
229
+ () => expect(corpus['version'], kWebviewBridgeVersion));
157
230
  for (final c in cases) {
158
231
  final op = c['op'] as String;
159
232
  final args = (c['args'] as Map).cast<String, Object?>();
@@ -163,8 +236,12 @@ void main() {
163
236
  final bridge = NomosBridge(transport);
164
237
  await bridge.ready;
165
238
  final typed = await _invoke(bridge, op, args);
166
- expect((transport.lastRequest!['args'] as Map).cast<String, Object?>(), equals(args), reason: 'sent request args');
167
- if (op != 'connect') expect(typed, equals(_expectedTyped(op, result)), reason: 'parsed typed result');
239
+ expect((transport.lastRequest!['args'] as Map).cast<String, Object?>(),
240
+ equals(args),
241
+ reason: 'sent request args');
242
+ if (op != 'connect')
243
+ expect(typed, equals(_expectedTyped(op, result)),
244
+ reason: 'parsed typed result');
168
245
  await bridge.dispose();
169
246
  });
170
247
  }
@@ -172,50 +249,116 @@ void main() {
172
249
 
173
250
  late FakeRunner runner;
174
251
  late NomosBridge bridge;
175
- setUp(() { runner = FakeRunner(); bridge = NomosBridge(runner); });
252
+ setUp(() {
253
+ runner = FakeRunner();
254
+ bridge = NomosBridge(runner);
255
+ });
176
256
  tearDown(() => bridge.dispose());
177
257
 
178
258
  test('ready completes once the runner boots', () async {
179
259
  await bridge.ready.timeout(const Duration(seconds: 1));
180
260
  });
181
261
 
182
- test('dispatch round-trips a typed head; args reach the runner verbatim', () async {
262
+ test('offer round-trips a typed head; args reach the runner verbatim',
263
+ () async {
183
264
  await bridge.ready;
184
- final head = await bridge.dispatch(domain: 'guestbook', directiveId: 'signGuestbook', payload: {'author': 'ada'}, domainHash: 'abc');
185
- expect(head, 'HEAD-signGuestbook');
186
- final req = runner.sent.firstWhere((m) => m['op'] == 'dispatch');
187
- expect((req['args'] as Map)['payload'], {'author': 'ada'});
188
- expect((req['args'] as Map)['domainHash'], 'abc');
265
+ final intentBytes = base64Encode(utf8.encode(jsonEncode({
266
+ 'v': 2,
267
+ 'intent': {
268
+ 'domain': 'guestbook',
269
+ 'directiveId': 'signGuestbook',
270
+ 'payload': {'author': 'ada'},
271
+ 'domainHash': 'abc'
272
+ }
273
+ })));
274
+ final head = await bridge.offer(intentBytes: intentBytes);
275
+ expect(head, 'HEAD-offer');
276
+ final req = runner.sent.firstWhere((m) => m['op'] == 'offer');
277
+ expect((req['args'] as Map)['intentBytes'], intentBytes);
189
278
  });
190
279
 
191
- test('connect / deliverShare / query / queryById / count / sync / lawStatus all wrap the runner', () async {
280
+ test(
281
+ 'connect / query / queryById / count / sync / lawStatus all wrap the runner',
282
+ () async {
192
283
  await bridge.ready;
193
284
  await bridge.connect(cloud: 'https://c', workspace: 'ws', clientId: 'c1');
194
- expect(await bridge.deliverShare(quote: {'publicKey': 'k'}, domainHash: 'h'), 'HEAD-share');
195
- expect((await bridge.query('byAuthor', {'author': 'ada'})).first['data'], {'q': 'byAuthor'});
285
+ expect((await bridge.query('byAuthor', {'author': 'ada'})).first['data'],
286
+ {'q': 'byAuthor'});
196
287
  expect((await bridge.queryById('Entry_1')).first['id'], 'Entry_1');
197
288
  expect(await bridge.count('entries'), 7);
198
289
  expect(await bridge.sync(), {'pushed': true});
199
- expect(await bridge.lawStatus('abc'), {'installed': true, 'drifted': false});
290
+ expect(
291
+ await bridge.lawStatus('abc'), {'installed': true, 'drifted': false});
200
292
  });
201
293
 
202
- test('watch is a reactive stream; a runner push reaches the listener; cancel unwatches', () async {
294
+ test(
295
+ 'watch is a reactive stream; a runner push reaches the listener; cancel unwatches',
296
+ () async {
203
297
  await bridge.ready;
204
298
  final got = <List<dynamic>>[];
205
299
  final sub = bridge.watch('byAuthor', {'author': 'ada'}).listen(got.add);
206
300
  await Future<void>.delayed(Duration.zero); // let the watch register
207
- runner.pushWatch(runner.lastWatchSubId!, [{'id': 'a', 'data': {'n': 2}}]);
301
+ runner.pushWatch(runner.lastWatchSubId!, [
302
+ {
303
+ 'id': 'a',
304
+ 'data': {'n': 2}
305
+ }
306
+ ]);
208
307
  await Future<void>.delayed(Duration.zero);
209
- expect(got, [[{'id': 'a', 'data': {'n': 2}}]]);
308
+ expect(got, [
309
+ [
310
+ {
311
+ 'id': 'a',
312
+ 'data': {'n': 2}
313
+ }
314
+ ]
315
+ ]);
210
316
  await sub.cancel();
211
317
  expect(runner.sent.any((m) => m['op'] == 'unwatch'), isTrue);
212
318
  });
213
319
 
214
- test('a law refusal throws NomosBridgeError with the verbatim message', () async {
320
+ test('signal events are forwarded on their own stream', () async {
321
+ await bridge.ready;
322
+ final got = <Map<String, Object?>>[];
323
+ final sub = bridge.signalEvents.listen(got.add);
324
+ runner.pushSignal({
325
+ 'type': 'signal.projection',
326
+ 'domain': 'presence',
327
+ 'projection': {
328
+ 'peers': [
329
+ {'clientId': 'prof-a', 'behindBest': 141},
330
+ ],
331
+ },
332
+ });
333
+ await Future<void>.delayed(Duration.zero);
334
+ expect(got.single['signal'], isA<Map>());
335
+ expect(((got.single['signal'] as Map)['projection'] as Map)['peers'],
336
+ isA<List>());
337
+ await sub.cancel();
338
+ });
339
+
340
+ test('autoSync forwards a scoped signal id', () async {
341
+ await bridge.ready;
342
+ await bridge.autoSync(signalId: 'signal:ws:list:main');
343
+ final req = runner.sent.firstWhere((m) => m['op'] == 'autoSync');
344
+ expect((req['args'] as Map)['signalId'], 'signal:ws:list:main');
345
+ });
346
+
347
+ test('a law refusal throws NomosBridgeError with the verbatim message',
348
+ () async {
215
349
  await bridge.ready;
216
350
  Object? err;
217
351
  try {
218
- await bridge.dispatch(domain: 'x', directiveId: 'refuseMe', payload: {}, domainHash: 'h');
352
+ await bridge.offer(
353
+ intentBytes: base64Encode(utf8.encode(jsonEncode({
354
+ 'v': 2,
355
+ 'intent': {
356
+ 'domain': 'x',
357
+ 'directiveId': 'refuseMe',
358
+ 'payload': {},
359
+ 'domainHash': 'h'
360
+ }
361
+ }))));
219
362
  } on NomosBridgeError catch (e) {
220
363
  err = e;
221
364
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@githolon/dsl",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "type": "module",
5
5
  "description": "Nomos 2 domain-authoring DSL: aggregates + directives in TS, executed and encoded to the Rust kernel's wire shapes.",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -11,8 +11,8 @@
11
11
  },
12
12
  "main": "src/index.ts",
13
13
  "bin": {
14
- "nomos-compile": "./compile_package.mjs",
15
- "holon-compile": "./compile_package.mjs"
14
+ "nomos-compile": "compile_package.mjs",
15
+ "holon-compile": "compile_package.mjs"
16
16
  },
17
17
  "exports": {
18
18
  ".": "./src/index.ts",
@@ -44,6 +44,8 @@
44
44
  "src",
45
45
  "!src/**/*.test.ts",
46
46
  "dart",
47
+ "!dart/.dart_tool",
48
+ "!dart/build",
47
49
  "compile_package.mjs"
48
50
  ],
49
51
  "publishConfig": {
@@ -157,7 +157,7 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
157
157
  const aggregates = [...byId.values()];
158
158
  const directiveIds = new Set(directives.map((d) => d.id));
159
159
  for (const task of spec.impureCapabilities ?? []) {
160
- for (const directive of [task.order, task.complete, task.fail, task.block, task.deadLetter]) {
160
+ for (const directive of [task.obligation, task.complete, task.fail, task.block, task.deadLetter]) {
161
161
  if (!directiveIds.has(directive.id)) {
162
162
  throw new Error(
163
163
  `domain '${spec.domain ?? spec.name}' declares impureCapability '${task.aggregate.id}' ` +
@@ -16,7 +16,7 @@
16
16
  * properties — invisible to those walks. This helper, applied at each plane's
17
17
  * merge point (the engine lump's `mergeModules`, the package composer's merge,
18
18
  * the compile CLI's merge), ADDS the bundle's pieces to the exports bag under
19
- * synthetic names (`<export>$aggregate`, `<export>$order`, …) so every
19
+ * synthetic names (`<export>$aggregate`, `<export>$obligation`, …) so every
20
20
  * existing shape-walk discovers them with ZERO new walk semantics. The
21
21
  * original bundle export stays (config-named lanes still resolve it); the
22
22
  * walks ignore its unmatched shape exactly as before.
@@ -34,7 +34,7 @@ export function isCapabilityDecl(v: unknown): v is Record<string, unknown> {
34
34
  );
35
35
  }
36
36
 
37
- const PIECES = ["aggregate", "order", "complete", "fail", "block", "deadLetter", "tasks"] as const;
37
+ const PIECES = ["aggregate", "obligation", "complete", "fail", "block", "deadLetter", "tasks"] as const;
38
38
 
39
39
  /**
40
40
  * Expand every `capability()` bundle in an exports bag into discoverable
@@ -15,13 +15,13 @@
15
15
  * emit (e.g. `{name:"HQ", createdAt:1000}`);
16
16
  * - typed READ accessors per aggregate + per declared query — reactive
17
17
  * `watch…`/one-shot `read…` over the GitHolon `query()`/`query_by_id()`
18
- * surface (the read half of the dispatch(intent) boundary);
18
+ * surface (the read half of the offer/query boundary);
19
19
  * - a Dart enum per `t.enum([...])` field;
20
20
  * - a Dart intent class per domain action — a pure typed DTO. Each class exposes its
21
21
  * `domain` / `intentType` (so the call site never hand-types a string) and a
22
22
  * `toPayloadJson()` that serialises only its typed fields to data. It builds no
23
23
  * `Event` and no `Hlc`: the GitHolon executes the installed domain plan.
24
- * - a single generic write verb [`dispatch`] that ships {domain, intentType,
24
+ * - a single generic effectful write verb [`offer`] that ships {domain, intentType,
25
25
  * toPayloadJson()} to the GitHolon `author(domain, intentType, payloadJson, actor)`.
26
26
  *
27
27
  * It does NOT hand-write the fixed wire types (`Value`/`Op`/`Event`/...): those
@@ -828,7 +828,7 @@ function emitPermissionVocabulary(vocab: PermissionVocabulary | undefined): stri
828
828
  return [
829
829
  `/// Generated permission vocabulary from the domain role catalogue.`,
830
830
  `///`,
831
- `/// This is frontend read/dispatch vocabulary only. Domain execution and`,
831
+ `/// This is frontend read/offer vocabulary only. Domain execution and`,
832
832
  `/// admission still happen inside the installed GitHolon domain law.`,
833
833
  `class PermissionRole {`,
834
834
  ` const PermissionRole(this.value, this.capabilities);`,
@@ -1125,7 +1125,7 @@ function emitEvidenceRefClass(): string {
1125
1125
  ` );`,
1126
1126
  ` }`,
1127
1127
  ``,
1128
- ` /// The authored payload shape (for dispatch): the typed value-object the DSL`,
1128
+ ` /// The authored payload shape (for offer): the typed value-object the DSL`,
1129
1129
  ` /// \`t.evidence()\` field lowers to a kernel \`Value::Evidence\`.`,
1130
1130
  ` Map<String, Object?> toJson() => <String, Object?>{`,
1131
1131
  ` 'hash': hash,`,
@@ -1536,7 +1536,7 @@ function emitDirective(
1536
1536
  `/// Generated typed intent DTO for '${d.id}' (marker: ${d.marker},`,
1537
1537
  `/// aggregate: ${d.aggregateId}). It carries its [domain] + [intentType] and a`,
1538
1538
  `/// [toPayloadJson] that ships only typed fields as data. It builds no`,
1539
- `/// \`Event\` and no \`Hlc\`. Pass it to [dispatch]; the GitHolon executes`,
1539
+ `/// \`Event\` and no \`Hlc\`. Pass it to [offer]; the GitHolon executes`,
1540
1540
  `/// the installed domain plan.`,
1541
1541
  `class ${className} implements NomosIntent {`,
1542
1542
  ` /// The policy domain this intent belongs to (the kernel \`author\` \`domain\`).`,
@@ -1587,7 +1587,7 @@ export function toJsonExpr(spec: PayloadFieldSpec, accessor: string): string {
1587
1587
 
1588
1588
  // ---- read accessors (typed subscriptions) ----------------------------------
1589
1589
  //
1590
- // The READ half of the peer dispatch surface (Jack 2026-06-01): the app reads ONLY via
1590
+ // The READ half of the peer query surface (Jack 2026-06-18): the app reads ONLY via
1591
1591
  // these generated typed accessors over the GitHolon read surface
1592
1592
  // (`query(query_id, params_json)` + `query_by_id(aggregate_id)`, #140's indexed/PK
1593
1593
  // read-closure). It invents NO new Rust read primitive. Two emitters:
@@ -2503,11 +2503,11 @@ export interface DomainModule {
2503
2503
  */
2504
2504
  readModelAggregates?: string[];
2505
2505
  /**
2506
- * The domain's `impureCapability` capabilities (framework Order→Receipt; `impure_capability.ts`)
2506
+ * The domain's `impureCapability` capabilities (framework Obligation→Receipt; `impure_capability.ts`)
2507
2507
  * for which to ALSO emit the typed PROVIDER SDK — the symmetric twin of the peer
2508
- * payload classes. For each declared task the codegen emits a typed `XOrder` (+
2508
+ * payload classes. For each declared task the codegen emits a typed `XObligation` (+
2509
2509
  * `fromJson`), `XResult`/`XFailure`, a `XHandler` typedef and a `XProvider` dispatcher
2510
- * that decodes the incoming Order JSON and encodes the Receipt (reusing the
2510
+ * that decodes the incoming Obligation JSON and encodes the Receipt (reusing the
2511
2511
  * peer-codegen `CompleteXPayload`/`FailXPayload`, so the Receipt is an ordinary
2512
2512
  * intent through the ONE write path). ADDITIVE + OPTIONAL: a domain that omits this is
2513
2513
  * byte-identical to before. NOT carried into any manifest/hash — a pure provider-side
@@ -2571,10 +2571,10 @@ export function generateDartDomain(mod: DomainModule): string {
2571
2571
  const mintedBlock = emitMintedCreates(mod.aggregates, mod.directives, domainKey, dartTypes);
2572
2572
 
2573
2573
  // PROVIDER SDK (the symmetric twin of the peer payload classes): for each declared
2574
- // `impureCapability` capability, a typed Order/Result/Failure + handler typedef + dispatcher.
2574
+ // `impureCapability` capability, a typed Obligation/Result/Failure + handler typedef + dispatcher.
2575
2575
  // ADDITIVE + omit-when-empty: a domain with no `impureCapabilities` emits nothing (and the
2576
2576
  // `provider.dart` import is omitted), so it is byte-identical to before. The enum map is
2577
- // SHARED with the directive/aggregate codegen so an enum-typed order param references the
2577
+ // SHARED with the directive/aggregate codegen so an enum-typed obligation param references the
2578
2578
  // already-emitted domain enum (no duplicate decl).
2579
2579
  const providerBlock = emitProviderSdk(mod.impureCapabilities ?? [], dartTypes);
2580
2580
 
@@ -2766,7 +2766,7 @@ export function generateDartDomain(mod: DomainModule): string {
2766
2766
  ``,
2767
2767
  ]
2768
2768
  : []),
2769
- `// ---- public intents (typed DTOs; ship via the generic dispatch() verb) ----`,
2769
+ `// ---- public intents (typed DTOs; ship via the generic offer() verb) ----`,
2770
2770
  dirBlock,
2771
2771
  ``,
2772
2772
  ...(mintedBlock !== ""
@@ -13,7 +13,7 @@
13
13
  * hashed (the deployed law's content hash baked in), carrying a `<Domain>Client` over a connected
14
14
  * [NomosBridge] (the WebView runner) with:
15
15
  *
16
- * * a typed DISPATCH method per directive — takes the generated `…Payload`, serialises via its
16
+ * * a typed OFFER method per directive — takes the generated `…Payload`, serialises via its
17
17
  * `toPayloadJson()`, auto-stamps omitted `…At` timestamps (the same caller-clock sugar the TS
18
18
  * client applies), returns the new local head;
19
19
  * * a typed declared-QUERY accessor per `query(...)` — one-shot `Future<List<…ReadModel>>` + a
@@ -81,7 +81,7 @@ function rowToModel(model: string, rowVar: string): string {
81
81
  }
82
82
 
83
83
  export interface DartBridgeClientOptions {
84
- /** sha256 of the built `.package.usda` — the law this client dispatches under. */
84
+ /** sha256 of the built `.package.usda` — the law this client offers under. */
85
85
  readonly domainHash: string;
86
86
  }
87
87
 
@@ -95,7 +95,7 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
95
95
 
96
96
  const methods: string[] = [];
97
97
 
98
- // ── directives → typed dispatch ──
98
+ // ── directives → typed offer ──
99
99
  const dirs = (mod.directives as AnyDirective[]).filter(
100
100
  (d) => d && typeof d.id === "string" && typeof (d as { payloadSchema?: unknown }).payloadSchema === "object",
101
101
  );
@@ -106,12 +106,12 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
106
106
  if (stamps.length > 0) {
107
107
  methods.push(
108
108
  ` /// Author \`${domain}/${d.id}\` locally under the installed law; returns the new local head.`,
109
- ` /// Omitted ${stamps.map((s) => `\`${s}\``).join(" / ")} auto-stamp(s) ISO now() at dispatch.`,
109
+ ` /// Omitted ${stamps.map((s) => `\`${s}\``).join(" / ")} auto-stamp(s) ISO now() at offer.`,
110
110
  ` Future<String> ${method}(${payloadClass} payload) {`,
111
111
  ` final data = payload.toPayloadJson();`,
112
112
  ` final now = DateTime.now().toUtc().toIso8601String();`,
113
113
  ...stamps.map((s) => ` data.putIfAbsent(${dartStr(s)}, () => now);`),
114
- ` return bridge.dispatch(domain: ${dartStr(domain)}, directiveId: ${dartStr(d.id)}, payload: data, domainHash: domainHash);`,
114
+ ` return bridge.offer(intentBytes: _encodeIntentOffer(${dartStr(domain)}, ${dartStr(d.id)}, data, domainHash));`,
115
115
  ` }`,
116
116
  ``,
117
117
  );
@@ -119,7 +119,7 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
119
119
  methods.push(
120
120
  ` /// Author \`${domain}/${d.id}\` locally under the installed law; returns the new local head.`,
121
121
  ` Future<String> ${method}(${payloadClass} payload) =>`,
122
- ` bridge.dispatch(domain: ${dartStr(domain)}, directiveId: ${dartStr(d.id)}, payload: payload.toPayloadJson(), domainHash: domainHash);`,
122
+ ` bridge.offer(intentBytes: _encodeIntentOffer(${dartStr(domain)}, ${dartStr(d.id)}, payload.toPayloadJson(), domainHash));`,
123
123
  ``,
124
124
  );
125
125
  }
@@ -196,6 +196,8 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
196
196
  `// final app = ${className}(bridge);`,
197
197
  `// await app.${dirs[0] ? safeMethod(dirs[0].id) : "someDirective"}(/* typed payload */);`,
198
198
  ``,
199
+ `import 'dart:convert';`,
200
+ ``,
199
201
  `import 'nomos_dsl.dart';`,
200
202
  `import '${mod.name}.dart';`,
201
203
  `// Re-export the sibling types (read models + payloads + the bridge runtime) so ONE import of this`,
@@ -203,9 +205,20 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
203
205
  `export '${mod.name}.dart';`,
204
206
  `export 'nomos_dsl.dart' show NomosBridge, BridgeTransport, NomosBridgeError, kWebviewBridgeVersion;`,
205
207
  ``,
206
- `/// sha256 of the deployed \`.package.usda\` — the installed law this client dispatches under.`,
208
+ `/// sha256 of the deployed \`.package.usda\` — the installed law this client offers under.`,
207
209
  `const String ${hashConst} = ${dartStr(opts.domainHash)};`,
208
210
  ``,
211
+ `String _encodeIntentOffer(String domain, String directiveId, Object? payload, String domainHash) =>`,
212
+ ` base64Encode(utf8.encode(jsonEncode({`,
213
+ ` 'v': 2,`,
214
+ ` 'intent': {`,
215
+ ` 'domain': domain,`,
216
+ ` 'directiveId': directiveId,`,
217
+ ` 'payload': payload,`,
218
+ ` 'domainHash': domainHash,`,
219
+ ` },`,
220
+ ` })));`,
221
+ ``,
209
222
  `/// Typed async client for the \`${domain}\` domain, bound to a connected [NomosBridge].`,
210
223
  `class ${className} {`,
211
224
  ` final NomosBridge bridge;`,