@owlmeans/planning 0.1.18-rc.2 → 0.1.18-rc.21

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 (228) hide show
  1. package/README.md +69 -26
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/planning/SKILL.md +157 -45
  4. package/build/aliases/types.d.ts +49 -0
  5. package/build/aliases/types.d.ts.map +1 -0
  6. package/build/aliases/types.js +2 -0
  7. package/build/aliases/types.js.map +1 -0
  8. package/build/aliases.d.ts +4 -0
  9. package/build/aliases.d.ts.map +1 -0
  10. package/build/aliases.js +27 -0
  11. package/build/aliases.js.map +1 -0
  12. package/build/consts.d.ts +23 -34
  13. package/build/consts.d.ts.map +1 -1
  14. package/build/consts.js +26 -25
  15. package/build/consts.js.map +1 -1
  16. package/build/entrypoints/types.d.ts +96 -0
  17. package/build/entrypoints/types.d.ts.map +1 -0
  18. package/build/entrypoints/types.js +2 -0
  19. package/build/entrypoints/types.js.map +1 -0
  20. package/build/entrypoints.d.ts +5 -1
  21. package/build/entrypoints.d.ts.map +1 -1
  22. package/build/entrypoints.js +17 -6
  23. package/build/entrypoints.js.map +1 -1
  24. package/build/errors.d.ts +41 -0
  25. package/build/errors.d.ts.map +1 -1
  26. package/build/errors.js +61 -0
  27. package/build/errors.js.map +1 -1
  28. package/build/helpers/apply/consts.local.d.ts +7 -0
  29. package/build/helpers/apply/consts.local.d.ts.map +1 -0
  30. package/build/helpers/apply/consts.local.js +9 -0
  31. package/build/helpers/apply/consts.local.js.map +1 -0
  32. package/build/helpers/apply/types.d.ts +32 -0
  33. package/build/helpers/apply/types.d.ts.map +1 -0
  34. package/build/helpers/apply/types.js +2 -0
  35. package/build/helpers/apply/types.js.map +1 -0
  36. package/build/helpers/apply.d.ts +5 -23
  37. package/build/helpers/apply.d.ts.map +1 -1
  38. package/build/helpers/apply.js +121 -143
  39. package/build/helpers/apply.js.map +1 -1
  40. package/build/helpers/card/types.d.ts +20 -0
  41. package/build/helpers/card/types.d.ts.map +1 -0
  42. package/build/helpers/card/types.js +2 -0
  43. package/build/helpers/card/types.js.map +1 -0
  44. package/build/helpers/card.d.ts +11 -7
  45. package/build/helpers/card.d.ts.map +1 -1
  46. package/build/helpers/card.js +41 -28
  47. package/build/helpers/card.js.map +1 -1
  48. package/build/helpers/changes/consts.local.d.ts +13 -0
  49. package/build/helpers/changes/consts.local.d.ts.map +1 -0
  50. package/build/helpers/changes/consts.local.js +13 -0
  51. package/build/helpers/changes/consts.local.js.map +1 -0
  52. package/build/helpers/changes/types.d.ts +58 -0
  53. package/build/helpers/changes/types.d.ts.map +1 -0
  54. package/build/helpers/changes/types.js +2 -0
  55. package/build/helpers/changes/types.js.map +1 -0
  56. package/build/helpers/changes.d.ts +11 -42
  57. package/build/helpers/changes.d.ts.map +1 -1
  58. package/build/helpers/changes.js +234 -251
  59. package/build/helpers/changes.js.map +1 -1
  60. package/build/helpers/code/types.d.ts +27 -0
  61. package/build/helpers/code/types.d.ts.map +1 -0
  62. package/build/helpers/code/types.js +2 -0
  63. package/build/helpers/code/types.js.map +1 -0
  64. package/build/helpers/code.d.ts +6 -17
  65. package/build/helpers/code.d.ts.map +1 -1
  66. package/build/helpers/code.js +39 -40
  67. package/build/helpers/code.js.map +1 -1
  68. package/build/helpers/index.d.ts +12 -0
  69. package/build/helpers/index.d.ts.map +1 -1
  70. package/build/helpers/index.js +2 -0
  71. package/build/helpers/index.js.map +1 -1
  72. package/build/helpers/query/types.d.ts +23 -0
  73. package/build/helpers/query/types.d.ts.map +1 -0
  74. package/build/helpers/query/types.js +2 -0
  75. package/build/helpers/query/types.js.map +1 -0
  76. package/build/helpers/query.d.ts +25 -17
  77. package/build/helpers/query.d.ts.map +1 -1
  78. package/build/helpers/query.js +108 -280
  79. package/build/helpers/query.js.map +1 -1
  80. package/build/helpers/scoped/types.d.ts +59 -0
  81. package/build/helpers/scoped/types.d.ts.map +1 -0
  82. package/build/helpers/scoped/types.js +2 -0
  83. package/build/helpers/scoped/types.js.map +1 -0
  84. package/build/helpers/scoped.d.ts +24 -0
  85. package/build/helpers/scoped.d.ts.map +1 -0
  86. package/build/helpers/scoped.js +197 -0
  87. package/build/helpers/scoped.js.map +1 -0
  88. package/build/helpers/specification/types.d.ts +24 -0
  89. package/build/helpers/specification/types.d.ts.map +1 -0
  90. package/build/helpers/specification/types.js +2 -0
  91. package/build/helpers/specification/types.js.map +1 -0
  92. package/build/helpers/specification.d.ts +9 -13
  93. package/build/helpers/specification.d.ts.map +1 -1
  94. package/build/helpers/specification.js +47 -44
  95. package/build/helpers/specification.js.map +1 -1
  96. package/build/helpers/status/types.d.ts +48 -0
  97. package/build/helpers/status/types.d.ts.map +1 -0
  98. package/build/helpers/status/types.js +2 -0
  99. package/build/helpers/status/types.js.map +1 -0
  100. package/build/helpers/status.d.ts +16 -29
  101. package/build/helpers/status.d.ts.map +1 -1
  102. package/build/helpers/status.js +77 -73
  103. package/build/helpers/status.js.map +1 -1
  104. package/build/helpers/types.local.d.ts +17 -0
  105. package/build/helpers/types.local.d.ts.map +1 -0
  106. package/build/helpers/types.local.js +2 -0
  107. package/build/helpers/types.local.js.map +1 -0
  108. package/build/helpers/validate/types.d.ts +30 -0
  109. package/build/helpers/validate/types.d.ts.map +1 -0
  110. package/build/helpers/validate/types.js +2 -0
  111. package/build/helpers/validate/types.js.map +1 -0
  112. package/build/helpers/validate.d.ts +8 -19
  113. package/build/helpers/validate.d.ts.map +1 -1
  114. package/build/helpers/validate.js +74 -79
  115. package/build/helpers/validate.js.map +1 -1
  116. package/build/helpers/wire/types.d.ts +28 -0
  117. package/build/helpers/wire/types.d.ts.map +1 -0
  118. package/build/helpers/wire/types.js +2 -0
  119. package/build/helpers/wire/types.js.map +1 -0
  120. package/build/helpers/wire.d.ts +4 -0
  121. package/build/helpers/wire.d.ts.map +1 -0
  122. package/build/helpers/wire.js +215 -0
  123. package/build/helpers/wire.js.map +1 -0
  124. package/build/i18n/be.json +5 -0
  125. package/build/i18n/de.json +5 -0
  126. package/build/i18n/en.json +5 -0
  127. package/build/i18n/es.json +5 -0
  128. package/build/i18n/fr.json +56 -0
  129. package/build/i18n/pl.json +5 -0
  130. package/build/i18n/ru.json +5 -0
  131. package/build/i18n/uk.json +5 -0
  132. package/build/i18n.js +5 -4
  133. package/build/i18n.js.map +1 -1
  134. package/build/index.d.ts +4 -0
  135. package/build/index.d.ts.map +1 -1
  136. package/build/index.js +2 -0
  137. package/build/index.js.map +1 -1
  138. package/build/models/index.d.ts +2 -3
  139. package/build/models/index.d.ts.map +1 -1
  140. package/build/models/index.js +1 -15
  141. package/build/models/index.js.map +1 -1
  142. package/build/models/model.d.ts +5 -0
  143. package/build/models/model.d.ts.map +1 -0
  144. package/build/models/model.js +16 -0
  145. package/build/models/model.js.map +1 -0
  146. package/build/models/project.d.ts +2 -1
  147. package/build/models/project.d.ts.map +1 -1
  148. package/build/models/project.js.map +1 -1
  149. package/build/models/specification.d.ts +2 -1
  150. package/build/models/specification.d.ts.map +1 -1
  151. package/build/models/specification.js.map +1 -1
  152. package/build/models/types.d.ts +69 -0
  153. package/build/models/types.d.ts.map +1 -0
  154. package/build/models/types.js +2 -0
  155. package/build/models/types.js.map +1 -0
  156. package/build/models/workcard.d.ts +2 -1
  157. package/build/models/workcard.d.ts.map +1 -1
  158. package/build/models/workcard.js +12 -12
  159. package/build/models/workcard.js.map +1 -1
  160. package/build/schemas.d.ts +10 -2
  161. package/build/schemas.d.ts.map +1 -1
  162. package/build/schemas.js +87 -1
  163. package/build/schemas.js.map +1 -1
  164. package/build/types.d.ts +199 -148
  165. package/build/types.d.ts.map +1 -1
  166. package/build/utils.d.ts +6 -0
  167. package/build/utils.d.ts.map +1 -0
  168. package/build/utils.js +6 -0
  169. package/build/utils.js.map +1 -0
  170. package/package.json +11 -11
  171. package/src/aliases/types.ts +38 -0
  172. package/src/aliases.ts +31 -0
  173. package/src/consts.ts +26 -25
  174. package/src/entrypoints/types.ts +68 -0
  175. package/src/entrypoints.ts +34 -14
  176. package/src/errors.ts +71 -0
  177. package/src/helpers/apply/consts.local.ts +10 -0
  178. package/src/helpers/apply/types.ts +32 -0
  179. package/src/helpers/apply.ts +135 -152
  180. package/src/helpers/card/types.ts +20 -0
  181. package/src/helpers/card.ts +59 -33
  182. package/src/helpers/changes/consts.local.ts +17 -0
  183. package/src/helpers/changes/types.ts +66 -0
  184. package/src/helpers/changes.ts +267 -280
  185. package/src/helpers/code/types.ts +31 -0
  186. package/src/helpers/code.ts +52 -48
  187. package/src/helpers/index.ts +13 -0
  188. package/src/helpers/query/types.ts +26 -0
  189. package/src/helpers/query.ts +144 -314
  190. package/src/helpers/scoped/types.ts +64 -0
  191. package/src/helpers/scoped.ts +257 -0
  192. package/src/helpers/specification/types.ts +26 -0
  193. package/src/helpers/specification.ts +69 -51
  194. package/src/helpers/status/types.ts +46 -0
  195. package/src/helpers/status.ts +117 -93
  196. package/src/helpers/types.local.ts +18 -0
  197. package/src/helpers/validate/types.ts +30 -0
  198. package/src/helpers/validate.ts +84 -77
  199. package/src/helpers/wire/types.ts +31 -0
  200. package/src/helpers/wire.ts +251 -0
  201. package/src/i18n/be.json +5 -0
  202. package/src/i18n/de.json +5 -0
  203. package/src/i18n/en.json +5 -0
  204. package/src/i18n/es.json +5 -0
  205. package/src/i18n/fr.json +56 -0
  206. package/src/i18n/pl.json +5 -0
  207. package/src/i18n/ru.json +5 -0
  208. package/src/i18n/uk.json +5 -0
  209. package/src/i18n.ts +6 -4
  210. package/src/index.ts +4 -0
  211. package/src/models/index.ts +2 -17
  212. package/src/models/model.ts +18 -0
  213. package/src/models/project.ts +2 -1
  214. package/src/models/specification.ts +2 -1
  215. package/src/models/types.ts +70 -0
  216. package/src/models/workcard.ts +14 -16
  217. package/src/schemas.ts +98 -2
  218. package/src/types.ts +207 -126
  219. package/src/utils.ts +9 -0
  220. package/tests/apply.spec.ts +95 -31
  221. package/tests/code.spec.ts +9 -9
  222. package/tests/entrypoints.spec.ts +18 -2
  223. package/tests/error-status.spec.ts +6 -1
  224. package/tests/models.spec.ts +16 -15
  225. package/tests/query.spec.ts +13 -15
  226. package/tests/schemas.spec.ts +8 -8
  227. package/tests/scoped.spec.ts +154 -0
  228. package/tests/status.spec.ts +31 -22
package/src/aliases.ts ADDED
@@ -0,0 +1,31 @@
1
+ import type { PlanningAliases, PlanningAliasHelper, PlanningDefinitionAliases } from './aliases/types.js'
2
+
3
+ export const createPlanningAliasHelper = (): PlanningAliasHelper => {
4
+ const planningAliases = (base: string): PlanningAliases => Object.freeze({
5
+ base,
6
+ schema: Object.freeze({ list: `${base}:schema:list` }),
7
+ card: Object.freeze({
8
+ list: `${base}:card:list`,
9
+ summary: `${base}:card:summary`,
10
+ get: `${base}:card:get`,
11
+ transitions: `${base}:card:transitions`,
12
+ specifications: `${base}:card:specifications`,
13
+ }),
14
+ spec: Object.freeze({
15
+ get: `${base}:specification:get`,
16
+ revisions: `${base}:specification:revisions`,
17
+ }),
18
+ link: Object.freeze({ list: `${base}:link:list` }),
19
+ transition: Object.freeze({ get: `${base}:transition:get` }),
20
+ execute: `${base}:execute`,
21
+ commit: Object.freeze({ get: `${base}:commit:get`, events: `${base}:commit:events` }),
22
+ })
23
+
24
+ const planningDefinitionAliases = (base: string): PlanningDefinitionAliases => Object.freeze({
25
+ define: `${base}:schema:define`,
26
+ })
27
+
28
+ return { planningAliases, planningDefinitionAliases }
29
+ }
30
+
31
+ export const planningAliasHelper = createPlanningAliasHelper()
package/src/consts.ts CHANGED
@@ -65,6 +65,32 @@ export enum CodeScope {
65
65
  Entity = 'entity',
66
66
  }
67
67
 
68
+ /** What a data-defined schema record declares: a card type or a status flow. */
69
+ export enum PlanningSchemaKind {
70
+ Type = 'type',
71
+ Flow = 'flow',
72
+ }
73
+
74
+ /**
75
+ * The layer a resolved type or flow comes from. `code` is the in-process registry (plugins),
76
+ * `entity` an organization-wide record, `project` a record scoped to one project card.
77
+ */
78
+ export enum SchemaOrigin {
79
+ Code = 'code',
80
+ Entity = 'entity',
81
+ Project = 'project',
82
+ }
83
+
84
+ /** How a `schema.define` request writes its declarations. */
85
+ export enum SchemaWriteMode {
86
+ /** Each declaration lands at its layer's next version. */
87
+ Define = 'define',
88
+ /** Compare-and-set: each declaration's `version` must be its layer's next version. */
89
+ Put = 'put',
90
+ /** Only keys the layer lacks are written, at version 1. */
91
+ Seed = 'seed',
92
+ }
93
+
68
94
  /** Service alias of the planning facade host. Identical on the server and in a client. */
69
95
  export const PLANNING_SERVICE = 'planning'
70
96
 
@@ -109,28 +135,3 @@ export const QUERY_TEXT_MAX = 256
109
135
  /** The longest list a wire query carries in one value (ids, parents). */
110
136
  export const MAX_QUERY_LIST = 500
111
137
 
112
- /**
113
- * The aliases one planning tree answers under, derived from the mount's base alias.
114
- *
115
- * Two mounts of the tree in one deployment are two base aliases, never two copies of this table —
116
- * which is why nothing here is a global constant.
117
- */
118
- export const planningAliases = (base: string) => Object.freeze({
119
- base,
120
- schema: Object.freeze({ list: `${base}:schema:list` }),
121
- card: Object.freeze({
122
- list: `${base}:card:list`,
123
- summary: `${base}:card:summary`,
124
- get: `${base}:card:get`,
125
- transitions: `${base}:card:transitions`,
126
- specifications: `${base}:card:specifications`,
127
- }),
128
- spec: Object.freeze({
129
- get: `${base}:specification:get`,
130
- revisions: `${base}:specification:revisions`,
131
- }),
132
- link: Object.freeze({ list: `${base}:link:list` }),
133
- transition: Object.freeze({ get: `${base}:transition:get` }),
134
- execute: `${base}:execute`,
135
- commit: Object.freeze({ get: `${base}:commit:get`, events: `${base}:commit:events` }),
136
- })
@@ -0,0 +1,68 @@
1
+ import type { EntrypointProtocol, OpenRequest, OpenValue } from '@owlmeans/entrypoint'
2
+ import type { ListResult } from '@owlmeans/resource'
3
+ import type { RouteParent } from '@owlmeans/route'
4
+ import type {
5
+ CommitEvent, CommitFeedQuery, CommitQuery, CommitStatus, ExecuteRequest, Relationship, RelationshipQueryWire,
6
+ RevisionsQuery, SchemaDefineReply, SchemaDefineRequest, SchemaListQuery, ScopedSchemaBundle, Specification,
7
+ SpecificationQueryWire, SpecificationRevisionList, SummaryQueryWire, SummaryView, Transition, TransitionParams,
8
+ TransitionQueryWire, TransitionReceiptView, Workcard, WorkcardParams, WorkcardQueryWire,
9
+ } from '../types.js'
10
+
11
+ export interface PlanningBaseOptions {
12
+ /** The base alias every leaf alias derives from (`planningAliases`). */
13
+ alias: string
14
+ /** Defaults to `PLANNING_PATH`. */
15
+ path?: string
16
+ parent?: RouteParent
17
+ service?: string
18
+ }
19
+
20
+ export interface PlanningProtocolOptions {
21
+ base: PlanningBaseOptions
22
+ /** The guard(s) the base carries; every HTTP leaf inherits them. */
23
+ guards: string | readonly string[]
24
+ gate?: { alias: string, params?: string | readonly string[] }
25
+ /**
26
+ * The socket base the commit feed hangs under. The feed inherits THAT base's guards; the
27
+ * planning base when omitted.
28
+ */
29
+ socketBase?: RouteParent
30
+ /**
31
+ * Declare the data-defined schema surface: `schema.list` takes a `project` query and answers
32
+ * that layer's scoped bundle, and `schema.define` writes. Off by default — a tree without it
33
+ * declares exactly the other leaves.
34
+ */
35
+ definitions?: boolean
36
+ }
37
+
38
+ export interface PlanningProtocols {
39
+ base: EntrypointProtocol<OpenRequest, OpenValue>
40
+ schema: {
41
+ /** The plain bundle — or, on a tree declared with `definitions`, a layer's scoped one. */
42
+ list: EntrypointProtocol<{ query?: SchemaListQuery }, ScopedSchemaBundle>
43
+ /** Present on a tree declared with `definitions` only. */
44
+ define?: EntrypointProtocol<{ body: SchemaDefineRequest }, SchemaDefineReply>
45
+ }
46
+ card: {
47
+ list: EntrypointProtocol<{ query: WorkcardQueryWire }, ListResult<Workcard>>
48
+ summary: EntrypointProtocol<{ query: SummaryQueryWire }, SummaryView>
49
+ get: EntrypointProtocol<{ params: WorkcardParams }, Workcard>
50
+ transitions: EntrypointProtocol<{ params: WorkcardParams, query: TransitionQueryWire }, ListResult<Transition>>
51
+ specifications: EntrypointProtocol<{ params: WorkcardParams, query: SpecificationQueryWire }, ListResult<Specification>>
52
+ }
53
+ spec: {
54
+ get: EntrypointProtocol<{ params: WorkcardParams }, Specification>
55
+ revisions: EntrypointProtocol<{ params: WorkcardParams, query: RevisionsQuery }, SpecificationRevisionList>
56
+ }
57
+ link: {
58
+ list: EntrypointProtocol<{ query: RelationshipQueryWire }, ListResult<Relationship>>
59
+ }
60
+ transition: {
61
+ get: EntrypointProtocol<{ params: TransitionParams }, Transition>
62
+ }
63
+ execute: EntrypointProtocol<{ body: ExecuteRequest }, TransitionReceiptView>
64
+ commit: {
65
+ get: EntrypointProtocol<{ params: TransitionParams, query: CommitQuery }, CommitStatus>
66
+ events: EntrypointProtocol<{ query: CommitFeedQuery }, CommitEvent>
67
+ }
68
+ }
@@ -1,19 +1,20 @@
1
1
  import { contract, openProtocol, protocol, typed } from '@owlmeans/entrypoint'
2
2
  import type { ListResult } from '@owlmeans/resource'
3
- import { backend, route, RouteMethod, socket } from '@owlmeans/route'
4
- import type { RouteOptions } from '@owlmeans/route'
5
- import { PLANNING_PATH, planningAliases } from './consts.js'
3
+ import { backend, route, RouteMethod, socket, type RouteOptions } from '@owlmeans/route'
4
+ import { PLANNING_PATH } from './consts.js'
5
+ import { planningAliasHelper } from './aliases.js'
6
6
  import {
7
7
  CommitQuerySchema, ExecuteRequestSchema, RelationshipQuerySchema, RevisionsQuerySchema,
8
- SpecificationQuerySchema, SummaryQuerySchema, TransitionParamsSchema, TransitionQuerySchema,
9
- WorkcardParamsSchema, WorkcardQuerySchema,
8
+ SchemaDefineRequestSchema, SchemaListQuerySchema, SpecificationQuerySchema, SummaryQuerySchema,
9
+ TransitionParamsSchema, TransitionQuerySchema, WorkcardParamsSchema, WorkcardQuerySchema,
10
10
  } from './schemas.js'
11
+ import type { PlanningProtocolOptions, PlanningProtocols } from './entrypoints/types.js'
11
12
  import type {
12
- CommitEvent, CommitFeedQuery, CommitQuery, CommitStatus, ExecuteRequest, PlanningProtocolOptions,
13
- PlanningProtocols, PlanningSchemaBundle, Relationship, RelationshipQueryWire, RevisionsQuery,
14
- Specification, SpecificationQueryWire, SpecificationRevisionList, SummaryQueryWire, SummaryView,
15
- Transition, TransitionParams, TransitionQueryWire, TransitionReceiptView, Workcard, WorkcardParams,
16
- WorkcardQueryWire,
13
+ CommitEvent, CommitFeedQuery, CommitQuery, CommitStatus, ExecuteRequest,
14
+ PlanningSchemaBundle, Relationship, RelationshipQueryWire, RevisionsQuery,
15
+ SchemaDefineReply, SchemaDefineRequest, SchemaListQuery, ScopedSchemaBundle, Specification, SpecificationQueryWire,
16
+ SpecificationRevisionList, SummaryQueryWire, SummaryView, Transition, TransitionParams,
17
+ TransitionQueryWire, TransitionReceiptView, Workcard, WorkcardParams, WorkcardQueryWire,
17
18
  } from './types.js'
18
19
 
19
20
  /**
@@ -26,9 +27,13 @@ import type {
26
27
  *
27
28
  * Queries travel as their `*Wire` shapes: encode with `encode*Query` before a call, decode with
28
29
  * `decode*Query` in a handler.
30
+ *
31
+ * `definitions: true` adds the data-defined schema surface: `schema.list` takes a `project` query
32
+ * and answers that layer's scoped bundle, and `schema.define` (POST `/schemas`) writes. Without it
33
+ * the tree declares exactly the other leaves.
29
34
  */
30
35
  export const makePlanningProtocols = (opts: PlanningProtocolOptions): PlanningProtocols => {
31
- const aliases = planningAliases(opts.base.alias)
36
+ const aliases = planningAliasHelper.planningAliases(opts.base.alias)
32
37
  const mount: Partial<RouteOptions> = {
33
38
  ...(opts.base.parent != null ? { parent: opts.base.parent } : {}),
34
39
  ...(opts.base.service != null ? { service: opts.base.service } : {}),
@@ -40,12 +45,27 @@ export const makePlanningProtocols = (opts: PlanningProtocolOptions): PlanningPr
40
45
  })
41
46
  const get = (alias: string, path: string) => route(alias, path, backend({ parent: base }, RouteMethod.GET))
42
47
 
48
+ const schema: PlanningProtocols['schema'] = opts.definitions === true
49
+ ? Object.freeze({
50
+ list: protocol(
51
+ get(aliases.schema.list, '/schemas'),
52
+ contract.request({ query: typed<SchemaListQuery>(SchemaListQuerySchema) }, typed<ScopedSchemaBundle>())
53
+ ) as unknown as PlanningProtocols['schema']['list'],
54
+ define: protocol(
55
+ route(planningAliasHelper.planningDefinitionAliases(opts.base.alias).define, '/schemas', backend({ parent: base }, RouteMethod.POST)),
56
+ contract.request({ body: typed<SchemaDefineRequest>(SchemaDefineRequestSchema) }, typed<SchemaDefineReply>())
57
+ ),
58
+ })
59
+ : Object.freeze({
60
+ list: protocol(
61
+ get(aliases.schema.list, '/schemas'), contract(typed<PlanningSchemaBundle>())
62
+ ) as unknown as PlanningProtocols['schema']['list'],
63
+ })
64
+
43
65
  return Object.freeze({
44
66
  base,
45
67
 
46
- schema: Object.freeze({
47
- list: protocol(get(aliases.schema.list, '/schemas'), contract(typed<PlanningSchemaBundle>())),
48
- }),
68
+ schema,
49
69
 
50
70
  card: Object.freeze({
51
71
  list: protocol(
package/src/errors.ts CHANGED
@@ -206,6 +206,72 @@ export class PlanningScopeMismatch extends PlanningError {
206
206
  }
207
207
  }
208
208
 
209
+ /**
210
+ * A data-defined schema write lost its compare-and-set: the layer's record is not at the version
211
+ * the write replaces. Re-read the layer and retry.
212
+ */
213
+ export class SchemaConflict extends PlanningError {
214
+ public static override typeName: string = `${PlanningError.typeName}SchemaConflict`
215
+ /** The stored record moved past the expected version: answered 409. */
216
+ public static httpStatus = 409
217
+
218
+ constructor(message: string = 'error') {
219
+ super(`schema-conflict:${message}`)
220
+ this.type = SchemaConflict.typeName
221
+ }
222
+ }
223
+
224
+ /** A flow cannot be retired while a live type of an affected layer still resolves it. */
225
+ export class SchemaInUse extends PlanningError {
226
+ public static override typeName: string = `${PlanningError.typeName}SchemaInUse`
227
+ /** Other declarations still depend on the key: answered 409. */
228
+ public static httpStatus = 409
229
+
230
+ constructor(message: string = 'error') {
231
+ super(`schema-in-use:${message}`)
232
+ this.type = SchemaInUse.typeName
233
+ }
234
+ }
235
+
236
+ /**
237
+ * The key is registered in code and does not declare itself `overridable` — or it is a project
238
+ * or specification type, which only code declares.
239
+ */
240
+ export class SchemaSealed extends PlanningError {
241
+ public static override typeName: string = `${PlanningError.typeName}SchemaSealed`
242
+ /** The key cannot be declared as data: answered 422. */
243
+ public static httpStatus = 422
244
+
245
+ constructor(message: string = 'error') {
246
+ super(`schema-sealed:${message}`)
247
+ this.type = SchemaSealed.typeName
248
+ }
249
+ }
250
+
251
+ /** A data-defined declaration fails its closed-form checks. */
252
+ export class SchemaInvalid extends PlanningError {
253
+ public static override typeName: string = `${PlanningError.typeName}SchemaInvalid`
254
+ /** The declaration's content is refused: answered 422. */
255
+ public static httpStatus = 422
256
+
257
+ constructor(message: string = 'error') {
258
+ super(`schema-invalid:${message}`)
259
+ this.type = SchemaInvalid.typeName
260
+ }
261
+ }
262
+
263
+ /** The request's access does not grant the write (creating or deleting a project, defining schemas). */
264
+ export class PlanningForbidden extends PlanningError {
265
+ public static override typeName: string = `${PlanningError.typeName}Forbidden`
266
+ /** The caller lacks the grant the write needs: answered 403. */
267
+ public static httpStatus = 403
268
+
269
+ constructor(message: string = 'error') {
270
+ super(`forbidden:${message}`)
271
+ this.type = PlanningForbidden.typeName
272
+ }
273
+ }
274
+
209
275
  /** The store behind this type cannot do what was asked (a foreign provider without a log). */
210
276
  export class PlanningUnsupported extends PlanningError {
211
277
  public static override typeName: string = `${PlanningError.typeName}Unsupported`
@@ -234,4 +300,9 @@ ResilientError.registerErrorClass(WorkcardConflict)
234
300
  ResilientError.registerErrorClass(CommitTimeout)
235
301
  ResilientError.registerErrorClass(CommitFailed)
236
302
  ResilientError.registerErrorClass(PlanningScopeMismatch)
303
+ ResilientError.registerErrorClass(SchemaConflict)
304
+ ResilientError.registerErrorClass(SchemaInUse)
305
+ ResilientError.registerErrorClass(SchemaSealed)
306
+ ResilientError.registerErrorClass(SchemaInvalid)
307
+ ResilientError.registerErrorClass(PlanningForbidden)
237
308
  ResilientError.registerErrorClass(PlanningUnsupported)
@@ -0,0 +1,10 @@
1
+ /** Never written by a transition's `changes`: the fold takes them from the transition itself. */
2
+ export const IDENTITY_KEYS = new Set(['id', 'kind', 'type', 'entityId', 'seq', 'head', 'createdAt'])
3
+
4
+ /** Cannot be cleared: a record without them is not a record. */
5
+ export const REQUIRED_KEYS = new Set([
6
+ ...IDENTITY_KEYS, 'title', 'parents', 'status', 'intrinsic', 'flows', 'labels', 'fields',
7
+ ])
8
+
9
+ /** Merged shallowly into the card's own map rather than replacing it. */
10
+ export const MERGED_KEYS = new Set(['fields', 'flows'])
@@ -0,0 +1,32 @@
1
+ import type { Relationship, Transition, Workcard } from '../../types.js'
2
+
3
+ /** The pure fold — the card and its edges after one transition. */
4
+ export interface ApplyHelper {
5
+ /**
6
+ * THE fold: the card after one transition. Pure, total, no I/O — every store (memory, Mongo, a
7
+ * client mirror) folds with this and nothing else, so one log always means one card.
8
+ *
9
+ * - An already-applied transition (`seq <= card.seq`) returns the card unchanged: re-folding is
10
+ * safe.
11
+ * - Anything but the next seq (`card.seq + 1`, or 1 for a create with no card) is refused with
12
+ * `PlanningError('fold:out-of-order')`.
13
+ * - The log is replayed as written: a transition the executor would refuse today (an update naming
14
+ * `createdBy`, appended before that refusal existed) still folds. Guards live at admission.
15
+ * - `create` builds the whole record from `changes` plus the transition's identity; `delete`
16
+ * answers `null`; `link`/`unlink` only move `seq`/`head`/`updatedAt` (the edges are
17
+ * {@link ApplyHelper.applyRelationship}'s); `update`/`transit` write `changes` — `fields` and
18
+ * `flows` merge, everything else replaces — and clear `unset`.
19
+ * - Every result has `seq = transition.seq`, `head = max(head, seq)`, `updatedAt = transition.at`.
20
+ *
21
+ * @throws {PlanningError} `planning:fold:out-of-order:…`
22
+ */
23
+ applyTransition: <T extends Workcard = Workcard>(card: T | null | undefined, transition: Transition) => T | null
24
+ /**
25
+ * The relationships after one transition. Pure and total, like {@link ApplyHelper.applyTransition}.
26
+ *
27
+ * `create` adds its `links`, `link` adds its `link`, `unlink` removes the matching edge, and a
28
+ * `delete` removes every edge touching the card. An edge that already exists is kept as it is, so
29
+ * re-folding adds nothing. Every other action answers the list unchanged. Always a new array.
30
+ */
31
+ applyRelationship: (links: Relationship[], transition: Transition) => Relationship[]
32
+ }