@shardworks/clerk-apparatus 0.1.270 → 0.1.271
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/README.md +208 -48
- package/dist/children-behavior-engine.d.ts +163 -0
- package/dist/children-behavior-engine.d.ts.map +1 -0
- package/dist/children-behavior-engine.js +353 -0
- package/dist/children-behavior-engine.js.map +1 -0
- package/dist/clerk.d.ts +14 -28
- package/dist/clerk.d.ts.map +1 -1
- package/dist/clerk.js +515 -206
- package/dist/clerk.js.map +1 -1
- package/dist/index.d.ts +10 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -5
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts +43 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +68 -0
- package/dist/testing.js.map +1 -0
- package/dist/tools/commission-post.d.ts.map +1 -1
- package/dist/tools/commission-post.js +14 -2
- package/dist/tools/commission-post.js.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/{piece-add.d.ts → step-add.d.ts} +1 -1
- package/dist/tools/step-add.d.ts.map +1 -0
- package/dist/tools/{piece-add.js → step-add.js} +12 -7
- package/dist/tools/step-add.js.map +1 -0
- package/dist/tools/writ-list.d.ts +17 -4
- package/dist/tools/writ-list.d.ts.map +1 -1
- package/dist/tools/writ-list.js +88 -3
- package/dist/tools/writ-list.js.map +1 -1
- package/dist/tools/writ-show.d.ts +4 -0
- package/dist/tools/writ-show.d.ts.map +1 -1
- package/dist/tools/writ-show.js +121 -8
- package/dist/tools/writ-show.js.map +1 -1
- package/dist/tools/writ-tree.d.ts +13 -4
- package/dist/tools/writ-tree.d.ts.map +1 -1
- package/dist/tools/writ-tree.js +34 -17
- package/dist/tools/writ-tree.js.map +1 -1
- package/dist/types.d.ts +320 -40
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +6 -1
- package/dist/types.js.map +1 -1
- package/dist/writ-presentation.d.ts +92 -0
- package/dist/writ-presentation.d.ts.map +1 -0
- package/dist/writ-presentation.js +67 -0
- package/dist/writ-presentation.js.map +1 -0
- package/dist/writ-type-config.d.ts +222 -0
- package/dist/writ-type-config.d.ts.map +1 -0
- package/dist/writ-type-config.js +305 -0
- package/dist/writ-type-config.js.map +1 -0
- package/package.json +8 -4
- package/pages/writs/index.html +304 -54
- package/pages/writs/writs-hierarchy.test.js +291 -29
- package/dist/tools/piece-add.d.ts.map +0 -1
- package/dist/tools/piece-add.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# `@shardworks/clerk-apparatus`
|
|
2
2
|
|
|
3
|
-
The Clerk manages the lifecycle of **writs** — lightweight work orders
|
|
3
|
+
The Clerk manages the lifecycle of **writs** — lightweight work orders whose state machine is declared per writ type via a `WritTypeConfig`. The Clerk's own built-in `mandate` type flows through a six-state lifecycle (below); other plugin-registered types declare their own states and transitions. Writs can be organized into parent/child hierarchies for decomposing complex work.
|
|
4
4
|
|
|
5
|
-
Writ documents follow a Kubernetes-style spec/status split: **`phase`** is the Clerk-owned lifecycle state (the phase machine below),
|
|
5
|
+
Writ documents follow a Kubernetes-style spec/status split: **`phase`** is the Clerk-owned lifecycle state (the phase machine below), **`status`** is a plugin-owned observation slot keyed by plugin id — a place for apparatuses like Spider to record side-channel observations (last rig, stuck cause, progress ratchets) without mutating the phase — and **`ext`** is a sibling plugin-owned metadata slot of the same shape, reserved for metadata-shape data (provenance, cross-references, classifier tags) attached at registration time rather than the post-hoc observation `status` records. See [Spec/Status Convention](#specstatus-convention) below.
|
|
6
6
|
|
|
7
7
|
The Clerk sits downstream of The Stacks: `stacks ← clerk`.
|
|
8
8
|
|
|
@@ -34,7 +34,14 @@ const clerk = guild().apparatus<ClerkApi>('clerk');
|
|
|
34
34
|
|
|
35
35
|
### `post(request): Promise<WritDoc>`
|
|
36
36
|
|
|
37
|
-
Post a new commission, creating a writ in `
|
|
37
|
+
Post a new commission, creating a writ in its registered type's declared `initial` state. For mandate that's `new` (a draft).
|
|
38
|
+
|
|
39
|
+
> **API vs. tool — auto-publish UX lives in the tool layer.** `clerk.post()` always lands the writ in the type's `initial` state and never advances it on its own — the API surface is intentionally minimal and predictable. The two tool wrappers reach `open` for you:
|
|
40
|
+
>
|
|
41
|
+
> - **`commission-post`** transitions newly-posted **mandate** writs to `open` automatically unless `draft: true` is passed. Other types (anything plugin-registered) are left in their `initial` state — advancing them without a type-specific tool would be silent coupling, so the auto-advance is confined to mandate.
|
|
42
|
+
> - **`piece-add`** unconditionally transitions the new piece to `open` (the tool has no `draft` parameter).
|
|
43
|
+
>
|
|
44
|
+
> Direct `clerk.post()` callers — including most plugin code — keep the `initial`-state landing semantics; if you want the writ to be dispatchable, follow up with `clerk.transition(id, 'open')`.
|
|
38
45
|
|
|
39
46
|
```typescript
|
|
40
47
|
const writ = await clerk.post({
|
|
@@ -50,16 +57,16 @@ const writ = await clerk.post({
|
|
|
50
57
|
|---|---|---|
|
|
51
58
|
| `title` | `string` | Short human-readable title |
|
|
52
59
|
| `body` | `string` | Detail text (required) |
|
|
53
|
-
| `type` | `string` | Writ type — must be
|
|
60
|
+
| `type` | `string` | Writ type — must be a registered type (optional) |
|
|
54
61
|
| `codex` | `string` | Target codex name (optional, inherited from parent if omitted) |
|
|
55
62
|
| `parentId` | `string` | Parent writ id for hierarchical decomposition (optional) |
|
|
56
63
|
|
|
57
64
|
When `parentId` is provided:
|
|
58
|
-
- The parent must exist and be in
|
|
65
|
+
- The parent must exist and not be in a terminal state (as classified by its type config).
|
|
59
66
|
- The child inherits the parent's `codex` if no explicit codex is provided.
|
|
60
67
|
- The entire operation is atomic.
|
|
61
68
|
|
|
62
|
-
Throws if the writ type is not
|
|
69
|
+
Throws if the writ type is not registered — register types with [`registerWritType`](#registerwrittypeconfig-void) from your plugin's `start()`.
|
|
63
70
|
|
|
64
71
|
### `show(id): Promise<WritDoc>`
|
|
65
72
|
|
|
@@ -118,14 +125,13 @@ const total = await clerk.count({ phase: 'open' });
|
|
|
118
125
|
|
|
119
126
|
### `listWritTypes(): WritTypeInfo[]`
|
|
120
127
|
|
|
121
|
-
List all registered writ types
|
|
128
|
+
List all registered writ types. Returns an entry for each type registered via [`registerWritType`](#registerwrittypeconfig-void), including the Clerk's own `mandate`.
|
|
122
129
|
|
|
123
130
|
```typescript
|
|
124
131
|
const types = clerk.listWritTypes();
|
|
125
132
|
// [
|
|
126
133
|
// { name: 'mandate', description: null, source: 'builtin', isDefault: true },
|
|
127
|
-
// { name: '
|
|
128
|
-
// { name: 'quality-audit', description: 'Code quality audit', source: 'my-kit', isDefault: false },
|
|
134
|
+
// { name: 'piece', description: null, source: 'plugin', isDefault: false },
|
|
129
135
|
// ]
|
|
130
136
|
```
|
|
131
137
|
|
|
@@ -134,11 +140,47 @@ Each entry includes:
|
|
|
134
140
|
| Field | Type | Description |
|
|
135
141
|
|---|---|---|
|
|
136
142
|
| `name` | `string` | The writ type name |
|
|
137
|
-
| `description` | `string \| null` |
|
|
138
|
-
| `source` | `
|
|
143
|
+
| `description` | `string \| null` | Reserved; always `null` today — `WritTypeConfig` does not currently model a description field |
|
|
144
|
+
| `source` | `"builtin" \| "plugin"` | `"builtin"` for mandate (registered by the Clerk plugin itself); `"plugin"` for every other registered type |
|
|
139
145
|
| `isDefault` | `boolean` | Whether this is the guild's default writ type |
|
|
140
146
|
|
|
141
|
-
|
|
147
|
+
Use [`getWritTypeConfig(name)`](#getwrittypeconfigname-writtypeconfig--undefined) when you need the full `WritTypeConfig` (states, transitions, etc.).
|
|
148
|
+
|
|
149
|
+
### `registerWritType(config): void`
|
|
150
|
+
|
|
151
|
+
Register a writ type's state machine with the Clerk. The config is validated via `validateWritTypeConfig` (validator errors propagate verbatim); registration-specific failures — duplicate names, or calls after the startup window has closed — throw with a `[clerk] registerWritType:` prefix.
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
// From a plugin's own start():
|
|
155
|
+
const clerk = guild().apparatus<ClerkApi>('clerk');
|
|
156
|
+
clerk.registerWritType({
|
|
157
|
+
name: 'audit',
|
|
158
|
+
states: [
|
|
159
|
+
{ name: 'new', classification: 'initial', allowedTransitions: ['open', 'cancelled'] },
|
|
160
|
+
{ name: 'open', classification: 'active', allowedTransitions: ['completed', 'failed', 'cancelled'] },
|
|
161
|
+
{ name: 'completed', classification: 'terminal', attrs: ['success'], allowedTransitions: [] },
|
|
162
|
+
{ name: 'failed', classification: 'terminal', attrs: ['failure'], allowedTransitions: [] },
|
|
163
|
+
{ name: 'cancelled', classification: 'terminal', attrs: ['cancelled'], allowedTransitions: [] },
|
|
164
|
+
],
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`registerWritType` is the only path for a plugin to contribute a writ type. The registry is sealed at the framework's global `phase:started` signal — call it from your apparatus's `start()`. There is no guild-config writTypes field and no kit-contribution channel.
|
|
169
|
+
|
|
170
|
+
### `getWritTypeConfig(name): WritTypeConfig | undefined`
|
|
171
|
+
|
|
172
|
+
Return the registered `WritTypeConfig` for a writ type, or `undefined` when the name is not registered. Use this accessor when composing higher-level predicates or inspecting a type abstractly (e.g. to render a state-machine diagram).
|
|
173
|
+
|
|
174
|
+
### `isInitial(writ): boolean` / `isActive(writ): boolean` / `isTerminal(writ): boolean`
|
|
175
|
+
|
|
176
|
+
Classify a writ's current state by consulting its type's registered `WritTypeConfig`. Each predicate throws the fail-loud diagnostic when the writ's type is not registered, or when its stored state is not declared in that type's config.
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const writ = await clerk.show(id);
|
|
180
|
+
if (clerk.isTerminal(writ)) { /* ... */ }
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The three predicates partition registered states cleanly — every state in a validated `WritTypeConfig` carries exactly one classification.
|
|
142
184
|
|
|
143
185
|
### `link(sourceId, targetId, label, kind?): Promise<WritLinkDoc>`
|
|
144
186
|
|
|
@@ -219,11 +261,17 @@ await clerk.transition(id, 'failed', { resolution: 'Build pipeline broke' });
|
|
|
219
261
|
await clerk.transition(id, 'cancelled', { resolution: 'No longer needed' });
|
|
220
262
|
```
|
|
221
263
|
|
|
222
|
-
Throws if the transition is not legal for the writ's current phase.
|
|
264
|
+
Throws if the transition is not legal for the writ's current phase. The rejection message carries the writ id, current state, attempted target, and the list of legal transitions declared by the writ's type config (or `none (terminal state)` when the current state is terminal).
|
|
265
|
+
|
|
266
|
+
`transition()` strips the phase machine's managed fields from the body — `id`, `createdAt`, `updatedAt`, `resolvedAt`, and `parentId` — and rejects attempts to override `phase` through the `fields` argument with `[clerk] transition: cannot override phase via fields argument`. The plugin-owned slots `status` and `ext` are writable only via their dedicated writers (`setWritStatus()` for the observation slot, `setWritExt()` for the metadata slot — the two sanctioned slot-write paths), each of which performs a transactional read-modify-write on the sub-slot keyed by `pluginId` so sibling sub-slots are preserved under concurrent writers. See [Spec/Status Convention](#specstatus-convention).
|
|
223
267
|
|
|
224
|
-
|
|
268
|
+
The children-behavior engine — a Phase 1 watcher on the writs book — drives **three** cascade-engine branches. When any writ transitions to a terminal state, the engine evaluates the relevant `WritTypeConfig.childrenBehavior` block(s) and applies the configured actions via `transition`:
|
|
225
269
|
|
|
226
|
-
**
|
|
270
|
+
- **Upward** (terminal child → parent lift) reads the *parent's* `childrenBehavior`. `anyFailure` is evaluated first; if it fires, `allSuccess` is skipped. When the firing trigger declares `copyResolution: true`, the triggering child's `resolution` string is copied verbatim onto the parent. On every upward fire the engine *also* publishes the immediate triggering child's id under the parent's Clerk-owned status sub-slot (`status['clerk'].triggeringChildId`) **before** the transition records — see [Worked example: `status.clerk.triggeringChildId`](#worked-example-statusclerktriggeringchildid) — so downstream observers (the Reckoner today) can chase the cascade chain back to the leaf cause without parsing the parent's resolution string.
|
|
271
|
+
- **Downward** (terminal parent → non-terminal-children cancellation) reads the *triggering writ's own* `childrenBehavior.parentTerminal`. The downward branch fires only on `failure`- or `cancelled`-attr terminals; the `success` attr is handled by the tripwire branch instead. For each non-terminal child, the engine calls `transition` with the action's configured target and resolution. Already-terminal children are skipped (idempotent on re-fire); a child whose type cannot accept the configured target throws and rolls back the cascade.
|
|
272
|
+
- **Tripwire** (success-attr terminal with non-terminal descendant — enforced invariant). When a writ whose type opts into `childrenBehavior` reaches a `success`-attr terminal state and any non-terminal descendant remains, the engine throws inside the firing transaction and Phase 1 atomicity rolls the offending parent transition back. The cascade engine itself can never produce this state — `allSuccess` enumerates every direct sibling and requires terminal-success — so any path that does is a direct `clerk.transition()` caller bypassing the cascade. Surfacing the gap as a hard error (rather than a log-only warn) makes it unrepresentable in the writs book and catches caller bugs at commit time. The branch walks the descendant subtree directly through the writs book (recursing through terminal nodes too — a bypass further down the tree could leave a non-terminal grandchild beneath an already-terminal child) and the throw message names the offending writ id, the success-attr state, and the non-terminal descendants.
|
|
273
|
+
|
|
274
|
+
Types whose configs omit `childrenBehavior` are silent no-ops across all three branches — they have announced they do not couple parent and child outcomes. Mandate opts into all three upward/downward triggers (`allSuccess`, `anyFailure`, `parentTerminal`) and is therefore covered by the tripwire too; piece and observation-set declare none. Cascade writes join the triggering transaction (Phase 1 atomicity); grandparent lift and grandchild cancel both fall out naturally as the next update event re-enters the watcher.
|
|
227
275
|
|
|
228
276
|
### `setWritStatus(writId, pluginId, value): Promise<WritDoc>`
|
|
229
277
|
|
|
@@ -241,9 +289,27 @@ The write runs in a Stacks transaction (read-modify-write), so concurrent writes
|
|
|
241
289
|
|
|
242
290
|
Throws if `writId` or `pluginId` is missing, or if the writ does not exist.
|
|
243
291
|
|
|
292
|
+
### `setWritExt(writId, pluginId, value): Promise<WritDoc>`
|
|
293
|
+
|
|
294
|
+
Write (or overwrite) a plugin-owned sub-slot inside the writ's metadata `ext` map. Sibling to `setWritStatus`: the `ext` field on `WritDoc` is the same plugin-keyed `Record<string, unknown>` shape, written through the same transactional read-modify-write contract, with the same CDC event emission and the same terminal-survival guarantee. The semantic distinction is what each slot is meant to hold — `ext` carries metadata-shape data attached at registration time, while `status` records post-hoc observations. See [`ext` (metadata) vs `status` (observation)](#ext-metadata-vs-status-observation).
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
// Reckoner attaches the originating petition id when the writ is created
|
|
298
|
+
await clerk.setWritExt(writ.id, 'reckoner', { petitionId: 'pet-01' });
|
|
299
|
+
|
|
300
|
+
// A different plugin writes its own metadata key in the same slot — disjoint sub-slot, no clobber.
|
|
301
|
+
await clerk.setWritExt(writ.id, 'astrolabe', { tag: 'spike' });
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
The write runs in a Stacks transaction (read-modify-write), so concurrent writes from different plugins to different sub-slots are disjoint and safe. The slot is optional and absent on freshly-posted writs (reads `ext === undefined` until the first sub-slot is written), and is not cleared on terminal transitions — metadata persists for cross-reference reads.
|
|
305
|
+
|
|
306
|
+
Throws if `writId` or `pluginId` is missing, or if the writ does not exist.
|
|
307
|
+
|
|
244
308
|
---
|
|
245
309
|
|
|
246
|
-
##
|
|
310
|
+
## Mandate's lifecycle (an example registered type)
|
|
311
|
+
|
|
312
|
+
Mandate is the one writ type the Clerk plugin registers for itself. Its lifecycle — six states, the transitions below — is just one example of a `WritTypeConfig`; other plugin-registered types declare their own state machines.
|
|
247
313
|
|
|
248
314
|
```
|
|
249
315
|
new ──► open ──┬──► completed
|
|
@@ -260,10 +326,10 @@ new ──► open ──┬──► completed
|
|
|
260
326
|
└───────┴────┴──► cancelled
|
|
261
327
|
```
|
|
262
328
|
|
|
263
|
-
- `completed`, `failed`, and `cancelled` are **terminal** — no transitions out.
|
|
264
|
-
- `stuck` is **non-terminal** — a "needs attention" phase for writs whose rig hit an engine failure. Recovery
|
|
329
|
+
- `completed`, `failed`, and `cancelled` are **terminal** (classification: `terminal`) — no transitions out.
|
|
330
|
+
- `stuck` is **non-terminal** (classification: `active`) — a "needs attention" phase for writs whose rig hit an engine failure. Recovery transitions back to `open`; giving up transitions to `failed` or `cancelled`.
|
|
265
331
|
|
|
266
|
-
###
|
|
332
|
+
### Mandate's allowed transitions
|
|
267
333
|
|
|
268
334
|
| To | From |
|
|
269
335
|
|---|---|
|
|
@@ -273,15 +339,16 @@ new ──► open ──┬──► completed
|
|
|
273
339
|
| `failed` | `open`, `stuck` |
|
|
274
340
|
| `cancelled` | `new`, `open`, `stuck` |
|
|
275
341
|
|
|
342
|
+
The same table is carried as `allowedTransitions` on each state in mandate's `WritTypeConfig`. Other types (`piece`, `observation-set`, your own) live alongside mandate in the Clerk's runtime registry — their allowed transitions come from their own configs, not this table.
|
|
343
|
+
|
|
276
344
|
---
|
|
277
345
|
|
|
278
346
|
## Parent/Child Hierarchies
|
|
279
347
|
|
|
280
348
|
Writs can be organized into parent/child relationships for decomposing complex work:
|
|
281
349
|
|
|
282
|
-
- **Creating children:** Pass `parentId` to `post()`. The parent stays in its current phase. Parents in
|
|
283
|
-
- **
|
|
284
|
-
- **Cancellation cascade:** When a parent reaches `failed` or `cancelled`, all non-terminal children are cancelled. When a parent reaches `completed` with non-terminal children still present, the Clerk logs a warning and leaves the children alone — this signals an upstream bookkeeping gap rather than normal flow.
|
|
350
|
+
- **Creating children:** Pass `parentId` to `post()`. The parent stays in its current phase. Parents accept children in any non-terminal state; a parent in a terminal state (as classified by its type config) rejects new children with a clear error.
|
|
351
|
+
- **Children-behavior cascade:** the children-behavior engine drives three branches. Upward (terminal child → parent lift) evaluates the parent's `childrenBehavior` (`anyFailure` before `allSuccess`; a failing child wins precedence; `copyResolution: true` copies the triggering child's resolution onto the parent verbatim). Downward (terminal parent → non-terminal-children cancellation) evaluates the triggering writ's own type's `parentTerminal` action when the writ reaches a `failure`- or `cancelled`-attr terminal — every non-terminal descendant is driven to the configured target with the configured resolution string. Tripwire (enforced invariant) throws and rolls back when a cascade-opt-in writ would reach a `success`-attr terminal with any non-terminal descendant: the cascade itself can never produce this state, so any path that does is upstream-broken (a direct `transition()` caller bypassing the cascade), and Phase 1 atomicity makes the gap unrepresentable in the writs book. Cascade is opt-in per type: a type with no `childrenBehavior` block is a silent no-op across all three branches. Mandate opts into all three triggers; piece and observation-set declare none. Cascade fires inside the transaction that triggered it (Phase 1); grandparent lift and grandchild cancel both fall out naturally as the next update event re-enters the watcher.
|
|
285
352
|
- **Codex inheritance:** Children inherit the parent's codex if none is specified.
|
|
286
353
|
- **Immutability:** `parentId` cannot be changed after creation.
|
|
287
354
|
|
|
@@ -293,15 +360,73 @@ Clerk follows a Kubernetes-style spec/status split:
|
|
|
293
360
|
|
|
294
361
|
- **Spec fields** are the declared intent of the writ — `title`, `body`, `type`, `codex`, `parentId`, and the Clerk-owned lifecycle field `phase`. These describe *what should happen* and *where the writ currently sits on the phase machine*.
|
|
295
362
|
- **Status slot** (the `status` field on `WritDoc`) is a `Record<string, unknown>` — a free-form observation map keyed by plugin id. Each plugin owns one sub-slot and uses it to record side-channel observations about the writ: last rig used, stuck cause, progress ratchets, planner version, etc.
|
|
363
|
+
- **Ext slot** (the `ext` field on `WritDoc`) is the structural sibling of `status` — same plugin-keyed `Record<string, unknown>` shape, same transactional write contract, same terminal-survival rule — but reserved for metadata-shape data (petition ids, cross-references, classifier tags, configuration extensions) attached at registration time rather than the post-hoc observation `status` records. See [`ext` (metadata) vs `status` (observation)](#ext-metadata-vs-status-observation) below.
|
|
364
|
+
|
|
365
|
+
Both slots are soft conventions rather than hard enforcement boundaries. Wherever a rule below names one slot and writer, the same rule holds for the sibling.
|
|
366
|
+
|
|
367
|
+
- No runtime guard stops a plugin from reading another plugin's sub-slot — the convention is *write only your own key*, and the dedicated APIs (`setWritStatus()` / `setWritExt()`) make the right thing easy.
|
|
368
|
+
- **One sanctioned slot-write path per slot.** The observation slot is writable only via `setWritStatus(writId, pluginId, value)`; the metadata slot only via `setWritExt(writId, pluginId, value)`. Each performs a transactional read-modify-write on the sub-slot keyed by `pluginId` so sibling sub-slots are preserved under concurrent writers. `transition()` silently strips both `status` and `ext` from its body alongside the other managed fields (`id`, `phase`, `createdAt`, `updatedAt`, `resolvedAt`, `parentId`). The generic `put()` / `patch()` paths on the `clerk/writs` book are not supported slot-write mechanisms — every route other than the dedicated writer would wholesale-replace the slot and clobber sibling sub-slots.
|
|
369
|
+
- Concurrent writes from different plugins to different sub-slots are disjoint and safe: `setWritStatus()` and `setWritExt()` each run their read-modify-write inside a Stacks transaction.
|
|
370
|
+
- Within a single plugin's sub-slot, concurrent writes are last-writer-wins at the sub-slot level — the dedicated writer replaces the plugin's sub-slot value wholesale. Per-key atomicity inside a sub-slot is deferred until real contention appears.
|
|
371
|
+
- Slot writes emit CDC events like any other field change. Downstream observers (page renderers, audits, further observation pipelines) can watch the writs book for `update` events and react to the new `status` / `ext` contents.
|
|
372
|
+
- Terminal transitions do **not** clear the slots. Observations and metadata persist on the writ for post-mortem inspection and ongoing cross-reference reads.
|
|
296
373
|
|
|
297
|
-
|
|
374
|
+
### `ext` (metadata) vs `status` (observation)
|
|
298
375
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
-
|
|
302
|
-
-
|
|
303
|
-
|
|
304
|
-
|
|
376
|
+
Both slots are plugin-keyed `Record<string, unknown>` maps with identical mechanics. The semantic distinction is the *kind* of data each is meant to hold:
|
|
377
|
+
|
|
378
|
+
- **`status` is for post-hoc observation** — what a plugin has *observed* about a writ after the fact. Examples: a stuck cause recorded by Spider's engine-failure handler, a triggering child id recorded by the Clerk's children-behavior cascade, a gate result recorded by an evaluator. The defining feature is that the observation is the plugin's reaction to something the writ has been through.
|
|
379
|
+
- **`ext` is for attached metadata** — what a plugin needs the writ to *carry* as an attribute of its identity. Examples: a petition id linking a writ back to its originating registration, a foreign-system reference, a classifier tag baked in at creation. The defining feature is that the metadata is part of *what the writ is*, not a record of what has happened to it.
|
|
380
|
+
|
|
381
|
+
Picking the wrong slot layers metadata under an observation contract or vice versa, so plugin authors should choose consciously. When in doubt: ask whether the data is set as the writ comes into being (or is registered with another system) — that points to `ext` — versus updated reactively as the writ evolves — that points to `status`.
|
|
382
|
+
|
|
383
|
+
#### Worked example: `ext['reckoner'].petitionId`
|
|
384
|
+
|
|
385
|
+
The Reckoner registers a petition for a writ at the moment the writ is created on its behalf, and attaches the petition id under `ext['reckoner']` so downstream consumers can chase the cross-reference back to the petition record without a separate index. The shape is established at attach time and stable for the writ's lifetime — a textbook metadata-shape consumer rather than an observation. See `docs/architecture/petitioner-registration.md` for the full Reckoner contract; the slot itself is opaque to the Clerk and validated only by the Reckoner.
|
|
386
|
+
|
|
387
|
+
### Worked example: `status.clerk.triggeringChildId`
|
|
388
|
+
|
|
389
|
+
The Clerk's children-behavior cascade engine writes a sub-slot of its
|
|
390
|
+
own. When a parent writ is lifted into a terminal state by the cascade
|
|
391
|
+
(one of its children's terminal transitions fired the parent's
|
|
392
|
+
`WritTypeConfig.childrenBehavior` trigger), the engine records the
|
|
393
|
+
immediate triggering child's id under `status['clerk']` *before* the
|
|
394
|
+
parent's `transition()` call:
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
interface ClerkWritStatus {
|
|
398
|
+
/**
|
|
399
|
+
* Id of the immediate child whose terminal transition fired the
|
|
400
|
+
* children-behavior cascade onto this writ. Absent on writs that
|
|
401
|
+
* reached terminal through a direct (non-cascaded) transition.
|
|
402
|
+
*/
|
|
403
|
+
triggeringChildId?: string;
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
The slot is owned by the Clerk; downstream observers (today, the
|
|
408
|
+
Reckoner) read it through the standard plugin convention:
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
const clerkStatus = writ.status?.clerk as
|
|
412
|
+
| { triggeringChildId?: string }
|
|
413
|
+
| undefined;
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
**Why the ordering matters.** Phase 2 CDC observers read `event.entry`
|
|
417
|
+
(the post-commit snapshot) at emit time, keyed on the terminal-
|
|
418
|
+
transition's `updatedAt`. Writing the slot *after* the transition would
|
|
419
|
+
deliver the pulse against a snapshot that pre-dates the slot's
|
|
420
|
+
existence and degrade the leaf-cause surface. The dual-write sequence
|
|
421
|
+
(`setWritStatus(parent, 'clerk', …)` then `transition(parent, …)`) is
|
|
422
|
+
preserved instead of relaxing `transition()`'s safe-fields strip —
|
|
423
|
+
`status` continues to be writable only through `setWritStatus()`.
|
|
424
|
+
|
|
425
|
+
**Chase-chain on the consumer side.** Multi-level cascades (root → mid
|
|
426
|
+
→ leaf) leave each parent in the chain carrying its own immediate
|
|
427
|
+
triggering child id; consumers walk the chain by reading each successive
|
|
428
|
+
writ's own `status['clerk'].triggeringChildId`. Cascade depth is bounded
|
|
429
|
+
by Stacks' `MAX_CASCADE_DEPTH = 16` invariant.
|
|
305
430
|
|
|
306
431
|
### Worked example: `status.spider.stuckCause`
|
|
307
432
|
|
|
@@ -347,18 +472,9 @@ Configure The Clerk under the `"clerk"` key in your guild config:
|
|
|
347
472
|
}
|
|
348
473
|
```
|
|
349
474
|
|
|
350
|
-
|
|
475
|
+
Only `defaultType` is honored. It must name a writ type registered with the Clerk (via `registerWritType` from a plugin's `start()`); an unregistered default fails the guild's startup with a clear `[clerk] guild config:` error.
|
|
351
476
|
|
|
352
|
-
|
|
353
|
-
{
|
|
354
|
-
"writTypes": {
|
|
355
|
-
"epic": { "description": "A significant multi-step task" },
|
|
356
|
-
"errand": { "description": "A small one-off task" }
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
The built-in types `mandate` and `summon` are always available without declaration.
|
|
477
|
+
There is no guild-config `writTypes` field. Plugins contribute writ types via `ClerkApi.registerWritType(config)` from their own apparatus's `start()`. The Clerk itself registers `mandate` the same way.
|
|
362
478
|
|
|
363
479
|
---
|
|
364
480
|
|
|
@@ -377,7 +493,8 @@ The Clerk contributes books, tools, and pages to the guild:
|
|
|
377
493
|
|
|
378
494
|
| Tool | Permission | Description |
|
|
379
495
|
|---|---|---|
|
|
380
|
-
| `commission-post` | `clerk:write` | Post a new commission (create a writ, optionally as child) |
|
|
496
|
+
| `commission-post` | `clerk:write` | Post a new commission (create a writ, optionally as child). Auto-publishes mandate writs to `open` unless `draft: true` is passed; other types stay in their `initial` state. |
|
|
497
|
+
| `piece-add` | `clerk:write` | Add a child `piece` writ to a mandate from a structured task description; the piece is auto-published to `open` and joins the implement-loop queue. |
|
|
381
498
|
| `writ-show` | `clerk:read` | Show full detail for a writ (includes parent/children context) |
|
|
382
499
|
| `writ-list` | `clerk:read` | List writs with optional filters (phase, type, parentId) |
|
|
383
500
|
| `writ-tree` | `clerk:read` | Render the writ hierarchy as a depth-aware tree (forest or single subtree); supports `phase` / `type` filters with prune semantics and a `depth` cap. Output is a box-drawing ASCII tree by default (`--format text`) or the structured `WritTree[]` forest (`--format json`). |
|
|
@@ -397,12 +514,15 @@ The Clerk contributes books, tools, and pages to the guild:
|
|
|
397
514
|
## Key Types
|
|
398
515
|
|
|
399
516
|
```typescript
|
|
517
|
+
// Mandate-specific state union (kept for callers that knowingly downcast).
|
|
518
|
+
// The structural type of WritDoc.phase is `string` so any plugin-registered
|
|
519
|
+
// writ type's state name round-trips through the book.
|
|
400
520
|
type WritPhase = 'new' | 'open' | 'stuck' | 'completed' | 'failed' | 'cancelled';
|
|
401
521
|
|
|
402
522
|
interface WritDoc {
|
|
403
523
|
id: string; // ULID-like, prefixed "w-"
|
|
404
|
-
type: string; //
|
|
405
|
-
phase:
|
|
524
|
+
type: string; // a registered writ type
|
|
525
|
+
phase: string; // Clerk-owned lifecycle state (any registered type's state name)
|
|
406
526
|
title: string;
|
|
407
527
|
body: string;
|
|
408
528
|
codex?: string; // target codex name
|
|
@@ -412,6 +532,7 @@ interface WritDoc {
|
|
|
412
532
|
resolvedAt?: string; // ISO timestamp, set on any terminal transition
|
|
413
533
|
resolution?: string; // summary of how the writ resolved
|
|
414
534
|
status?: Record<string, unknown>; // plugin-owned observation slot (keyed by plugin id)
|
|
535
|
+
ext?: Record<string, unknown>; // plugin-owned metadata slot (keyed by plugin id)
|
|
415
536
|
}
|
|
416
537
|
|
|
417
538
|
interface PostCommissionRequest {
|
|
@@ -454,10 +575,10 @@ interface WritTreeParams {
|
|
|
454
575
|
}
|
|
455
576
|
|
|
456
577
|
interface WritTypeInfo {
|
|
457
|
-
name: string;
|
|
458
|
-
description: string | null;
|
|
459
|
-
source:
|
|
460
|
-
isDefault: boolean;
|
|
578
|
+
name: string; // writ type name
|
|
579
|
+
description: string | null; // reserved; always null today
|
|
580
|
+
source: 'builtin' | 'plugin'; // "builtin" for mandate; "plugin" otherwise
|
|
581
|
+
isDefault: boolean; // whether this is the default type
|
|
461
582
|
}
|
|
462
583
|
|
|
463
584
|
interface WritLinkDoc {
|
|
@@ -497,7 +618,46 @@ See `src/types.ts` for the complete type definitions.
|
|
|
497
618
|
The package exports all public types and the `createClerk()` factory:
|
|
498
619
|
|
|
499
620
|
```typescript
|
|
500
|
-
import clerkPlugin, {
|
|
621
|
+
import clerkPlugin, {
|
|
622
|
+
createClerk,
|
|
623
|
+
CLERK_PLUGIN_ID,
|
|
624
|
+
type ClerkApi,
|
|
625
|
+
type ClerkWritStatus,
|
|
626
|
+
type WritTypeInfo,
|
|
627
|
+
} from '@shardworks/clerk-apparatus';
|
|
501
628
|
```
|
|
502
629
|
|
|
630
|
+
`CLERK_PLUGIN_ID` is the constant (`'clerk'`) used as the `status` sub-slot key for the Clerk's own observations (see [Worked example: `status.clerk.triggeringChildId`](#worked-example-statusclerktriggeringchildid)). `ClerkWritStatus` is the writer-side shape of that slot.
|
|
631
|
+
|
|
503
632
|
The default export is a pre-built plugin instance, ready for guild installation.
|
|
633
|
+
|
|
634
|
+
### Writ-type configuration
|
|
635
|
+
|
|
636
|
+
The package also exports the structural shape that describes a writ type's state machine and lifecycle behavior, plus a pure structural validator. These primitives are the foundation for plugin-registered writ types; they do not yet participate in the runtime lifecycle.
|
|
637
|
+
|
|
638
|
+
```typescript
|
|
639
|
+
import {
|
|
640
|
+
validateWritTypeConfig,
|
|
641
|
+
type WritTypeConfig,
|
|
642
|
+
type WritTypeStateDefinition,
|
|
643
|
+
type WritTypeStateClassification,
|
|
644
|
+
type WritTypeStateAttr,
|
|
645
|
+
type KnownWritTypeStateAttr,
|
|
646
|
+
type WritTypeChildrenBehavior,
|
|
647
|
+
type WritTypeChildrenBehaviorAction,
|
|
648
|
+
} from '@shardworks/clerk-apparatus';
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
A `WritTypeConfig` names the type, enumerates its lifecycle states (each with a `classification` of `'initial' | 'active' | 'terminal'`, an optional `attrs` vocabulary, and per-state `allowedTransitions`), and optionally declares `childrenBehavior` triggers (`allSuccess`, `anyFailure`) that lift terminal-child outcomes back onto the parent.
|
|
652
|
+
|
|
653
|
+
`validateWritTypeConfig(config)` throws a plain `Error` on the first structural violation it encounters and returns `void` on success. Error messages take the shape `[clerk] writTypeConfig.<path>: <problem>; received <value>` with the path naming the offending field (e.g. `states[2].classification`, `childrenBehavior.anyFailure.transition`). The validator enforces:
|
|
654
|
+
|
|
655
|
+
- non-empty `name`
|
|
656
|
+
- non-empty `states` array with unique non-empty state names
|
|
657
|
+
- every `classification` drawn from the known vocabulary
|
|
658
|
+
- every `allowedTransitions` entry references an existing state
|
|
659
|
+
- exactly one state classified `initial`
|
|
660
|
+
- every non-initial state has at least one inbound transition
|
|
661
|
+
- terminal states declare no outbound transitions
|
|
662
|
+
- every declared `childrenBehavior` trigger carries an action with a `transition` field that references an existing state
|
|
663
|
+
- every `childrenBehavior` transition target is reachable from every non-terminal state via `allowedTransitions`
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Children-behavior cascade engine.
|
|
3
|
+
*
|
|
4
|
+
* Subscribes to `update` events on the `clerk/writs` book and, when a writ
|
|
5
|
+
* transitions to a terminal state, evaluates the relevant
|
|
6
|
+
* `WritTypeConfig.childrenBehavior` block(s) and applies the configured
|
|
7
|
+
* action via the supplied `transition` callback. The engine is generic in
|
|
8
|
+
* writ type — any registered type whose config declares a
|
|
9
|
+
* `childrenBehavior` block opts in; types that omit the block are no-ops.
|
|
10
|
+
*
|
|
11
|
+
* Three cascade-engine branches, all driven by the same firing rule
|
|
12
|
+
* (terminal transition on `update` events) and dispatched from the same
|
|
13
|
+
* `handle` function:
|
|
14
|
+
*
|
|
15
|
+
* - **Upward** (terminal child → parent lift). Drives the parent through
|
|
16
|
+
* the parent type's `allSuccess` / `anyFailure` triggers when the
|
|
17
|
+
* triggering *child* reaches a `success`- or `failure`-attr terminal
|
|
18
|
+
* state respectively.
|
|
19
|
+
* - **Downward** (terminal parent → non-terminal children cancellation).
|
|
20
|
+
* Drives every non-terminal descendant through the parent type's
|
|
21
|
+
* `parentTerminal` trigger when the *parent itself* reaches a
|
|
22
|
+
* `failure`- or `cancelled`-attr terminal state. Recursion to
|
|
23
|
+
* grandchildren happens via natural CDC re-fire on each child's own
|
|
24
|
+
* transition, not by an in-handler walk.
|
|
25
|
+
* - **Tripwire** (success-attr terminal with non-terminal descendant —
|
|
26
|
+
* enforced invariant). Throws when an entry whose type opts into
|
|
27
|
+
* `childrenBehavior` reaches a `success`-attr terminal state while
|
|
28
|
+
* any non-terminal descendant remains. The throw rolls the entry's
|
|
29
|
+
* transition back via Phase 1 atomicity, so the bookkeeping gap is
|
|
30
|
+
* unrepresentable in the writs book rather than a log-only signal.
|
|
31
|
+
* The cascade engine itself can never produce this state —
|
|
32
|
+
* `allSuccess` enumerates every sibling and requires terminal-success
|
|
33
|
+
* — so any path that does produce it is upstream-broken (a direct
|
|
34
|
+
* `clerk.transition()` caller bypassing the cascade); surfacing it as
|
|
35
|
+
* a hard error catches caller bugs early.
|
|
36
|
+
*
|
|
37
|
+
* Upward firing rule (in order — first-fail short-circuits):
|
|
38
|
+
*
|
|
39
|
+
* 1. Event is an `update`.
|
|
40
|
+
* 2. The entry's phase actually changed (`entry.phase !== prev.phase`).
|
|
41
|
+
* 3. The entry is in a terminal state.
|
|
42
|
+
* 4. The entry has a `parentId`.
|
|
43
|
+
* 5. The parent writ exists. (Throws if not — dangling parent is a
|
|
44
|
+
* data-integrity violation.)
|
|
45
|
+
* 6. The parent's writ-type is registered. (Throws if not — same
|
|
46
|
+
* fail-loud shape as `classifyWritState`.)
|
|
47
|
+
* 7. The parent type declares a `childrenBehavior` block. (Silent
|
|
48
|
+
* no-op when absent.)
|
|
49
|
+
* 8. The parent itself is non-terminal. (Idempotent short-circuit.)
|
|
50
|
+
*
|
|
51
|
+
* Upward trigger evaluation order: `anyFailure` is evaluated first; if it
|
|
52
|
+
* fires, `allSuccess` is skipped. Otherwise `allSuccess` is evaluated by
|
|
53
|
+
* enumerating *every* sibling under the same parent (not via the limited
|
|
54
|
+
* `api.list` path) and checking that every sibling has reached a terminal
|
|
55
|
+
* state and every terminal state carries the `success` attr.
|
|
56
|
+
*
|
|
57
|
+
* Downward firing rule (in order — first-fail short-circuits):
|
|
58
|
+
*
|
|
59
|
+
* 1. Event is an `update`.
|
|
60
|
+
* 2. The entry's phase actually changed (`entry.phase !== prev.phase`).
|
|
61
|
+
* 3. The entry is in a terminal state carrying the `failure` or
|
|
62
|
+
* `cancelled` attr (the parent-itself-terminated signal). The
|
|
63
|
+
* `success` attr — i.e. natural completion — is handled by the
|
|
64
|
+
* tripwire branch instead.
|
|
65
|
+
* 4. The entry's own writ-type is registered.
|
|
66
|
+
* 5. The entry's type declares a `parentTerminal` action. (Silent
|
|
67
|
+
* no-op when absent.)
|
|
68
|
+
* 6. The entry has at least one non-terminal child. The branch
|
|
69
|
+
* enumerates children via direct-book read (bypassing `api.list`'s
|
|
70
|
+
* 20-row default) and skips already-terminal children using
|
|
71
|
+
* `isTerminal`.
|
|
72
|
+
*
|
|
73
|
+
* For each non-terminal child, the engine calls `api.transition` with the
|
|
74
|
+
* action's configured `transition` target and `resolution` string (or the
|
|
75
|
+
* child's own resolution when `copyResolution: true`). If a child cannot
|
|
76
|
+
* accept the configured target — typically a child-type declaring no
|
|
77
|
+
* transition into the configured state from the child's current state —
|
|
78
|
+
* `api.transition` throws and the Phase 1 transaction rolls back per the
|
|
79
|
+
* engine's existing fail-loud convention. Already-terminal children are
|
|
80
|
+
* skipped (idempotent on re-fire).
|
|
81
|
+
*
|
|
82
|
+
* Tripwire firing rule (in order — first-fail short-circuits):
|
|
83
|
+
*
|
|
84
|
+
* 1. Event is an `update`.
|
|
85
|
+
* 2. The entry's phase actually changed (`entry.phase !== prev.phase`).
|
|
86
|
+
* 3. The entry is in a terminal state carrying the `success` attr.
|
|
87
|
+
* 4. The entry's own writ-type is registered.
|
|
88
|
+
* 5. The entry's type declares a `childrenBehavior` block. (Silent
|
|
89
|
+
* no-op when absent — types that decline `childrenBehavior` have
|
|
90
|
+
* announced they do not couple parent and child outcomes.)
|
|
91
|
+
* 6. The entry has at least one non-terminal descendant (direct child
|
|
92
|
+
* OR deeper). Descendants are enumerated by walking the subtree
|
|
93
|
+
* through the writs book directly (bypassing `api.list`'s 20-row
|
|
94
|
+
* default), recursing through terminal nodes too — a terminal
|
|
95
|
+
* child can still hide a non-terminal grandchild when an upstream
|
|
96
|
+
* caller bypassed the cascade.
|
|
97
|
+
*
|
|
98
|
+
* On all six conditions, the tripwire throws a fail-loud error naming
|
|
99
|
+
* the offending entry id, the `success`-attr terminal state, and the
|
|
100
|
+
* non-terminal descendants. Phase 1 atomicity rolls the entry's
|
|
101
|
+
* transition back, so a parent cannot land in a `success`-attr terminal
|
|
102
|
+
* with open descendants — the gap is unrepresentable in the writs book.
|
|
103
|
+
* The branch reuses the same enumeration approach as the downward branch
|
|
104
|
+
* (direct-book read, type-aware `isTerminal` filtering) and stays
|
|
105
|
+
* idempotent under CDC re-fire: a re-fire with no phase change (rule 2)
|
|
106
|
+
* short-circuits before evaluation, and a re-fire with all descendants
|
|
107
|
+
* terminal is a no-op.
|
|
108
|
+
*
|
|
109
|
+
* Cascade ordering when both directions fire on the same chain (e.g. an
|
|
110
|
+
* upward `anyFailure` that lifts a parent into `failed`, which then
|
|
111
|
+
* needs to push down into the parent's other open siblings) is handled
|
|
112
|
+
* by natural CDC re-fire: the parent's own update event re-enters this
|
|
113
|
+
* handler and triggers the downward branch on the parent's now-terminal
|
|
114
|
+
* transition.
|
|
115
|
+
*
|
|
116
|
+
* When a trigger fires with `copyResolution: true`, the triggering
|
|
117
|
+
* writ's `resolution` string is copied onto the target as part of the
|
|
118
|
+
* transition. When a trigger fires with `resolution: '...'`, that static
|
|
119
|
+
* string is written onto every transitioned writ.
|
|
120
|
+
*
|
|
121
|
+
* On every upward fire, before the parent's transition is recorded, the
|
|
122
|
+
* engine publishes a structured record onto the parent's Clerk-owned
|
|
123
|
+
* status sub-slot (`status['clerk']`) containing the immediate triggering
|
|
124
|
+
* child's id. The write must precede the transition: downstream observers
|
|
125
|
+
* (notably the Reckoner) are CDC-driven from the terminal transition's
|
|
126
|
+
* `updatedAt`, and they read `status['clerk']` from the post-commit
|
|
127
|
+
* `entry` snapshot at that moment. Writing the slot *after* the transition
|
|
128
|
+
* would deliver the pulse against a snapshot that pre-dates the slot's
|
|
129
|
+
* existence and degrade the leaf-cause surface.
|
|
130
|
+
*
|
|
131
|
+
* The engine never writes to the writ document directly — every state
|
|
132
|
+
* change goes through `transition`, so allowedTransitions enforcement,
|
|
133
|
+
* terminal `resolvedAt` tagging, and CDC re-fire all behave identically
|
|
134
|
+
* to a direct caller.
|
|
135
|
+
*/
|
|
136
|
+
import type { Book, ChangeEvent } from '@shardworks/stacks-apparatus';
|
|
137
|
+
import type { WritDoc, WritPhase } from './types.ts';
|
|
138
|
+
import type { WritTypeConfig } from './writ-type-config.ts';
|
|
139
|
+
/**
|
|
140
|
+
* Dependencies the engine needs from the surrounding Clerk runtime.
|
|
141
|
+
*
|
|
142
|
+
* `writs` is the book the engine reads sibling lists from directly (the
|
|
143
|
+
* limited `api.list` default would silently truncate parents with >20
|
|
144
|
+
* children). `getWritTypeConfig` is the registry accessor.
|
|
145
|
+
* `isTerminal` is the writ-type-classification predicate.
|
|
146
|
+
* `transition` is the sole sanctioned phase-change surface.
|
|
147
|
+
* `setWritStatus` is the sanctioned slot-write path; the engine uses it
|
|
148
|
+
* to publish `status['clerk']` immediately before the parent's terminal
|
|
149
|
+
* transition.
|
|
150
|
+
*/
|
|
151
|
+
export interface ChildrenBehaviorEngineDeps {
|
|
152
|
+
writs: Book<WritDoc>;
|
|
153
|
+
getWritTypeConfig(name: string): WritTypeConfig | undefined;
|
|
154
|
+
isTerminal(writ: WritDoc): boolean;
|
|
155
|
+
transition(id: string, to: WritPhase, fields?: Partial<WritDoc>): Promise<WritDoc>;
|
|
156
|
+
setWritStatus(writId: string, pluginId: string, value: unknown): Promise<WritDoc>;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Build the engine handler. Returns an async function suitable for
|
|
160
|
+
* passing to `stacks.watch('clerk', 'writs', handler, { failOnError: true })`.
|
|
161
|
+
*/
|
|
162
|
+
export declare function createChildrenBehaviorEngine(deps: ChildrenBehaviorEngineDeps): (event: ChangeEvent<WritDoc>) => Promise<void>;
|
|
163
|
+
//# sourceMappingURL=children-behavior-engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"children-behavior-engine.d.ts","sourceRoot":"","sources":["../src/children-behavior-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsIG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAEtE,OAAO,KAAK,EAAmB,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEtE,OAAO,KAAK,EAEV,cAAc,EAEf,MAAM,uBAAuB,CAAC;AAE/B;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACrB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAAC;IAC5D,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;IACnC,UAAU,CACR,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,SAAS,EACb,MAAM,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GACxB,OAAO,CAAC,OAAO,CAAC,CAAC;IACpB,aAAa,CACX,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,0BAA0B,GAC/B,CAAC,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CA+OhD"}
|