@owlmeans/server-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 (217) hide show
  1. package/README.md +40 -12
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/server-planning/SKILL.md +193 -18
  4. package/build/actions/cards.d.ts +3 -5
  5. package/build/actions/cards.d.ts.map +1 -1
  6. package/build/actions/cards.js +1 -1
  7. package/build/actions/cards.js.map +1 -1
  8. package/build/actions/execute.d.ts +24 -3
  9. package/build/actions/execute.d.ts.map +1 -1
  10. package/build/actions/execute.js +82 -8
  11. package/build/actions/execute.js.map +1 -1
  12. package/build/actions/schemas.d.ts +27 -5
  13. package/build/actions/schemas.d.ts.map +1 -1
  14. package/build/actions/schemas.js +67 -7
  15. package/build/actions/schemas.js.map +1 -1
  16. package/build/actions/specs.d.ts +2 -4
  17. package/build/actions/specs.d.ts.map +1 -1
  18. package/build/actions/specs.js.map +1 -1
  19. package/build/actions/types.d.ts +4 -0
  20. package/build/actions/types.d.ts.map +1 -0
  21. package/build/actions/types.js +2 -0
  22. package/build/actions/types.js.map +1 -0
  23. package/build/actions/watch.d.ts.map +1 -1
  24. package/build/actions/watch.js +4 -2
  25. package/build/actions/watch.js.map +1 -1
  26. package/build/conformance/assert.d.ts +20 -0
  27. package/build/conformance/assert.d.ts.map +1 -0
  28. package/build/conformance/assert.js +54 -0
  29. package/build/conformance/assert.js.map +1 -0
  30. package/build/conformance/cases.d.ts +7 -0
  31. package/build/conformance/cases.d.ts.map +1 -0
  32. package/build/conformance/cases.js +447 -0
  33. package/build/conformance/cases.js.map +1 -0
  34. package/build/conformance/consts.d.ts +19 -0
  35. package/build/conformance/consts.d.ts.map +1 -0
  36. package/build/conformance/consts.js +42 -0
  37. package/build/conformance/consts.js.map +1 -0
  38. package/build/conformance/fixtures.d.ts +14 -0
  39. package/build/conformance/fixtures.d.ts.map +1 -0
  40. package/build/conformance/fixtures.js +109 -0
  41. package/build/conformance/fixtures.js.map +1 -0
  42. package/build/conformance/index.d.ts +12 -0
  43. package/build/conformance/index.d.ts.map +1 -0
  44. package/build/conformance/index.js +11 -0
  45. package/build/conformance/index.js.map +1 -0
  46. package/build/conformance/types.d.ts +18 -0
  47. package/build/conformance/types.d.ts.map +1 -0
  48. package/build/conformance/types.js +2 -0
  49. package/build/conformance/types.js.map +1 -0
  50. package/build/consts.d.ts +5 -0
  51. package/build/consts.d.ts.map +1 -1
  52. package/build/consts.js +5 -0
  53. package/build/consts.js.map +1 -1
  54. package/build/definitions.d.ts +17 -0
  55. package/build/definitions.d.ts.map +1 -0
  56. package/build/definitions.js +211 -0
  57. package/build/definitions.js.map +1 -0
  58. package/build/executor/changes.d.ts +2 -2
  59. package/build/executor/changes.d.ts.map +1 -1
  60. package/build/executor/changes.js.map +1 -1
  61. package/build/executor/code.d.ts +3 -3
  62. package/build/executor/code.d.ts.map +1 -1
  63. package/build/executor/code.js +1 -1
  64. package/build/executor/code.js.map +1 -1
  65. package/build/executor/consts.local.d.ts +5 -0
  66. package/build/executor/consts.local.d.ts.map +1 -0
  67. package/build/executor/consts.local.js +6 -0
  68. package/build/executor/consts.local.js.map +1 -0
  69. package/build/executor/creator.d.ts +30 -0
  70. package/build/executor/creator.d.ts.map +1 -0
  71. package/build/executor/creator.js +46 -0
  72. package/build/executor/creator.js.map +1 -0
  73. package/build/executor/resolve.d.ts +31 -23
  74. package/build/executor/resolve.d.ts.map +1 -1
  75. package/build/executor/resolve.js +74 -15
  76. package/build/executor/resolve.js.map +1 -1
  77. package/build/executor/types.d.ts +24 -0
  78. package/build/executor/types.d.ts.map +1 -0
  79. package/build/executor/types.js +2 -0
  80. package/build/executor/types.js.map +1 -0
  81. package/build/executor/validate.d.ts +6 -5
  82. package/build/executor/validate.d.ts.map +1 -1
  83. package/build/executor/validate.js +18 -15
  84. package/build/executor/validate.js.map +1 -1
  85. package/build/executor.d.ts +10 -7
  86. package/build/executor.d.ts.map +1 -1
  87. package/build/executor.js +17 -14
  88. package/build/executor.js.map +1 -1
  89. package/build/facade.d.ts +20 -2
  90. package/build/facade.d.ts.map +1 -1
  91. package/build/facade.js +128 -18
  92. package/build/facade.js.map +1 -1
  93. package/build/helper.d.ts +3 -2
  94. package/build/helper.d.ts.map +1 -1
  95. package/build/helper.js +7 -3
  96. package/build/helper.js.map +1 -1
  97. package/build/index.d.ts +1 -0
  98. package/build/index.d.ts.map +1 -1
  99. package/build/index.js +1 -0
  100. package/build/index.js.map +1 -1
  101. package/build/projection.d.ts +2 -10
  102. package/build/projection.d.ts.map +1 -1
  103. package/build/projection.js +1 -1
  104. package/build/projection.js.map +1 -1
  105. package/build/queue/consts.d.ts +5 -0
  106. package/build/queue/consts.d.ts.map +1 -0
  107. package/build/queue/consts.js +5 -0
  108. package/build/queue/consts.js.map +1 -0
  109. package/build/queue/index.d.ts +4 -16
  110. package/build/queue/index.d.ts.map +1 -1
  111. package/build/queue/index.js +2 -4
  112. package/build/queue/index.js.map +1 -1
  113. package/build/queue/types.d.ts +13 -0
  114. package/build/queue/types.d.ts.map +1 -0
  115. package/build/queue/types.js +2 -0
  116. package/build/queue/types.js.map +1 -0
  117. package/build/registry.d.ts +2 -21
  118. package/build/registry.d.ts.map +1 -1
  119. package/build/registry.js +3 -1
  120. package/build/registry.js.map +1 -1
  121. package/build/service.d.ts +3 -16
  122. package/build/service.d.ts.map +1 -1
  123. package/build/service.js +9 -0
  124. package/build/service.js.map +1 -1
  125. package/build/store/commits.d.ts.map +1 -1
  126. package/build/store/commits.js +3 -1
  127. package/build/store/commits.js.map +1 -1
  128. package/build/store/composite.d.ts +1 -0
  129. package/build/store/composite.d.ts.map +1 -1
  130. package/build/store/composite.js +3 -0
  131. package/build/store/composite.js.map +1 -1
  132. package/build/store/consts.local.d.ts +2 -0
  133. package/build/store/consts.local.d.ts.map +1 -0
  134. package/build/store/consts.local.js +2 -0
  135. package/build/store/consts.local.js.map +1 -0
  136. package/build/store/fold.d.ts +4 -3
  137. package/build/store/fold.d.ts.map +1 -1
  138. package/build/store/fold.js +14 -6
  139. package/build/store/fold.js.map +1 -1
  140. package/build/store/memory.d.ts +12 -0
  141. package/build/store/memory.d.ts.map +1 -1
  142. package/build/store/memory.js +0 -0
  143. package/build/store/memory.js.map +1 -1
  144. package/build/store/types.d.ts +15 -1
  145. package/build/store/types.d.ts.map +1 -1
  146. package/build/store/types.local.d.ts +3 -0
  147. package/build/store/types.local.d.ts.map +1 -0
  148. package/build/store/types.local.js +2 -0
  149. package/build/store/types.local.js.map +1 -0
  150. package/build/types.d.ts +111 -1
  151. package/build/types.d.ts.map +1 -1
  152. package/build/types.local.d.ts +7 -0
  153. package/build/types.local.d.ts.map +1 -0
  154. package/build/types.local.js +2 -0
  155. package/build/types.local.js.map +1 -0
  156. package/build/utils/index.d.ts +1 -0
  157. package/build/utils/index.d.ts.map +1 -1
  158. package/build/utils/scope.d.ts +39 -2
  159. package/build/utils/scope.d.ts.map +1 -1
  160. package/build/utils/scope.js +79 -15
  161. package/build/utils/scope.js.map +1 -1
  162. package/build/utils/types.d.ts +8 -0
  163. package/build/utils/types.d.ts.map +1 -0
  164. package/build/utils/types.js +2 -0
  165. package/build/utils/types.js.map +1 -0
  166. package/package.json +28 -20
  167. package/src/actions/cards.ts +2 -6
  168. package/src/actions/execute.ts +95 -12
  169. package/src/actions/schemas.ts +81 -9
  170. package/src/actions/specs.ts +1 -2
  171. package/src/actions/types.ts +4 -0
  172. package/src/actions/watch.ts +5 -2
  173. package/src/conformance/assert.ts +59 -0
  174. package/src/conformance/cases.ts +522 -0
  175. package/src/conformance/consts.ts +44 -0
  176. package/src/conformance/fixtures.ts +121 -0
  177. package/src/conformance/index.ts +11 -0
  178. package/src/conformance/types.ts +20 -0
  179. package/src/consts.ts +6 -0
  180. package/src/definitions.ts +250 -0
  181. package/src/executor/changes.ts +2 -6
  182. package/src/executor/code.ts +3 -9
  183. package/src/executor/consts.local.ts +8 -0
  184. package/src/executor/creator.ts +51 -0
  185. package/src/executor/resolve.ts +84 -40
  186. package/src/executor/types.ts +25 -0
  187. package/src/executor/validate.ts +22 -26
  188. package/src/executor.ts +21 -21
  189. package/src/facade.ts +143 -25
  190. package/src/helper.ts +7 -3
  191. package/src/index.ts +1 -0
  192. package/src/projection.ts +4 -14
  193. package/src/queue/consts.ts +5 -0
  194. package/src/queue/index.ts +6 -21
  195. package/src/queue/types.ts +14 -0
  196. package/src/registry.ts +5 -29
  197. package/src/service.ts +13 -20
  198. package/src/store/commits.ts +5 -4
  199. package/src/store/composite.ts +3 -0
  200. package/src/store/consts.local.ts +1 -0
  201. package/src/store/fold.ts +15 -12
  202. package/src/store/memory.ts +0 -0
  203. package/src/store/types.local.ts +3 -0
  204. package/src/store/types.ts +16 -1
  205. package/src/types.local.ts +7 -0
  206. package/src/types.ts +126 -4
  207. package/src/utils/index.ts +1 -0
  208. package/src/utils/scope.ts +97 -17
  209. package/src/utils/types.ts +8 -0
  210. package/tests/access.spec.ts +160 -0
  211. package/tests/conformance.spec.ts +47 -0
  212. package/tests/create-example.spec.ts +202 -0
  213. package/tests/creator.spec.ts +257 -0
  214. package/tests/entrypoints.spec.ts +5 -0
  215. package/tests/narrowing.spec.ts +94 -0
  216. package/tests/plugins.spec.ts +4 -3
  217. package/tests/writes.spec.ts +140 -0
@@ -1,33 +1,22 @@
1
- import {
2
- CardTypeNotAllowed, isProject, normalizeParents, ParentNotFound, PlanningError, PlanningScopeMismatch,
3
- primaryFlowOf, projectOf, slotOf, TransitionAction, UnknownStatusFlow, WorkcardKind, WorkcardNotFound,
4
- } from '@owlmeans/planning'
5
- import type {
6
- AnyTypeSchema, PlanningFacade, PlanningStore, ProjectTypeSchema, Specification, SpecificationSlot,
7
- StatusFlowSchema, TransitionExecution, Workcard, WorkcardDraft,
8
- } from '@owlmeans/planning'
9
- import type { PlanningRuntime } from '../service.js'
10
-
11
- /** Everything the steps after resolution read about an execution. */
12
- export interface Resolved {
13
- create: boolean
14
- /** The existing card (not a create). */
15
- card?: Workcard
16
- type: AnyTypeSchema
17
- flowId: string
18
- flow: StatusFlowSchema
19
- /** The primary parent, when it resolves. */
20
- parent?: Workcard
21
- /** The slot a specification belongs to. */
22
- slot?: SpecificationSlot
23
- /** The store the type is written to. */
24
- store: PlanningStore
1
+ import { CardTypeNotAllowed, isProject, normalizeParents, ParentNotFound, PlanningError, PlanningSchemaKind, PlanningScopeMismatch, primaryFlowOf, projectOf, SchemaOrigin, slotOf, TransitionAction, UnknownStatusFlow, UnknownWorkcardType, WorkcardKind, WorkcardNotFound, type AnyTypeSchema, type PlanningFacade, type PlanningSchemaRegistry, type ProjectTypeSchema, type ScopedSchemaRegistry, type Specification, type TransitionExecution, type Workcard, type WorkcardDraft } from '@owlmeans/planning'
2
+ import type { PlanningRuntime } from '../types.js'
3
+ import { TRIMMED } from './consts.local.js'
4
+ import type { Resolved } from './types.js'
5
+
6
+ const isScoped = (schemas: PlanningSchemaRegistry): schemas is ScopedSchemaRegistry =>
7
+ typeof (schemas as Partial<ScopedSchemaRegistry>).originOf === 'function'
8
+
9
+ /** A type the layer resolves from a data-defined record rather than from code. */
10
+ const dataDefined = (schemas: PlanningSchemaRegistry | undefined, type: string): boolean => {
11
+ if (schemas == null || !isScoped(schemas) || !schemas.has(type)) {
12
+ return false
13
+ }
14
+ const origin = schemas.originOf(PlanningSchemaKind.Type, type)
15
+ return origin === SchemaOrigin.Entity || origin === SchemaOrigin.Project
25
16
  }
26
17
 
27
18
  const trimmed = (value: unknown): unknown => typeof value === 'string' ? value.trim() : value
28
19
 
29
- const TRIMMED = ['title', 'description', 'code'] as const
30
-
31
20
  const trim = <T extends object>(record: T | undefined): T | undefined => {
32
21
  if (record == null) {
33
22
  return record
@@ -45,7 +34,7 @@ const trim = <T extends object>(record: T | undefined): T | undefined => {
45
34
  * Step 1: a deep copy with trimmed text, `parents ∋ parent` on a draft, and no duplicate `unset`
46
35
  * paths. The caller's object is never touched.
47
36
  */
48
- export const normalizeExecution = (input: TransitionExecution): TransitionExecution => {
37
+ const normalizeExecution = (input: TransitionExecution): TransitionExecution => {
49
38
  const exec = structuredClone(input) as TransitionExecution
50
39
  if (exec.card != null && typeof exec.card === 'object') {
51
40
  const draft = trim(exec.card) as WorkcardDraft
@@ -61,9 +50,15 @@ export const normalizeExecution = (input: TransitionExecution): TransitionExecut
61
50
  return exec
62
51
  }
63
52
 
64
- /** The parent type's rule for a child of `kind`/`type`. @throws {CardTypeNotAllowed} */
65
- export const assertChildAllowed = (
66
- runtime: PlanningRuntime, parent: Workcard, kind: WorkcardKind, type: string
53
+ /**
54
+ * The parent type's rule for a child of `kind`/`type`: a project lists the `cardTypes` (and
55
+ * `projectTypes`) it admits, and one declaring `scopedCardTypes` also admits a card type its layer
56
+ * (`view`, the parent project's resolved schemas) defines as data. Project types are code's alone.
57
+ *
58
+ * @throws {CardTypeNotAllowed}
59
+ */
60
+ const assertChildAllowed = (
61
+ runtime: PlanningRuntime, parent: Workcard, kind: WorkcardKind, type: string, view?: PlanningSchemaRegistry
67
62
  ): void => {
68
63
  if (kind === WorkcardKind.Specification) {
69
64
  return
@@ -73,24 +68,37 @@ export const assertChildAllowed = (
73
68
  }
74
69
  const parentType = runtime.service().schemas.type(parent.type) as ProjectTypeSchema
75
70
  const allowed = kind === WorkcardKind.Project ? parentType.projectTypes ?? [] : parentType.cardTypes ?? []
76
- if (!allowed.includes(type)) {
77
- throw new CardTypeNotAllowed(`${parent.type}:${type}`)
71
+ if (allowed.includes(type)) {
72
+ return
73
+ }
74
+ if (kind === WorkcardKind.Card && parentType.scopedCardTypes === true && dataDefined(view, type)) {
75
+ return
78
76
  }
77
+ throw new CardTypeNotAllowed(`${parent.type}:${type}`)
79
78
  }
80
79
 
80
+ /** The layer a parent project admits data-defined children through — `undefined` without a schema port. */
81
+ const childViewOf = async (
82
+ runtime: PlanningRuntime, entityId: string, parent: Workcard
83
+ ): Promise<PlanningSchemaRegistry | undefined> =>
84
+ runtime.schemaStore() == null || !isProject(parent) ? undefined : await runtime.schemasFor(entityId, parent.id)
85
+
81
86
  /**
82
87
  * Step 4: the card, its type and flow, its parent and — for a specification — its slot.
83
88
  *
84
89
  * @throws {WorkcardNotFound | PlanningScopeMismatch | UnknownWorkcardType | UnknownStatusFlow}
85
90
  * @throws {ParentNotFound | CardTypeNotAllowed | PlanningError}
86
91
  */
87
- export const resolveExecution = async (
92
+ const resolveExecution = async (
88
93
  runtime: PlanningRuntime, facade: PlanningFacade, exec: TransitionExecution
89
94
  ): Promise<Resolved> => {
90
95
  const service = runtime.service()
91
- const schemas = service.schemas
92
96
  const entityId = facade.scope.entityId
93
97
  const create = exec.action === TransitionAction.Create
98
+ // Without a schema port every step reads the code registry itself, in today's order; with one,
99
+ // the card's project is found first and the step reads that project's resolved layer.
100
+ const layered = runtime.schemaStore() != null
101
+ let schemas: PlanningSchemaRegistry = service.schemas
94
102
 
95
103
  let card: Workcard | undefined
96
104
  let type: AnyTypeSchema
@@ -101,14 +109,34 @@ export const resolveExecution = async (
101
109
  throw new PlanningError('malformed:create-without-draft')
102
110
  }
103
111
  const draft = exec.card
112
+ const parents = normalizeParents(draft.parent, draft.parents)
113
+ const loaded = new Map<string, Workcard>()
114
+ if (layered) {
115
+ // Loaded through the facade, so a parent this scope cannot see is absent here too.
116
+ const primary = parents[0] != null ? await facade.cards.load(parents[0]) : null
117
+ if (parents[0] != null && primary == null) {
118
+ throw new ParentNotFound(parents[0])
119
+ }
120
+ if (primary != null) {
121
+ loaded.set(parents[0], primary)
122
+ }
123
+ const project = draft.kind === WorkcardKind.Project || primary == null
124
+ ? undefined
125
+ : projectOf({ kind: draft.kind, parent: parents[0], parents } as unknown as Workcard, primary)
126
+ schemas = await runtime.schemasFor(entityId, project)
127
+ }
104
128
  type = schemas.type(draft.type)
105
129
  if (type.kind !== draft.kind) {
106
130
  throw new PlanningError(`malformed:kind:${draft.type}:${draft.kind}`)
107
131
  }
132
+ if (layered && isScoped(schemas) && schemas.isRetired(PlanningSchemaKind.Type, draft.type)) {
133
+ // Existing cards keep resolving a retired type; nothing new is made of it.
134
+ throw new UnknownWorkcardType(`retired:${draft.type}`)
135
+ }
108
136
  // Normalized: the primary parent is first. It must accept the child; so must every project
109
137
  // among the secondary parents.
110
- for (const [index, id] of normalizeParents(draft.parent, draft.parents).entries()) {
111
- const found = await facade.cards.load(id)
138
+ for (const [index, id] of parents.entries()) {
139
+ const found = loaded.get(id) ?? await facade.cards.load(id)
112
140
  if (found == null) {
113
141
  throw new ParentNotFound(id)
114
142
  }
@@ -116,7 +144,8 @@ export const resolveExecution = async (
116
144
  parent = found
117
145
  }
118
146
  if (index === 0 || isProject(found)) {
119
- assertChildAllowed(runtime, found, draft.kind, draft.type)
147
+ const view = !layered ? undefined : index === 0 && isProject(found) ? schemas : await childViewOf(runtime, entityId, found)
148
+ assertChildAllowed(runtime, found, draft.kind, draft.type, view)
120
149
  }
121
150
  }
122
151
  if (draft.kind === WorkcardKind.Specification && parent == null) {
@@ -133,9 +162,19 @@ export const resolveExecution = async (
133
162
  if (found.entityId !== entityId) {
134
163
  throw new PlanningScopeMismatch(exec.card)
135
164
  }
165
+ if (facade.scope.projects != null && await facade.cards.load(exec.card) == null) {
166
+ // Outside the scope's projects: absent, exactly like another entity's card.
167
+ throw new WorkcardNotFound(exec.card)
168
+ }
136
169
  card = found
137
- type = schemas.type(card.type)
138
- parent = card.parent != null ? await facade.cards.load(card.parent) ?? undefined : undefined
170
+ if (layered) {
171
+ parent = card.parent != null ? await facade.cards.load(card.parent) ?? undefined : undefined
172
+ schemas = await runtime.schemasFor(entityId, projectOf(card, parent))
173
+ type = schemas.type(card.type)
174
+ } else {
175
+ type = schemas.type(card.type)
176
+ parent = card.parent != null ? await facade.cards.load(card.parent) ?? undefined : undefined
177
+ }
139
178
  }
140
179
 
141
180
  const flowId = exec.flow ?? primaryFlowOf(type)
@@ -158,11 +197,12 @@ export const resolveExecution = async (
158
197
  ...(parent != null ? { parent } : {}),
159
198
  ...(slot != null ? { slot } : {}),
160
199
  store: service.store(type.type),
200
+ schemas,
161
201
  }
162
202
  }
163
203
 
164
204
  /** The project a transition is filed under: a project's own id, else its parent's project. */
165
- export const projectFor = (resolved: Resolved, exec: TransitionExecution, cardId: string): string | undefined => {
205
+ const projectFor = (resolved: Resolved, exec: TransitionExecution, cardId: string): string | undefined => {
166
206
  if (resolved.card != null) {
167
207
  return isProject(resolved.card) ? resolved.card.id : projectOf(resolved.card, resolved.parent)
168
208
  }
@@ -172,3 +212,7 @@ export const projectFor = (resolved: Resolved, exec: TransitionExecution, cardId
172
212
  }
173
213
  return projectOf({ kind: draft.kind, parent: draft.parent, parents: draft.parents ?? [] } as unknown as Workcard, resolved.parent)
174
214
  }
215
+
216
+ export const createResolveHelper = () => ({ normalizeExecution, assertChildAllowed, childViewOf, resolveExecution, projectFor })
217
+
218
+ export const resolveHelper = createResolveHelper()
@@ -0,0 +1,25 @@
1
+ import type { AnyTypeSchema, PlanningSchemaRegistry, PlanningStore, SpecificationSlot, StatusFlowSchema, Workcard } from '@owlmeans/planning'
2
+ import type { createResolveHelper } from './resolve.js'
3
+
4
+ /** Everything the steps after resolution read about an execution. */
5
+ export interface Resolved {
6
+ create: boolean
7
+ /** The existing card (not a create). */
8
+ card?: Workcard
9
+ type: AnyTypeSchema
10
+ flowId: string
11
+ flow: StatusFlowSchema
12
+ /** The primary parent, when it resolves. */
13
+ parent?: Workcard
14
+ /** The slot a specification belongs to. */
15
+ slot?: SpecificationSlot
16
+ /** The store the type is written to. */
17
+ store: PlanningStore
18
+ /**
19
+ * The registry every later step reads: the service's code registry itself, or — where the store
20
+ * holds data-defined schemas — the resolved layer of the card's project.
21
+ */
22
+ schemas: PlanningSchemaRegistry
23
+ }
24
+
25
+ export type ResolveHelper = ReturnType<typeof createResolveHelper>
@@ -1,17 +1,9 @@
1
- import {
2
- applyUnset, assertMutable, BODY_MAX, CODE_MAX, DESCRIPTION_MAX, IllegalTransition,
3
- LabelNotAllowed, MAX_LABELS, MAX_PARENTS, mergeFields, normalizeParents, ParentNotFound, PlanningError,
4
- RelationshipRefused, ruleOf, SpecificationRevisionConflict, SpecificationSlotUnknown, slotOf, TITLE_MAX,
5
- TransitionAction, validateFields, validateSpecificationBody, WorkcardKind,
6
- } from '@owlmeans/planning'
7
- import type {
8
- PlanningFacade, RelationshipDraft, Specification, TransitionExecution, WorkcardDraft,
9
- } from '@owlmeans/planning'
10
- import type { PlanningRuntime } from '../service.js'
11
- import { assertChildAllowed } from './resolve.js'
12
- import type { Resolved } from './resolve.js'
13
-
14
- const ACTIONS = new Set<string>(Object.values(TransitionAction))
1
+ import { applyUnset, assertMutable, BODY_MAX, CODE_MAX, DESCRIPTION_MAX, IllegalTransition, LabelNotAllowed, MAX_LABELS, MAX_PARENTS, mergeFields, normalizeParents, ParentNotFound, PlanningError, RelationshipRefused, ruleOf, SpecificationRevisionConflict, SpecificationSlotUnknown, slotOf, TITLE_MAX, TransitionAction, validateFields, validateSpecificationBody, WorkcardKind, type PlanningFacade, type RelationshipDraft, type Specification, type TransitionExecution, type WorkcardDraft } from '@owlmeans/planning'
2
+ import type { PlanningRuntime } from '../types.js'
3
+ import { assertCreatorFixed } from './creator.js'
4
+ import type { Resolved } from './types.js'
5
+ import { ACTIONS } from './consts.local.js'
6
+ import { resolveHelper } from './resolve.js'
15
7
 
16
8
  const malformed = (what: string): PlanningError => new PlanningError(`malformed:${what}`)
17
9
 
@@ -58,10 +50,10 @@ const assertShape = (exec: TransitionExecution, resolved: Resolved): void => {
58
50
  assertText('code', changes.code ?? undefined, CODE_MAX, false)
59
51
  }
60
52
 
61
- const assertFlow = (exec: TransitionExecution, resolved: Resolved, runtime: PlanningRuntime): void => {
53
+ const assertFlow = (exec: TransitionExecution, resolved: Resolved): void => {
62
54
  if (resolved.create) {
63
55
  const status = (exec.card as WorkcardDraft).status
64
- const primary = runtime.service().schemas.primaryFlow(resolved.type.type)
56
+ const primary = resolved.schemas.primaryFlow(resolved.type.type)
65
57
  if (status != null && !primary.statuses.some(definition => definition.key === status)) {
66
58
  throw new IllegalTransition(`${primary.id}:create:${status}`)
67
59
  }
@@ -77,8 +69,8 @@ const assertFlow = (exec: TransitionExecution, resolved: Resolved, runtime: Plan
77
69
  }
78
70
  }
79
71
 
80
- const assertFields = (exec: TransitionExecution, resolved: Resolved, runtime: PlanningRuntime): void => {
81
- const schemas = runtime.service().schemas
72
+ const assertFields = (exec: TransitionExecution, resolved: Resolved): void => {
73
+ const schemas = resolved.schemas
82
74
  if (resolved.create) {
83
75
  const draft = exec.card as WorkcardDraft
84
76
  validateFields(schemas, resolved.type.type, mergeFields(draft.fields, exec.changes?.fields))
@@ -127,18 +119,20 @@ const assertParentMove = async (
127
119
  if (found == null) {
128
120
  throw new ParentNotFound(id)
129
121
  }
130
- assertChildAllowed(runtime, found, resolved.card!.kind, resolved.card!.type)
122
+ resolveHelper.assertChildAllowed(
123
+ runtime, found, resolved.card!.kind, resolved.card!.type, await resolveHelper.childViewOf(runtime, facade.scope.entityId, found)
124
+ )
131
125
  }
132
126
  }
133
127
 
134
128
  const assertSpecification = async (
135
- exec: TransitionExecution, resolved: Resolved, runtime: PlanningRuntime, facade: PlanningFacade
129
+ exec: TransitionExecution, resolved: Resolved, facade: PlanningFacade
136
130
  ): Promise<void> => {
137
131
  const kind = resolved.create ? (exec.card as WorkcardDraft).kind : resolved.card!.kind
138
132
  if (kind !== WorkcardKind.Specification) {
139
133
  return
140
134
  }
141
- const schemas = runtime.service().schemas
135
+ const schemas = resolved.schemas
142
136
 
143
137
  if (resolved.create) {
144
138
  const draft = exec.card as WorkcardDraft
@@ -232,8 +226,9 @@ const assertRelationships = async (
232
226
  }
233
227
 
234
228
  /**
235
- * Step 6: shape, flow rule, immutables, `fields`, labels, parent moves, the specification slot and
236
- * relationships — in that order. Nothing is written by a refusal here.
229
+ * Step 6: shape, flow rule, immutables (`createdBy` among them, {@link assertCreatorFixed}),
230
+ * `fields`, labels, parent moves, the specification slot and relationships — in that order.
231
+ * Nothing is written by a refusal here.
237
232
  *
238
233
  * @throws {PlanningError} `malformed:*` / `immutable:*`
239
234
  * @throws {IllegalTransition | FieldsInvalid | LabelNotAllowed | ParentNotFound | CardTypeNotAllowed}
@@ -243,11 +238,12 @@ export const validateExecution = async (
243
238
  runtime: PlanningRuntime, facade: PlanningFacade, exec: TransitionExecution, resolved: Resolved
244
239
  ): Promise<void> => {
245
240
  assertShape(exec, resolved)
246
- assertFlow(exec, resolved, runtime)
241
+ assertFlow(exec, resolved)
247
242
  assertMutable(exec, resolved.type)
248
- assertFields(exec, resolved, runtime)
243
+ assertCreatorFixed(exec)
244
+ assertFields(exec, resolved)
249
245
  assertLabels(exec, resolved)
250
246
  await assertParentMove(exec, resolved, runtime, facade)
251
- await assertSpecification(exec, resolved, runtime, facade)
247
+ await assertSpecification(exec, resolved, facade)
252
248
  await assertRelationships(exec, resolved, facade)
253
249
  }
package/src/executor.ts CHANGED
@@ -1,17 +1,16 @@
1
1
  import { uuid } from '@owlmeans/basic-ids'
2
2
  import type { BasicConfig, BasicContext } from '@owlmeans/context'
3
- import { PlanningError, PlanningUnsupported, WorkcardConflict } from '@owlmeans/planning'
4
- import type {
5
- ExecuteOptions, PlanningExecContext, PlanningFacade, PlanningPlugin, PlanningStore, Transition,
6
- TransitionExecution, TransitionReceipt,
7
- } from '@owlmeans/planning'
3
+ import { PlanningError, PlanningUnsupported, WorkcardConflict, type ExecuteOptions, type PlanningExecContext, type PlanningFacade, type PlanningPlugin, type PlanningStore, type Transition, type TransitionExecution, type TransitionReceipt } from '@owlmeans/planning'
8
4
  import { changeSetOf, transitionOf } from './executor/changes.js'
9
5
  import { assignCode } from './executor/code.js'
6
+ import { withCreator } from './executor/creator.js'
10
7
  import { makeReceipt } from './executor/receipt.js'
11
- import { normalizeExecution, projectFor, resolveExecution } from './executor/resolve.js'
12
- import type { Resolved } from './executor/resolve.js'
8
+ import type { Resolved } from './executor/types.js'
13
9
  import { validateExecution } from './executor/validate.js'
14
- import type { PlanningRuntime } from './service.js'
10
+ import type { PlanningRuntime } from './types.js'
11
+ import { resolveHelper } from './executor/resolve.js'
12
+
13
+ export { assertCreatorFixed, creatorOf, withCreator } from './executor/creator.js'
15
14
 
16
15
  const isoNow = (): string => new Date().toISOString()
17
16
 
@@ -25,7 +24,7 @@ const execContextOf = (
25
24
  ) => (plugin: PlanningPlugin): PlanningExecContext => ({
26
25
  context: runtime.context() as BasicContext<BasicConfig>,
27
26
  scope: facade.scope,
28
- schemas: runtime.service().schemas,
27
+ schemas: resolved.schemas,
29
28
  store: resolved.store,
30
29
  facade,
31
30
  plugin,
@@ -64,11 +63,13 @@ const currentReceipt = async (
64
63
  /**
65
64
  * THE write path. Nothing is appended before step 11, so every refusal leaves the log untouched.
66
65
  *
67
- * 1 normalize → 2 scope → 3 idempotency (a known `key` answers its first receipt, before any
68
- * validation) → 4 resolve → 5 the plugins' `before` chain (re-resolved once when it moved the type
69
- * or the parent) → 6 validate → 7 allocate the card id → 8 code → 9 changes (an update changing
70
- * nothing answers the current receipt and appends nothing) → 10 seq, a CAS against the head →
71
- * 11 append → 12 request the projection → 13 receipt → 14 `wait`.
66
+ * 1 normalize (a create's `createdBy` defaults to the scope's subject — {@link withCreator}) →
67
+ * 2 scope → 3 idempotency (a known `key` answers its first receipt, before any validation) →
68
+ * 4 resolve → 5 the plugins' `before` chain (re-resolved once when it moved the type or the parent)
69
+ * → 6 validate (a `createdBy` in `changes`/`unset` is refused — {@link assertCreatorFixed}) →
70
+ * 7 allocate the card id → 8 code → 9 changes (an update changing nothing answers
71
+ * the current receipt and appends nothing) → 10 seq, a CAS against the head → 11 append →
72
+ * 12 request the projection → 13 receipt → 14 `wait`.
72
73
  *
73
74
  * `after` hooks are NOT run here — the process that folds runs them, once per commit.
74
75
  */
@@ -76,10 +77,9 @@ export const executeTransition = async (
76
77
  runtime: PlanningRuntime, facade: PlanningFacade, input: TransitionExecution, opts?: ExecuteOptions
77
78
  ): Promise<TransitionReceipt> => {
78
79
  const scope = facade.scope
79
- const service = runtime.service()
80
80
  const at = (runtime.options.now ?? isoNow)()
81
81
 
82
- let exec = normalizeExecution(input)
82
+ let exec = withCreator(resolveHelper.normalizeExecution(input), scope)
83
83
 
84
84
  if (scope.entityId == null || scope.entityId === '') {
85
85
  throw new PlanningError('malformed:scope-without-entity')
@@ -92,12 +92,12 @@ export const executeTransition = async (
92
92
  }
93
93
  }
94
94
 
95
- let resolved = await resolveExecution(runtime, facade, exec)
95
+ let resolved = await resolveHelper.resolveExecution(runtime, facade, exec)
96
96
 
97
97
  const before = identityOf(exec)
98
- exec = normalizeExecution(await runtime.registry.before(exec, execContextOf(runtime, facade, resolved)))
98
+ exec = resolveHelper.normalizeExecution(await runtime.registry.before(exec, execContextOf(runtime, facade, resolved)))
99
99
  if (identityOf(exec) !== before) {
100
- resolved = await resolveExecution(runtime, facade, exec)
100
+ resolved = await resolveHelper.resolveExecution(runtime, facade, exec)
101
101
  }
102
102
 
103
103
  await validateExecution(runtime, facade, exec, resolved)
@@ -114,7 +114,7 @@ export const executeTransition = async (
114
114
 
115
115
  exec = await assignCode(exec, resolved, scope, runtime.registry, execContextOf(runtime, facade, resolved))
116
116
 
117
- const { set, empty } = changeSetOf(exec, resolved, service.schemas, at)
117
+ const { set, empty } = changeSetOf(exec, resolved, resolved.schemas, at)
118
118
  if (empty) {
119
119
  return await currentReceipt(store, resolved, scope.entityId, opts)
120
120
  }
@@ -126,7 +126,7 @@ export const executeTransition = async (
126
126
 
127
127
  const transition = transitionOf({
128
128
  exec, resolved, scope, set, cardId, seq, at,
129
- project: projectFor(resolved, exec, cardId),
129
+ project: resolveHelper.projectFor(resolved, exec, cardId),
130
130
  })
131
131
 
132
132
  let appended: Transition