@owlmeans/planning 0.1.18-rc.9 → 0.1.18

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 (295) hide show
  1. package/README.md +122 -29
  2. package/agent-meta/manifest.json +131 -4
  3. package/agent-meta/skills/planning/SKILL.md +78 -171
  4. package/agent-meta/skills/planning/parts/collaboration.md +67 -0
  5. package/agent-meta/skills/planning/parts/errors.md +51 -0
  6. package/agent-meta/skills/planning/parts/fold.md +69 -0
  7. package/agent-meta/skills/planning/parts/mounting.md +64 -0
  8. package/agent-meta/skills/planning/parts/types.md +141 -0
  9. package/build/aliases/types.d.ts +72 -0
  10. package/build/aliases/types.d.ts.map +1 -0
  11. package/build/aliases/types.js +2 -0
  12. package/build/aliases/types.js.map +1 -0
  13. package/build/aliases.d.ts +4 -0
  14. package/build/aliases.d.ts.map +1 -0
  15. package/build/aliases.js +28 -0
  16. package/build/aliases.js.map +1 -0
  17. package/build/consts.d.ts +43 -34
  18. package/build/consts.d.ts.map +1 -1
  19. package/build/consts.js +49 -25
  20. package/build/consts.js.map +1 -1
  21. package/build/entrypoints/types.d.ts +105 -0
  22. package/build/entrypoints/types.d.ts.map +1 -0
  23. package/build/entrypoints/types.js +2 -0
  24. package/build/entrypoints/types.js.map +1 -0
  25. package/build/entrypoints.d.ts +5 -1
  26. package/build/entrypoints.d.ts.map +1 -1
  27. package/build/entrypoints.js +24 -6
  28. package/build/entrypoints.js.map +1 -1
  29. package/build/errors.d.ts +41 -0
  30. package/build/errors.d.ts.map +1 -1
  31. package/build/errors.js +61 -0
  32. package/build/errors.js.map +1 -1
  33. package/build/helpers/app/consts.local.d.ts +5 -0
  34. package/build/helpers/app/consts.local.d.ts.map +1 -0
  35. package/build/helpers/app/consts.local.js +5 -0
  36. package/build/helpers/app/consts.local.js.map +1 -0
  37. package/build/helpers/app/types.d.ts +53 -0
  38. package/build/helpers/app/types.d.ts.map +1 -0
  39. package/build/helpers/app/types.js +2 -0
  40. package/build/helpers/app/types.js.map +1 -0
  41. package/build/helpers/app.d.ts +4 -0
  42. package/build/helpers/app.d.ts.map +1 -0
  43. package/build/helpers/app.js +139 -0
  44. package/build/helpers/app.js.map +1 -0
  45. package/build/helpers/apply/consts.local.d.ts +7 -0
  46. package/build/helpers/apply/consts.local.d.ts.map +1 -0
  47. package/build/helpers/apply/consts.local.js +9 -0
  48. package/build/helpers/apply/consts.local.js.map +1 -0
  49. package/build/helpers/apply/types.d.ts +32 -0
  50. package/build/helpers/apply/types.d.ts.map +1 -0
  51. package/build/helpers/apply/types.js +2 -0
  52. package/build/helpers/apply/types.js.map +1 -0
  53. package/build/helpers/apply.d.ts +5 -23
  54. package/build/helpers/apply.d.ts.map +1 -1
  55. package/build/helpers/apply.js +127 -144
  56. package/build/helpers/apply.js.map +1 -1
  57. package/build/helpers/card/types.d.ts +20 -0
  58. package/build/helpers/card/types.d.ts.map +1 -0
  59. package/build/helpers/card/types.js +2 -0
  60. package/build/helpers/card/types.js.map +1 -0
  61. package/build/helpers/card.d.ts +11 -7
  62. package/build/helpers/card.d.ts.map +1 -1
  63. package/build/helpers/card.js +41 -28
  64. package/build/helpers/card.js.map +1 -1
  65. package/build/helpers/changes/consts.local.d.ts +13 -0
  66. package/build/helpers/changes/consts.local.d.ts.map +1 -0
  67. package/build/helpers/changes/consts.local.js +13 -0
  68. package/build/helpers/changes/consts.local.js.map +1 -0
  69. package/build/helpers/changes/types.d.ts +58 -0
  70. package/build/helpers/changes/types.d.ts.map +1 -0
  71. package/build/helpers/changes/types.js +2 -0
  72. package/build/helpers/changes/types.js.map +1 -0
  73. package/build/helpers/changes.d.ts +11 -42
  74. package/build/helpers/changes.d.ts.map +1 -1
  75. package/build/helpers/changes.js +237 -251
  76. package/build/helpers/changes.js.map +1 -1
  77. package/build/helpers/code/types.d.ts +27 -0
  78. package/build/helpers/code/types.d.ts.map +1 -0
  79. package/build/helpers/code/types.js +2 -0
  80. package/build/helpers/code/types.js.map +1 -0
  81. package/build/helpers/code.d.ts +6 -17
  82. package/build/helpers/code.d.ts.map +1 -1
  83. package/build/helpers/code.js +39 -40
  84. package/build/helpers/code.js.map +1 -1
  85. package/build/helpers/index.d.ts +16 -0
  86. package/build/helpers/index.d.ts.map +1 -1
  87. package/build/helpers/index.js +4 -0
  88. package/build/helpers/index.js.map +1 -1
  89. package/build/helpers/query/types.d.ts +23 -0
  90. package/build/helpers/query/types.d.ts.map +1 -0
  91. package/build/helpers/query/types.js +2 -0
  92. package/build/helpers/query/types.js.map +1 -0
  93. package/build/helpers/query.d.ts +25 -17
  94. package/build/helpers/query.d.ts.map +1 -1
  95. package/build/helpers/query.js +108 -280
  96. package/build/helpers/query.js.map +1 -1
  97. package/build/helpers/reply/consts.d.ts +2 -0
  98. package/build/helpers/reply/consts.d.ts.map +1 -0
  99. package/build/helpers/reply/consts.js +2 -0
  100. package/build/helpers/reply/consts.js.map +1 -0
  101. package/build/helpers/reply/types.d.ts +10 -0
  102. package/build/helpers/reply/types.d.ts.map +1 -0
  103. package/build/helpers/reply/types.js +2 -0
  104. package/build/helpers/reply/types.js.map +1 -0
  105. package/build/helpers/reply.d.ts +5 -0
  106. package/build/helpers/reply.d.ts.map +1 -0
  107. package/build/helpers/reply.js +29 -0
  108. package/build/helpers/reply.js.map +1 -0
  109. package/build/helpers/scoped/types.d.ts +73 -0
  110. package/build/helpers/scoped/types.d.ts.map +1 -0
  111. package/build/helpers/scoped/types.js +2 -0
  112. package/build/helpers/scoped/types.js.map +1 -0
  113. package/build/helpers/scoped.d.ts +25 -0
  114. package/build/helpers/scoped.d.ts.map +1 -0
  115. package/build/helpers/scoped.js +223 -0
  116. package/build/helpers/scoped.js.map +1 -0
  117. package/build/helpers/specification/types.d.ts +24 -0
  118. package/build/helpers/specification/types.d.ts.map +1 -0
  119. package/build/helpers/specification/types.js +2 -0
  120. package/build/helpers/specification/types.js.map +1 -0
  121. package/build/helpers/specification.d.ts +9 -13
  122. package/build/helpers/specification.d.ts.map +1 -1
  123. package/build/helpers/specification.js +47 -44
  124. package/build/helpers/specification.js.map +1 -1
  125. package/build/helpers/status/types.d.ts +48 -0
  126. package/build/helpers/status/types.d.ts.map +1 -0
  127. package/build/helpers/status/types.js +2 -0
  128. package/build/helpers/status/types.js.map +1 -0
  129. package/build/helpers/status.d.ts +16 -29
  130. package/build/helpers/status.d.ts.map +1 -1
  131. package/build/helpers/status.js +77 -73
  132. package/build/helpers/status.js.map +1 -1
  133. package/build/helpers/types.local.d.ts +17 -0
  134. package/build/helpers/types.local.d.ts.map +1 -0
  135. package/build/helpers/types.local.js +2 -0
  136. package/build/helpers/types.local.js.map +1 -0
  137. package/build/helpers/validate/types.d.ts +31 -0
  138. package/build/helpers/validate/types.d.ts.map +1 -0
  139. package/build/helpers/validate/types.js +2 -0
  140. package/build/helpers/validate/types.js.map +1 -0
  141. package/build/helpers/validate.d.ts +8 -19
  142. package/build/helpers/validate.d.ts.map +1 -1
  143. package/build/helpers/validate.js +79 -79
  144. package/build/helpers/validate.js.map +1 -1
  145. package/build/helpers/wire/types.d.ts +28 -0
  146. package/build/helpers/wire/types.d.ts.map +1 -0
  147. package/build/helpers/wire/types.js +2 -0
  148. package/build/helpers/wire/types.js.map +1 -0
  149. package/build/helpers/wire.d.ts +4 -0
  150. package/build/helpers/wire.d.ts.map +1 -0
  151. package/build/helpers/wire.js +216 -0
  152. package/build/helpers/wire.js.map +1 -0
  153. package/build/i18n/be.json +5 -0
  154. package/build/i18n/de.json +5 -0
  155. package/build/i18n/en.json +5 -0
  156. package/build/i18n/es.json +5 -0
  157. package/build/i18n/fr.json +5 -0
  158. package/build/i18n/pl.json +5 -0
  159. package/build/i18n/ru.json +5 -0
  160. package/build/i18n/uk.json +5 -0
  161. package/build/i18n.js +3 -3
  162. package/build/i18n.js.map +1 -1
  163. package/build/index.d.ts +7 -0
  164. package/build/index.d.ts.map +1 -1
  165. package/build/index.js +3 -0
  166. package/build/index.js.map +1 -1
  167. package/build/models/index.d.ts +2 -3
  168. package/build/models/index.d.ts.map +1 -1
  169. package/build/models/index.js +1 -15
  170. package/build/models/index.js.map +1 -1
  171. package/build/models/model.d.ts +5 -0
  172. package/build/models/model.d.ts.map +1 -0
  173. package/build/models/model.js +16 -0
  174. package/build/models/model.js.map +1 -0
  175. package/build/models/project.d.ts +2 -1
  176. package/build/models/project.d.ts.map +1 -1
  177. package/build/models/project.js.map +1 -1
  178. package/build/models/specification.d.ts +2 -1
  179. package/build/models/specification.d.ts.map +1 -1
  180. package/build/models/specification.js.map +1 -1
  181. package/build/models/types.d.ts +69 -0
  182. package/build/models/types.d.ts.map +1 -0
  183. package/build/models/types.js +2 -0
  184. package/build/models/types.js.map +1 -0
  185. package/build/models/workcard.d.ts +2 -1
  186. package/build/models/workcard.d.ts.map +1 -1
  187. package/build/models/workcard.js +12 -12
  188. package/build/models/workcard.js.map +1 -1
  189. package/build/registry.d.ts +3 -2
  190. package/build/registry.d.ts.map +1 -1
  191. package/build/registry.js +51 -3
  192. package/build/registry.js.map +1 -1
  193. package/build/resources/mentions.d.ts +4 -0
  194. package/build/resources/mentions.d.ts.map +1 -0
  195. package/build/resources/mentions.js +15 -0
  196. package/build/resources/mentions.js.map +1 -0
  197. package/build/resources/protocol/types.d.ts +30 -0
  198. package/build/resources/protocol/types.d.ts.map +1 -0
  199. package/build/resources/protocol/types.js +2 -0
  200. package/build/resources/protocol/types.js.map +1 -0
  201. package/build/resources/protocol.d.ts +6 -0
  202. package/build/resources/protocol.d.ts.map +1 -0
  203. package/build/resources/protocol.js +31 -0
  204. package/build/resources/protocol.js.map +1 -0
  205. package/build/resources/types.d.ts +146 -0
  206. package/build/resources/types.d.ts.map +1 -0
  207. package/build/resources/types.js +2 -0
  208. package/build/resources/types.js.map +1 -0
  209. package/build/resources/types.local.d.ts +6 -0
  210. package/build/resources/types.local.d.ts.map +1 -0
  211. package/build/resources/types.local.js +2 -0
  212. package/build/resources/types.local.js.map +1 -0
  213. package/build/schemas.d.ts +21 -2
  214. package/build/schemas.d.ts.map +1 -1
  215. package/build/schemas.js +164 -2
  216. package/build/schemas.js.map +1 -1
  217. package/build/types.d.ts +330 -151
  218. package/build/types.d.ts.map +1 -1
  219. package/build/utils.d.ts +6 -0
  220. package/build/utils.d.ts.map +1 -0
  221. package/build/utils.js +6 -0
  222. package/build/utils.js.map +1 -0
  223. package/package.json +13 -13
  224. package/src/aliases/types.ts +41 -0
  225. package/src/aliases.ts +34 -0
  226. package/src/consts.ts +50 -25
  227. package/src/entrypoints/types.ts +77 -0
  228. package/src/entrypoints.ts +53 -24
  229. package/src/errors.ts +71 -0
  230. package/src/helpers/app/consts.local.ts +5 -0
  231. package/src/helpers/app/types.ts +54 -0
  232. package/src/helpers/app.ts +158 -0
  233. package/src/helpers/apply/consts.local.ts +10 -0
  234. package/src/helpers/apply/types.ts +32 -0
  235. package/src/helpers/apply.ts +141 -153
  236. package/src/helpers/card/types.ts +20 -0
  237. package/src/helpers/card.ts +59 -33
  238. package/src/helpers/changes/consts.local.ts +17 -0
  239. package/src/helpers/changes/types.ts +66 -0
  240. package/src/helpers/changes.ts +270 -280
  241. package/src/helpers/code/types.ts +31 -0
  242. package/src/helpers/code.ts +52 -48
  243. package/src/helpers/index.ts +17 -0
  244. package/src/helpers/query/types.ts +26 -0
  245. package/src/helpers/query.ts +144 -314
  246. package/src/helpers/reply/consts.ts +1 -0
  247. package/src/helpers/reply/types.ts +13 -0
  248. package/src/helpers/reply.ts +27 -0
  249. package/src/helpers/scoped/types.ts +78 -0
  250. package/src/helpers/scoped.ts +281 -0
  251. package/src/helpers/specification/types.ts +26 -0
  252. package/src/helpers/specification.ts +69 -51
  253. package/src/helpers/status/types.ts +46 -0
  254. package/src/helpers/status.ts +117 -93
  255. package/src/helpers/types.local.ts +18 -0
  256. package/src/helpers/validate/types.ts +31 -0
  257. package/src/helpers/validate.ts +89 -77
  258. package/src/helpers/wire/types.ts +31 -0
  259. package/src/helpers/wire.ts +252 -0
  260. package/src/i18n/be.json +5 -0
  261. package/src/i18n/de.json +5 -0
  262. package/src/i18n/en.json +5 -0
  263. package/src/i18n/es.json +5 -0
  264. package/src/i18n/fr.json +5 -0
  265. package/src/i18n/pl.json +5 -0
  266. package/src/i18n/ru.json +5 -0
  267. package/src/i18n/uk.json +5 -0
  268. package/src/i18n.ts +4 -3
  269. package/src/index.ts +8 -0
  270. package/src/models/index.ts +2 -17
  271. package/src/models/model.ts +18 -0
  272. package/src/models/project.ts +2 -1
  273. package/src/models/specification.ts +2 -1
  274. package/src/models/types.ts +70 -0
  275. package/src/models/workcard.ts +14 -16
  276. package/src/registry.ts +55 -4
  277. package/src/resources/mentions.ts +11 -0
  278. package/src/resources/protocol/types.ts +21 -0
  279. package/src/resources/protocol.ts +36 -0
  280. package/src/resources/types.local.ts +5 -0
  281. package/src/resources/types.ts +148 -0
  282. package/src/schemas.ts +179 -4
  283. package/src/types.ts +334 -130
  284. package/src/utils.ts +9 -0
  285. package/tests/app.spec.ts +188 -0
  286. package/tests/apply.spec.ts +95 -31
  287. package/tests/code.spec.ts +9 -9
  288. package/tests/csp.spec.ts +122 -0
  289. package/tests/entrypoints.spec.ts +18 -2
  290. package/tests/error-status.spec.ts +6 -1
  291. package/tests/models.spec.ts +24 -16
  292. package/tests/query.spec.ts +13 -15
  293. package/tests/schemas.spec.ts +8 -8
  294. package/tests/scoped.spec.ts +161 -0
  295. package/tests/status.spec.ts +31 -22
@@ -26,6 +26,11 @@ const DECLARED: Record<string, number | undefined> = {
26
26
  LabelNotAllowed: 422,
27
27
  SpecificationSlotUnknown: 422,
28
28
  RelationshipRefused: 422,
29
+ SchemaConflict: 409,
30
+ SchemaInUse: 409,
31
+ SchemaSealed: 422,
32
+ SchemaInvalid: 422,
33
+ PlanningForbidden: 403,
29
34
  CommitTimeout: undefined,
30
35
  CommitFailed: undefined,
31
36
  PlanningUnsupported: undefined,
@@ -40,7 +45,7 @@ describe('planning refusals — declared HTTP statuses', () => {
40
45
  expect(classes.map(([name]) => name).sort()).toEqual(Object.keys(DECLARED).sort())
41
46
  })
42
47
 
43
- test('an absent card is 404, a card in another state 409, refused content 422; faults declare nothing', () => {
48
+ test('an absent card is 404, a missing grant 403, a card in another state 409, refused content 422; faults declare nothing', () => {
44
49
  for (const [name, Class] of classes) {
45
50
  expect([name, statusOf(new Class('x'))]).toEqual([name, DECLARED[name]])
46
51
  }
@@ -1,16 +1,17 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { applyQuery } from '@owlmeans/resource'
3
2
  import { CommitState, TransitionAction, WorkcardKind } from '../src/consts.js'
4
- import { WorkcardNotFound } from '../src/errors.js'
5
- import { applyTransition } from '../src/helpers/apply.js'
6
- import { computeChanges, isEmptyChange } from '../src/helpers/changes.js'
7
- import { criteriaOf, summaryOf } from '../src/helpers/query.js'
8
- import { currentSpecification, slotOf } from '../src/helpers/specification.js'
3
+ import { PlanningUnsupported, WorkcardNotFound } from '../src/errors.js'
9
4
  import { makeProjectModel, makeWorkcardModel, modelOf } from '../src/models/index.js'
5
+ import type { ProjectModel } from '../src/models/types.js'
10
6
  import type {
11
7
  PlanningFacade, Project, Specification, TransitionExecution, Workcard, WorkcardDraft,
12
8
  } from '../src/types.js'
13
9
  import { AT, ENTITY, PROJECT_TYPE, STORY_TYPE, makeRegistry } from './fixtures.js'
10
+ import { applyHelper } from '../src/helpers/apply.js'
11
+ import { changesHelper } from '../src/helpers/changes.js'
12
+ import { queryHelper } from '../src/helpers/query.js'
13
+ import { specificationHelper } from '../src/helpers/specification.js'
14
+ import { recordQueryHelper } from '@owlmeans/resource'
14
15
 
15
16
  /**
16
17
  * The smallest facade the models can run against: maps, folded synchronously with the package's
@@ -21,21 +22,28 @@ const makeFacade = () => {
21
22
  const cards = new Map<string, Workcard>()
22
23
  const executed: TransitionExecution[] = []
23
24
  let next = 0
25
+ const unsupported = async (): Promise<never> => { throw new PlanningUnsupported('model-fixture:resources') }
24
26
 
25
27
  const facade: PlanningFacade = {
28
+ assignees: { get: unsupported, load: unsupported, list: unsupported, create: unsupported, update: unsupported, retire: unsupported },
29
+ teams: { get: unsupported, load: unsupported, list: unsupported, create: unsupported, update: unsupported, remove: unsupported,
30
+ members: unsupported, addMember: unsupported, removeMember: unsupported, projects: unsupported,
31
+ attach: unsupported, detach: unsupported, assignees: unsupported },
32
+ comments: { get: unsupported, load: unsupported, list: unsupported, create: unsupported, update: unsupported, remove: unsupported },
33
+ mentions: { get: unsupported, load: unsupported, list: unsupported, rebuild: unsupported },
26
34
  scope: { entityId: ENTITY },
27
35
  schemas,
28
36
  cards: {
29
37
  get: async id => cards.get(id) ?? Promise.reject(new WorkcardNotFound(id)),
30
38
  load: async id => cards.get(id) ?? null,
31
- list: async query => applyQuery([...cards.values()], criteriaOf(query, facade.scope)),
32
- count: async query => applyQuery([...cards.values()], criteriaOf(query, facade.scope)).total,
39
+ list: async query => recordQueryHelper.applyQuery([...cards.values()], queryHelper.criteriaOf(query, facade.scope)),
40
+ count: async query => recordQueryHelper.applyQuery([...cards.values()], queryHelper.criteriaOf(query, facade.scope)).total,
33
41
  summary: async (parents, query) =>
34
- summaryOf(applyQuery([...cards.values()], criteriaOf(query, facade.scope)).items, parents),
42
+ queryHelper.summaryOf(recordQueryHelper.applyQuery([...cards.values()], queryHelper.criteriaOf(query, facade.scope)).items, parents),
35
43
  },
36
44
  specifications: {
37
- current: async (parent, category) => currentSpecification([...cards.values()].filter(card => card.parent === parent), category),
38
- list: async parent => applyQuery([...cards.values()], { parent, kind: WorkcardKind.Specification }) as never,
45
+ current: async (parent, category) => specificationHelper.currentSpecification([...cards.values()].filter(card => card.parent === parent), category),
46
+ list: async parent => recordQueryHelper.applyQuery([...cards.values()], { parent, kind: WorkcardKind.Specification }) as never,
39
47
  get: async id => cards.get(id) as Specification,
40
48
  revisions: async () => [],
41
49
  },
@@ -53,18 +61,18 @@ const makeFacade = () => {
53
61
  const type = schemas.type(card?.type ?? draft!.type)
54
62
  const parent = cards.get(card?.parent ?? draft?.parent ?? '')
55
63
  const category = (card as Specification | undefined)?.category ?? draft?.category
56
- const slot = parent != null && category != null ? slotOf(schemas.type(parent.type), category) : undefined
57
- const set = computeChanges(card, exec, type, schemas, AT, { slot })
64
+ const slot = parent != null && category != null ? specificationHelper.slotOf(schemas.type(parent.type), category) : undefined
65
+ const set = changesHelper.computeChanges(card, exec, type, schemas, AT, { slot })
58
66
  const id = card?.id ?? `card-${++next}`
59
67
  const transition = {
60
68
  id: `t-${++next}`, entityId: ENTITY, card: id, kind: card?.kind ?? draft!.kind, type: type.type,
61
69
  seq: (card?.head ?? card?.seq ?? 0) + 1, action: exec.action, changes: set.changes, unset: set.unset,
62
70
  actor: {}, at: AT, commit: { state: CommitState.Committed },
63
71
  }
64
- if (exec.action === TransitionAction.Update && isEmptyChange(set)) {
72
+ if (exec.action === TransitionAction.Update && changesHelper.isEmptyChange(set)) {
65
73
  return { transition, card, committed: async () => card ?? null }
66
74
  }
67
- const folded = applyTransition(card, transition)
75
+ const folded = applyHelper.applyTransition(card, transition)
68
76
  folded == null ? cards.delete(id) : cards.set(id, folded)
69
77
  return { transition, card: folded, committed: async () => folded }
70
78
  },
@@ -125,7 +133,7 @@ describe('write', () => {
125
133
  const project = await seed({ kind: WorkcardKind.Project, type: PROJECT_TYPE.type, title: 'Shop' })
126
134
  await seed({ kind: WorkcardKind.Card, type: STORY_TYPE.type, parent: project.id, title: 'Story', fields: { area: 'user', primary: false } })
127
135
 
128
- const model = await facade.model(project) as unknown as ReturnType<typeof makeProjectModel>
136
+ const model = await facade.model(project) as unknown as ProjectModel
129
137
 
130
138
  expect(await model.summary()).toEqual({ total: 1, planned: 1, 'in-progress': 0, closed: 0 })
131
139
  expect((await model.cards()).total).toBe(1)
@@ -1,13 +1,11 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { applyQuery } from '@owlmeans/resource'
3
2
  import { IntrinsicStatus, WorkcardKind } from '../src/consts.js'
4
3
  import { PlanningError } from '../src/errors.js'
5
- import {
6
- criteriaOf, decodeSummaryQuery, decodeWorkcardQuery, encodeSummaryQuery, encodeWorkcardQuery, listOptionsOf,
7
- summaryOf,
8
- } from '../src/helpers/query.js'
9
4
  import type { Workcard, WorkcardQuery } from '../src/types.js'
10
5
  import { AT, ENTITY } from './fixtures.js'
6
+ import { queryHelper } from '../src/helpers/query.js'
7
+ import { wireHelper } from '../src/helpers/wire.js'
8
+ import { recordQueryHelper } from '@owlmeans/resource'
11
9
 
12
10
  const card = (id: string, extra: Partial<Workcard>): Workcard => ({
13
11
  id, kind: WorkcardKind.Card, type: 'test:story', entityId: ENTITY, title: `Card ${id}`, parent: 'p1',
@@ -23,11 +21,11 @@ const CARDS = [
23
21
  ]
24
22
 
25
23
  const ids = (query: WorkcardQuery): string[] =>
26
- applyQuery(CARDS, criteriaOf(query, { entityId: ENTITY }), listOptionsOf(query)).items.map(item => item.id!)
24
+ recordQueryHelper.applyQuery(CARDS, queryHelper.criteriaOf(query, { entityId: ENTITY }), queryHelper.listOptionsOf(query)).items.map(item => item.id!)
27
25
 
28
26
  describe('criteriaOf', () => {
29
27
  test('translates the table, always scoped to the entity and omitting what is undefined', () => {
30
- expect(criteriaOf({
28
+ expect(queryHelper.criteriaOf({
31
29
  kind: WorkcardKind.Card, type: ['test:story', 'test:task'], parent: 'p1', within: 'p2', status: undefined,
32
30
  labels: ['ui'], ids: ['a'], flow: { id: 'test:story', status: ['planned'] }, fields: { area: 'user' },
33
31
  code: 'US-AAA', updatedSince: AT,
@@ -60,30 +58,30 @@ describe('wire queries', () => {
60
58
  flow: { id: 'test:story', status: ['planned', 'failed'] }, fields: { area: 'user', primary: true },
61
59
  q: 'sign in', page: 1, size: 20, sort: ['order', { field: 'updatedAt', order: 'desc' }],
62
60
  }
63
- const wire = encodeWorkcardQuery(query)
61
+ const wire = wireHelper.encodeWorkcardQuery(query)
64
62
 
65
63
  expect(Object.values(wire).every(value => typeof value === 'string' || typeof value === 'number')).toBe(true)
66
64
  expect(wire.sort).toBe('order,-updatedAt')
67
- expect(criteriaOf(decodeWorkcardQuery(wire), { entityId: ENTITY })).toEqual(criteriaOf(query, { entityId: ENTITY }))
68
- expect(listOptionsOf(decodeWorkcardQuery(wire))).toEqual({ page: 1, size: 20, sort: ['order', { field: 'updatedAt', order: 'desc' }] })
65
+ expect(queryHelper.criteriaOf(wireHelper.decodeWorkcardQuery(wire), { entityId: ENTITY })).toEqual(queryHelper.criteriaOf(query, { entityId: ENTITY }))
66
+ expect(queryHelper.listOptionsOf(wireHelper.decodeWorkcardQuery(wire))).toEqual({ page: 1, size: 20, sort: ['order', { field: 'updatedAt', order: 'desc' }] })
69
67
  })
70
68
 
71
69
  test('decode accepts what a transport may have split already, and coerces numbers', () => {
72
- expect(decodeWorkcardQuery({ status: ['planned', 'failed'], size: '10', labels: 'ui' } as never))
70
+ expect(wireHelper.decodeWorkcardQuery({ status: ['planned', 'failed'], size: '10', labels: 'ui' } as never))
73
71
  .toEqual({ status: ['planned', 'failed'], size: 10, labels: ['ui'] })
74
- expect(decodeSummaryQuery(encodeSummaryQuery({ parents: ['p1', 'p2'], type: 'test:story' })))
72
+ expect(wireHelper.decodeSummaryQuery(wireHelper.encodeSummaryQuery({ parents: ['p1', 'p2'], type: 'test:story' })))
75
73
  .toEqual({ parents: ['p1', 'p2'], type: 'test:story' })
76
74
  })
77
75
 
78
76
  test('a value that does not decode is a malformed query', () => {
79
- expect(() => decodeWorkcardQuery({ fields: '{broken' })).toThrow(PlanningError)
80
- expect(() => decodeWorkcardQuery({ size: 'many' } as never)).toThrow('malformed:query:size')
77
+ expect(() => wireHelper.decodeWorkcardQuery({ fields: '{broken' })).toThrow(PlanningError)
78
+ expect(() => wireHelper.decodeWorkcardQuery({ size: 'many' } as never)).toThrow('malformed:query:size')
81
79
  })
82
80
  })
83
81
 
84
82
  describe('summaryOf', () => {
85
83
  test('counts direct children per parent by intrinsic state, with no key for a parent without any', () => {
86
- expect(summaryOf(CARDS.filter(item => item.entityId === ENTITY), ['p1', 'p3'])).toEqual({
84
+ expect(queryHelper.summaryOf(CARDS.filter(item => item.entityId === ENTITY), ['p1', 'p3'])).toEqual({
87
85
  p1: { total: 2, planned: 1, 'in-progress': 0, closed: 1 },
88
86
  })
89
87
  })
@@ -1,11 +1,11 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
2
  import * as schemas from '../src/schemas.js'
3
3
  import { TransitionAction, WorkcardKind } from '../src/consts.js'
4
- import { makeAjv, validateCard } from '../src/helpers/validate.js'
5
- import { applyTransition } from '../src/helpers/apply.js'
6
- import { computeChanges } from '../src/helpers/changes.js'
4
+ import { makeAjv, validateHelper } from '../src/helpers/validate.js'
7
5
  import { FieldsInvalid } from '../src/errors.js'
8
6
  import { AT, STORY_TYPE, makeRegistry, transitionOf } from './fixtures.js'
7
+ import { applyHelper } from '../src/helpers/apply.js'
8
+ import { changesHelper } from '../src/helpers/changes.js'
9
9
 
10
10
  type Node = Record<string, unknown>
11
11
 
@@ -53,15 +53,15 @@ describe('schemas', () => {
53
53
  exported.forEach(([, schema]) => ajv.compile(schema as object))
54
54
 
55
55
  const registry = makeRegistry()
56
- const { changes } = computeChanges(undefined, {
56
+ const { changes } = changesHelper.computeChanges(undefined, {
57
57
  action: TransitionAction.Create,
58
58
  card: { kind: WorkcardKind.Card, type: STORY_TYPE.type, title: 'Story', fields: { area: 'user', primary: true } },
59
59
  }, STORY_TYPE, registry, AT)
60
- const card = applyTransition(undefined, transitionOf({ card: 'c1', seq: 1, action: TransitionAction.Create, changes }))!
60
+ const card = applyHelper.applyTransition(undefined, transitionOf({ card: 'c1', seq: 1, action: TransitionAction.Create, changes }))!
61
61
 
62
- expect(() => validateCard(card, registry)).not.toThrow()
63
- expect(() => validateCard({ ...card, fields: { area: 'moon', primary: true } }, registry)).toThrow(FieldsInvalid)
64
- expect(() => validateCard({ ...card, fields: { 'a.b': 1 } }, registry)).toThrow('keys:a.b')
62
+ expect(() => validateHelper.validateCard(card, registry)).not.toThrow()
63
+ expect(() => validateHelper.validateCard({ ...card, fields: { area: 'moon', primary: true } }, registry)).toThrow(FieldsInvalid)
64
+ expect(() => validateHelper.validateCard({ ...card, fields: { 'a.b': 1 } }, registry)).toThrow('keys:a.b')
65
65
  })
66
66
 
67
67
  test('an execute request accepts a card id or a create draft, and refuses an unknown action', () => {
@@ -0,0 +1,161 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import {
3
+ IntrinsicStatus, PlanningSchemaKind, SchemaOrigin, WorkcardKind,
4
+ } from '../src/consts.js'
5
+ import { PlanningUnsupported, SchemaInvalid, SchemaSealed } from '../src/errors.js'
6
+ import { makeSchemaRegistry } from '../src/registry.js'
7
+ import type {
8
+ ProjectTypeSchema, ScopedSchemaRecord, StatusFlowSchema, WorkcardTypeSchema,
9
+ } from '../src/types.js'
10
+ import { scopedSchemaHelper } from '../src/helpers/scoped.js'
11
+
12
+ /** A community garden: plots are tended through a season flow; a shared tool shed is sealed. */
13
+ const SEASON: StatusFlowSchema = {
14
+ id: 'garden:season',
15
+ version: 1,
16
+ overridable: true,
17
+ statuses: [
18
+ { key: 'fallow', intrinsic: IntrinsicStatus.Planned, initial: true },
19
+ { key: 'growing', intrinsic: IntrinsicStatus.InProgress },
20
+ { key: 'harvested', intrinsic: IntrinsicStatus.Closed },
21
+ ],
22
+ transitions: [
23
+ { name: 'sow', from: ['fallow'], to: 'growing' },
24
+ { name: 'harvest', from: ['growing'], to: 'harvested' },
25
+ ],
26
+ }
27
+
28
+ const ROTA: StatusFlowSchema = {
29
+ id: 'garden:rota',
30
+ version: 1,
31
+ statuses: [{ key: 'open', intrinsic: IntrinsicStatus.Planned, initial: true }],
32
+ transitions: [],
33
+ }
34
+
35
+ const PLOT: WorkcardTypeSchema = {
36
+ type: 'garden:plot', kind: WorkcardKind.Card, version: 1, overridable: true,
37
+ fields: { type: 'object' }, flows: [SEASON.id], specifications: [],
38
+ }
39
+
40
+ const SHED: WorkcardTypeSchema = {
41
+ type: 'garden:shed', kind: WorkcardKind.Card, version: 1,
42
+ fields: { type: 'object' }, flows: [ROTA.id], specifications: [],
43
+ }
44
+
45
+ const GARDEN: ProjectTypeSchema = {
46
+ type: 'garden:site', kind: WorkcardKind.Project, version: 1, fields: { type: 'object' },
47
+ flows: [ROTA.id], specifications: [], cardTypes: [PLOT.type], scopedCardTypes: true,
48
+ }
49
+
50
+ const code = makeSchemaRegistry({ flows: [SEASON, ROTA], types: [PLOT, SHED, GARDEN] })
51
+
52
+ const record = (patch: Partial<ScopedSchemaRecord> & Pick<ScopedSchemaRecord, 'kind' | 'key' | 'definition'>): ScopedSchemaRecord => ({
53
+ entityId: 'org-1', version: 1, createdAt: '2026-09-01T00:00:00.000Z', ...patch,
54
+ })
55
+
56
+ const BED: WorkcardTypeSchema = {
57
+ type: 'garden:bed', kind: WorkcardKind.Card, version: 1, fields: { type: 'object' }, flows: [SEASON.id], specifications: [],
58
+ }
59
+
60
+ describe('scoped schemas — layering', () => {
61
+ test('an organization record adds a type, a project record overrides it for that project only', () => {
62
+ const records = [
63
+ record({ kind: PlanningSchemaKind.Type, key: BED.type, definition: BED }),
64
+ record({ kind: PlanningSchemaKind.Type, key: BED.type, project: 'site-1', version: 3, definition: { ...BED, label: 'Raised bed' } }),
65
+ ]
66
+
67
+ const shared = scopedSchemaHelper.scopedRegistryOf(scopedSchemaHelper.resolveScopedBundle(code.bundle(), records, { entityId: 'org-1', revision: 2 }))
68
+ const site = scopedSchemaHelper.scopedRegistryOf(scopedSchemaHelper.resolveScopedBundle(code.bundle(), records, { entityId: 'org-1', project: 'site-1' }))
69
+ const other = scopedSchemaHelper.scopedRegistryOf(scopedSchemaHelper.resolveScopedBundle(code.bundle(), records, { entityId: 'org-2' }))
70
+
71
+ expect(shared.type(BED.type).label).toBeUndefined()
72
+ expect(shared.originOf(PlanningSchemaKind.Type, BED.type)).toBe(SchemaOrigin.Entity)
73
+ expect(shared.revision).toBe(2)
74
+ expect(site.type(BED.type)).toMatchObject({ label: 'Raised bed', version: 3 })
75
+ expect(site.originOf(PlanningSchemaKind.Type, BED.type)).toBe(SchemaOrigin.Project)
76
+ expect(site.originOf(PlanningSchemaKind.Type, PLOT.type)).toBe(SchemaOrigin.Code)
77
+ expect(other.has(BED.type)).toBe(false)
78
+ })
79
+
80
+ test('a sealed code key ignores a record; an overridable one takes it', () => {
81
+ const records = [
82
+ record({ kind: PlanningSchemaKind.Type, key: SHED.type, definition: { ...SHED, label: 'Hijacked' } }),
83
+ record({ kind: PlanningSchemaKind.Type, key: PLOT.type, definition: { ...PLOT, label: 'Allotment' } }),
84
+ record({ kind: PlanningSchemaKind.Type, key: GARDEN.type, definition: { ...BED, type: GARDEN.type } }),
85
+ ]
86
+ const view = scopedSchemaHelper.scopedRegistryOf(scopedSchemaHelper.resolveScopedBundle(code.bundle(), records, { entityId: 'org-1' }))
87
+
88
+ expect(view.type(SHED.type).label).toBeUndefined()
89
+ expect(view.type(PLOT.type).label).toBe('Allotment')
90
+ expect(view.type(GARDEN.type).kind).toBe(WorkcardKind.Project)
91
+ expect(() => scopedSchemaHelper.assertOverridable(code, PlanningSchemaKind.Type, SHED.type)).toThrow(SchemaSealed)
92
+ expect(() => scopedSchemaHelper.assertOverridable(code, PlanningSchemaKind.Type, GARDEN.type)).toThrow(SchemaSealed)
93
+ expect(() => scopedSchemaHelper.assertOverridable(code, PlanningSchemaKind.Flow, ROTA.id)).toThrow(SchemaSealed)
94
+ expect(() => scopedSchemaHelper.assertOverridable(code, PlanningSchemaKind.Flow, SEASON.id)).not.toThrow()
95
+ })
96
+
97
+ test('a retired record gives way to a live one below it, and resolves retired when none is left', () => {
98
+ const records = [
99
+ record({ kind: PlanningSchemaKind.Type, key: PLOT.type, retired: true, definition: { ...PLOT, label: 'Old' } }),
100
+ record({ kind: PlanningSchemaKind.Type, key: BED.type, retired: true, definition: BED }),
101
+ ]
102
+ const view = scopedSchemaHelper.scopedRegistryOf(scopedSchemaHelper.resolveScopedBundle(code.bundle(), records, { entityId: 'org-1' }))
103
+
104
+ expect(view.type(PLOT.type).label).toBeUndefined()
105
+ expect(view.isRetired(PlanningSchemaKind.Type, PLOT.type)).toBe(false)
106
+ expect(view.type(BED.type).type).toBe(BED.type)
107
+ expect(view.isRetired(PlanningSchemaKind.Type, BED.type)).toBe(true)
108
+ expect(view.bundle().retired).toEqual({ types: [BED.type], flows: [] })
109
+ })
110
+
111
+ test('the view is read-only', () => {
112
+ const view = scopedSchemaHelper.scopedRegistryOf(scopedSchemaHelper.resolveScopedBundle(code.bundle(), [], { entityId: 'org-1' }))
113
+
114
+ expect(() => view.registerType(BED)).toThrow(PlanningUnsupported)
115
+ expect(() => view.load(code.bundle())).toThrow(PlanningUnsupported)
116
+ expect(view.primaryFlow(PLOT.type).id).toBe(SEASON.id)
117
+ })
118
+
119
+ test('a flow retired with no live fallback is in use while a live type runs it', () => {
120
+ const flow: StatusFlowSchema = { ...ROTA, id: 'garden:watering' }
121
+ const using: WorkcardTypeSchema = { ...BED, type: 'garden:hose', flows: [flow.id] }
122
+ const retiredFlow = record({ kind: PlanningSchemaKind.Flow, key: flow.id, retired: true, definition: flow })
123
+ const live = scopedSchemaHelper.resolveScopedBundle(code.bundle(), [
124
+ retiredFlow, record({ kind: PlanningSchemaKind.Type, key: using.type, definition: using }),
125
+ ], { entityId: 'org-1' })
126
+ const idle = scopedSchemaHelper.resolveScopedBundle(code.bundle(), [
127
+ retiredFlow, record({ kind: PlanningSchemaKind.Type, key: using.type, retired: true, definition: using }),
128
+ ], { entityId: 'org-1' })
129
+
130
+ expect(scopedSchemaHelper.flowInUse(live, flow.id)).toBe(using.type)
131
+ expect(scopedSchemaHelper.flowInUse(idle, flow.id)).toBeUndefined()
132
+ })
133
+ })
134
+
135
+ describe('scoped schemas — closed-form checks', () => {
136
+ test('a flow needs unique statuses, declared targets and a from that is * or declared', () => {
137
+ expect(() => scopedSchemaHelper.assertFlowSchema(SEASON)).not.toThrow()
138
+ expect(() => scopedSchemaHelper.assertFlowSchema({ ...SEASON, statuses: [...SEASON.statuses, SEASON.statuses[0]] }))
139
+ .toThrow('status-repeated:fallow')
140
+ expect(() => scopedSchemaHelper.assertFlowSchema({ ...SEASON, transitions: [{ name: 'compost', from: '*', to: 'compost' }] }))
141
+ .toThrow('compost:to:compost')
142
+ expect(() => scopedSchemaHelper.assertFlowSchema({ ...SEASON, transitions: [{ name: 'sow', from: ['frozen'], to: 'growing' }] }))
143
+ .toThrow('sow:from:frozen')
144
+ expect(() => scopedSchemaHelper.assertFlowSchema({ ...SEASON, statuses: [] })).toThrow(SchemaInvalid)
145
+ })
146
+
147
+ test('a type must be a card with a compiling fields schema and unique flows that resolve', () => {
148
+ expect(() => scopedSchemaHelper.assertTypeSchema(BED, code)).not.toThrow()
149
+ expect(() => scopedSchemaHelper.assertTypeSchema({ ...BED, kind: WorkcardKind.Specification }, code)).toThrow('kind:specification')
150
+ expect(() => scopedSchemaHelper.assertTypeSchema({ ...BED, fields: { type: 'nonsense' } }, code)).toThrow('fields:')
151
+ expect(() => scopedSchemaHelper.assertTypeSchema({ ...BED, flows: [SEASON.id, SEASON.id] }, code)).toThrow('flow-repeated')
152
+ expect(() => scopedSchemaHelper.assertTypeSchema({ ...BED, flows: ['garden:frost'] }, code)).toThrow('flow:garden:frost')
153
+ })
154
+
155
+ test('a type and a flow may carry a tone and an icon as presentation hints; other keys stay refused', () => {
156
+ expect(() => scopedSchemaHelper.assertTypeSchema({ ...BED, tone: 'green', icon: 'sprout' }, code)).not.toThrow()
157
+ expect(() => scopedSchemaHelper.assertFlowSchema({ ...SEASON, tone: 'amber', icon: 'sun' })).not.toThrow()
158
+ expect(() => scopedSchemaHelper.assertTypeSchema({ ...BED, colour: 'green' } as WorkcardTypeSchema, code)).toThrow(SchemaInvalid)
159
+ expect(() => scopedSchemaHelper.assertFlowSchema({ ...SEASON, tone: 'x'.repeat(65) })).toThrow(SchemaInvalid)
160
+ })
161
+ })
@@ -1,55 +1,64 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
2
  import { IntrinsicStatus } from '../src/consts.js'
3
3
  import { UnknownStatusFlow, UnknownWorkcardType } from '../src/errors.js'
4
- import {
5
- canTransit, initialFlowsOf, initialStatusOf, intrinsicOf, resolveIntrinsic, ruleOf, transitionsFrom,
6
- } from '../src/helpers/status.js'
7
4
  import { PROJECT_TYPE, REVIEW_FLOW, STORY_FLOW, STORY_TYPE, TASK_TYPE, makeRegistry } from './fixtures.js'
5
+ import { statusHelper } from '../src/helpers/status.js'
8
6
 
9
7
  const registry = makeRegistry()
10
8
 
11
9
  describe('status flows', () => {
12
10
  test('every status maps onto its intrinsic state; the initial one is the marked one', () => {
13
- expect(intrinsicOf(STORY_FLOW, 'failed')).toBe(IntrinsicStatus.Planned)
14
- expect(intrinsicOf(STORY_FLOW, 'completed')).toBe(IntrinsicStatus.Closed)
15
- expect(intrinsicOf(STORY_FLOW, 'unknown')).toBeUndefined()
16
- expect(initialStatusOf(REVIEW_FLOW)).toBe('pending')
11
+ expect(statusHelper.intrinsicOf(STORY_FLOW, 'failed')).toBe(IntrinsicStatus.Planned)
12
+ expect(statusHelper.intrinsicOf(STORY_FLOW, 'completed')).toBe(IntrinsicStatus.Closed)
13
+ expect(statusHelper.intrinsicOf(STORY_FLOW, 'unknown')).toBeUndefined()
14
+ expect(statusHelper.initialStatusOf(REVIEW_FLOW)).toBe('pending')
17
15
  })
18
16
 
19
17
  test('a repeated name picks the rule whose from matches, and a wildcard answers last', () => {
20
- expect(ruleOf(STORY_FLOW, 'start', 'failed')?.to).toBe('in-progress')
21
- expect(ruleOf(STORY_FLOW, 'start', 'completed')).toBeUndefined()
22
- expect(ruleOf(REVIEW_FLOW, 'reopen', 'approved')?.to).toBe('reviewing')
23
- expect(ruleOf(REVIEW_FLOW, 'reopen', 'reviewing')?.to).toBe('pending')
18
+ expect(statusHelper.ruleOf(STORY_FLOW, 'start', 'failed')?.to).toBe('in-progress')
19
+ expect(statusHelper.ruleOf(STORY_FLOW, 'start', 'completed')).toBeUndefined()
20
+ expect(statusHelper.ruleOf(REVIEW_FLOW, 'reopen', 'approved')?.to).toBe('reviewing')
21
+ expect(statusHelper.ruleOf(REVIEW_FLOW, 'reopen', 'reviewing')?.to).toBe('pending')
22
+ })
23
+
24
+ test('a status the flow does not declare matches every rule of the name, the first declared answering', () => {
25
+ // A card whose flow changed under it: `abandoned` is no longer a status of the flow.
26
+ expect(statusHelper.ruleOf(STORY_FLOW, 'start', 'abandoned')?.to).toBe('in-progress')
27
+ expect(statusHelper.ruleOf(STORY_FLOW, 'complete', 'abandoned')?.to).toBe('completed')
28
+ expect(statusHelper.ruleOf(STORY_FLOW, 'reset', 'abandoned')?.to).toBe('planned')
29
+ expect(statusHelper.ruleOf(STORY_FLOW, 'nope', 'abandoned')).toBeUndefined()
30
+ // A declared status keeps its own rules only.
31
+ expect(statusHelper.ruleOf(STORY_FLOW, 'complete', 'planned')).toBeUndefined()
32
+ expect(statusHelper.transitionsFrom(STORY_FLOW, 'abandoned').map(rule => rule.name)).toEqual(['start', 'complete', 'fail', 'reset'])
24
33
  })
25
34
 
26
35
  test('canTransit and transitionsFrom read the same rules, one per name', () => {
27
- expect(canTransit(STORY_FLOW, 'reset', 'planned')).toBe(true)
28
- expect(canTransit(STORY_FLOW, 'complete', 'planned')).toBe(false)
29
- expect(transitionsFrom(STORY_FLOW, 'in-progress').map(rule => rule.name)).toEqual(['complete', 'fail', 'reset'])
30
- expect(transitionsFrom(STORY_FLOW, 'failed', { explicit: true }).map(rule => `${rule.name}→${rule.to}`))
36
+ expect(statusHelper.canTransit(STORY_FLOW, 'reset', 'planned')).toBe(true)
37
+ expect(statusHelper.canTransit(STORY_FLOW, 'complete', 'planned')).toBe(false)
38
+ expect(statusHelper.transitionsFrom(STORY_FLOW, 'in-progress').map(rule => rule.name)).toEqual(['complete', 'fail', 'reset'])
39
+ expect(statusHelper.transitionsFrom(STORY_FLOW, 'failed', { explicit: true }).map(rule => `${rule.name}→${rule.to}`))
31
40
  .toEqual(['start→in-progress', 'reset→planned'])
32
41
  })
33
42
  })
34
43
 
35
44
  describe('intrinsic resolution', () => {
36
45
  test('primary policy reads the primary flow only', () => {
37
- expect(resolveIntrinsic(STORY_TYPE, { 'test:story': 'in-progress' }, registry)).toBe(IntrinsicStatus.InProgress)
38
- expect(resolveIntrinsic(PROJECT_TYPE, { 'test:story': 'completed' }, registry)).toBe(IntrinsicStatus.Closed)
46
+ expect(statusHelper.resolveIntrinsic(STORY_TYPE, { 'test:story': 'in-progress' }, registry)).toBe(IntrinsicStatus.InProgress)
47
+ expect(statusHelper.resolveIntrinsic(PROJECT_TYPE, { 'test:story': 'completed' }, registry)).toBe(IntrinsicStatus.Closed)
39
48
  })
40
49
 
41
50
  test('IntrinsicPolicy.All takes the least advanced flow', () => {
42
- expect(resolveIntrinsic(TASK_TYPE, { 'test:story': 'completed', 'test:review': 'reviewing' }, registry))
51
+ expect(statusHelper.resolveIntrinsic(TASK_TYPE, { 'test:story': 'completed', 'test:review': 'reviewing' }, registry))
43
52
  .toBe(IntrinsicStatus.InProgress)
44
- expect(resolveIntrinsic(TASK_TYPE, { 'test:story': 'completed', 'test:review': 'approved' }, registry))
53
+ expect(statusHelper.resolveIntrinsic(TASK_TYPE, { 'test:story': 'completed', 'test:review': 'approved' }, registry))
45
54
  .toBe(IntrinsicStatus.Closed)
46
- expect(resolveIntrinsic(TASK_TYPE, { 'test:story': 'failed', 'test:review': 'approved' }, registry))
55
+ expect(statusHelper.resolveIntrinsic(TASK_TYPE, { 'test:story': 'failed', 'test:review': 'approved' }, registry))
47
56
  .toBe(IntrinsicStatus.Planned)
48
57
  })
49
58
 
50
59
  test('initialFlowsOf starts every flow at its initial status, the primary at the draft status', () => {
51
- expect(initialFlowsOf(TASK_TYPE, registry)).toEqual({ 'test:story': 'planned', 'test:review': 'pending' })
52
- expect(initialFlowsOf(TASK_TYPE, registry, 'failed')).toEqual({ 'test:story': 'failed', 'test:review': 'pending' })
60
+ expect(statusHelper.initialFlowsOf(TASK_TYPE, registry)).toEqual({ 'test:story': 'planned', 'test:review': 'pending' })
61
+ expect(statusHelper.initialFlowsOf(TASK_TYPE, registry, 'failed')).toEqual({ 'test:story': 'failed', 'test:review': 'pending' })
53
62
  })
54
63
  })
55
64