@githolon/dsl 0.14.0 → 0.16.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.
Files changed (42) hide show
  1. package/dart/example/nomos_webview_transport.dart +2 -2
  2. package/dart/lib/nomos_dsl.dart +4 -4
  3. package/dart/lib/src/{dispatch.dart → offer.dart} +7 -7
  4. package/dart/lib/src/provider.dart +30 -30
  5. package/dart/lib/src/subscriptions.dart +3 -3
  6. package/dart/lib/src/webview_bridge.dart +56 -21
  7. package/dart/test/webview_bridge_test.dart +35 -34
  8. package/package.json +5 -3
  9. package/src/aggregate.ts +9 -1
  10. package/src/authoring.ts +10 -1
  11. package/src/build_package.ts +1 -1
  12. package/src/capability_exports.ts +2 -2
  13. package/src/codegen_dart.ts +34 -24
  14. package/src/codegen_dart_client.ts +74 -12
  15. package/src/codegen_provider_dart.ts +67 -61
  16. package/src/codegen_ts.ts +45 -17
  17. package/src/compile_package_main.ts +103 -36
  18. package/src/ctx.ts +13 -0
  19. package/src/engine_entry.ts +22 -5
  20. package/src/fields.ts +44 -0
  21. package/src/flow_gate.ts +208 -0
  22. package/src/framework/capability.ts +29 -11
  23. package/src/framework/encryption_scope.ts +9 -1
  24. package/src/framework/genesis.ts +5 -1
  25. package/src/framework/impure_capability.ts +73 -67
  26. package/src/framework/relations.ts +23 -4
  27. package/src/framework/signing_identity.ts +1 -1
  28. package/src/framework/sync_lifecycle.ts +3 -3
  29. package/src/framework/workspace_invariant.ts +29 -8
  30. package/src/framework/workspaces.ts +7 -0
  31. package/src/index.ts +25 -1
  32. package/src/manifest.ts +11 -17
  33. package/src/ops.ts +42 -1
  34. package/src/principal_evidence.ts +112 -0
  35. package/src/relation_sugar.ts +10 -4
  36. package/src/shape_compat.ts +208 -0
  37. package/src/usd.ts +287 -0
  38. package/src/usd_layers.ts +213 -26
  39. package/src/wire.ts +8 -0
  40. package/src/wire_encode.ts +75 -2
  41. package/src/workspace_sharding.ts +197 -24
  42. package/src/workspace_type.ts +1 -0
@@ -6,7 +6,7 @@
6
6
  // If a file isn't this / hosting this / authoring for this / proving this — it's gone.
7
7
 
8
8
  /**
9
- * framework/impure_capability.ts — the FRAMEWORK Order→Receipt mechanism (tenant-agnostic).
9
+ * framework/impure_capability.ts — the FRAMEWORK Obligation→Receipt mechanism (tenant-agnostic).
10
10
  *
11
11
  * ── Why this module exists (the framework↔tenant line) ───────────────────────
12
12
  * A *provider-side intent* in Nomos 1 was an ephemeral job: dispatch → the
@@ -17,12 +17,12 @@
17
17
  *
18
18
  * Nomos 2's law is "**every authored intent folds SOME aggregate**" (contract §1) —
19
19
  * there is no ephemeral job. The faithful shape of an async impure capability is therefore
20
- * an **Order → Receipt** pair of ordinary `WireIntent`s with a first-class lifecycle law:
20
+ * an **Obligation → Receipt** pair of ordinary `WireIntent`s with a first-class lifecycle law:
21
21
  *
22
- * • **Order** — a normal intent whose role is the request: a peer dispatches
23
- * `order…(params, status:'requested')`,
24
- * `.creates` a task aggregate keyed by a KERNEL-MINTED order id
25
- * (`<OrderType>_<uuidv7>`, via the marker-driven front-door; idempotent on
22
+ * • **Obligation** — a normal intent whose role is the request: a peer dispatches
23
+ * `oblige…(params, status:'requested')`,
24
+ * `.creates` a task aggregate keyed by a KERNEL-MINTED obligation id
25
+ * (`<ObligationType>_<uuidv7>`, via the marker-driven front-door; idempotent on
26
26
  * replay by capture).
27
27
  * • **(impure work)** — a capability provider watches `status:'requested'` rows, performs
28
28
  * the side-effect (rendering / upload / multi-target provider work — a host/report
@@ -33,14 +33,14 @@
33
33
  * Some domains also expose explicit blocked/DLQ receipts using the
34
34
  * same envelope fields below.
35
35
  *
36
- * Order and Receipt are roles/facets of `Intent`, not special admission objects, job-table rows,
36
+ * Obligation and Receipt are roles/facets of `Intent`, not special admission objects, job-table rows,
37
37
  * or a privileged mutation channel.
38
38
  *
39
39
  * **Reporting upward = the authoring peer reactively WATCHES the task aggregate.** The receipt
40
40
  * arrives as an ordinary projection update (no polling, no fire-and-forget push, no
41
41
  * response-mutation). This is exactly the proven `ExportJobAggregate` shape
42
42
  * (the tenant domains package `thing_structures`) generalised into a reusable framework pattern so a
43
- * domain dev does NOT hand-roll the {aggregate + order + complete + fail} quartet each
43
+ * domain dev does NOT hand-roll the {aggregate + obligation + complete + fail} quartet each
44
44
  * time — they DECLARE the task's params and get the quartet for free, like `relations.ts`
45
45
  * gives grant/revoke for free.
46
46
  *
@@ -77,7 +77,7 @@ import type { Ports } from "../ctx.js";
77
77
 
78
78
  /**
79
79
  * The impure-capability lifecycle vocabulary — framework-fixed. A task is `requested` when
80
- * the peer orders it; the provider authors the ordinary Receipt intent that flips it
80
+ * the peer obligates it; the provider authors the ordinary Receipt intent that flips it
81
81
  * to `completed` (with a `resultRef`) or `failed` (with a `failureReason`). One-way after a
82
82
  * terminal state: the lifecycle law is terminal-once at the gate/envelope, not a separate
83
83
  * privileged mutation channel.
@@ -92,14 +92,14 @@ export const IMPURE_CAPABILITY_STATUSES = [
92
92
  export type ImpureCapabilityStatus = (typeof IMPURE_CAPABILITY_STATUSES)[number];
93
93
 
94
94
  /**
95
- * The framework envelope fields EVERY impure-capability aggregate carries — the Order→Receipt
95
+ * The framework envelope fields EVERY impure-capability aggregate carries — the Obligation→Receipt
96
96
  * lifecycle, independent of the tenant's request params:
97
97
  *
98
98
  * - requestId Lww — the task's own id (== the aggregate instance id; a
99
- * KERNEL-MINTED `<OrderType>_<uuidv7>`). Immutable-after-create (the
99
+ * KERNEL-MINTED `<ObligationType>_<uuidv7>`). Immutable-after-create (the
100
100
  * create-only gap; modelled Lww).
101
- * - requestedBy Lww — who ordered the task (provenance). Immutable-after-create.
102
- * - requestedAt Int — epoch-ms the order was authored. Immutable-after-create.
101
+ * - requestedBy Lww — who obligated the task (provenance). Immutable-after-create.
102
+ * - requestedAt Int — epoch-ms the obligation was authored. Immutable-after-create.
103
103
  * - status Lww enum — `requested` → the reactor flips to a terminal status.
104
104
  * - resultRef Lww json (OPTIONAL) — the content-addressed RESULT the reactor
105
105
  * records on completion (a blob ref / download url JSON leaf). Absent
@@ -111,10 +111,10 @@ export type ImpureCapabilityStatus = (typeof IMPURE_CAPABILITY_STATUSES)[number]
111
111
  * external condition.
112
112
  * - blockedAt Int (OPTIONAL) — epoch-ms the task was parked as blocked.
113
113
  * - nextAction Lww (OPTIONAL) — human/host-action hint, e.g. refreshAuth.
114
- * - deadLetterReason Lww (OPTIONAL) — why the order was moved to the DLQ.
115
- * - deadLetteredAt Int (OPTIONAL) — epoch-ms the order entered the DLQ.
114
+ * - deadLetterReason Lww (OPTIONAL) — why the obligation was moved to the DLQ.
115
+ * - deadLetteredAt Int (OPTIONAL) — epoch-ms the obligation entered the DLQ.
116
116
  *
117
- * The OPTIONALs are `.optional()` so a freshly-ordered task (which folds none of them)
117
+ * The OPTIONALs are `.optional()` so a freshly-obligated task (which folds none of them)
118
118
  * decodes on the generated read type — the same read-decode discipline the rest of the
119
119
  * framework uses (identity NOTE 1a / gap 1).
120
120
  */
@@ -122,6 +122,8 @@ export const IMPURE_CAPABILITY_ENVELOPE = {
122
122
  requestId: t.string().merge(Lww),
123
123
  requestedBy: t.string().merge(Lww),
124
124
  requestedAt: t.int().merge(Lww),
125
+ scheduledAt: t.int().merge(Lww),
126
+ deadlineAt: t.int().merge(Lww),
125
127
  status: t.enum(IMPURE_CAPABILITY_STATUSES).merge(Lww),
126
128
  resultRef: t.json().merge(Lww).optional(),
127
129
  failureReason: t.string().merge(Lww).optional(),
@@ -131,16 +133,16 @@ export const IMPURE_CAPABILITY_ENVELOPE = {
131
133
  nextAction: t.string().merge(Lww).optional(),
132
134
  deadLetterReason: t.string().merge(Lww).optional(),
133
135
  deadLetteredAt: t.int().merge(Lww).optional(),
134
- // The FRAMEWORK Order↔Receipt CLOSURE link (OPTIONAL): the id of the Order this
136
+ // The FRAMEWORK Obligation↔Receipt CLOSURE link (OPTIONAL): the id of the Obligation this
135
137
  // RECEIPT closes (== the task's own requestId — the reactor mutates the task instance).
136
138
  // GENERIC, written by every impureCapability Receipt (complete OR fail), so the read
137
- // projection answers "is Order O closed, by which Receipt, with what result" in O(1)
138
- // off the `(closesOrder, order_id)` index — never a ledger scan. Absent while
139
- // `requested` (a fresh order has no closing receipt). The kernel supplies its
139
+ // projection answers "is Obligation O closed, by which Receipt, with what result" in O(1)
140
+ // off the `(closesObligation, obligation_id)` index — never a ledger scan. Absent while
141
+ // `requested` (a fresh obligation has no closing receipt). The kernel supplies its
140
142
  // framework-fixed Lww driver (`kernel::manifest_view::IMPURE_CAPABILITY_RECEIPT_FIELDS`)
141
- // and the gate enforces the lifecycle around it (`nomos_executor::check_order_receipt` —
142
- // admit step 5.5: OrphanReceipt / ReceiptOnTerminalOrder, terminal-once as law).
143
- closesOrder: t.string().merge(Lww).optional(),
143
+ // and the gate enforces the lifecycle around it (`nomos_executor::check_obligation_receipt` —
144
+ // admit step 5.5: OrphanReceipt / ReceiptOnTerminalObligation, terminal-once as law).
145
+ closesObligation: t.string().merge(Lww).optional(),
144
146
  } as const;
145
147
 
146
148
  /**
@@ -163,9 +165,9 @@ export function impureCapabilityAggregate<
163
165
  }
164
166
 
165
167
  /**
166
- * The deterministic instance-id rule: a impure capability is keyed by its OWN order id.
167
- * That id is KERNEL-MINTED (`<OrderType>_<uuidv7>`, via the marker-driven front-door),
168
- * captured + committed — so the `order` create is idempotent on replay by capture, and the
168
+ * The deterministic instance-id rule: a impure capability is keyed by its OWN obligation id.
169
+ * That id is KERNEL-MINTED (`<ObligationType>_<uuidv7>`, via the marker-driven front-door),
170
+ * captured + committed — so the `obligation` create is idempotent on replay by capture, and the
169
171
  * `complete`/`fail` receipt addresses the SAME instance by reading the committed minted id.
170
172
  * Trivial today (identity), but centralised here so the keying rule has ONE home (the
171
173
  * symmetry with `relations.ts`'s `relationTupleInstanceId`).
@@ -174,11 +176,13 @@ export function impureCapabilityInstanceId(requestId: string): string {
174
176
  return requestId;
175
177
  }
176
178
 
177
- /** The Zod schema for the framework envelope fields the ORDER payload always carries. */
178
- const orderEnvelopeSchema = z.object({
179
+ /** The Zod schema for the framework envelope fields the Obligation payload always carries. */
180
+ const obligationEnvelopeSchema = z.object({
179
181
  requestId: z.string(),
180
182
  requestedBy: z.string(),
181
183
  requestedAt: z.number().int(),
184
+ scheduledAt: z.number().int(),
185
+ deadlineAt: z.number().int(),
182
186
  });
183
187
 
184
188
  /** The receipt-COMPLETE payload: the request id + the content-addressed result + when. */
@@ -204,7 +208,7 @@ const blockSchema = z.object({
204
208
  nextAction: z.string(),
205
209
  });
206
210
 
207
- /** The receipt-DLQ payload: terminal dead-letter, requiring a fresh order to recover. */
211
+ /** The receipt-DLQ payload: terminal dead-letter, requiring a fresh obligation to recover. */
208
212
  const deadLetterSchema = z.object({
209
213
  requestId: z.string(),
210
214
  deadLetterReason: z.string(),
@@ -232,13 +236,13 @@ function envelopeInstance(aggregateId: string, requestId: string) {
232
236
  }
233
237
 
234
238
  /**
235
- * orderOps — the ORDER half. Seeds the envelope provenance + `status:'requested'`,
239
+ * obligationOps — the Obligation half. Seeds the envelope provenance + timing + `status:'requested'`,
236
240
  * PLUS the tenant's request params (the `paramOps` the caller's `.plan` supplies for
237
241
  * its own fields). The task is bound to its requestId instance (#105).
238
242
  */
239
- export function orderOps(
243
+ export function obligationOps(
240
244
  aggregateId: string,
241
- p: z.infer<typeof orderEnvelopeSchema>,
245
+ p: z.infer<typeof obligationEnvelopeSchema>,
242
246
  paramOps: PlannedOp[] = [],
243
247
  ): PlannedOp[] {
244
248
  const taskInstance = envelopeInstance(aggregateId, p.requestId);
@@ -246,6 +250,8 @@ export function orderOps(
246
250
  set(taskInstance, "requestId", p.requestId),
247
251
  set(taskInstance, "requestedBy", p.requestedBy),
248
252
  set(taskInstance, "requestedAt", p.requestedAt),
253
+ set(taskInstance, "scheduledAt", p.scheduledAt),
254
+ set(taskInstance, "deadlineAt", p.deadlineAt),
249
255
  set(taskInstance, "status", "requested"),
250
256
  ...paramOps,
251
257
  ];
@@ -266,10 +272,10 @@ export function completeOps(
266
272
  set(taskInstance, "status", "completed"),
267
273
  set(taskInstance, "resultRef", p.resultRef),
268
274
  set(taskInstance, "completedAt", p.completedAt),
269
- // The FRAMEWORK Order↔Receipt CLOSURE link — this Receipt closes the Order whose
275
+ // The FRAMEWORK Obligation↔Receipt CLOSURE link — this Receipt closes the Obligation whose
270
276
  // id == requestId (the task instance the reactor mutates). Indexed by the read
271
277
  // projection for the O(1) closure read. Wire-identical to the Rust reactor's op.
272
- set(taskInstance, "closesOrder", impureCapabilityInstanceId(p.requestId)),
278
+ set(taskInstance, "closesObligation", impureCapabilityInstanceId(p.requestId)),
273
279
  // The TENANT RESULT FIELDS the Receipt writes into the task aggregate (e.g.
274
280
  // `transcription = <text>`). The envelope is auto; these are the tenant's own
275
281
  // structured result. Framework writes them verbatim; the tenant owns their meaning.
@@ -293,8 +299,8 @@ export function failOps(
293
299
  set(taskInstance, "failureReason", p.failureReason),
294
300
  set(taskInstance, "completedAt", p.failedAt),
295
301
  // CLOSURE link — a FAILED task is still CLOSED by its receipt (the closure read
296
- // reports it CLOSED with no result). Same `closesOrder == requestId` as `completeOps`.
297
- set(taskInstance, "closesOrder", impureCapabilityInstanceId(p.requestId)),
302
+ // reports it CLOSED with no result). Same `closesObligation == requestId` as `completeOps`.
303
+ set(taskInstance, "closesObligation", impureCapabilityInstanceId(p.requestId)),
298
304
  ];
299
305
  }
300
306
 
@@ -309,11 +315,11 @@ export function blockOps(
309
315
  set(taskInstance, "blockedReason", p.blockedReason),
310
316
  set(taskInstance, "blockedAt", p.blockedAt),
311
317
  set(taskInstance, "nextAction", p.nextAction),
312
- set(taskInstance, "closesOrder", impureCapabilityInstanceId(p.requestId)),
318
+ set(taskInstance, "closesObligation", impureCapabilityInstanceId(p.requestId)),
313
319
  ];
314
320
  }
315
321
 
316
- /** deadLetterOps — terminal DLQ receipt. Recovery is a fresh explicit Order. */
322
+ /** deadLetterOps — terminal DLQ receipt. Recovery is a fresh explicit Obligation. */
317
323
  export function deadLetterOps(
318
324
  aggregateId: string,
319
325
  p: z.infer<typeof deadLetterSchema>,
@@ -323,7 +329,7 @@ export function deadLetterOps(
323
329
  set(taskInstance, "status", "deadLettered"),
324
330
  set(taskInstance, "deadLetterReason", p.deadLetterReason),
325
331
  set(taskInstance, "deadLetteredAt", p.deadLetteredAt),
326
- set(taskInstance, "closesOrder", impureCapabilityInstanceId(p.requestId)),
332
+ set(taskInstance, "closesObligation", impureCapabilityInstanceId(p.requestId)),
327
333
  ];
328
334
  }
329
335
 
@@ -331,11 +337,11 @@ export function deadLetterOps(
331
337
  * The generated directive QUARTET for a impure capability. A domain dev calls
332
338
  * {@link impureCapability} ONCE and gets:
333
339
  * • `aggregate` — the task aggregate handle (envelope ⊕ params),
334
- * • `order` — `.creates` directive (the authored Order),
340
+ * • `obligation` — `.creates` directive (the authored Obligation),
335
341
  * • `complete` — `.mutates` directive (the reactor's success Receipt),
336
342
  * • `fail` — `.mutates` directive (the reactor's failure Receipt),
337
- * plus the deterministic `instanceId` rule. The `order` payload is the framework
338
- * order-envelope MERGED with the tenant's `paramsSchema`; `complete`/`fail` are fixed.
343
+ * plus the deterministic `instanceId` rule. The `obligation` payload is the framework
344
+ * obligation-envelope MERGED with the tenant's `paramsSchema`; `complete`/`fail` are fixed.
339
345
  */
340
346
  export interface ImpureCapability<
341
347
  Id extends string,
@@ -343,7 +349,7 @@ export interface ImpureCapability<
343
349
  F extends Record<string, Field>,
344
350
  > {
345
351
  readonly aggregate: AggregateHandle<Id, typeof IMPURE_CAPABILITY_ENVELOPE & F>;
346
- readonly order: RequirableDirective<OrderPayload<ParamsShape>>;
352
+ readonly obligation: RequirableDirective<ObligationPayload<ParamsShape>>;
347
353
  readonly complete: RequirableDirective<z.infer<typeof completeSchema>>;
348
354
  readonly fail: RequirableDirective<z.infer<typeof failSchema>>;
349
355
  readonly block: RequirableDirective<z.infer<typeof blockSchema>>;
@@ -364,17 +370,17 @@ export interface ImpureCapability<
364
370
  }
365
371
 
366
372
  /**
367
- * The inferred ORDER payload type = the framework envelope schema EXTENDED with the
368
- * tenant's param shape — the EXACT inference `orderEnvelopeSchema.extend(paramsSchema)`
369
- * produces (so the declared `ImpureCapability.order` directive type and the value the factory
373
+ * The inferred Obligation payload type = the framework envelope schema EXTENDED with the
374
+ * tenant's param shape — the EXACT inference `obligationEnvelopeSchema.extend(paramsSchema)`
375
+ * produces (so the declared `ImpureCapability.obligation` directive type and the value the factory
370
376
  * builds are the same type, with no brittle hand-written intersection).
371
377
  */
372
- export type OrderPayload<ParamsShape extends z.ZodRawShape> = z.infer<
373
- ReturnType<typeof orderEnvelopeSchema.extend<ParamsShape>>
378
+ export type ObligationPayload<ParamsShape extends z.ZodRawShape> = z.infer<
379
+ ReturnType<typeof obligationEnvelopeSchema.extend<ParamsShape>>
374
380
  >;
375
381
 
376
382
  /**
377
- * impureCapability — the framework FACTORY. Declares an Order→Receipt task in ONE call:
383
+ * impureCapability — the framework FACTORY. Declares an Obligation→Receipt task in ONE call:
378
384
  *
379
385
  * ```ts
380
386
  * // Build the aggregate handle FIRST so `writeParams` references it without a
@@ -384,7 +390,7 @@ export type OrderPayload<ParamsShape extends z.ZodRawShape> = z.infer<
384
390
  *
385
391
  * export const exportJob = impureCapability({
386
392
  * aggregateId: "ExportJobAggregate",
387
- * orderDirectiveId: "createExportJob",
393
+ * obligationDirectiveId: "createExportJob",
388
394
  * completeDirectiveId: "completeExportJob",
389
395
  * failDirectiveId: "failExportJob",
390
396
  * blockDirectiveId: "blockExportJob",
@@ -400,33 +406,33 @@ export type OrderPayload<ParamsShape extends z.ZodRawShape> = z.infer<
400
406
  *
401
407
  * The tenant declares the wire ids (so they're stable, minify-safe DATA — the same
402
408
  * declared-once rule as `aggregate()`/`directive()`), the param FIELDS (for the
403
- * aggregate shape + codegen) and the param SCHEMA (for the order payload + the
409
+ * aggregate shape + codegen) and the param SCHEMA (for the obligation payload + the
404
410
  * `writeParams` plan fragment). The framework wires the lifecycle: status defaulting,
405
411
  * the requestId keying, and the complete/fail receipt directives.
406
412
  *
407
413
  * `writeParams(taskInstanceId, params)` returns the `PlannedOp[]` for the tenant's OWN
408
- * fields (the framework already writes the envelope) — the param half of the order
414
+ * fields (the framework already writes the envelope) — the param half of the obligation
409
415
  * plan, kept type-checked against the tenant's param fields via the `set()` helper.
410
416
  */
411
417
  export function impureCapability<
412
418
  const Id extends string,
413
- const OrderId extends string,
419
+ const ObligationId extends string,
414
420
  const CompleteId extends string,
415
421
  const FailId extends string,
416
422
  ParamsShape extends z.ZodRawShape,
417
423
  F extends Record<string, Field>,
418
424
  >(spec: {
419
425
  aggregateId: Id;
420
- orderDirectiveId: OrderId;
426
+ obligationDirectiveId: ObligationId;
421
427
  completeDirectiveId: CompleteId;
422
428
  failDirectiveId: FailId;
423
429
  blockDirectiveId: string;
424
430
  deadLetterDirectiveId: string;
425
431
  /** The task's request-param FIELDS (merged onto the envelope for the aggregate shape). */
426
432
  params: F;
427
- /** The task's request-param Zod SHAPE (merged onto the order envelope schema). */
433
+ /** The task's request-param Zod SHAPE (merged onto the obligation envelope schema). */
428
434
  paramsSchema: ParamsShape;
429
- /** Plan fragment writing the tenant's param fields for the order (envelope is auto). */
435
+ /** Plan fragment writing the tenant's param fields for the obligation (envelope is auto). */
430
436
  writeParams: (
431
437
  taskInstanceId: string,
432
438
  params: z.infer<z.ZodObject<ParamsShape>>,
@@ -434,7 +440,7 @@ export function impureCapability<
434
440
  /**
435
441
  * OPTIONAL plan fragment writing the tenant's STRUCTURED RESULT fields onto the task
436
442
  * aggregate when the `complete` Receipt is authored (the lifecycle envelope + the
437
- * `closesOrder` link are auto). The receipt's `resultRef` carries the content-addressed
443
+ * `closesObligation` link is auto). The receipt's `resultRef` carries the content-addressed
438
444
  * Evidence ref (the authoritative artifact); a tenant that ALSO wants the result in a
439
445
  * typed, queryable field (e.g. a voice-note's `transcription`) supplies it here. The
440
446
  * `resultRef` string is passed through so the tenant can derive the field value from the
@@ -451,22 +457,22 @@ export function impureCapability<
451
457
  }): ImpureCapability<Id, ParamsShape, F> {
452
458
  const taskAggregate = impureCapabilityAggregate(spec.aggregateId, spec.params);
453
459
 
454
- // The order payload = the framework provenance envelope ⊕ the tenant's params.
455
- const orderPayload = orderEnvelopeSchema.extend(spec.paramsSchema);
460
+ // The obligation payload = the framework provenance/timing envelope ⊕ the tenant's params.
461
+ const obligationPayload = obligationEnvelopeSchema.extend(spec.paramsSchema);
456
462
 
457
- const order: RequirableDirective<OrderPayload<ParamsShape>> = directive(
458
- spec.orderDirectiveId,
463
+ const obligation: RequirableDirective<ObligationPayload<ParamsShape>> = directive(
464
+ spec.obligationDirectiveId,
459
465
  )
460
466
  .creates(taskAggregate)
461
- .payload(orderPayload)
467
+ .payload(obligationPayload)
462
468
  .plan((p) => {
463
469
  // The validated payload carries BOTH the envelope (required string/int) and the
464
- // tenant params. `orderOps` writes the envelope; the tenant's `writeParams`
470
+ // tenant params. `obligationOps` writes the envelope; the tenant's `writeParams`
465
471
  // writes its own fields. Extract the envelope explicitly (it is required on the
466
472
  // schema, so these are present non-undefined values).
467
- const envelope = orderEnvelopeSchema.parse(p);
473
+ const envelope = obligationEnvelopeSchema.parse(p);
468
474
  const params = p as z.infer<z.ZodObject<ParamsShape>>;
469
- return orderOps(
475
+ return obligationOps(
470
476
  spec.aggregateId,
471
477
  envelope,
472
478
  spec.writeParams(impureCapabilityInstanceId(envelope.requestId), params),
@@ -502,7 +508,7 @@ export function impureCapability<
502
508
 
503
509
  return {
504
510
  aggregate: taskAggregate,
505
- order,
511
+ obligation,
506
512
  complete,
507
513
  fail,
508
514
  block,
@@ -516,7 +522,7 @@ export function impureCapability<
516
522
 
517
523
  // ─────────────────────────── THE CAPABILITY PROVIDER ─────────────────────────
518
524
  //
519
- // The Order half (`order`) is an ordinary Intent authored by the peer through the one write
525
+ // The Obligation half (`obligation`) is an ordinary Intent authored by the peer through the one write
520
526
  // path. The Receipt half (`complete`/`fail`) is an ordinary Intent authored by a capability
521
527
  // PROVIDER — itself a Nomos author (`admission-hosting.md`: hosted/provider-originated
522
528
  // intents author *through* a policy unit too"). The provider is the runtime ROLE; this
@@ -46,6 +46,7 @@ import { Lww, MapOf } from "../drivers.js";
46
46
  import { directive } from "../directive.js";
47
47
  import { set, setEntry, withMarker, type PlannedOp } from "../ops.js";
48
48
  import { query } from "../query.js";
49
+ import { endorsementOf, type AttributionPrincipal, type EndorsedAuthority } from "../principal_evidence.js";
49
50
 
50
51
  /** Tuple subject lifecycle — the same vocabulary the kernel read drops a non-active leaf by. */
51
52
  export const RELATION_STATUSES = ["active", "revoked"] as const;
@@ -126,8 +127,15 @@ function compact(obj: Record<string, unknown>): RelationSubjectLeaf {
126
127
  interface WriteTupleInput {
127
128
  object: string;
128
129
  relation: string;
129
- subject: string;
130
- grantedBy: string;
130
+ /**
131
+ * THE AUTHORITY SINK (the endorse model — security_model.md): who is granted the relation. A plain
132
+ * subject string (`user:<id>` from the payload) OR `ctx.principal().asAuthoritySubject(reason)` — an
133
+ * un-endorsed identity (`asAttribution()`) cannot be a subject (it won't typecheck), so identity
134
+ * never SILENTLY decides a grant.
135
+ */
136
+ subject: string | EndorsedAuthority<string>;
137
+ /** ATTRIBUTION (free): who recorded the grant. A plain string OR `ctx.principal().asAttribution()`. */
138
+ grantedBy: string | AttributionPrincipal;
131
139
  grantedAt: string;
132
140
  }
133
141
 
@@ -141,19 +149,30 @@ function lowerTuple(
141
149
  },
142
150
  ): PlannedOp[] {
143
151
  const rt = instance(RelationTuple, relationTupleInstanceId(p.object, p.relation));
152
+ // MATERIALISE PrincipalEvidence subjects/grantedBy → the raw principal (the endorse model). The
153
+ // subject's endorsement is recorded on the setEntry op so the discovery harvest lifts it into
154
+ // nomosFlowGate.endorse; grantedBy is free attribution (materialised, not gated).
155
+ //
156
+ // The captured authoring principal IS already a wire-form subject (`user:<uid>` / `key:<hash>`, the
157
+ // host-stamped actor — the SAME form the write gate parses via `Subject::parse`), so an endorsed
158
+ // authority subject lowers to that leaf verbatim and the gate's `engine.check` matches it.
159
+ const subjectE = endorsementOf(p.subject);
160
+ const subjectRaw = subjectE ? subjectE.principal : (p.subject as string);
161
+ const grantedByRaw = endorsementOf(p.grantedBy)?.principal ?? (p.grantedBy as string);
144
162
  const leaf = compact({
145
163
  status: p.status,
146
- grantedBy: p.grantedBy,
164
+ grantedBy: grantedByRaw,
147
165
  grantedAt: p.grantedAt,
148
166
  revokedBy: p.revokedBy,
149
167
  revokedAt: p.revokedAt,
150
168
  });
169
+ const subjectEntry = setEntry(rt, "subjects", subjectRaw, JSON.stringify(leaf));
151
170
  return [
152
171
  // upsert the aggregate head (first relate creates it); without this the referential
153
172
  // gate would reject a setEntry on an absent id.
154
173
  withMarker(set(rt, "object", p.object), "ensures"),
155
174
  set(rt, "relation", p.relation),
156
- setEntry(rt, "subjects", p.subject, JSON.stringify(leaf)),
175
+ subjectE ? { ...subjectEntry, endorsement: { facet: subjectE.facet, reason: subjectE.reason } } : subjectEntry,
157
176
  ];
158
177
  }
159
178
 
@@ -67,7 +67,7 @@ const sha256Hex = z.string().regex(/^[0-9a-f]{64}$/, "expected lowercase sha256
67
67
  * bootstrap chicken-and-egg. The forge surface a bare `relateSubject` would open is closed by
68
68
  * DOMAIN PLACEMENT: this directive is composed into the CONTROL-PLANE governance law (domain
69
69
  * `workspaces`/`nomos`), which the open session lane structurally refuses
70
- * (`cloud/holon-host/src/engine.mjs` — only the birth door + the secret-gated `/author` + admin
70
+ * (`cloud/cloudflare-shell/src/engine.mjs` — only the birth door + the secret-gated `/author` + admin
71
71
  * lanes author it), so no tenant can forge a signer for another principal.
72
72
  *
73
73
  * THE ORDER IS THE CONTRACT (verified, zero gate change): `enrollSigner` (E, unsigned/pre-warrant) →
@@ -10,7 +10,7 @@
10
10
  * Nomos sync lifecycle.
11
11
  *
12
12
  * Sync is a control-plane impure capability: Nomos records the desired workspace
13
- * reconciliation as an ordinary Order, a host/provider performs transport using
13
+ * reconciliation as an ordinary Obligation, a host/provider performs transport using
14
14
  * explicit capabilities, and the outcome comes back as an ordinary Receipt. The
15
15
  * remote/JWT/network is never ambient domain state; the ledger carries request,
16
16
  * status, cursors, heads, and errors.
@@ -85,7 +85,7 @@ function syncResultOps(taskId: string, resultRef: string): PlannedOp[] {
85
85
 
86
86
  const workspaceSyncTask = impureCapability({
87
87
  aggregateId: "WorkspaceSyncOperation",
88
- orderDirectiveId: "requestWorkspaceSync",
88
+ obligationDirectiveId: "requestWorkspaceSync",
89
89
  completeDirectiveId: "completeWorkspaceSync",
90
90
  failDirectiveId: "failWorkspaceSync",
91
91
  blockDirectiveId: "blockWorkspaceSync",
@@ -113,7 +113,7 @@ const workspaceSyncTask = impureCapability({
113
113
  completeResult: syncResultOps,
114
114
  });
115
115
 
116
- export const requestWorkspaceSync = workspaceSyncTask.order.requires("requestWorkspaceSync");
116
+ export const requestWorkspaceSync = workspaceSyncTask.obligation.requires("requestWorkspaceSync");
117
117
  export const completeWorkspaceSync = workspaceSyncTask.complete.requires("completeWorkspaceSync");
118
118
  export const failWorkspaceSync = workspaceSyncTask.fail.requires("failWorkspaceSync");
119
119
  export const blockWorkspaceSync = workspaceSyncTask.block.requires("blockWorkspaceSync");
@@ -99,6 +99,14 @@ export interface WorkspaceInvariantDecl {
99
99
  readonly reads: WorkspaceInvariantReads;
100
100
  /** The executable predicate — ships in the engine bundle, NOT the manifest. */
101
101
  readonly assert: WorkspaceInvariantAssert;
102
+ /**
103
+ * THE DECLASSIFY WAIVER (nomosFlowGate rule 4 — architecture/security_model.md): present iff
104
+ * the author acknowledged that this invariant's refusal MAY correlate with a secret it reads
105
+ * (a `requires`/`owned` aggregate). The reason is hashed into the `nomosFlowGate` era key (so
106
+ * it is replay-verified + the entire audit surface). Absent ⇒ the flow gate refuses to compile
107
+ * a secret-correlated refusal.
108
+ */
109
+ readonly declassify?: string;
102
110
  /** Brand so a plain object is never mistaken for a declaration. */
103
111
  readonly __isWorkspaceInvariant: true;
104
112
  }
@@ -112,22 +120,35 @@ export function workspaceInvariant(id: string): {
112
120
  on(directiveId: string): {
113
121
  reads(reads: WorkspaceInvariantReads): {
114
122
  assert(assert: WorkspaceInvariantAssert): WorkspaceInvariantDecl;
123
+ /** Acknowledge an intended secret-correlated refusal (nomosFlowGate rule 4). The reason
124
+ * rides the `nomosFlowGate` era key; then chain `.assert(...)` as usual. */
125
+ declassify(reason: string): { assert(assert: WorkspaceInvariantAssert): WorkspaceInvariantDecl };
115
126
  };
116
127
  };
117
128
  } {
129
+ const finish = (directiveId: string, reads: WorkspaceInvariantReads, declassify: string | undefined) => ({
130
+ assert(assert: WorkspaceInvariantAssert): WorkspaceInvariantDecl {
131
+ return {
132
+ id,
133
+ on: directiveId,
134
+ reads,
135
+ assert,
136
+ ...(declassify !== undefined ? { declassify } : {}),
137
+ __isWorkspaceInvariant: true as const,
138
+ };
139
+ },
140
+ });
118
141
  return {
119
142
  on(directiveId: string) {
120
143
  return {
121
144
  reads(reads: WorkspaceInvariantReads) {
122
145
  return {
123
- assert(assert: WorkspaceInvariantAssert): WorkspaceInvariantDecl {
124
- return {
125
- id,
126
- on: directiveId,
127
- reads,
128
- assert,
129
- __isWorkspaceInvariant: true,
130
- };
146
+ assert: finish(directiveId, reads, undefined).assert,
147
+ declassify(reason: string) {
148
+ if (typeof reason !== "string" || reason.trim().length === 0) {
149
+ throw new Error(`workspaceInvariant('${id}').declassify(reason) needs a non-empty human reason — it is the audit record for an intended secret-correlated refusal.`);
150
+ }
151
+ return finish(directiveId, reads, reason);
131
152
  },
132
153
  };
133
154
  },
@@ -75,6 +75,13 @@ import { query } from "../query.js";
75
75
  import { workspaceInvariant, refAs } from "./workspace_invariant.js";
76
76
  import { writeTuple, removeTuple, RelationTuple } from "./relations.js";
77
77
  import { relationType, self, user, key } from "../relation_schema.js";
78
+ // THE GENESIS BOOTSTRAP (platform-birth genesis bootstrap, Approach A): governance carries the
79
+ // warrant-flip primitives so the birth door can lawfully bootstrap a virgin platform's founding admin —
80
+ // E `enrollSigner` (the founding signer tuple) → W `seedGenesis` (the GenesisWarrant) → A signed
81
+ // `grantAdmin` (admitted by the kernel genesis clause). Re-exported so the package's shape-discovery picks
82
+ // them up as part of the `workspaces` domain. (architecture/platform_birth_genesis_bootstrap.md)
83
+ export { GenesisWarrant, seedGenesis } from "./genesis.js";
84
+ export { identityRelations, enrollSigner } from "./signing_identity.js";
78
85
 
79
86
  // ── WORKSPACE SELF-IDENTITY (the law reads its OWN install-context) ──────────────────────────────────
80
87
  // The governance bundle runs on root AND every born platform. A workspace knows its ROLE via this
package/src/index.ts CHANGED
@@ -32,7 +32,21 @@ export {
32
32
  type FieldKind,
33
33
  type EvidenceRefValue,
34
34
  evidenceRefSchema,
35
+ // FIELD FACETS (the endorse model — security_model.md): authority/standing/attribution gate
36
+ // set() acceptance of PrincipalEvidence; secret/public are the confidentiality lattice.
37
+ authorityField,
38
+ standingField,
39
+ attributionField,
40
+ secretField,
41
+ publicField,
35
42
  } from "./fields.js";
43
+ export {
44
+ type PrincipalEvidence,
45
+ type AttributionPrincipal,
46
+ type EndorsedAuthority,
47
+ type EndorsedStanding,
48
+ type FieldFacet,
49
+ } from "./principal_evidence.js";
36
50
  export {
37
51
  Lww,
38
52
  LwwCausal,
@@ -278,7 +292,7 @@ export {
278
292
  impureCapabilityAggregate,
279
293
  impureCapabilityInstanceId,
280
294
  impureCapability,
281
- orderOps,
295
+ obligationOps,
282
296
  completeOps,
283
297
  failOps,
284
298
  blockOps,
@@ -410,6 +424,16 @@ export {
410
424
  type InvariantVerdict,
411
425
  } from "./relation.js";
412
426
  export { generateDartDomain, type DomainModule } from "./codegen_dart.js";
427
+ // SHAPE COMPATIBILITY (#72) — the structural contract a client negotiates with a workspace
428
+ // by, instead of the law hash. Pure + deterministic; the editor/CI mirror imports these.
429
+ export {
430
+ domainInterface,
431
+ compatible,
432
+ type DomainInterface,
433
+ type CompatVerdict,
434
+ type DirectivePayloadShape,
435
+ type AggregateReadShape,
436
+ } from "./shape_compat.js";
413
437
  // The OPTIONAL Graphviz `.dot` ER-diagram emitter (off-by-default presentation projection,
414
438
  // turned on by the `--emit-dot` compiler flag). Pure `DomainModule -> string`, deterministic;
415
439
  // NOT part of the domain identity. Barrel-safe (no node:* imports), so the engine bundle can