@owlmeans/planning 0.1.18-rc.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 (139) hide show
  1. package/README.md +169 -0
  2. package/agent-meta/manifest.json +16 -0
  3. package/agent-meta/skills/planning/SKILL.md +248 -0
  4. package/build/consts.d.ts +126 -0
  5. package/build/consts.d.ts.map +1 -0
  6. package/build/consts.js +129 -0
  7. package/build/consts.js.map +1 -0
  8. package/build/entrypoints.d.ts +14 -0
  9. package/build/entrypoints.d.ts.map +1 -0
  10. package/build/entrypoints.js +69 -0
  11. package/build/entrypoints.js.map +1 -0
  12. package/build/errors.d.ts +94 -0
  13. package/build/errors.d.ts.map +1 -0
  14. package/build/errors.js +169 -0
  15. package/build/errors.js.map +1 -0
  16. package/build/helpers/apply.d.ts +27 -0
  17. package/build/helpers/apply.d.ts.map +1 -0
  18. package/build/helpers/apply.js +162 -0
  19. package/build/helpers/apply.js.map +1 -0
  20. package/build/helpers/card.d.ts +17 -0
  21. package/build/helpers/card.d.ts.map +1 -0
  22. package/build/helpers/card.js +31 -0
  23. package/build/helpers/card.js.map +1 -0
  24. package/build/helpers/changes.d.ts +51 -0
  25. package/build/helpers/changes.d.ts.map +1 -0
  26. package/build/helpers/changes.js +277 -0
  27. package/build/helpers/changes.js.map +1 -0
  28. package/build/helpers/code.d.ts +24 -0
  29. package/build/helpers/code.d.ts.map +1 -0
  30. package/build/helpers/code.js +47 -0
  31. package/build/helpers/code.js.map +1 -0
  32. package/build/helpers/index.d.ts +9 -0
  33. package/build/helpers/index.d.ts.map +1 -0
  34. package/build/helpers/index.js +9 -0
  35. package/build/helpers/index.js.map +1 -0
  36. package/build/helpers/query.d.ts +39 -0
  37. package/build/helpers/query.d.ts.map +1 -0
  38. package/build/helpers/query.js +293 -0
  39. package/build/helpers/query.js.map +1 -0
  40. package/build/helpers/specification.d.ts +21 -0
  41. package/build/helpers/specification.d.ts.map +1 -0
  42. package/build/helpers/specification.js +48 -0
  43. package/build/helpers/specification.js.map +1 -0
  44. package/build/helpers/status.d.ts +44 -0
  45. package/build/helpers/status.d.ts.map +1 -0
  46. package/build/helpers/status.js +78 -0
  47. package/build/helpers/status.js.map +1 -0
  48. package/build/helpers/validate.d.ts +30 -0
  49. package/build/helpers/validate.d.ts.map +1 -0
  50. package/build/helpers/validate.js +93 -0
  51. package/build/helpers/validate.js.map +1 -0
  52. package/build/i18n/be.json +51 -0
  53. package/build/i18n/de.json +51 -0
  54. package/build/i18n/en.json +51 -0
  55. package/build/i18n/es.json +51 -0
  56. package/build/i18n/pl.json +51 -0
  57. package/build/i18n/ru.json +51 -0
  58. package/build/i18n/uk.json +51 -0
  59. package/build/i18n.d.ts +2 -0
  60. package/build/i18n.d.ts.map +1 -0
  61. package/build/i18n.js +19 -0
  62. package/build/i18n.js.map +1 -0
  63. package/build/index.d.ts +10 -0
  64. package/build/index.d.ts.map +1 -0
  65. package/build/index.js +9 -0
  66. package/build/index.js.map +1 -0
  67. package/build/models/index.d.ts +7 -0
  68. package/build/models/index.d.ts.map +1 -0
  69. package/build/models/index.js +19 -0
  70. package/build/models/index.js.map +1 -0
  71. package/build/models/project.d.ts +4 -0
  72. package/build/models/project.d.ts.map +1 -0
  73. package/build/models/project.js +17 -0
  74. package/build/models/project.js.map +1 -0
  75. package/build/models/specification.d.ts +4 -0
  76. package/build/models/specification.d.ts.map +1 -0
  77. package/build/models/specification.js +15 -0
  78. package/build/models/specification.js.map +1 -0
  79. package/build/models/workcard.d.ts +14 -0
  80. package/build/models/workcard.d.ts.map +1 -0
  81. package/build/models/workcard.js +103 -0
  82. package/build/models/workcard.js.map +1 -0
  83. package/build/queue/index.d.ts +25 -0
  84. package/build/queue/index.d.ts.map +1 -0
  85. package/build/queue/index.js +14 -0
  86. package/build/queue/index.js.map +1 -0
  87. package/build/registry.d.ts +10 -0
  88. package/build/registry.d.ts.map +1 -0
  89. package/build/registry.js +75 -0
  90. package/build/registry.js.map +1 -0
  91. package/build/schemas.d.ts +56 -0
  92. package/build/schemas.d.ts.map +1 -0
  93. package/build/schemas.js +578 -0
  94. package/build/schemas.js.map +1 -0
  95. package/build/types.d.ts +785 -0
  96. package/build/types.d.ts.map +1 -0
  97. package/build/types.js +2 -0
  98. package/build/types.js.map +1 -0
  99. package/package.json +58 -0
  100. package/src/consts.ts +136 -0
  101. package/src/entrypoints.ts +127 -0
  102. package/src/errors.ts +207 -0
  103. package/src/helpers/apply.ts +183 -0
  104. package/src/helpers/card.ts +42 -0
  105. package/src/helpers/changes.ts +333 -0
  106. package/src/helpers/code.ts +66 -0
  107. package/src/helpers/index.ts +8 -0
  108. package/src/helpers/query.ts +345 -0
  109. package/src/helpers/specification.ts +65 -0
  110. package/src/helpers/status.ts +117 -0
  111. package/src/helpers/validate.ts +107 -0
  112. package/src/i18n/be.json +51 -0
  113. package/src/i18n/de.json +51 -0
  114. package/src/i18n/en.json +51 -0
  115. package/src/i18n/es.json +51 -0
  116. package/src/i18n/pl.json +51 -0
  117. package/src/i18n/ru.json +51 -0
  118. package/src/i18n/uk.json +51 -0
  119. package/src/i18n.ts +21 -0
  120. package/src/index.ts +9 -0
  121. package/src/models/index.ts +21 -0
  122. package/src/models/project.ts +24 -0
  123. package/src/models/specification.ts +21 -0
  124. package/src/models/workcard.ts +138 -0
  125. package/src/queue/index.ts +31 -0
  126. package/src/registry.ts +90 -0
  127. package/src/schemas.ts +654 -0
  128. package/src/types.ts +837 -0
  129. package/tests/apply.spec.ts +176 -0
  130. package/tests/code.spec.ts +40 -0
  131. package/tests/entrypoints.spec.ts +57 -0
  132. package/tests/fixtures.ts +124 -0
  133. package/tests/i18n.spec.ts +30 -0
  134. package/tests/models.spec.ts +133 -0
  135. package/tests/query.spec.ts +90 -0
  136. package/tests/schemas.spec.ts +74 -0
  137. package/tests/status.spec.ts +75 -0
  138. package/tests/tsconfig.json +17 -0
  139. package/tsconfig.json +15 -0
package/src/types.ts ADDED
@@ -0,0 +1,837 @@
1
+ import type { BasicConfig, BasicContext } from '@owlmeans/context'
2
+ import type { EntrypointProtocol, OpenRequest, OpenValue } from '@owlmeans/entrypoint'
3
+ import type { Criteria, ListOptions, ListResult, ResourceRecord } from '@owlmeans/resource'
4
+ import type { RouteParent } from '@owlmeans/route'
5
+ import type { AnySchema, ValidateFunction } from 'ajv'
6
+ import type {
7
+ CodeScope, CodeStyle, CommitState, IntrinsicPolicy, IntrinsicStatus, SpecificationFormat,
8
+ TransitionAction, WorkcardKind,
9
+ } from './consts.js'
10
+
11
+ // ─── Records ─────────────────────────────────────────────────────────────────────────────────────
12
+
13
+ /**
14
+ * The super type. Base fields live at the top level; everything a provider's type declares lives in
15
+ * `fields` and is validated by that type's `fields` JSON schema. Generic code — lists, boards,
16
+ * gates, the executor — reads only the top level. Timestamps are ISO-8601 UTC strings.
17
+ */
18
+ export interface Workcard extends ResourceRecord {
19
+ id?: string
20
+ kind: WorkcardKind
21
+ /** A registered type key (`viable:user-story`). */
22
+ type: string
23
+ /** Organization entity (tenant) — the stable id, never a slug. */
24
+ entityId: string
25
+ code?: string
26
+ title: string
27
+ description?: string
28
+ /** Primary parent id. Absent on a root project. */
29
+ parent?: string
30
+ /** EVERY parent — many-to-many membership with no junction row. Always contains `parent`. */
31
+ parents: string[]
32
+ /** Status key of the PRIMARY flow. Mirror of `flows[primaryFlow]`. */
33
+ status: string
34
+ /** Mirror of the type's intrinsic resolution over `flows`. */
35
+ intrinsic: IntrinsicStatus
36
+ /** Status per flow the type declares, INCLUDING the primary one. Authoritative. */
37
+ flows: Record<string, string>
38
+ labels: string[]
39
+ /** A double — a card may sit between two others (`3.5`). */
40
+ order?: number
41
+ /** Type-declared fields. Keys may not contain `.` or `$`. */
42
+ fields: Record<string, unknown>
43
+ /** Sequence of the last transition FOLDED into this record. */
44
+ seq: number
45
+ /** Highest sequence ALLOCATED. `head > seq` means a transition is in flight. */
46
+ head?: number
47
+ createdBy?: string
48
+ createdAt: string
49
+ updatedAt?: string
50
+ closedAt?: string
51
+ }
52
+
53
+ export interface Project extends Workcard {
54
+ kind: WorkcardKind.Project
55
+ }
56
+
57
+ export interface Card extends Workcard {
58
+ kind: WorkcardKind.Card
59
+ }
60
+
61
+ /**
62
+ * A document attached to a parent card through a slot the PARENT's type declares. One record per
63
+ * `(parent, category)` unless the slot is `multiple`; a `revisioned` slot increments `revision` in
64
+ * place and earlier bodies are read back from the transition log.
65
+ */
66
+ export interface Specification extends Workcard {
67
+ kind: WorkcardKind.Specification
68
+ category: string
69
+ format: SpecificationFormat
70
+ body?: string
71
+ ref?: string
72
+ revision?: number
73
+ version?: number
74
+ bodyChars?: number
75
+ }
76
+
77
+ export type AnyWorkcard = Card | Project | Specification
78
+
79
+ /** A typed many-to-many edge. Membership is `parent`/`parents`, never a relationship. */
80
+ export interface Relationship extends ResourceRecord {
81
+ id?: string
82
+ entityId: string
83
+ type: string
84
+ from: string
85
+ to: string
86
+ project?: string
87
+ fields?: Record<string, unknown>
88
+ createdAt: string
89
+ /** The transition that created it. */
90
+ transition?: string
91
+ }
92
+
93
+ export interface RelationshipDraft {
94
+ type: string
95
+ /** Defaults to the card the transition is about. */
96
+ from?: string
97
+ to: string
98
+ fields?: Record<string, unknown>
99
+ }
100
+
101
+ /** Who wrote a transition. Filled by the server from the authenticated request — never the wire. */
102
+ export interface TransitionActor {
103
+ profileId?: string
104
+ userId?: string
105
+ service?: string
106
+ agent?: string
107
+ runId?: string
108
+ /** Where the write came from (`web`, `connect`, `agent` …) — a provider's own vocabulary. */
109
+ channel?: string
110
+ }
111
+
112
+ export interface TransitionCommit {
113
+ state: CommitState
114
+ at?: string
115
+ error?: string
116
+ }
117
+
118
+ /**
119
+ * The values a transition writes.
120
+ *
121
+ * Every key carries a NEW VALUE, never a delta. `fields` and `flows` merge shallowly into the
122
+ * card's; every other key replaces. Clearing is `Transition.unset`.
123
+ */
124
+ export type WorkcardChanges =
125
+ & Partial<Omit<Workcard, 'id' | 'kind' | 'type' | 'entityId' | 'seq' | 'head' | 'createdAt'>>
126
+ & Partial<Pick<Specification, 'category' | 'format' | 'body' | 'ref' | 'revision' | 'version' | 'bodyChars'>>
127
+
128
+ /** The event. Append-only, never edited; folding every transition of a card in `seq` order IS the card. */
129
+ export interface Transition extends ResourceRecord {
130
+ id?: string
131
+ entityId: string
132
+ card: string
133
+ kind: WorkcardKind
134
+ type: string
135
+ /** The project the card belongs to — a project's OWN id on its own rows. */
136
+ project?: string
137
+ /** 1 is the create. */
138
+ seq: number
139
+ action: TransitionAction
140
+ flow?: string
141
+ transition?: string
142
+ from?: string
143
+ to?: string
144
+ changes: WorkcardChanges
145
+ /** Top-level field names, or `fields.<key>` / `flows.<id>`, to clear. */
146
+ unset?: string[]
147
+ link?: RelationshipDraft
148
+ /** Relationships created together with the card (create only). */
149
+ links?: RelationshipDraft[]
150
+ actor: TransitionActor
151
+ cause?: string
152
+ /** Idempotency key, unique per entity. */
153
+ key?: string
154
+ at: string
155
+ commit: TransitionCommit
156
+ }
157
+
158
+ // ─── Type and flow schemas (data, not code) ──────────────────────────────────────────────────────
159
+
160
+ export interface StatusDefinition {
161
+ key: string
162
+ intrinsic: IntrinsicStatus
163
+ initial?: boolean
164
+ terminal?: boolean
165
+ label?: string
166
+ tone?: string
167
+ }
168
+
169
+ /**
170
+ * One named move. A name may repeat with different `from` sets (`start` from `planned` and from
171
+ * `failed`); `'*'` matches every status and is consulted last.
172
+ */
173
+ export interface StatusTransitionRule {
174
+ name: string
175
+ from: string[] | '*'
176
+ to: string
177
+ label?: string
178
+ /** Offered to a person as an action. */
179
+ explicit?: boolean
180
+ }
181
+
182
+ /** Shareable across types; a type may run several. */
183
+ export interface StatusFlowSchema {
184
+ id: string
185
+ version: number
186
+ statuses: StatusDefinition[]
187
+ transitions: StatusTransitionRule[]
188
+ label?: string
189
+ }
190
+
191
+ export interface SpecificationSlot {
192
+ category: string
193
+ format: SpecificationFormat
194
+ required?: boolean
195
+ /** Several documents of this category under one parent. */
196
+ multiple?: boolean
197
+ /** An update increments `revision` in place. */
198
+ revisioned?: boolean
199
+ /** The minimum history depth `revisions()` must answer. */
200
+ keepRevisions?: number
201
+ /** A JSON slot's body schema. */
202
+ schema?: AnySchema
203
+ version?: number
204
+ label?: string
205
+ /** The specification type records of this slot use; the sole registered one when omitted. */
206
+ type?: string
207
+ }
208
+
209
+ export interface CodePolicy {
210
+ prefix?: string
211
+ style: CodeStyle
212
+ length?: number
213
+ uppercase?: boolean
214
+ uniqueWithin: CodeScope
215
+ mutable?: boolean
216
+ }
217
+
218
+ export interface RelationshipType {
219
+ name: string
220
+ /** Card types allowed at the `from` end. Any when omitted. */
221
+ from?: string[]
222
+ /** Card types allowed at the `to` end. Any when omitted. */
223
+ to?: string[]
224
+ inverse?: string
225
+ /** At most one relationship of this type leaves a card. */
226
+ single?: boolean
227
+ label?: string
228
+ }
229
+
230
+ export interface WorkcardTypeSchema {
231
+ type: string
232
+ kind: WorkcardKind.Card | WorkcardKind.Specification
233
+ version: number
234
+ /** JSON schema of `fields`. */
235
+ fields: AnySchema
236
+ /** Flow ids; `[0]` is the primary flow. */
237
+ flows: string[]
238
+ intrinsic?: IntrinsicPolicy
239
+ /** Slots the type's cards carry as children. */
240
+ specifications: SpecificationSlot[]
241
+ relationships?: RelationshipType[]
242
+ /** Allowed labels. Any when omitted. */
243
+ labels?: string[]
244
+ code?: CodePolicy
245
+ label?: string
246
+ }
247
+
248
+ export interface ProjectTypeSchema extends Omit<WorkcardTypeSchema, 'kind'> {
249
+ kind: WorkcardKind.Project
250
+ cardTypes: string[]
251
+ projectTypes?: string[]
252
+ }
253
+
254
+ export type AnyTypeSchema = WorkcardTypeSchema | ProjectTypeSchema
255
+
256
+ export interface PlanningSchemaBundle {
257
+ version: number
258
+ types: AnyTypeSchema[]
259
+ flows: StatusFlowSchema[]
260
+ }
261
+
262
+ export interface PlanningSchemaRegistry {
263
+ registerType: (schema: AnyTypeSchema) => void
264
+ registerFlow: (flow: StatusFlowSchema) => void
265
+ types: () => AnyTypeSchema[]
266
+ /** @throws {UnknownWorkcardType} */
267
+ type: (type: string) => AnyTypeSchema
268
+ has: (type: string) => boolean
269
+ flows: () => StatusFlowSchema[]
270
+ /** @throws {UnknownStatusFlow} */
271
+ flow: (id: string) => StatusFlowSchema
272
+ /** @throws {UnknownWorkcardType | UnknownStatusFlow} */
273
+ primaryFlow: (type: string) => StatusFlowSchema
274
+ /** The compiled, cached validator of a type's `fields`. */
275
+ validator: (type: string) => ValidateFunction
276
+ bundle: () => PlanningSchemaBundle
277
+ /** Replace everything with a bundle — a client's boot. */
278
+ load: (bundle: PlanningSchemaBundle) => void
279
+ }
280
+
281
+ // ─── Execution and receipts ──────────────────────────────────────────────────────────────────────
282
+
283
+ export interface WorkcardDraft {
284
+ kind: WorkcardKind
285
+ type: string
286
+ parent?: string
287
+ parents?: string[]
288
+ title: string
289
+ description?: string
290
+ code?: string
291
+ labels?: string[]
292
+ order?: number
293
+ fields?: Record<string, unknown>
294
+ /** Initial status of the primary flow; the flow's initial status when omitted. */
295
+ status?: string
296
+ createdBy?: string
297
+ category?: string
298
+ format?: SpecificationFormat
299
+ body?: string
300
+ ref?: string
301
+ version?: number
302
+ }
303
+
304
+ export interface TransitionExecution {
305
+ /** A card id, or the draft of the card a `create` makes. */
306
+ card: string | WorkcardDraft
307
+ action: TransitionAction
308
+ transition?: string
309
+ flow?: string
310
+ changes?: WorkcardChanges
311
+ unset?: string[]
312
+ link?: RelationshipDraft
313
+ links?: RelationshipDraft[]
314
+ /** Ignored on the wire — the server fills it from the request. */
315
+ actor?: TransitionActor
316
+ cause?: string
317
+ key?: string
318
+ /**
319
+ * Optimistic concurrency against the card's HEAD. A model fills it from its record unless given
320
+ * `null`, which opts out.
321
+ */
322
+ expectSeq?: number | null
323
+ }
324
+
325
+ export interface ExecuteOptions {
326
+ wait?: boolean
327
+ timeout?: number
328
+ }
329
+
330
+ export interface ExecuteRequest extends TransitionExecution, ExecuteOptions { }
331
+
332
+ export interface TransitionReceipt {
333
+ transition: Transition
334
+ /** Present when the commit already landed; `null` for a committed delete. */
335
+ card?: Workcard | null
336
+ /** @throws {CommitFailed | CommitTimeout} */
337
+ committed: (opts?: { timeout?: number }) => Promise<Workcard | null>
338
+ }
339
+
340
+ export interface TransitionReceiptView {
341
+ transition: Transition
342
+ card?: Workcard | null
343
+ }
344
+
345
+ // ─── Commits ─────────────────────────────────────────────────────────────────────────────────────
346
+
347
+ /** On a cross-process bus it carries ids only; `record` is filled where a subscriber needs it. */
348
+ export interface CommitEvent {
349
+ transition: string
350
+ card: string
351
+ entityId: string
352
+ project?: string
353
+ kind: WorkcardKind
354
+ type: string
355
+ seq: number
356
+ action: TransitionAction
357
+ state: CommitState
358
+ at: string
359
+ error?: string
360
+ record?: Workcard | null
361
+ }
362
+
363
+ export interface CommitStatus {
364
+ transition: string
365
+ state: CommitState
366
+ at?: string
367
+ error?: string
368
+ card?: Workcard | null
369
+ }
370
+
371
+ export interface CommitFilter {
372
+ entityId?: string
373
+ project?: string
374
+ card?: string
375
+ kind?: WorkcardKind
376
+ }
377
+
378
+ export type Unsubscribe = () => void
379
+
380
+ export interface CommitSource {
381
+ status: (transition: string) => Promise<CommitStatus>
382
+ subscribe: (listener: (event: CommitEvent) => void | Promise<void>, filter?: CommitFilter) => Unsubscribe | Promise<Unsubscribe>
383
+ /** @throws {CommitFailed | CommitTimeout} */
384
+ wait: (transition: string, opts?: { timeout?: number }) => Promise<Workcard | null>
385
+ }
386
+
387
+ // ─── Ports ───────────────────────────────────────────────────────────────────────────────────────
388
+
389
+ export interface TransitionWhere {
390
+ entityId: string
391
+ card?: string | string[]
392
+ project?: string
393
+ sinceSeq?: number
394
+ state?: CommitState
395
+ action?: TransitionAction | TransitionAction[]
396
+ }
397
+
398
+ export interface TransitionStore {
399
+ append: (transition: Transition) => Promise<Transition>
400
+ get: (id: string) => Promise<Transition | null>
401
+ byKey: (entityId: string, key: string) => Promise<Transition | null>
402
+ list: (where: TransitionWhere, opts?: ListOptions<Transition>) => Promise<ListResult<Transition>>
403
+ /** Allocates the next seq with a CAS against the head. @throws {WorkcardConflict} */
404
+ nextSeq: (card: string, expect?: number | null) => Promise<number>
405
+ head: (card: string) => Promise<number>
406
+ commit: (id: string, commit: TransitionCommit) => Promise<void>
407
+ purge: (where: TransitionWhere) => Promise<number>
408
+ }
409
+
410
+ export interface IntrinsicCounts extends Record<IntrinsicStatus, number> {
411
+ total: number
412
+ }
413
+
414
+ /** Counts of DIRECT children per parent. A parent with no children has no key. */
415
+ export interface SummaryView {
416
+ [parent: string]: IntrinsicCounts
417
+ }
418
+
419
+ export interface ProjectionStore {
420
+ get: (id: string, entityId: string) => Promise<Workcard | null>
421
+ list: (where: Criteria<Workcard>, opts?: ListOptions<Workcard>) => Promise<ListResult<Workcard>>
422
+ count: (where: Criteria<Workcard>) => Promise<number>
423
+ summary: (parents: string[], where?: Criteria<Workcard>) => Promise<SummaryView>
424
+ put: (card: Workcard) => Promise<void>
425
+ drop: (id: string, entityId: string) => Promise<void>
426
+ /** Fold what is pending for the card — now (a sync store) or queued (a durable one). */
427
+ project: (card: string, hint?: { transition?: string }) => Promise<void>
428
+ /** Remove a project and everything under it. */
429
+ purge: (project: string, entityId: string) => Promise<number>
430
+ }
431
+
432
+ export interface SpecificationRevision {
433
+ revision: number
434
+ body?: string
435
+ ref?: string
436
+ bodyChars?: number
437
+ version?: number
438
+ at: string
439
+ by?: TransitionActor
440
+ transition: string
441
+ }
442
+
443
+ export interface SpecificationRevisionList {
444
+ items: SpecificationRevision[]
445
+ }
446
+
447
+ export interface SpecificationStore {
448
+ current: (parent: string, category: string, entityId: string) => Promise<Specification | null>
449
+ list: (parent: string, entityId: string, query?: SpecificationQuery) => Promise<ListResult<Specification>>
450
+ revisions: (id: string, entityId: string, limit?: number) => Promise<SpecificationRevision[]>
451
+ }
452
+
453
+ export interface RelationshipWhere {
454
+ entityId: string
455
+ id?: string
456
+ from?: string | string[]
457
+ to?: string | string[]
458
+ type?: string | string[]
459
+ }
460
+
461
+ export interface RelationshipStore {
462
+ list: (where: RelationshipWhere, opts?: ListOptions<Relationship>) => Promise<ListResult<Relationship>>
463
+ put: (link: Relationship) => Promise<Relationship>
464
+ drop: (where: RelationshipWhere) => Promise<number>
465
+ }
466
+
467
+ export interface PlanningStoreCapabilities {
468
+ transitions: boolean
469
+ sync: boolean
470
+ purge: boolean
471
+ revisions: boolean
472
+ }
473
+
474
+ /** Only `cards` is required — which is what keeps a foreign provider possible. */
475
+ export interface PlanningStore {
476
+ alias?: string
477
+ capabilities?: PlanningStoreCapabilities
478
+ /** The id a new card gets. */
479
+ newId?: () => string
480
+ transitions?: TransitionStore
481
+ cards: ProjectionStore
482
+ specs?: SpecificationStore
483
+ links?: RelationshipStore
484
+ commits?: CommitSource
485
+ }
486
+
487
+ // ─── Queries ─────────────────────────────────────────────────────────────────────────────────────
488
+
489
+ export interface WorkcardQuery extends ListOptions<Workcard> {
490
+ kind?: WorkcardKind | WorkcardKind[]
491
+ type?: string | string[]
492
+ /** Direct children only. */
493
+ parent?: string
494
+ /** Membership anywhere — matched against `parents`. */
495
+ within?: string
496
+ status?: string | string[]
497
+ intrinsic?: IntrinsicStatus | IntrinsicStatus[]
498
+ flow?: { id: string, status: string | string[] }
499
+ /** Any of. */
500
+ labels?: string[]
501
+ code?: string | string[]
502
+ ids?: string[]
503
+ /** Equality per key, read as `fields.<key>`. */
504
+ fields?: Record<string, unknown>
505
+ /** Title or description contains, or code starts with. */
506
+ q?: string
507
+ category?: string | string[]
508
+ updatedSince?: string
509
+ }
510
+
511
+ export interface SpecificationQuery extends ListOptions<Specification> {
512
+ category?: string | string[]
513
+ /** Every document of the slot, not only the current one. */
514
+ all?: boolean
515
+ }
516
+
517
+ export interface RelationshipQuery extends ListOptions<Relationship> {
518
+ from?: string | string[]
519
+ to?: string | string[]
520
+ type?: string | string[]
521
+ }
522
+
523
+ export interface TransitionQuery extends ListOptions<Transition> {
524
+ card?: string
525
+ project?: string
526
+ sinceSeq?: number
527
+ action?: TransitionAction | TransitionAction[]
528
+ state?: CommitState
529
+ }
530
+
531
+ export interface SummaryQuery {
532
+ parents: string[]
533
+ kind?: WorkcardKind
534
+ type?: string | string[]
535
+ }
536
+
537
+ /**
538
+ * The query objects as they travel in a URL.
539
+ *
540
+ * A query string carries neither arrays nor nested objects through the OwlMeans transport (axios
541
+ * writes `key[]=`, fastify reads that key literally), so every value is a scalar: a list is
542
+ * comma-joined (JSON when an element holds a comma), an object is JSON, a sort is
543
+ * `field,-field`. `encode*Query` writes this shape, `decode*Query` reads it back and also accepts
544
+ * the rich form.
545
+ */
546
+ export interface ListWire {
547
+ page?: number
548
+ size?: number
549
+ sort?: string
550
+ }
551
+
552
+ export interface WorkcardQueryWire extends ListWire {
553
+ kind?: string
554
+ type?: string
555
+ parent?: string
556
+ within?: string
557
+ status?: string
558
+ intrinsic?: string
559
+ flow?: string
560
+ labels?: string
561
+ code?: string
562
+ ids?: string
563
+ fields?: string
564
+ q?: string
565
+ category?: string
566
+ updatedSince?: string
567
+ }
568
+
569
+ export interface SpecificationQueryWire extends ListWire {
570
+ category?: string
571
+ all?: boolean
572
+ }
573
+
574
+ export interface RelationshipQueryWire extends ListWire {
575
+ from?: string
576
+ to?: string
577
+ type?: string
578
+ }
579
+
580
+ export interface TransitionQueryWire extends ListWire {
581
+ card?: string
582
+ project?: string
583
+ sinceSeq?: number
584
+ action?: string
585
+ state?: string
586
+ }
587
+
588
+ export interface SummaryQueryWire {
589
+ parents: string
590
+ kind?: string
591
+ type?: string
592
+ }
593
+
594
+ export interface WorkcardParams {
595
+ id: string
596
+ }
597
+
598
+ export interface TransitionParams {
599
+ transition: string
600
+ }
601
+
602
+ export interface CommitQuery {
603
+ /** Seconds to hold the poll; clamped by the server. */
604
+ wait?: number
605
+ }
606
+
607
+ export interface CommitFeedQuery {
608
+ project?: string
609
+ card?: string
610
+ }
611
+
612
+ export interface RevisionsQuery {
613
+ limit?: number
614
+ }
615
+
616
+ // ─── Facade, scope, service, plugin seam ─────────────────────────────────────────────────────────
617
+
618
+ export interface PlanningScope {
619
+ entityId: string
620
+ profileId?: string
621
+ userId?: string
622
+ service?: string
623
+ channel?: string
624
+ actor?: TransitionActor
625
+ }
626
+
627
+ export interface PlanningFacade {
628
+ scope: PlanningScope
629
+ schemas: PlanningSchemaRegistry
630
+ cards: {
631
+ /** @throws {WorkcardNotFound} */
632
+ get: (id: string) => Promise<Workcard>
633
+ load: (id: string) => Promise<Workcard | null>
634
+ list: (query?: WorkcardQuery) => Promise<ListResult<Workcard>>
635
+ count: (query?: WorkcardQuery) => Promise<number>
636
+ summary: (parents: string[], query?: Omit<SummaryQuery, 'parents'>) => Promise<SummaryView>
637
+ }
638
+ specifications: {
639
+ current: (parent: string, category: string) => Promise<Specification | null>
640
+ list: (parent: string, query?: SpecificationQuery) => Promise<ListResult<Specification>>
641
+ /** @throws {WorkcardNotFound} */
642
+ get: (id: string) => Promise<Specification>
643
+ revisions: (id: string, limit?: number) => Promise<SpecificationRevision[]>
644
+ }
645
+ relationships: {
646
+ list: (query?: RelationshipQuery) => Promise<ListResult<Relationship>>
647
+ }
648
+ transitions: {
649
+ get: (id: string) => Promise<Transition>
650
+ list: (query: TransitionQuery) => Promise<ListResult<Transition>>
651
+ }
652
+ commits: CommitSource
653
+ execute: (exec: TransitionExecution, opts?: ExecuteOptions) => Promise<TransitionReceipt>
654
+ model: <T extends Workcard = Workcard>(card: T | string) => Promise<WorkcardModel<T>>
655
+ }
656
+
657
+ export interface WithPlanningService {
658
+ planning: () => PlanningService
659
+ }
660
+
661
+ export interface PlanningExecContext {
662
+ context: BasicContext<BasicConfig>
663
+ scope: PlanningScope
664
+ schemas: PlanningSchemaRegistry
665
+ store: PlanningStore
666
+ /** So a middleware can read (count what is in progress, and so on). */
667
+ facade: PlanningFacade
668
+ card?: Workcard
669
+ parent?: Workcard
670
+ type?: AnyTypeSchema
671
+ flow?: StatusFlowSchema
672
+ plugin: PlanningPlugin
673
+ }
674
+
675
+ export interface PlanningHookContext {
676
+ context: BasicContext<BasicConfig>
677
+ schemas: PlanningSchemaRegistry
678
+ store: PlanningStore
679
+ plugin: PlanningPlugin
680
+ /** A facade scoped to the event's entity, acting as the service itself. */
681
+ facade: PlanningFacade
682
+ transition?: Transition
683
+ }
684
+
685
+ export interface PlanningMiddleware {
686
+ (exec: TransitionExecution, ctx: PlanningExecContext): Promise<TransitionExecution | void>
687
+ }
688
+
689
+ export interface PlanningCommitHook {
690
+ (event: CommitEvent, ctx: PlanningHookContext): Promise<void>
691
+ }
692
+
693
+ export interface PlanningMappers {
694
+ toRecord?: (external: unknown, type: string) => Workcard
695
+ fromRecord?: (card: Workcard) => unknown
696
+ }
697
+
698
+ export interface PlanningPlugin {
699
+ name: string
700
+ /** Ascending; `DEFAULT_PLUGIN_ORDER` (50) when omitted. */
701
+ order?: number
702
+ schemas?: { types?: AnyTypeSchema[], flows?: StatusFlowSchema[] }
703
+ owns?: (type: string) => boolean
704
+ store?: PlanningStore | ((ctx: BasicContext<BasicConfig>) => PlanningStore)
705
+ mintCode?: (draft: WorkcardDraft, taken: (code: string) => Promise<boolean>, ctx: PlanningExecContext) => Promise<string | undefined>
706
+ before?: PlanningMiddleware
707
+ after?: PlanningCommitHook
708
+ mappers?: PlanningMappers
709
+ }
710
+
711
+ export interface PlanningService {
712
+ use: (plugin: PlanningPlugin) => void
713
+ plugins: () => PlanningPlugin[]
714
+ schemas: PlanningSchemaRegistry
715
+ store: (type?: string) => PlanningStore
716
+ for: (scope: PlanningScope) => PlanningFacade
717
+ /** Runs the ordered `after` chain — called by the process that FOLDED the transition. */
718
+ committed: (event: CommitEvent) => Promise<void>
719
+ }
720
+
721
+ // ─── Models ──────────────────────────────────────────────────────────────────────────────────────
722
+
723
+ export interface ExecuteMeta {
724
+ actor?: TransitionActor
725
+ cause?: string
726
+ key?: string
727
+ expectSeq?: number | null
728
+ }
729
+
730
+ export type ModelExecuteOptions = ExecuteOptions & ExecuteMeta
731
+
732
+ export interface SpecificationWriteOptions extends ModelExecuteOptions {
733
+ format?: SpecificationFormat
734
+ version?: number
735
+ ref?: string
736
+ /** The title a new document gets; the category when omitted. */
737
+ title?: string
738
+ /** The specification type; resolved from the slot and the registry when omitted. */
739
+ type?: string
740
+ }
741
+
742
+ export interface WorkcardModel<T extends Workcard = Workcard> {
743
+ record: T
744
+ id: string
745
+ kind: WorkcardKind
746
+ type: string
747
+ schema: () => AnyTypeSchema
748
+ flow: (id?: string) => StatusFlowSchema
749
+ statusOf: (flowId?: string) => string
750
+ intrinsicOf: (flowId?: string) => IntrinsicStatus
751
+ can: (transition: string, flowId?: string) => boolean
752
+ available: (flowId?: string) => StatusTransitionRule[]
753
+ pending: () => boolean
754
+ transit: (transition: string, changes?: WorkcardChanges, opts?: ModelExecuteOptions & { flow?: string }) => Promise<TransitionReceipt>
755
+ update: (changes: WorkcardChanges, opts?: ModelExecuteOptions & { unset?: string[] }) => Promise<TransitionReceipt>
756
+ remove: (opts?: ModelExecuteOptions) => Promise<TransitionReceipt>
757
+ link: (type: string, to: string, fields?: Record<string, unknown>, opts?: ModelExecuteOptions) => Promise<TransitionReceipt>
758
+ unlink: (type: string, to: string, opts?: ModelExecuteOptions) => Promise<TransitionReceipt>
759
+ children: (query?: Omit<WorkcardQuery, 'parent'>) => Promise<ListResult<Workcard>>
760
+ relationships: (query?: RelationshipQuery) => Promise<ListResult<Relationship>>
761
+ transitions: (query?: Omit<TransitionQuery, 'card'>) => Promise<ListResult<Transition>>
762
+ specification: (category: string) => Promise<Specification | null>
763
+ specifications: (query?: SpecificationQuery) => Promise<ListResult<Specification>>
764
+ revisions: (category: string, limit?: number) => Promise<SpecificationRevision[]>
765
+ /** Create the slot's document, or revise it when it already exists. */
766
+ write: (category: string, body: string, opts?: SpecificationWriteOptions) => Promise<TransitionReceipt>
767
+ reload: () => Promise<WorkcardModel<T>>
768
+ }
769
+
770
+ export interface ProjectModel extends WorkcardModel<Project> {
771
+ cards: (query?: Omit<WorkcardQuery, 'within' | 'kind'>) => Promise<ListResult<Workcard>>
772
+ projects: (query?: Omit<WorkcardQuery, 'within' | 'kind'>) => Promise<ListResult<Workcard>>
773
+ summary: (query?: Omit<SummaryQuery, 'parents'>) => Promise<IntrinsicCounts>
774
+ /** A `delete` of the project — the store removes everything under it. */
775
+ purge: (opts?: ModelExecuteOptions) => Promise<TransitionReceipt>
776
+ reload: () => Promise<ProjectModel>
777
+ }
778
+
779
+ export interface SpecificationModel extends WorkcardModel<Specification> {
780
+ body: () => string | undefined
781
+ revise: (body: string, opts?: ModelExecuteOptions & { version?: number, ref?: string }) => Promise<TransitionReceipt>
782
+ history: (limit?: number) => Promise<SpecificationRevision[]>
783
+ reload: () => Promise<SpecificationModel>
784
+ }
785
+
786
+ // ─── Protocol tree ───────────────────────────────────────────────────────────────────────────────
787
+
788
+ export interface PlanningBaseOptions {
789
+ /** The base alias every leaf alias derives from (`planningAliases`). */
790
+ alias: string
791
+ /** Defaults to `PLANNING_PATH`. */
792
+ path?: string
793
+ parent?: RouteParent
794
+ service?: string
795
+ }
796
+
797
+ export interface PlanningProtocolOptions {
798
+ base: PlanningBaseOptions
799
+ /** The guard(s) the base carries; every HTTP leaf inherits them. */
800
+ guards: string | readonly string[]
801
+ gate?: { alias: string, params?: string | readonly string[] }
802
+ /**
803
+ * The socket base the commit feed hangs under. The feed inherits THAT base's guards; the
804
+ * planning base when omitted.
805
+ */
806
+ socketBase?: RouteParent
807
+ }
808
+
809
+ export interface PlanningProtocols {
810
+ base: EntrypointProtocol<OpenRequest, OpenValue>
811
+ schema: {
812
+ list: EntrypointProtocol<{}, PlanningSchemaBundle>
813
+ }
814
+ card: {
815
+ list: EntrypointProtocol<{ query: WorkcardQueryWire }, ListResult<Workcard>>
816
+ summary: EntrypointProtocol<{ query: SummaryQueryWire }, SummaryView>
817
+ get: EntrypointProtocol<{ params: WorkcardParams }, Workcard>
818
+ transitions: EntrypointProtocol<{ params: WorkcardParams, query: TransitionQueryWire }, ListResult<Transition>>
819
+ specifications: EntrypointProtocol<{ params: WorkcardParams, query: SpecificationQueryWire }, ListResult<Specification>>
820
+ }
821
+ spec: {
822
+ get: EntrypointProtocol<{ params: WorkcardParams }, Specification>
823
+ revisions: EntrypointProtocol<{ params: WorkcardParams, query: RevisionsQuery }, SpecificationRevisionList>
824
+ }
825
+ link: {
826
+ list: EntrypointProtocol<{ query: RelationshipQueryWire }, ListResult<Relationship>>
827
+ }
828
+ transition: {
829
+ get: EntrypointProtocol<{ params: TransitionParams }, Transition>
830
+ }
831
+ execute: EntrypointProtocol<{ body: ExecuteRequest }, TransitionReceiptView>
832
+ commit: {
833
+ get: EntrypointProtocol<{ params: TransitionParams, query: CommitQuery }, CommitStatus>
834
+ events: EntrypointProtocol<{ query: CommitFeedQuery }, CommitEvent>
835
+ }
836
+ }
837
+