@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.
- package/dart/example/nomos_webview_transport.dart +2 -2
- package/dart/lib/nomos_dsl.dart +4 -4
- package/dart/lib/src/{dispatch.dart → offer.dart} +7 -7
- package/dart/lib/src/provider.dart +30 -30
- package/dart/lib/src/subscriptions.dart +3 -3
- package/dart/lib/src/webview_bridge.dart +157 -29
- package/dart/test/webview_bridge_test.dart +193 -50
- package/package.json +5 -3
- package/src/build_package.ts +1 -1
- package/src/capability_exports.ts +2 -2
- package/src/codegen_dart.ts +12 -12
- package/src/codegen_dart_client.ts +20 -7
- package/src/codegen_provider_dart.ts +67 -61
- package/src/codegen_ts.ts +18 -15
- package/src/compile_package_main.ts +13 -16
- package/src/framework/capability.ts +10 -10
- package/src/framework/impure_capability.ts +73 -67
- package/src/framework/sync_lifecycle.ts +3 -3
- package/src/framework/workspaces.ts +7 -0
- package/src/index.ts +1 -1
- package/src/usd.ts +156 -0
- package/src/usd_layers.ts +170 -26
- package/src/workspace_sharding.ts +2 -7
|
@@ -9,32 +9,32 @@
|
|
|
9
9
|
* Provider SDK codegen — the SYMMETRIC TWIN of the typed peer write codegen
|
|
10
10
|
* (`codegen_dart.ts`).
|
|
11
11
|
*
|
|
12
|
-
*
|
|
12
|
+
* Obligation and Receipt are ordinary intent roles. This codegen gives a capability provider typed
|
|
13
13
|
* decode/encode helpers; it does not create a separate admission job protocol or a privileged
|
|
14
14
|
* mutation path.
|
|
15
15
|
*
|
|
16
16
|
* ── Why this exists (Jack 2026-06-04) ────────────────────────────────────────
|
|
17
17
|
* Code is ~free; INTEGRATION POINTS are expensive + fuckup-guaranteed. A capability
|
|
18
18
|
* PROVIDER (Whisper, a renderer, a multi-target PR transporter) sits at
|
|
19
|
-
* the far end of the framework `impureCapability`
|
|
20
|
-
* hand-parse the incoming
|
|
19
|
+
* the far end of the framework `impureCapability` Obligation→Receipt pair. Today a dev would
|
|
20
|
+
* hand-parse the incoming Obligation intent JSON and hand-build the Receipt — exactly the
|
|
21
21
|
* brittle integration the peer write codegen eliminated on the WRITE side. This generator
|
|
22
22
|
* eliminates it on the PROVIDE side: a dev writes ONE typed method
|
|
23
|
-
* `handler(
|
|
23
|
+
* `handler(Obligation) -> Result | Failure`, and the generated dispatcher DECODES the Obligation
|
|
24
24
|
* and ENCODES the ordinary Receipt intent — proven in ISOLATION (a Dart unit test), with NO deploy /
|
|
25
25
|
* daemon / e2e.
|
|
26
26
|
*
|
|
27
27
|
* ── What it emits, per `impureCapability` capability (the STANDARD shape, never invented) ──
|
|
28
|
-
* 1. a typed `
|
|
28
|
+
* 1. a typed `XObligation` — the Obligation params (envelope provenance `requestId` /
|
|
29
29
|
* `requestedBy` / `requestedAt` ⊕ the tenant params from the DSL), with a
|
|
30
|
-
* `fromJson` decoding the incoming
|
|
30
|
+
* `fromJson` decoding the incoming Obligation intent's payload JSON;
|
|
31
31
|
* 2. a typed `XResult` — the success payload the handler returns (`resultRef`, the
|
|
32
32
|
* content-addressed Evidence handle — the framework's `completeSchema.resultRef`),
|
|
33
33
|
* implementing `TaskResult.toResultRef()`;
|
|
34
34
|
* 3. typed failure/blocked/DLQ outcomes;
|
|
35
|
-
* 4. a handler typedef `XHandler = TaskOutcome<XResult> Function(
|
|
35
|
+
* 4. a handler typedef `XHandler = TaskOutcome<XResult> Function(XObligation)`;
|
|
36
36
|
* 5. a generated `XProvider` dispatcher (`implements TaskProvider`): given the
|
|
37
|
-
* registered typed handler, `
|
|
37
|
+
* registered typed handler, `handleObligation(obligationJson, now)` DECODES → calls the
|
|
38
38
|
* handler → ENCODES the typed outcome into the ordinary RECEIPT intent — REUSING the peer write
|
|
39
39
|
* codegen's `CompleteXPayload` / `FailXPayload` / `BlockXPayload` /
|
|
40
40
|
* `DeadLetterXPayload`. The Receipt is an
|
|
@@ -61,7 +61,7 @@ import {
|
|
|
61
61
|
/**
|
|
62
62
|
* The minimal shape of a `impureCapability` declaration the provider codegen needs. A
|
|
63
63
|
* `ImpureCapability` (framework `impure_capability.ts`) carries EXACTLY this — its task `aggregate`
|
|
64
|
-
* handle (whose `.id` is the wire type) and its `.
|
|
64
|
+
* handle (whose `.id` is the wire type) and its `.obligation` / receipt directives
|
|
65
65
|
* directives — so a `impureCapability(...)` return value satisfies `ImpureCapabilityDecl`
|
|
66
66
|
* STRUCTURALLY. The emit script passes the values straight through: no re-derivation, no
|
|
67
67
|
* fragile structural inference over the flattened directive list.
|
|
@@ -69,8 +69,8 @@ import {
|
|
|
69
69
|
export interface ImpureCapabilityDecl {
|
|
70
70
|
/** The task aggregate handle; `.id` is the wire type (e.g. `TranscribeVoiceNoteAggregate`). */
|
|
71
71
|
readonly aggregate: { readonly id: string };
|
|
72
|
-
/** The
|
|
73
|
-
readonly
|
|
72
|
+
/** The Obligation directive: `.creates`, payload = envelope ⊕ tenant params. */
|
|
73
|
+
readonly obligation: { readonly id: string; readonly payloadSchema: z.ZodTypeAny };
|
|
74
74
|
/** The COMPLETE (success) Receipt directive: payload = {requestId, resultRef, completedAt}. */
|
|
75
75
|
readonly complete: { readonly id: string };
|
|
76
76
|
/** The FAIL (failure) Receipt directive: payload = {requestId, failureReason, failedAt}. */
|
|
@@ -81,17 +81,23 @@ export interface ImpureCapabilityDecl {
|
|
|
81
81
|
readonly deadLetter: { readonly id: string };
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
/** The framework
|
|
85
|
-
* type carries them (the provider sees the whole
|
|
84
|
+
/** The framework Obligation envelope field names — provenance/timing, NOT tenant params. The Obligation
|
|
85
|
+
* type carries them (the provider sees the whole Obligation intent) but the codegen labels
|
|
86
86
|
* them so the doc separates provenance from the tenant's request params. */
|
|
87
|
-
const
|
|
87
|
+
const OBLIGATION_ENVELOPE_FIELDS = new Set([
|
|
88
|
+
"requestId",
|
|
89
|
+
"requestedBy",
|
|
90
|
+
"requestedAt",
|
|
91
|
+
"scheduledAt",
|
|
92
|
+
"deadlineAt",
|
|
93
|
+
]);
|
|
88
94
|
|
|
89
|
-
/** A stable PascalCase base for a task's generated class family, derived from its
|
|
90
|
-
* directive id with a leading `create`/`
|
|
95
|
+
/** A stable PascalCase base for a task's generated class family, derived from its Obligation
|
|
96
|
+
* directive id with a leading `create`/`oblige` verb stripped (so `createTranscribeVoiceNote`
|
|
91
97
|
* → `TranscribeVoiceNote`). Pure + deterministic. */
|
|
92
|
-
export function taskBaseName(
|
|
93
|
-
const stripped =
|
|
94
|
-
const base = stripped === "" ?
|
|
98
|
+
export function taskBaseName(obligationDirectiveId: string): string {
|
|
99
|
+
const stripped = obligationDirectiveId.replace(/^(create|oblige)/, "");
|
|
100
|
+
const base = stripped === "" ? obligationDirectiveId : stripped;
|
|
95
101
|
return cap(base);
|
|
96
102
|
}
|
|
97
103
|
|
|
@@ -109,21 +115,21 @@ function deadLetterPayloadClass(deadLetterDirectiveId: string): string {
|
|
|
109
115
|
return `${cap(deadLetterDirectiveId)}Payload`;
|
|
110
116
|
}
|
|
111
117
|
|
|
112
|
-
/** Dart field DECL for one
|
|
113
|
-
function
|
|
114
|
-
const provenance =
|
|
118
|
+
/** Dart field DECL for one Obligation field (optional → nullable). */
|
|
119
|
+
function obligationFieldDecl(s: PayloadFieldSpec): string {
|
|
120
|
+
const provenance = OBLIGATION_ENVELOPE_FIELDS.has(s.name)
|
|
115
121
|
? ` // envelope provenance (framework)`
|
|
116
122
|
: ``;
|
|
117
123
|
return ` final ${s.dartType}${s.optional ? "?" : ""} ${s.name};${provenance}`;
|
|
118
124
|
}
|
|
119
125
|
|
|
120
|
-
/** Dart ctor PARAM for one
|
|
121
|
-
function
|
|
126
|
+
/** Dart ctor PARAM for one Obligation field. */
|
|
127
|
+
function obligationCtorParam(s: PayloadFieldSpec): string {
|
|
122
128
|
return ` ${s.optional ? "" : "required "}this.${s.name},`;
|
|
123
129
|
}
|
|
124
130
|
|
|
125
131
|
/**
|
|
126
|
-
* Expression decoding ONE
|
|
132
|
+
* Expression decoding ONE Obligation field FROM the already-`jsonDecode`d payload map `j`.
|
|
127
133
|
* The REVERSE of the peer codegen's `toJsonExpr` — same field-kind matrix:
|
|
128
134
|
* • scalar — read directly, cast to the Dart scalar type.
|
|
129
135
|
* • enum — `XEnum.fromWire(j['f'] as String)`.
|
|
@@ -132,7 +138,7 @@ function orderCtorParam(s: PayloadFieldSpec): string {
|
|
|
132
138
|
* Optional fields tolerate absence (return null); required fields cast strictly (a
|
|
133
139
|
* missing required field throws on the cast — the strict-decode contract).
|
|
134
140
|
*/
|
|
135
|
-
function
|
|
141
|
+
function obligationFromJsonExpr(s: PayloadFieldSpec): string {
|
|
136
142
|
const raw = `j['${s.name}']`;
|
|
137
143
|
switch (s.toJson) {
|
|
138
144
|
case "scalar": {
|
|
@@ -168,22 +174,22 @@ function orderFromJsonExpr(s: PayloadFieldSpec): string {
|
|
|
168
174
|
}
|
|
169
175
|
}
|
|
170
176
|
|
|
171
|
-
/** Emit the typed `
|
|
172
|
-
function
|
|
177
|
+
/** Emit the typed `XObligation` class — the DECODED incoming Obligation. */
|
|
178
|
+
function emitObligationClass(
|
|
173
179
|
base: string,
|
|
174
|
-
|
|
180
|
+
obligationDirectiveId: string,
|
|
175
181
|
specs: PayloadFieldSpec[],
|
|
176
182
|
): string {
|
|
177
|
-
const cls = `${base}
|
|
178
|
-
const fieldDecls = specs.map(
|
|
179
|
-
const ctorParams = specs.map(
|
|
183
|
+
const cls = `${base}Obligation`;
|
|
184
|
+
const fieldDecls = specs.map(obligationFieldDecl).join("\n");
|
|
185
|
+
const ctorParams = specs.map(obligationCtorParam).join("\n");
|
|
180
186
|
const fromJsonAssigns = specs
|
|
181
|
-
.map((s) => ` ${s.name}: ${
|
|
187
|
+
.map((s) => ` ${s.name}: ${obligationFromJsonExpr(s)},`)
|
|
182
188
|
.join("\n");
|
|
183
189
|
return [
|
|
184
|
-
`/// The typed
|
|
190
|
+
`/// The typed Obligation for the \`${obligationDirectiveId}\` capability — what the provider`,
|
|
185
191
|
`/// RECEIVES. Carries the framework envelope provenance (requestId / requestedBy /`,
|
|
186
|
-
`/// requestedAt) ⊕ the tenant request params. Decoded from the incoming
|
|
192
|
+
`/// requestedAt / scheduledAt / deadlineAt) ⊕ the tenant request params. Decoded from the incoming Obligation intent's`,
|
|
187
193
|
`/// payload JSON via [${cls}.fromJson]; the handler sees ONLY this typed value.`,
|
|
188
194
|
`class ${cls} {`,
|
|
189
195
|
fieldDecls,
|
|
@@ -192,8 +198,8 @@ function emitOrderClass(
|
|
|
192
198
|
ctorParams,
|
|
193
199
|
` });`,
|
|
194
200
|
``,
|
|
195
|
-
` /// Decode an
|
|
196
|
-
` /// peer's \`${
|
|
201
|
+
` /// Decode an Obligation from the already-\`jsonDecode\`d payload map (the EXACT shape the`,
|
|
202
|
+
` /// peer's \`${obligationDirectiveId}\` \`toPayloadJson()\` produced). Reverse of the peer`,
|
|
197
203
|
` /// encode — types stop here, at the JSON serde.`,
|
|
198
204
|
` factory ${cls}.fromJson(Map<String, Object?> j) => ${cls}(`,
|
|
199
205
|
fromJsonAssigns,
|
|
@@ -224,7 +230,7 @@ function emitResultClasses(base: string, completeDirectiveId: string): string {
|
|
|
224
230
|
``,
|
|
225
231
|
`/// The typed FAILURE the \`${base}\` handler returns — the recorded reason the side-effect`,
|
|
226
232
|
`/// failed. Maps to the framework \`fail…\` Receipt (status:failed + failureReason +`,
|
|
227
|
-
`///
|
|
233
|
+
`/// closesObligation); a recorded failure is terminal, NOT a retry storm.`,
|
|
228
234
|
`class ${failureCls} {`,
|
|
229
235
|
` final String failureReason;`,
|
|
230
236
|
` const ${failureCls}({required this.failureReason});`,
|
|
@@ -237,7 +243,7 @@ function emitProviderClass(
|
|
|
237
243
|
task: ImpureCapabilityDecl,
|
|
238
244
|
base: string,
|
|
239
245
|
): string {
|
|
240
|
-
const
|
|
246
|
+
const obligationCls = `${base}Obligation`;
|
|
241
247
|
const resultCls = `${base}Result`;
|
|
242
248
|
const handlerTypedef = `${base}Handler`;
|
|
243
249
|
const providerCls = `${base}Provider`;
|
|
@@ -247,25 +253,25 @@ function emitProviderClass(
|
|
|
247
253
|
const deadLetterCls = deadLetterPayloadClass(task.deadLetter.id);
|
|
248
254
|
return [
|
|
249
255
|
`/// The typed HANDLER the dev writes for the \`${base}\` capability: given the typed`,
|
|
250
|
-
`/// [${
|
|
256
|
+
`/// [${obligationCls}], perform the side-effect and return a typed [TaskOutcome] — a`,
|
|
251
257
|
`/// [TaskSuccess]<[${resultCls}]>, [TaskFailure], [TaskBlocked], or [TaskDeadLetter]. NO raw JSON, NO directive id, NO`,
|
|
252
258
|
`/// Receipt construction: the dev writes pure typed business logic; the [${providerCls}]`,
|
|
253
259
|
`/// does the decode/encode round-trip.`,
|
|
254
|
-
`typedef ${handlerTypedef} = TaskOutcome<${resultCls}> Function(${
|
|
260
|
+
`typedef ${handlerTypedef} = TaskOutcome<${resultCls}> Function(${obligationCls} obligation);`,
|
|
255
261
|
``,
|
|
256
262
|
`/// The GENERATED dispatcher for the \`${base}\` capability — the symmetric twin of the`,
|
|
257
|
-
`/// peer's typed dispatch. Register a typed [${handlerTypedef}]; [
|
|
258
|
-
`/// incoming
|
|
263
|
+
`/// peer's typed dispatch. Register a typed [${handlerTypedef}]; [handleObligation] DECODES an`,
|
|
264
|
+
`/// incoming Obligation intent JSON into [${obligationCls}], runs the handler, and ENCODES the typed`,
|
|
259
265
|
`/// outcome into the ordinary RECEIPT intent: [${completeCls}], [${failCls}],`,
|
|
260
266
|
`/// [${blockCls}], or [${deadLetterCls}]. The returned [NomosIntent] ships through the SAME`,
|
|
261
267
|
`/// \`dispatch\` gate as any authored write (ONE-WRITE-PATH).`,
|
|
262
|
-
`class ${providerCls} implements TaskProvider<${
|
|
268
|
+
`class ${providerCls} implements TaskProvider<${obligationCls}, ${resultCls}> {`,
|
|
263
269
|
` /// The dev's typed handler.`,
|
|
264
270
|
` final ${handlerTypedef} handler;`,
|
|
265
271
|
` const ${providerCls}(this.handler);`,
|
|
266
272
|
``,
|
|
267
273
|
` @override`,
|
|
268
|
-
` String get
|
|
274
|
+
` String get obligationDirectiveId => '${task.obligation.id}';`,
|
|
269
275
|
` @override`,
|
|
270
276
|
` String get completeDirectiveId => '${task.complete.id}';`,
|
|
271
277
|
` @override`,
|
|
@@ -276,39 +282,39 @@ function emitProviderClass(
|
|
|
276
282
|
` String get deadLetterDirectiveId => '${task.deadLetter.id}';`,
|
|
277
283
|
``,
|
|
278
284
|
` @override`,
|
|
279
|
-
` NomosIntent
|
|
280
|
-
`
|
|
285
|
+
` NomosIntent handleObligation(String obligationPayloadJson, {required HostClock now}) =>`,
|
|
286
|
+
` handleObligationMap(decodeObligationPayload(obligationPayloadJson), now: now);`,
|
|
281
287
|
``,
|
|
282
288
|
` @override`,
|
|
283
|
-
` NomosIntent
|
|
284
|
-
` Map<String, Object?>
|
|
289
|
+
` NomosIntent handleObligationMap(`,
|
|
290
|
+
` Map<String, Object?> obligationPayload, {`,
|
|
285
291
|
` required HostClock now,`,
|
|
286
292
|
` }) {`,
|
|
287
|
-
` final
|
|
288
|
-
` final outcome = handler(
|
|
293
|
+
` final obligation = ${obligationCls}.fromJson(obligationPayload);`,
|
|
294
|
+
` final outcome = handler(obligation);`,
|
|
289
295
|
` // ENCODE the outcome into the ordinary RECEIPT intent (reusing the peer-codegen Receipt`,
|
|
290
296
|
` // payload classes — same generated file). The Receipt addresses the SAME instance`,
|
|
291
|
-
` // the
|
|
297
|
+
` // the Obligation created (keyed by requestId), so closesObligation == requestId by`,
|
|
292
298
|
` // construction in the engine's \`.plan()\` — terminal-once is the kernel's gate.`,
|
|
293
299
|
` return switch (outcome) {`,
|
|
294
300
|
` TaskSuccess<${resultCls}>(:final result) => ${completeCls}(`,
|
|
295
|
-
` requestId:
|
|
301
|
+
` requestId: obligation.requestId,`,
|
|
296
302
|
` resultRef: result.toResultRef(),`,
|
|
297
303
|
` completedAt: now(),`,
|
|
298
304
|
` ),`,
|
|
299
305
|
` TaskFailure<${resultCls}>(:final failureReason) => ${failCls}(`,
|
|
300
|
-
` requestId:
|
|
306
|
+
` requestId: obligation.requestId,`,
|
|
301
307
|
` failureReason: failureReason,`,
|
|
302
308
|
` failedAt: now(),`,
|
|
303
309
|
` ),`,
|
|
304
310
|
` TaskBlocked<${resultCls}>(:final blockedReason, :final nextAction) => ${blockCls}(`,
|
|
305
|
-
` requestId:
|
|
311
|
+
` requestId: obligation.requestId,`,
|
|
306
312
|
` blockedReason: blockedReason,`,
|
|
307
313
|
` blockedAt: now(),`,
|
|
308
314
|
` nextAction: nextAction,`,
|
|
309
315
|
` ),`,
|
|
310
316
|
` TaskDeadLetter<${resultCls}>(:final deadLetterReason) => ${deadLetterCls}(`,
|
|
311
|
-
` requestId:
|
|
317
|
+
` requestId: obligation.requestId,`,
|
|
312
318
|
` deadLetterReason: deadLetterReason,`,
|
|
313
319
|
` deadLetteredAt: now(),`,
|
|
314
320
|
` ),`,
|
|
@@ -319,20 +325,20 @@ function emitProviderClass(
|
|
|
319
325
|
}
|
|
320
326
|
|
|
321
327
|
/**
|
|
322
|
-
* Emit the full provider SDK for ONE `impureCapability`: the typed
|
|
328
|
+
* Emit the full provider SDK for ONE `impureCapability`: the typed Obligation/Result/Failure
|
|
323
329
|
* classes + the handler typedef + the dispatcher. `enumDartTypes` is the SAME map the
|
|
324
|
-
* domain's `collectEnums` produced (so an enum-typed
|
|
330
|
+
* domain's `collectEnums` produced (so an enum-typed obligation param references the
|
|
325
331
|
* already-emitted domain enum — NO duplicate enum decl).
|
|
326
332
|
*/
|
|
327
333
|
export function emitProviderTask(
|
|
328
334
|
task: ImpureCapabilityDecl,
|
|
329
335
|
enumDartTypes: Map<string, string>,
|
|
330
336
|
): string {
|
|
331
|
-
const base = taskBaseName(task.
|
|
332
|
-
const specs = introspectPayload(task.
|
|
337
|
+
const base = taskBaseName(task.obligation.id);
|
|
338
|
+
const specs = introspectPayload(task.obligation.payloadSchema, enumDartTypes);
|
|
333
339
|
return [
|
|
334
340
|
`// ── provider SDK: ${base} (impureCapability '${task.aggregate.id}') ──`,
|
|
335
|
-
|
|
341
|
+
emitObligationClass(base, task.obligation.id, specs),
|
|
336
342
|
``,
|
|
337
343
|
emitResultClasses(base, task.complete.id),
|
|
338
344
|
``,
|
package/src/codegen_ts.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Dart apps get generated payload classes / read models / typed query accessors;
|
|
12
12
|
* until this file, a web app on `@githolon/client` hand-typed directive ids and
|
|
13
|
-
* payload shapes against the raw `
|
|
13
|
+
* payload shapes against the raw `offer()` surface. `generateTsClient(modules,
|
|
14
14
|
* opts)` closes that: ONE generated `.ts` file per package, emitted by
|
|
15
15
|
* `nomos-compile` AFTER the package is hashed (the deployed law's content hash is
|
|
16
16
|
* baked in as a constant), carrying:
|
|
@@ -20,13 +20,13 @@
|
|
|
20
20
|
* becomes `?`, `.int()` stays `number`), so a payload the type system accepts is
|
|
21
21
|
* a payload the engine accepts; required string fields named `…At` are
|
|
22
22
|
* CALLER-STAMPED TIMESTAMPS — optional in the type, auto-filled with ISO now()
|
|
23
|
-
* at
|
|
23
|
+
* at offer (see {@link autoStampFields});
|
|
24
24
|
* * a READ-MODEL interface per aggregate — derived from the DSL `Field` kinds the
|
|
25
25
|
* projection decodes by (set→`T[]`, map→`Record<string, V>` via `mapValueKind`,
|
|
26
26
|
* `t.jsonObject()`→`Record<string, unknown>`, plain `t.json()`→`unknown`), with
|
|
27
27
|
* derived/combined read fields merged in (typed off their declared zod returns).
|
|
28
28
|
* EVERY field is `?`: a projection row carries only what has folded so far;
|
|
29
|
-
* * a CLIENT FACTORY per domain — typed `
|
|
29
|
+
* * a CLIENT FACTORY per domain — typed `offer` wrappers bound to the baked
|
|
30
30
|
* domainHash, typed declared-query accessors, and by-id read/watch helpers —
|
|
31
31
|
* over a STRUCTURAL `NomosHolon` interface, so the file has ZERO imports and
|
|
32
32
|
* binds to `@githolon/client`'s `connect()` result (browser or node) as-is.
|
|
@@ -173,7 +173,7 @@ function tsTypeOfZod(zt: z.ZodTypeAny, indent: string): string {
|
|
|
173
173
|
/**
|
|
174
174
|
* Top-level REQUIRED string payload fields named `…At` (`signedAt`, `updatedAt`,
|
|
175
175
|
* `stampedAt`, …) are CALLER-STAMPED TIMESTAMPS by convention: the generated client
|
|
176
|
-
* fills an omitted one with `new Date().toISOString()` at
|
|
176
|
+
* fills an omitted one with `new Date().toISOString()` at offer. The stamp rides
|
|
177
177
|
* IN the payload — the plan stays a pure function of `(payload, ports)` and replays
|
|
178
178
|
* byte-identical on every peer — so the domain dev stops thinking about clocks.
|
|
179
179
|
* Passing the field explicitly always wins (scheduling-shaped fields like
|
|
@@ -210,11 +210,11 @@ function tsTypeOfPayload(
|
|
|
210
210
|
f = zodDef(f).innerType!;
|
|
211
211
|
}
|
|
212
212
|
const note = stamped.has(name)
|
|
213
|
-
? " // auto-stamped with ISO now() at
|
|
213
|
+
? " // auto-stamped with ISO now() at offer when omitted"
|
|
214
214
|
: mintedSet.has(name)
|
|
215
|
-
? " // kernel-minted at
|
|
215
|
+
? " // kernel-minted at offer when omitted (the front-door mint; returned on the result)"
|
|
216
216
|
: placedSet.has(name)
|
|
217
|
-
? " // picked LEAST-LOADED over the coordinator's law-state at
|
|
217
|
+
? " // picked LEAST-LOADED over the coordinator's law-state at offer when omitted (sharding §3)"
|
|
218
218
|
: "";
|
|
219
219
|
return ` ${name}${optional ? "?" : ""}: ${tsTypeOfZod(f, " ")};${note}`;
|
|
220
220
|
});
|
|
@@ -287,7 +287,7 @@ function aggregatesOf(mod: Record<string, unknown>): AggregateHandle[] {
|
|
|
287
287
|
export interface TsClientOptions {
|
|
288
288
|
/** The package name (`nomos.package.mjs` `name`) — used for the hash constant. */
|
|
289
289
|
readonly packageName: string;
|
|
290
|
-
/** sha256 of the built `.package.usda` — the law this client
|
|
290
|
+
/** sha256 of the built `.package.usda` — the law this client offers under. */
|
|
291
291
|
readonly domainHash: string;
|
|
292
292
|
}
|
|
293
293
|
|
|
@@ -319,7 +319,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
319
319
|
`// const holon = await connect({ cloud, workspace, clientId });`,
|
|
320
320
|
`// const app = ${tsClientFactoryName(modules[0]?.domain ?? "domain")}(holon);`,
|
|
321
321
|
``,
|
|
322
|
-
`/** sha256 of the deployed \`.package.usda\` — the installed law this client
|
|
322
|
+
`/** sha256 of the deployed \`.package.usda\` — the installed law this client offers under. */`,
|
|
323
323
|
`export const ${hashConst} = ${JSON.stringify(opts.domainHash)};`,
|
|
324
324
|
``,
|
|
325
325
|
`/** One projection row: the aggregate's wire type, its id, and the folded fields. */`,
|
|
@@ -327,7 +327,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
327
327
|
``,
|
|
328
328
|
`/** The holon surface this client binds to (structurally satisfied by @githolon/client). */`,
|
|
329
329
|
`export interface NomosHolon {`,
|
|
330
|
-
`
|
|
330
|
+
` offer(intentBytes: string | Uint8Array): Promise<string>;`,
|
|
331
331
|
` query(queryId: string, params?: Record<string, unknown>): Promise<unknown>;`,
|
|
332
332
|
` queryById(aggregateId: string): Promise<unknown>;`,
|
|
333
333
|
` watchById(aggregateId: string, cb: (rows: unknown) => void, opts?: { intervalMs?: number }): () => void;`,
|
|
@@ -347,6 +347,9 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
347
347
|
` discardDeadLetter(id: string): Promise<{ ok: boolean; removed: number }>;`,
|
|
348
348
|
`}`,
|
|
349
349
|
``,
|
|
350
|
+
`const encodeIntentOffer = (domain: string, directiveId: string, payload: unknown, domainHash: string): Uint8Array =>`,
|
|
351
|
+
` new TextEncoder().encode(JSON.stringify({ v: 2, intent: { domain, directiveId, payload, domainHash } }));`,
|
|
352
|
+
``,
|
|
350
353
|
);
|
|
351
354
|
|
|
352
355
|
const anyAutoStamp = modules.some((mod) =>
|
|
@@ -553,7 +556,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
553
556
|
|
|
554
557
|
const mintPlan = mintPlans.get(mod);
|
|
555
558
|
// PLACEMENT directives (packed taxonomy births): their `shard` is AUTO-PICKED
|
|
556
|
-
// least-loaded by the routed holon at
|
|
559
|
+
// least-loaded by the routed holon at offer — optional on the typed surface
|
|
557
560
|
// (the §7 transparency contract: a dev never names a shard).
|
|
558
561
|
const placementDirs = new Set<string>();
|
|
559
562
|
if (mod.workspaceTypes !== undefined && mod.workspaceTypes.length > 0) {
|
|
@@ -573,7 +576,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
573
576
|
const mint = mintPlan?.get(d.id);
|
|
574
577
|
const placed = placementDirs.has(d.id) ? ["shard"] : [];
|
|
575
578
|
out.push(
|
|
576
|
-
`/** \`${domain}/${d.id}\` payload (mirrors the engine's zod schema).${stamps.length ? ` Omitted ${stamps.map((s) => `\`${s}\``).join("/")} auto-stamp(s) ISO now() at
|
|
579
|
+
`/** \`${domain}/${d.id}\` payload (mirrors the engine's zod schema).${stamps.length ? ` Omitted ${stamps.map((s) => `\`${s}\``).join("/")} auto-stamp(s) ISO now() at offer.` : ""}${mint ? ` Omitted \`${mint.field}\` is KERNEL-MINTED at offer (returned on the result).` : ""}${placed.length ? " Omitted `shard` is picked LEAST-LOADED at offer (the routed holon)." : ""} */`,
|
|
577
580
|
`export type ${payloadName} = ${tsTypeOfPayload(schema, stamps, mint ? [mint.field] : [], placed)};`,
|
|
578
581
|
``,
|
|
579
582
|
);
|
|
@@ -592,7 +595,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
592
595
|
` * — ids stay internal plumbing — and returns it with the new local head. */`,
|
|
593
596
|
` ${d.id}: async (payload: ${payloadName}): Promise<{ head: string; ${mint.field}: string }> => {`,
|
|
594
597
|
` const ${mint.field} = payload.${mint.field} ?? (await (holon as NomosRoutedHolon).mint(${JSON.stringify(mint.mintType)}${mintOpts}));`,
|
|
595
|
-
` const head = await holon.
|
|
598
|
+
` const head = await holon.offer(encodeIntentOffer(${JSON.stringify(domain)}, ${JSON.stringify(d.id)}, ${body}, domainHash));`,
|
|
596
599
|
` return { head, ${mint.field} };`,
|
|
597
600
|
` },`,
|
|
598
601
|
);
|
|
@@ -601,8 +604,8 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
601
604
|
methods.push(
|
|
602
605
|
` /** Author \`${domain}/${d.id}\` locally under the installed law; returns the new local head. */`,
|
|
603
606
|
stamps.length
|
|
604
|
-
? ` ${d.id}: (payload: ${payloadName}) => holon.
|
|
605
|
-
: ` ${d.id}: (payload: ${payloadName}) => holon.
|
|
607
|
+
? ` ${d.id}: (payload: ${payloadName}) => holon.offer(encodeIntentOffer(${JSON.stringify(domain)}, ${JSON.stringify(d.id)}, stampNow(payload, ${JSON.stringify(stamps)}), domainHash)),`
|
|
608
|
+
: ` ${d.id}: (payload: ${payloadName}) => holon.offer(encodeIntentOffer(${JSON.stringify(domain)}, ${JSON.stringify(d.id)}, payload, domainHash)),`,
|
|
606
609
|
);
|
|
607
610
|
}
|
|
608
611
|
|
|
@@ -30,13 +30,10 @@
|
|
|
30
30
|
* It emits into `outDir` (default `<config dir>/build`):
|
|
31
31
|
* * `<name>.package.usda` — the deployable package (engine bundle + USD IR);
|
|
32
32
|
* its sha256 IS the deploy `domainHash` (`policy:{hash}`).
|
|
33
|
-
* *
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* identityHashes}`: the EXACT body `POST /v1/workspaces/:ws/domains` accepts as
|
|
38
|
-
* `application/json` on Nomos Cloud (package + per-workspace manifest overlay in
|
|
39
|
-
* one deploy).
|
|
33
|
+
* * `<name>.deploy.json` — `{packageUsda}`: the EXACT body
|
|
34
|
+
* `POST /v1/workspaces/:ws/domains` accepts as `application/json` on Nomos Cloud.
|
|
35
|
+
* Admission/projection manifests are embedded in the package USDA and are not host
|
|
36
|
+
* sidecars.
|
|
40
37
|
* * `<name>.proof.mts` — the GENERATED PROOF (codegen_proof.ts): a
|
|
41
38
|
* runnable, domain-shaped live e2e synthesized from the law's own directives /
|
|
42
39
|
* queries / counts. Run it with `githolon proof`. Never fatal: an unsampleable
|
|
@@ -406,7 +403,7 @@ const DART_RELATIVE_IMPORT = `import 'nomos_dsl.dart';`;
|
|
|
406
403
|
const DART_SUPPORT_SRC = [
|
|
407
404
|
"wire.dart",
|
|
408
405
|
"driver.dart",
|
|
409
|
-
"
|
|
406
|
+
"offer.dart",
|
|
410
407
|
"subscriptions.dart",
|
|
411
408
|
"provider.dart",
|
|
412
409
|
// The async WebView bridge runtime (NomosBridge / BridgeTransport) the generated
|
|
@@ -519,7 +516,7 @@ async function main(): Promise<void> {
|
|
|
519
516
|
capability: string;
|
|
520
517
|
domain: string;
|
|
521
518
|
aggregateId: string;
|
|
522
|
-
|
|
519
|
+
obligation: string;
|
|
523
520
|
complete: string;
|
|
524
521
|
fail: string;
|
|
525
522
|
block: string;
|
|
@@ -750,7 +747,7 @@ async function main(): Promise<void> {
|
|
|
750
747
|
capability: cap,
|
|
751
748
|
domain: d.key,
|
|
752
749
|
aggregateId: c.aggregate.id,
|
|
753
|
-
|
|
750
|
+
obligation: c.obligation.id,
|
|
754
751
|
complete: c.complete.id,
|
|
755
752
|
fail: c.fail.id,
|
|
756
753
|
block: c.block.id,
|
|
@@ -1252,7 +1249,8 @@ async function main(): Promise<void> {
|
|
|
1252
1249
|
relPaths.push(`layers/${base}.usda`);
|
|
1253
1250
|
}
|
|
1254
1251
|
}
|
|
1255
|
-
const
|
|
1252
|
+
const flat = flattenLayeredUsd(allLayers);
|
|
1253
|
+
const flatJson = JSON.stringify(flat);
|
|
1256
1254
|
if (flatJson !== usdJson) {
|
|
1257
1255
|
fail(
|
|
1258
1256
|
`layered emission does not flatten to the canonical composed IR — the module fold and ` +
|
|
@@ -1264,11 +1262,13 @@ async function main(): Promise<void> {
|
|
|
1264
1262
|
}
|
|
1265
1263
|
for (const w of pendingWrites) writeFileSync(w.path, w.text, "utf8");
|
|
1266
1264
|
layeredRootPath = path.join(outDir, `${cfg.name}.layered.usda`);
|
|
1267
|
-
writeFileSync(layeredRootPath, layeredRootUsda(relPaths), "utf8");
|
|
1265
|
+
writeFileSync(layeredRootPath, layeredRootUsda(relPaths, flat), "utf8");
|
|
1268
1266
|
layeredFileCount = relPaths.length;
|
|
1269
1267
|
}
|
|
1270
1268
|
|
|
1271
1269
|
// ── 3. the READ manifest + IDENTITY artifacts ──
|
|
1270
|
+
// These are generated from the same source functions embedded into USDA by emitUsd().
|
|
1271
|
+
// They remain local diagnostics for now; the deploy/cloud/runtime contract ignores them.
|
|
1272
1272
|
const readManifest = buildReadManifest(domainModules);
|
|
1273
1273
|
const readPath = path.join(outDir, "domain_manifests.json");
|
|
1274
1274
|
writeFileSync(readPath, JSON.stringify(readManifest, null, 2) + "\n", "utf8");
|
|
@@ -1276,7 +1276,7 @@ async function main(): Promise<void> {
|
|
|
1276
1276
|
const identity = buildIdentity(domainModules);
|
|
1277
1277
|
const { manifestsPath } = writeIdentity(identity, outDir);
|
|
1278
1278
|
|
|
1279
|
-
// ── 4. the one-call deploy body (package
|
|
1279
|
+
// ── 4. the one-call deploy body (package only) ──
|
|
1280
1280
|
// SELF-DESCRIBING: `summary` states what the (otherwise hex-opaque) package
|
|
1281
1281
|
// contains — purely derived from the inputs (no timestamps), ignored by the
|
|
1282
1282
|
// deploy endpoint, and mirrored as build/<name>.summary.txt for a human or
|
|
@@ -1297,9 +1297,6 @@ async function main(): Promise<void> {
|
|
|
1297
1297
|
const deployBody = {
|
|
1298
1298
|
summary,
|
|
1299
1299
|
packageUsda: text,
|
|
1300
|
-
readManifest,
|
|
1301
|
-
identityManifests: identity.manifests,
|
|
1302
|
-
identityHashes: identity.hashes,
|
|
1303
1300
|
// The capability lowering (unhashed sidecar; omit-when-empty — see the
|
|
1304
1301
|
// collector's doc above). The worker stages it on the manifest overlay:
|
|
1305
1302
|
// the deploy-time binding warning + the executor's scan map read it there.
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
/**
|
|
9
9
|
* framework/capability.ts — `capability()`: THE MARKETPLACE DECLARATION
|
|
10
10
|
* (`architecture/capability_marketplace.md` §4, ruling M2 — the framework-hidden
|
|
11
|
-
* lane). ONE call declares a typed
|
|
12
|
-
* sees a typed
|
|
11
|
+
* lane). ONE call declares a typed Obligation→Receipt capability interface; the dev
|
|
12
|
+
* sees a typed obligation call and a typed task row, and the framework supplies
|
|
13
13
|
* everything else through the SHIPPED `impureCapability()` quartet — so the gate
|
|
14
|
-
* law (`
|
|
14
|
+
* law (`check_obligation_receipt`), the kernel recognizer, codegen, and the stable-id
|
|
15
15
|
* lockfile all see a perfectly ordinary impure capability. Hidden means *not
|
|
16
|
-
* hand-rolled*, never *not auditable*: the
|
|
16
|
+
* hand-rolled*, never *not auditable*: the Obligation and Receipt facts ride the
|
|
17
17
|
* ledger verbatim.
|
|
18
18
|
*
|
|
19
19
|
* ```ts
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* receipt: { tokensIn: t.int(), tokensOut: t.int() },
|
|
23
23
|
* });
|
|
24
24
|
* // derived, deterministically from the name:
|
|
25
|
-
* // aggregate LlmCompleteTask
|
|
25
|
+
* // aggregate LlmCompleteTask obligation obligeLlmComplete
|
|
26
26
|
* // complete completeLlmComplete fail/block/deadLetter likewise
|
|
27
27
|
* // capability "llm.complete" (the CapabilityBinding the executor must hold)
|
|
28
28
|
* ```
|
|
@@ -84,7 +84,7 @@ export interface CapabilityDecl<
|
|
|
84
84
|
> extends ImpureCapability<string, RequestShape<Req>, Req & Rec> {
|
|
85
85
|
/** The capability name — the `CapabilityBinding` slot the executor must hold. */
|
|
86
86
|
readonly name: string;
|
|
87
|
-
/** The request fields' zod object (what `
|
|
87
|
+
/** The request fields' zod object (what `oblige<Pascal>`'s params validate as). */
|
|
88
88
|
readonly requestSchema: z.ZodObject<RequestShape<Req>>;
|
|
89
89
|
/**
|
|
90
90
|
* THE EXECUTOR'S SCAN LANE — a declared, indexed query over the task
|
|
@@ -127,7 +127,7 @@ export function capability<
|
|
|
127
127
|
for (const k of Object.keys(fields)) {
|
|
128
128
|
if (reserved.has(k)) {
|
|
129
129
|
throw new Error(
|
|
130
|
-
`capability('${name}'): ${side} field '${k}' shadows the reserved
|
|
130
|
+
`capability('${name}'): ${side} field '${k}' shadows the reserved Obligation→Receipt envelope — rename it (the envelope is framework law: ${[...reserved].join(", ")})`,
|
|
131
131
|
);
|
|
132
132
|
}
|
|
133
133
|
}
|
|
@@ -135,7 +135,7 @@ export function capability<
|
|
|
135
135
|
for (const k of Object.keys(receipt)) {
|
|
136
136
|
if (k in spec.request) {
|
|
137
137
|
throw new Error(
|
|
138
|
-
`capability('${name}'): '${k}' is declared on BOTH request and receipt — a field has one writer (the
|
|
138
|
+
`capability('${name}'): '${k}' is declared on BOTH request and receipt — a field has one writer (the obligation writes requests, the complete Receipt writes receipts); split or rename it`,
|
|
139
139
|
);
|
|
140
140
|
}
|
|
141
141
|
}
|
|
@@ -144,7 +144,7 @@ export function capability<
|
|
|
144
144
|
const aggregateId = `${pascal}Task`;
|
|
145
145
|
|
|
146
146
|
// Receipt fields fold only when the complete Receipt lands — auto-`.optional()`
|
|
147
|
-
// so a freshly-
|
|
147
|
+
// so a freshly-obligated task decodes on the generated read type (the same
|
|
148
148
|
// read-decode discipline the envelope's own optionals follow).
|
|
149
149
|
const receiptFields = Object.fromEntries(
|
|
150
150
|
Object.entries(receipt).map(([k, f]) => [k, f.optional()]),
|
|
@@ -162,7 +162,7 @@ export function capability<
|
|
|
162
162
|
|
|
163
163
|
const quartet = impureCapability({
|
|
164
164
|
aggregateId,
|
|
165
|
-
|
|
165
|
+
obligationDirectiveId: `oblige${pascal}`,
|
|
166
166
|
completeDirectiveId: `complete${pascal}`,
|
|
167
167
|
failDirectiveId: `fail${pascal}`,
|
|
168
168
|
blockDirectiveId: `block${pascal}`,
|