@owlmeans/server-planning 0.1.18-rc.8 → 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 (438) hide show
  1. package/README.md +92 -19
  2. package/agent-meta/manifest.json +105 -4
  3. package/agent-meta/skills/server-planning/SKILL.md +89 -138
  4. package/agent-meta/skills/server-planning/parts/auxiliary.md +54 -0
  5. package/agent-meta/skills/server-planning/parts/executor.md +82 -0
  6. package/agent-meta/skills/server-planning/parts/schemas.md +26 -0
  7. package/agent-meta/skills/server-planning/parts/serving.md +88 -0
  8. package/agent-meta/skills/server-planning/parts/stores.md +91 -0
  9. package/build/actions/apply-schema-request.d.ts +8 -0
  10. package/build/actions/apply-schema-request.d.ts.map +1 -0
  11. package/build/actions/apply-schema-request.js +33 -0
  12. package/build/actions/apply-schema-request.js.map +1 -0
  13. package/build/actions/commit.d.ts +3 -3
  14. package/build/actions/commit.d.ts.map +1 -1
  15. package/build/actions/commit.js +5 -4
  16. package/build/actions/commit.js.map +1 -1
  17. package/build/actions/define-schemas.d.ts +13 -0
  18. package/build/actions/define-schemas.d.ts.map +1 -0
  19. package/build/actions/define-schemas.js +28 -0
  20. package/build/actions/define-schemas.js.map +1 -0
  21. package/build/actions/execute.d.ts +11 -10
  22. package/build/actions/execute.d.ts.map +1 -1
  23. package/build/actions/execute.js +17 -27
  24. package/build/actions/execute.js.map +1 -1
  25. package/build/actions/execution/types.d.ts +34 -0
  26. package/build/actions/execution/types.d.ts.map +1 -0
  27. package/build/actions/execution/types.js +2 -0
  28. package/build/actions/execution/types.js.map +1 -0
  29. package/build/actions/execution.d.ts +4 -0
  30. package/build/actions/execution.d.ts.map +1 -0
  31. package/build/actions/execution.js +88 -0
  32. package/build/actions/execution.js.map +1 -0
  33. package/build/actions/get-card.d.ts +6 -0
  34. package/build/actions/get-card.d.ts.map +1 -0
  35. package/build/actions/get-card.js +9 -0
  36. package/build/actions/get-card.js.map +1 -0
  37. package/build/actions/get-specification.d.ts +6 -0
  38. package/build/actions/get-specification.d.ts.map +1 -0
  39. package/build/actions/get-specification.js +9 -0
  40. package/build/actions/get-specification.js.map +1 -0
  41. package/build/actions/index.d.ts +12 -3
  42. package/build/actions/index.d.ts.map +1 -1
  43. package/build/actions/index.js +11 -3
  44. package/build/actions/index.js.map +1 -1
  45. package/build/actions/links.d.ts +3 -3
  46. package/build/actions/links.d.ts.map +1 -1
  47. package/build/actions/links.js +6 -5
  48. package/build/actions/links.js.map +1 -1
  49. package/build/actions/list-card-specifications.d.ts +6 -0
  50. package/build/actions/list-card-specifications.d.ts.map +1 -0
  51. package/build/actions/list-card-specifications.js +11 -0
  52. package/build/actions/list-card-specifications.js.map +1 -0
  53. package/build/actions/list-card-transitions.d.ts +6 -0
  54. package/build/actions/list-card-transitions.d.ts.map +1 -0
  55. package/build/actions/list-card-transitions.js +11 -0
  56. package/build/actions/list-card-transitions.js.map +1 -0
  57. package/build/actions/list-cards.d.ts +6 -0
  58. package/build/actions/list-cards.d.ts.map +1 -0
  59. package/build/actions/list-cards.js +10 -0
  60. package/build/actions/list-cards.js.map +1 -0
  61. package/build/actions/list-schemas.d.ts +13 -0
  62. package/build/actions/list-schemas.d.ts.map +1 -0
  63. package/build/actions/list-schemas.js +20 -0
  64. package/build/actions/list-schemas.js.map +1 -0
  65. package/build/actions/list-specification-revisions.d.ts +6 -0
  66. package/build/actions/list-specification-revisions.d.ts.map +1 -0
  67. package/build/actions/list-specification-revisions.js +10 -0
  68. package/build/actions/list-specification-revisions.js.map +1 -0
  69. package/build/actions/resources.d.ts +5 -0
  70. package/build/actions/resources.d.ts.map +1 -0
  71. package/build/actions/resources.js +111 -0
  72. package/build/actions/resources.js.map +1 -0
  73. package/build/actions/summarize-cards.d.ts +6 -0
  74. package/build/actions/summarize-cards.d.ts.map +1 -0
  75. package/build/actions/summarize-cards.js +11 -0
  76. package/build/actions/summarize-cards.js.map +1 -0
  77. package/build/actions/transitions.d.ts +3 -3
  78. package/build/actions/transitions.d.ts.map +1 -1
  79. package/build/actions/transitions.js +4 -3
  80. package/build/actions/transitions.js.map +1 -1
  81. package/build/actions/types.d.ts +6 -0
  82. package/build/actions/types.d.ts.map +1 -0
  83. package/build/actions/types.js +2 -0
  84. package/build/actions/types.js.map +1 -0
  85. package/build/actions/watch.d.ts +2 -2
  86. package/build/actions/watch.d.ts.map +1 -1
  87. package/build/actions/watch.js +8 -6
  88. package/build/actions/watch.js.map +1 -1
  89. package/build/conformance/assert/types.d.ts +19 -0
  90. package/build/conformance/assert/types.d.ts.map +1 -0
  91. package/build/conformance/assert/types.js +2 -0
  92. package/build/conformance/assert/types.js.map +1 -0
  93. package/build/conformance/assert.d.ts +15 -0
  94. package/build/conformance/assert.d.ts.map +1 -0
  95. package/build/conformance/assert.js +52 -0
  96. package/build/conformance/assert.js.map +1 -0
  97. package/build/conformance/cases.d.ts +7 -0
  98. package/build/conformance/cases.d.ts.map +1 -0
  99. package/build/conformance/cases.js +544 -0
  100. package/build/conformance/cases.js.map +1 -0
  101. package/build/conformance/clock.d.ts +6 -0
  102. package/build/conformance/clock.d.ts.map +1 -0
  103. package/build/conformance/clock.js +12 -0
  104. package/build/conformance/clock.js.map +1 -0
  105. package/build/conformance/consts.d.ts +30 -0
  106. package/build/conformance/consts.d.ts.map +1 -0
  107. package/build/conformance/consts.js +159 -0
  108. package/build/conformance/consts.js.map +1 -0
  109. package/build/conformance/errors.d.ts +5 -0
  110. package/build/conformance/errors.d.ts.map +1 -0
  111. package/build/conformance/errors.js +8 -0
  112. package/build/conformance/errors.js.map +1 -0
  113. package/build/conformance/fixtures/types.d.ts +13 -0
  114. package/build/conformance/fixtures/types.d.ts.map +1 -0
  115. package/build/conformance/fixtures/types.js +2 -0
  116. package/build/conformance/fixtures/types.js.map +1 -0
  117. package/build/conformance/fixtures.d.ts +14 -0
  118. package/build/conformance/fixtures.d.ts.map +1 -0
  119. package/build/conformance/fixtures.js +26 -0
  120. package/build/conformance/fixtures.js.map +1 -0
  121. package/build/conformance/index.d.ts +15 -0
  122. package/build/conformance/index.d.ts.map +1 -0
  123. package/build/conformance/index.js +12 -0
  124. package/build/conformance/index.js.map +1 -0
  125. package/build/conformance/resources.d.ts +4 -0
  126. package/build/conformance/resources.d.ts.map +1 -0
  127. package/build/conformance/resources.js +228 -0
  128. package/build/conformance/resources.js.map +1 -0
  129. package/build/conformance/types.d.ts +18 -0
  130. package/build/conformance/types.d.ts.map +1 -0
  131. package/build/conformance/types.js +2 -0
  132. package/build/conformance/types.js.map +1 -0
  133. package/build/consts.d.ts +5 -0
  134. package/build/consts.d.ts.map +1 -1
  135. package/build/consts.js +5 -0
  136. package/build/consts.js.map +1 -1
  137. package/build/definitions.d.ts +18 -0
  138. package/build/definitions.d.ts.map +1 -0
  139. package/build/definitions.js +225 -0
  140. package/build/definitions.js.map +1 -0
  141. package/build/executor/changes/types.d.ts +36 -0
  142. package/build/executor/changes/types.d.ts.map +1 -0
  143. package/build/executor/changes/types.js +2 -0
  144. package/build/executor/changes/types.js.map +1 -0
  145. package/build/executor/changes.d.ts +3 -30
  146. package/build/executor/changes.d.ts.map +1 -1
  147. package/build/executor/changes.js +42 -51
  148. package/build/executor/changes.js.map +1 -1
  149. package/build/executor/code/types.d.ts +17 -0
  150. package/build/executor/code/types.d.ts.map +1 -0
  151. package/build/executor/code/types.js +2 -0
  152. package/build/executor/code/types.js.map +1 -0
  153. package/build/executor/code.d.ts +3 -14
  154. package/build/executor/code.d.ts.map +1 -1
  155. package/build/executor/code.js +50 -54
  156. package/build/executor/code.js.map +1 -1
  157. package/build/executor/consts.local.d.ts +5 -0
  158. package/build/executor/consts.local.d.ts.map +1 -0
  159. package/build/executor/consts.local.js +6 -0
  160. package/build/executor/consts.local.js.map +1 -0
  161. package/build/executor/creator/types.d.ts +33 -0
  162. package/build/executor/creator/types.d.ts.map +1 -0
  163. package/build/executor/creator/types.js +2 -0
  164. package/build/executor/creator/types.js.map +1 -0
  165. package/build/executor/creator.d.ts +11 -0
  166. package/build/executor/creator.d.ts.map +1 -0
  167. package/build/executor/creator.js +32 -0
  168. package/build/executor/creator.js.map +1 -0
  169. package/build/executor/resolve/types.d.ts +30 -0
  170. package/build/executor/resolve/types.d.ts.map +1 -0
  171. package/build/executor/resolve/types.js +2 -0
  172. package/build/executor/resolve/types.js.map +1 -0
  173. package/build/executor/resolve.d.ts +5 -34
  174. package/build/executor/resolve.d.ts.map +1 -1
  175. package/build/executor/resolve.js +169 -110
  176. package/build/executor/resolve.js.map +1 -1
  177. package/build/executor/types.d.ts +23 -0
  178. package/build/executor/types.d.ts.map +1 -0
  179. package/build/executor/types.js +2 -0
  180. package/build/executor/types.js.map +1 -0
  181. package/build/executor/validate.d.ts +6 -5
  182. package/build/executor/validate.d.ts.map +1 -1
  183. package/build/executor/validate.js +38 -27
  184. package/build/executor/validate.js.map +1 -1
  185. package/build/executor.d.ts +11 -7
  186. package/build/executor.d.ts.map +1 -1
  187. package/build/executor.js +27 -21
  188. package/build/executor.js.map +1 -1
  189. package/build/facade.d.ts +20 -2
  190. package/build/facade.d.ts.map +1 -1
  191. package/build/facade.js +144 -22
  192. package/build/facade.js.map +1 -1
  193. package/build/helper.d.ts +5 -9
  194. package/build/helper.d.ts.map +1 -1
  195. package/build/helper.js +12 -11
  196. package/build/helper.js.map +1 -1
  197. package/build/hierarchy/types.d.ts +20 -0
  198. package/build/hierarchy/types.d.ts.map +1 -0
  199. package/build/hierarchy/types.js +2 -0
  200. package/build/hierarchy/types.js.map +1 -0
  201. package/build/hierarchy.d.ts +5 -0
  202. package/build/hierarchy.d.ts.map +1 -0
  203. package/build/hierarchy.js +133 -0
  204. package/build/hierarchy.js.map +1 -0
  205. package/build/index.d.ts +1 -0
  206. package/build/index.d.ts.map +1 -1
  207. package/build/index.js +1 -0
  208. package/build/index.js.map +1 -1
  209. package/build/invariants/types.d.ts +6 -0
  210. package/build/invariants/types.d.ts.map +1 -0
  211. package/build/invariants/types.js +2 -0
  212. package/build/invariants/types.js.map +1 -0
  213. package/build/invariants.d.ts +5 -0
  214. package/build/invariants.d.ts.map +1 -0
  215. package/build/invariants.js +126 -0
  216. package/build/invariants.js.map +1 -0
  217. package/build/projection.d.ts +4 -10
  218. package/build/projection.d.ts.map +1 -1
  219. package/build/projection.js +7 -5
  220. package/build/projection.js.map +1 -1
  221. package/build/queue/consts.d.ts +5 -0
  222. package/build/queue/consts.d.ts.map +1 -0
  223. package/build/queue/consts.js +5 -0
  224. package/build/queue/consts.js.map +1 -0
  225. package/build/queue/declare.d.ts +5 -0
  226. package/build/queue/declare.d.ts.map +1 -0
  227. package/build/queue/declare.js +5 -0
  228. package/build/queue/declare.js.map +1 -0
  229. package/build/queue/index.d.ts +3 -18
  230. package/build/queue/index.d.ts.map +1 -1
  231. package/build/queue/index.js +2 -7
  232. package/build/queue/index.js.map +1 -1
  233. package/build/queue/types.d.ts +13 -0
  234. package/build/queue/types.d.ts.map +1 -0
  235. package/build/queue/types.js +2 -0
  236. package/build/queue/types.js.map +1 -0
  237. package/build/registry.d.ts +5 -22
  238. package/build/registry.d.ts.map +1 -1
  239. package/build/registry.js +16 -10
  240. package/build/registry.js.map +1 -1
  241. package/build/resources.d.ts +5 -0
  242. package/build/resources.d.ts.map +1 -0
  243. package/build/resources.js +287 -0
  244. package/build/resources.js.map +1 -0
  245. package/build/service.d.ts +3 -16
  246. package/build/service.d.ts.map +1 -1
  247. package/build/service.js +12 -0
  248. package/build/service.js.map +1 -1
  249. package/build/store/commits.d.ts.map +1 -1
  250. package/build/store/commits.js +3 -1
  251. package/build/store/commits.js.map +1 -1
  252. package/build/store/composite.d.ts +1 -0
  253. package/build/store/composite.d.ts.map +1 -1
  254. package/build/store/composite.js +7 -2
  255. package/build/store/composite.js.map +1 -1
  256. package/build/store/consts.local.d.ts +2 -0
  257. package/build/store/consts.local.d.ts.map +1 -0
  258. package/build/store/consts.local.js +2 -0
  259. package/build/store/consts.local.js.map +1 -0
  260. package/build/store/fold/types.d.ts +43 -0
  261. package/build/store/fold/types.d.ts.map +1 -0
  262. package/build/store/fold/types.js +2 -0
  263. package/build/store/fold/types.js.map +1 -0
  264. package/build/store/fold.d.ts +8 -30
  265. package/build/store/fold.d.ts.map +1 -1
  266. package/build/store/fold.js +190 -198
  267. package/build/store/fold.js.map +1 -1
  268. package/build/store/index.d.ts +1 -0
  269. package/build/store/index.d.ts.map +1 -1
  270. package/build/store/index.js.map +1 -1
  271. package/build/store/memory.d.ts +12 -0
  272. package/build/store/memory.d.ts.map +1 -1
  273. package/build/store/memory.js +0 -0
  274. package/build/store/memory.js.map +1 -1
  275. package/build/store/types.d.ts +15 -1
  276. package/build/store/types.d.ts.map +1 -1
  277. package/build/store/types.local.d.ts +5 -0
  278. package/build/store/types.local.d.ts.map +1 -0
  279. package/build/store/types.local.js +2 -0
  280. package/build/store/types.local.js.map +1 -0
  281. package/build/types.d.ts +126 -2
  282. package/build/types.d.ts.map +1 -1
  283. package/build/types.local.d.ts +7 -0
  284. package/build/types.local.d.ts.map +1 -0
  285. package/build/types.local.js +2 -0
  286. package/build/types.local.js.map +1 -0
  287. package/build/utils/access/types.d.ts +30 -0
  288. package/build/utils/access/types.d.ts.map +1 -0
  289. package/build/utils/access/types.js +2 -0
  290. package/build/utils/access/types.js.map +1 -0
  291. package/build/utils/access.d.ts +4 -0
  292. package/build/utils/access.d.ts.map +1 -0
  293. package/build/utils/access.js +28 -0
  294. package/build/utils/access.js.map +1 -0
  295. package/build/utils/guard/types.d.ts +23 -0
  296. package/build/utils/guard/types.d.ts.map +1 -0
  297. package/build/utils/guard/types.js +2 -0
  298. package/build/utils/guard/types.js.map +1 -0
  299. package/build/utils/guard.d.ts +7 -10
  300. package/build/utils/guard.d.ts.map +1 -1
  301. package/build/utils/guard.js +36 -32
  302. package/build/utils/guard.js.map +1 -1
  303. package/build/utils/handler/types.d.ts +26 -0
  304. package/build/utils/handler/types.d.ts.map +1 -0
  305. package/build/utils/handler/types.js +2 -0
  306. package/build/utils/handler/types.js.map +1 -0
  307. package/build/utils/handler.d.ts +6 -0
  308. package/build/utils/handler.d.ts.map +1 -0
  309. package/build/utils/handler.js +21 -0
  310. package/build/utils/handler.js.map +1 -0
  311. package/build/utils/index.d.ts +8 -0
  312. package/build/utils/index.d.ts.map +1 -1
  313. package/build/utils/index.js +3 -0
  314. package/build/utils/index.js.map +1 -1
  315. package/build/utils/request/types.d.ts +23 -0
  316. package/build/utils/request/types.d.ts.map +1 -0
  317. package/build/utils/request/types.js +2 -0
  318. package/build/utils/request/types.js.map +1 -0
  319. package/build/utils/request.d.ts +4 -0
  320. package/build/utils/request.d.ts.map +1 -0
  321. package/build/utils/request.js +27 -0
  322. package/build/utils/request.js.map +1 -0
  323. package/build/utils/scope.d.ts +17 -13
  324. package/build/utils/scope.d.ts.map +1 -1
  325. package/build/utils/scope.js +21 -35
  326. package/build/utils/scope.js.map +1 -1
  327. package/build/utils/types.d.ts +8 -0
  328. package/build/utils/types.d.ts.map +1 -0
  329. package/build/utils/types.js +2 -0
  330. package/build/utils/types.js.map +1 -0
  331. package/package.json +30 -22
  332. package/src/actions/apply-schema-request.ts +36 -0
  333. package/src/actions/commit.ts +7 -5
  334. package/src/actions/define-schemas.ts +34 -0
  335. package/src/actions/execute.ts +32 -34
  336. package/src/actions/execution/types.ts +34 -0
  337. package/src/actions/execution.ts +100 -0
  338. package/src/actions/get-card.ts +14 -0
  339. package/src/actions/get-specification.ts +14 -0
  340. package/src/actions/index.ts +13 -3
  341. package/src/actions/links.ts +8 -6
  342. package/src/actions/list-card-specifications.ts +15 -0
  343. package/src/actions/list-card-transitions.ts +15 -0
  344. package/src/actions/list-cards.ts +14 -0
  345. package/src/actions/list-schemas.ts +29 -0
  346. package/src/actions/list-specification-revisions.ts +15 -0
  347. package/src/actions/resources.ts +79 -0
  348. package/src/actions/summarize-cards.ts +15 -0
  349. package/src/actions/transitions.ts +6 -4
  350. package/src/actions/types.ts +5 -0
  351. package/src/actions/watch.ts +11 -7
  352. package/src/conformance/assert/types.ts +16 -0
  353. package/src/conformance/assert.ts +67 -0
  354. package/src/conformance/cases.ts +631 -0
  355. package/src/conformance/clock.ts +11 -0
  356. package/src/conformance/consts.ts +165 -0
  357. package/src/conformance/errors.ts +7 -0
  358. package/src/conformance/fixtures/types.ts +11 -0
  359. package/src/conformance/fixtures.ts +46 -0
  360. package/src/conformance/index.ts +14 -0
  361. package/src/conformance/resources.ts +227 -0
  362. package/src/conformance/types.ts +20 -0
  363. package/src/consts.ts +6 -0
  364. package/src/definitions.ts +264 -0
  365. package/src/executor/changes/types.ts +38 -0
  366. package/src/executor/changes.ts +49 -68
  367. package/src/executor/code/types.ts +23 -0
  368. package/src/executor/code.ts +62 -69
  369. package/src/executor/consts.local.ts +8 -0
  370. package/src/executor/creator/types.ts +33 -0
  371. package/src/executor/creator.ts +43 -0
  372. package/src/executor/resolve/types.ts +30 -0
  373. package/src/executor/resolve.ts +181 -139
  374. package/src/executor/types.ts +23 -0
  375. package/src/executor/validate.ts +40 -38
  376. package/src/executor.ts +33 -27
  377. package/src/facade.ts +132 -29
  378. package/src/helper.ts +12 -11
  379. package/src/hierarchy/types.ts +20 -0
  380. package/src/hierarchy.ts +113 -0
  381. package/src/index.ts +1 -0
  382. package/src/invariants/types.ts +6 -0
  383. package/src/invariants.ts +106 -0
  384. package/src/projection.ts +12 -18
  385. package/src/queue/consts.ts +5 -0
  386. package/src/queue/declare.ts +7 -0
  387. package/src/queue/index.ts +3 -25
  388. package/src/queue/types.ts +14 -0
  389. package/src/registry.ts +18 -36
  390. package/src/resources.ts +256 -0
  391. package/src/service.ts +16 -20
  392. package/src/store/commits.ts +5 -4
  393. package/src/store/composite.ts +7 -2
  394. package/src/store/consts.local.ts +1 -0
  395. package/src/store/fold/types.ts +44 -0
  396. package/src/store/fold.ts +214 -212
  397. package/src/store/index.ts +1 -0
  398. package/src/store/memory.ts +0 -0
  399. package/src/store/types.local.ts +3 -0
  400. package/src/store/types.ts +16 -1
  401. package/src/types.local.ts +7 -0
  402. package/src/types.ts +139 -6
  403. package/src/utils/access/types.ts +30 -0
  404. package/src/utils/access.ts +33 -0
  405. package/src/utils/guard/types.ts +21 -0
  406. package/src/utils/guard.ts +44 -31
  407. package/src/utils/handler/types.ts +28 -0
  408. package/src/utils/handler.ts +35 -0
  409. package/src/utils/index.ts +9 -0
  410. package/src/utils/request/types.ts +23 -0
  411. package/src/utils/request.ts +40 -0
  412. package/src/utils/scope.ts +39 -42
  413. package/src/utils/types.ts +8 -0
  414. package/tests/access.spec.ts +160 -0
  415. package/tests/app.spec.ts +112 -0
  416. package/tests/conformance.spec.ts +47 -0
  417. package/tests/create-example.spec.ts +202 -0
  418. package/tests/creator.spec.ts +257 -0
  419. package/tests/entrypoints.spec.ts +5 -0
  420. package/tests/memory-store.spec.ts +52 -0
  421. package/tests/narrowing.spec.ts +94 -0
  422. package/tests/plugins.spec.ts +4 -3
  423. package/tests/writes.spec.ts +140 -0
  424. package/build/actions/cards.d.ts +0 -16
  425. package/build/actions/cards.d.ts.map +0 -1
  426. package/build/actions/cards.js +0 -32
  427. package/build/actions/cards.js.map +0 -1
  428. package/build/actions/schemas.d.ts +0 -6
  429. package/build/actions/schemas.d.ts.map +0 -1
  430. package/build/actions/schemas.js +0 -9
  431. package/build/actions/schemas.js.map +0 -1
  432. package/build/actions/specs.d.ts +0 -10
  433. package/build/actions/specs.d.ts.map +0 -1
  434. package/build/actions/specs.js +0 -14
  435. package/build/actions/specs.js.map +0 -1
  436. package/src/actions/cards.ts +0 -52
  437. package/src/actions/schemas.ts +0 -15
  438. package/src/actions/specs.ts +0 -23
@@ -0,0 +1,165 @@
1
+ import { type AnyTypeSchema, type PlanningPlugin, type StatusFlowSchema, type WorkcardTypeSchema, AssigneeKind, PlanningResourceKind, CodeScope, CodeStyle, IntrinsicStatus, SpecificationFormat, WorkcardKind } from '@owlmeans/planning'
2
+
3
+ /**
4
+ * The conformance vocabulary: a lending library. Branches (projects) hold books and periodicals
5
+ * (cards); a book runs a circulation flow and a review flow; documents are pages.
6
+ */
7
+ export const LIBRARY = Object.freeze({
8
+ branch: 'library:branch',
9
+ room: 'library:reading-room',
10
+ book: 'library:book',
11
+ section: 'library:section',
12
+ human: 'library:human',
13
+ bot: 'library:bot',
14
+ guest: 'library:guest',
15
+ periodical: 'library:periodical',
16
+ page: 'library:page',
17
+ circulation: 'library:circulation',
18
+ review: 'library:review',
19
+ branchLife: 'library:branch-life',
20
+ pageLife: 'library:page-life',
21
+ })
22
+
23
+ export const BOOK_TYPE: WorkcardTypeSchema = {
24
+ type: LIBRARY.book,
25
+ kind: WorkcardKind.Card,
26
+ version: 1,
27
+ fields: {
28
+ type: 'object',
29
+ properties: { genre: { type: 'string', enum: ['poetry', 'history', 'fiction'] }, signed: { type: 'boolean' } },
30
+ additionalProperties: false,
31
+ },
32
+ flows: [LIBRARY.circulation, LIBRARY.review],
33
+ specifications: [{ category: 'summary', format: SpecificationFormat.Json, revisioned: true, keepRevisions: 3 }],
34
+ relationships: [{ name: 'sequel-of', to: [LIBRARY.book], single: true }, { name: 'shelved-near' }],
35
+ labels: ['rare', 'new', 'signed'],
36
+ code: { prefix: 'B-', style: CodeStyle.Random, length: 5, uppercase: true, uniqueWithin: CodeScope.Parent },
37
+ }
38
+
39
+ export const PERIODICAL_TYPE: WorkcardTypeSchema = {
40
+ type: LIBRARY.periodical,
41
+ kind: WorkcardKind.Card,
42
+ version: 1,
43
+ overridable: true,
44
+ label: 'Periodical',
45
+ fields: { type: 'object', additionalProperties: true },
46
+ flows: [LIBRARY.circulation],
47
+ specifications: [],
48
+ }
49
+
50
+ const flows: StatusFlowSchema[] = [
51
+ {
52
+ id: LIBRARY.circulation,
53
+ version: 1,
54
+ statuses: [
55
+ { key: 'shelved', intrinsic: IntrinsicStatus.Planned, initial: true },
56
+ { key: 'lent', intrinsic: IntrinsicStatus.InProgress },
57
+ { key: 'retired', intrinsic: IntrinsicStatus.Closed, terminal: true },
58
+ ],
59
+ transitions: [
60
+ { name: 'lend', from: ['shelved'], to: 'lent', explicit: true },
61
+ { name: 'return', from: ['lent'], to: 'shelved', explicit: true },
62
+ { name: 'retire', from: '*', to: 'retired' },
63
+ { name: 'restore', from: ['retired'], to: 'shelved' },
64
+ ],
65
+ },
66
+ {
67
+ id: LIBRARY.review,
68
+ version: 1,
69
+ statuses: [
70
+ { key: 'unreviewed', intrinsic: IntrinsicStatus.Planned, initial: true },
71
+ { key: 'reviewed', intrinsic: IntrinsicStatus.Closed },
72
+ ],
73
+ transitions: [{ name: 'review', from: ['unreviewed'], to: 'reviewed' }],
74
+ },
75
+ {
76
+ id: LIBRARY.branchLife,
77
+ version: 1,
78
+ statuses: [
79
+ { key: 'open', intrinsic: IntrinsicStatus.Planned, initial: true },
80
+ { key: 'active', intrinsic: IntrinsicStatus.InProgress },
81
+ ],
82
+ transitions: [{ name: 'activate', from: ['open'], to: 'active' }],
83
+ },
84
+ {
85
+ id: LIBRARY.pageLife,
86
+ version: 1,
87
+ statuses: [{ key: 'draft', intrinsic: IntrinsicStatus.Planned, initial: true }],
88
+ transitions: [],
89
+ },
90
+ ]
91
+
92
+ const types: AnyTypeSchema[] = [
93
+ {
94
+ type: LIBRARY.branch,
95
+ kind: WorkcardKind.Project,
96
+ version: 1,
97
+ fields: { type: 'object', additionalProperties: true },
98
+ flows: [LIBRARY.branchLife],
99
+ cardTypes: [LIBRARY.book, LIBRARY.periodical, LIBRARY.section],
100
+ projectTypes: [LIBRARY.branch],
101
+ scopedCardTypes: true,
102
+ specifications: [
103
+ { category: 'charter', format: SpecificationFormat.Markdown },
104
+ { category: 'notes', format: SpecificationFormat.Markdown, multiple: true },
105
+ ],
106
+ code: { style: CodeStyle.Slug, uniqueWithin: CodeScope.Entity, mutable: true },
107
+ },
108
+ {
109
+ type: LIBRARY.room,
110
+ kind: WorkcardKind.Project,
111
+ version: 1,
112
+ fields: { type: 'object', additionalProperties: true },
113
+ flows: [LIBRARY.branchLife],
114
+ cardTypes: [LIBRARY.book],
115
+ specifications: [],
116
+ },
117
+ {
118
+ type: LIBRARY.section, kind: WorkcardKind.Card, version: 1,
119
+ fields: { type: 'object', properties: { requester: { type: 'string' }, reviewers: { type: 'array', items: { type: 'string' }, uniqueItems: true } }, additionalProperties: false },
120
+ flows: [LIBRARY.circulation], specifications: [],
121
+ parents: { types: [LIBRARY.branch, LIBRARY.section], required: true },
122
+ children: { types: [LIBRARY.book, LIBRARY.section] },
123
+ relationships: [
124
+ { name: 'requested-by', field: 'requester', toKind: PlanningResourceKind.Assignee, to: [LIBRARY.human], inverse: 'requests' },
125
+ { name: 'reviewed-by', field: 'reviewers', multiple: true, toKind: PlanningResourceKind.Assignee },
126
+ ],
127
+ },
128
+ BOOK_TYPE,
129
+ PERIODICAL_TYPE,
130
+ {
131
+ type: LIBRARY.page,
132
+ kind: WorkcardKind.Specification,
133
+ version: 1,
134
+ fields: { type: 'object', additionalProperties: true },
135
+ flows: [LIBRARY.pageLife],
136
+ specifications: [],
137
+ },
138
+ ]
139
+
140
+ /** The types and flows every conformance case runs against — register it on the service under test. */
141
+ export const planningConformancePlugin: PlanningPlugin = Object.freeze({
142
+ name: 'planning-conformance',
143
+ order: 0,
144
+ schemas: { types, flows, assigneeTypes: [
145
+ { type: LIBRARY.human, version: 1, kind: AssigneeKind.Human, fields: { type: 'object', properties: { department: { type: 'string' } }, additionalProperties: false } },
146
+ { type: LIBRARY.bot, version: 1, kind: AssigneeKind.NonHuman, fields: { type: 'object', additionalProperties: true } },
147
+ { type: LIBRARY.guest, version: 1, kind: AssigneeKind.Human, authentication: 'optional' as const, fields: { type: 'object', additionalProperties: true } },
148
+ ] },
149
+ })
150
+
151
+ /**
152
+ * The library's own definition properties and base card fields — registered by the case that checks
153
+ * them for its duration only, then replaced by a plugin of the same name that declares nothing.
154
+ */
155
+ export const CONFORMANCE_APP_PLUGIN: PlanningPlugin = Object.freeze({
156
+ name: 'planning-conformance-app',
157
+ order: 1,
158
+ schemas: {
159
+ app: {
160
+ type: { properties: { description: { type: 'string', maxLength: 200 } } },
161
+ flow: { properties: { description: { type: 'string', maxLength: 200 } }, required: ['description'] },
162
+ card: { properties: { shelfmark: { type: 'string', pattern: '^[A-Z]-[0-9]+$' } } },
163
+ },
164
+ },
165
+ })
@@ -0,0 +1,7 @@
1
+ /** A conformance expectation that did not hold. Plain `Error`, so any test runner reports it. */
2
+ export class ConformanceFailure extends Error {
3
+ constructor(message: string) {
4
+ super(`planning conformance: ${message}`)
5
+ this.name = 'ConformanceFailure'
6
+ }
7
+ }
@@ -0,0 +1,11 @@
1
+ import type { TransitionReceipt, Workcard } from '@owlmeans/planning'
2
+
3
+ /** The records a conformance case creates and moves, through one organization's facade. */
4
+ export interface ConformanceFixtures {
5
+ /** A branch (project) at the root, written and committed. */
6
+ createBranch: (title?: string, extra?: Record<string, unknown>) => Promise<Workcard>
7
+ /** A book (card) under a branch, written and committed. */
8
+ createBook: (branch: string, title?: string, extra?: Record<string, unknown>) => Promise<Workcard>
9
+ /** A transit of a card, waited until it commits. */
10
+ transit: (card: string, transition: string, extra?: { flow?: string }) => Promise<TransitionReceipt>
11
+ }
@@ -0,0 +1,46 @@
1
+ import { memoHelper } from '@owlmeans/context'
2
+ import { TransitionAction, WorkcardKind, type PlanningFacade, type TransitionReceipt, type Workcard } from '@owlmeans/planning'
3
+ import { LIBRARY } from './consts.js'
4
+ import type { ConformanceFixtures } from './fixtures/types.js'
5
+
6
+ export const makeConformanceFixtures = (planning: PlanningFacade): ConformanceFixtures => {
7
+ const createBranch = async (
8
+ title: string = 'Riverside branch', extra: Record<string, unknown> = {}
9
+ ): Promise<Workcard> => (await planning.execute({
10
+ card: { kind: WorkcardKind.Project, type: LIBRARY.branch, title, ...extra },
11
+ action: TransitionAction.Create,
12
+ }, { wait: true })).card!
13
+
14
+ const createBook = async (
15
+ branch: string, title: string = 'A book', extra: Record<string, unknown> = {}
16
+ ): Promise<Workcard> => (await planning.execute({
17
+ card: { kind: WorkcardKind.Card, type: LIBRARY.book, parent: branch, title, ...extra },
18
+ action: TransitionAction.Create,
19
+ }, { wait: true })).card!
20
+
21
+ const transit = async (
22
+ card: string, transition: string, extra: { flow?: string } = {}
23
+ ): Promise<TransitionReceipt> => await planning.execute({
24
+ card, action: TransitionAction.Transit, transition, ...extra,
25
+ }, { wait: true })
26
+
27
+ return { createBranch, createBook, transit }
28
+ }
29
+
30
+ /** The fixtures of one facade — one per facade. */
31
+ export const conformanceFixturesOf = memoHelper.oncePer(makeConformanceFixtures)
32
+
33
+ /** @deprecated compat:factory-refactor — use `conformanceFixturesOf(planning).createBranch(…)` */
34
+ export const createBranch = async (
35
+ planning: PlanningFacade, title: string = 'Riverside branch', extra: Record<string, unknown> = {}
36
+ ): Promise<Workcard> => await conformanceFixturesOf(planning).createBranch(title, extra)
37
+
38
+ /** @deprecated compat:factory-refactor — use `conformanceFixturesOf(planning).createBook(…)` */
39
+ export const createBook = async (
40
+ planning: PlanningFacade, branch: string, title: string = 'A book', extra: Record<string, unknown> = {}
41
+ ): Promise<Workcard> => await conformanceFixturesOf(planning).createBook(branch, title, extra)
42
+
43
+ /** @deprecated compat:factory-refactor — use `conformanceFixturesOf(planning).transit(…)` */
44
+ export const transit = async (
45
+ planning: PlanningFacade, card: string, transition: string, extra: { flow?: string } = {}
46
+ ): Promise<TransitionReceipt> => await conformanceFixturesOf(planning).transit(card, transition, extra)
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The planning store conformance suite — a framework-agnostic list of named cases every
3
+ * `PlanningStore` answers identically. No test runner is imported: a store's own spec file loops
4
+ * over {@link conformanceCasesFor} and hands each case a {@link ConformanceSubject}; a case throws
5
+ * `ConformanceFailure` (a plain `Error`) when an expectation does not hold.
6
+ */
7
+ export * from './assert.js'
8
+ export * from './cases.js'
9
+ export type * from './types.js'
10
+ export type * from './assert/types.js'
11
+ export type * from './fixtures/types.js'
12
+ export * from './clock.js'
13
+ export * from './fixtures.js'
14
+ export * from './consts.js'
@@ -0,0 +1,227 @@
1
+ import { idHelper } from '@owlmeans/basic-ids'
2
+ import {
3
+ AssigneeKind, FieldsInvalid, mentionHelper, ParentNotFound, PlanningError, PlanningForbidden,
4
+ PlanningResourceKind, PlanningSchemaKind, PLANNING_ASSIGNEE, PLANNING_REPORTER, ProjectMode, RelationshipRefused,
5
+ SchemaInvalid, TransitionAction, WorkcardConflict, WorkcardKind, WorkcardNotFound,
6
+ type PlanningFacade, type Workcard,
7
+ } from '@owlmeans/planning'
8
+ import { assertHelper } from './assert.js'
9
+ import { LIBRARY } from './consts.js'
10
+ import { conformanceFixturesOf } from './fixtures.js'
11
+ import type { ConformanceCase } from './types.js'
12
+
13
+ const { same, sameSet, rejects, check } = assertHelper
14
+ const organization = () => `resource-conformance-${idHelper.uuid()}`
15
+ const human = async (planning: PlanningFacade, nickname = 'reader') => await planning.assignees.create({
16
+ nickname, type: LIBRARY.human, kind: AssigneeKind.Human, authentication: { provider: 'external-oidc', externalId: nickname }, fields: { department: 'Library' },
17
+ })
18
+ const section = async (planning: PlanningFacade, parent: string, extra: Record<string, unknown> = {}): Promise<Workcard> =>
19
+ (await planning.execute({ action: TransitionAction.Create, card: { kind: WorkcardKind.Card, type: LIBRARY.section, parent, title: 'Section', ...extra } }, { wait: true })).card!
20
+
21
+ /** Shared durable-resource and hierarchy contract, run unchanged against every stock provider. */
22
+ export const resourceConformance: readonly ConformanceCase[] = [
23
+ {
24
+ name: 'assignees use schema-validated fields, stable authentication subjects and scoped nicknames', needs: ['resources'],
25
+ run: async ({ facade }) => {
26
+ const org = organization(), p = facade(org), other = facade(organization())
27
+ await rejects(p.assignees.create({ nickname: 'missing-auth', type: LIBRARY.human, kind: AssigneeKind.Human }), FieldsInvalid, 'human requires authentication by default')
28
+ await rejects(p.assignees.create({ nickname: 'wrong-fields', type: LIBRARY.human, kind: AssigneeKind.Human, authentication: { provider: 'x', externalId: 'x' }, fields: { department: 42 } }), FieldsInvalid, 'assignee schema is enforced')
29
+ const actor = await human(p, 'Alice')
30
+ await rejects(human(p, 'aLiCe'), WorkcardConflict, 'nickname is case insensitive')
31
+ await rejects(p.assignees.create({ nickname: 'another', type: LIBRARY.human, kind: AssigneeKind.Human, authentication: actor.authentication }), WorkcardConflict, 'authentication subject is unique in organization')
32
+ await human(other, 'Alice')
33
+ same(await other.assignees.load(actor.id!), null, 'another organization cannot read assignee')
34
+ const renamed = await p.assignees.update(actor.id!, { nickname: 'Librarian' }, { version: 1 })
35
+ same([renamed.id, renamed.version, renamed.authentication], [actor.id, 2, actor.authentication], 'rename keeps stable identity')
36
+ await rejects(p.assignees.update(actor.id!, { nickname: 'stale' }, { version: 1 }), WorkcardConflict, 'assignee CAS')
37
+ same((await p.assignees.list({ nickname: 'LIBRARIAN' })).items[0].id, actor.id, 'normalized query')
38
+ const bot = await p.assignees.create({ nickname: 'bot', type: LIBRARY.bot, kind: AssigneeKind.NonHuman })
39
+ const guest = await p.assignees.create({ nickname: 'guest', type: LIBRARY.guest, kind: AssigneeKind.Human })
40
+ same([bot.authentication, guest.authentication], [undefined, undefined], 'nonhuman and optional human schemas admit unlinked identities')
41
+ await rejects(p.assignees.create({ nickname: 'mismatch', type: LIBRARY.bot, kind: AssigneeKind.Human }), FieldsInvalid, 'fundamental kind follows schema')
42
+ },
43
+ },
44
+ {
45
+ name: 'active assignee queries include unset and false retirement flags', needs: ['resources'],
46
+ run: async ({ facade, store }) => {
47
+ const org = organization(), p = facade(org)
48
+ const unset = await human(p, 'unset'), explicit = await human(p, 'explicit'), retired = await human(p, 'retired')
49
+ await store.assignees!.put({ ...explicit, retired: false, version: 2 })
50
+ await p.assignees.retire(retired.id!, { version: 1 })
51
+ sameSet((await p.assignees.list({ retired: false })).items.map(row => row.id), [unset.id, explicit.id], 'active means not retired')
52
+ sameSet((await p.assignees.list({ retired: true })).items.map(row => row.id), [retired.id], 'only retired records')
53
+ same((await p.assignees.list()).total, 3, 'unfiltered retains historical identities')
54
+ same((await p.assignees.list({ retired: false, nickname: 'UNSET', kind: AssigneeKind.Human })).items[0]?.id, unset.id, 'active filter composes with identity queries')
55
+ same((await facade(organization()).assignees.list({ retired: false })).total, 0, 'active filter preserves organization scope')
56
+ },
57
+ },
58
+ {
59
+ name: 'concurrent nickname claims create one assignee and leave subsequent writes usable', needs: ['resources'],
60
+ run: async ({ facade }) => {
61
+ const p = facade(organization())
62
+ const attempts = await Promise.allSettled(Array.from({ length: 4 }, () => human(p, 'claimed')))
63
+ same(attempts.filter(row => row.status === 'fulfilled').length, 1, 'one nickname winner')
64
+ same((await p.assignees.list({ nickname: 'CLAIMED' })).total, 1, 'one persisted identity')
65
+ await human(p, 'after-conflict')
66
+ same((await p.assignees.list()).total, 2, 'rejected writes do not poison durable operation journal')
67
+ },
68
+ },
69
+ {
70
+ name: 'comments retain trusted authors and derived mentions follow edits and removal', needs: ['resources'],
71
+ run: async ({ facade, service, store }) => {
72
+ const org = organization(), p = facade(org), actor = await human(p), mentioned = await human(p, 'mentioned')
73
+ const branch = await conformanceFixturesOf(p).createBranch(), card = await conformanceFixturesOf(p).createBook(branch.id!)
74
+ const authored = service.for({ ...p.scope, assigneeId: actor.id })
75
+ await rejects(p.comments.create({ card: card.id!, body: 'no author' }), PlanningForbidden, 'comment requires trusted author')
76
+ const comment = await authored.comments.create({ card: card.id!, body: `${mentionHelper.encode(mentioned.id!, mentioned.nickname)} ${mentionHelper.encode(mentioned.id!, mentioned.nickname)}` })
77
+ same([comment.author, comment.version], [actor.id, 1], 'trusted immutable author')
78
+ const mentions = await authored.mentions.list({ comment: comment.id })
79
+ same(mentions.items.map(row => [row.assignee, row.revision]), [[mentioned.id, 1]], 'stable-id mentions deduplicate')
80
+ // Remove the cache behind the facade; the next reader repairs it from the source revision.
81
+ await store.mentions!.drop(mentions.items[0].id!, org, mentions.items[0].version)
82
+ same((await authored.mentions.list({ comment: comment.id })).total, 1, 'cache self-heals after interrupted cache write')
83
+ const stranger = service.for({ ...p.scope, assigneeId: mentioned.id })
84
+ await rejects(stranger.comments.update(comment.id!, { body: 'taken' }, { version: 1 }), PlanningForbidden, 'another author cannot edit')
85
+ await authored.comments.update(comment.id!, { body: 'no mentions' }, { version: 1 })
86
+ same((await authored.mentions.list({ comment: comment.id })).total, 0, 'removed mention is removed from cache')
87
+ await rejects(authored.comments.update(comment.id!, { body: 'stale' }, { version: 1 }), WorkcardConflict, 'comment CAS')
88
+ await authored.comments.remove(comment.id!, { version: 2 })
89
+ same(await authored.comments.load(comment.id!), null, 'comment removed')
90
+ same((await store.mentions!.list({ entityId: org, comment: comment.id })).total, 0, 'no orphan cache records')
91
+ await rejects(authored.comments.create({ card: card.id!, body: '[@foreign](assignee:missing)' }), WorkcardNotFound, 'mention of unknown assignee is refused')
92
+ },
93
+ },
94
+ {
95
+ name: 'teams are reusable canonical relationships with deduplicated project assignees', needs: ['resources'],
96
+ run: async ({ facade, store }) => {
97
+ const org = organization(), p = facade(org), actor = await human(p), fixtures = conformanceFixturesOf(p)
98
+ const first = await fixtures.createBranch('First'), second = await fixtures.createBranch('Second')
99
+ const a = await p.teams.create({ name: 'Editors', externalId: 'permission-group:editors' }), b = await p.teams.create({ name: 'Reviewers' })
100
+ await p.teams.addMember(a.id!, actor.id!); await p.teams.addMember(a.id!, actor.id!); await p.teams.addMember(b.id!, actor.id!)
101
+ await p.teams.attach(a.id!, first.id!); await p.teams.attach(a.id!, second.id!); await p.teams.attach(b.id!, first.id!)
102
+ sameSet((await p.teams.members(a.id!)).map(row => row.id), [actor.id], 'membership is idempotent')
103
+ sameSet(await p.teams.projects(a.id!), [first.id, second.id], 'one reusable team on two projects')
104
+ same((await p.teams.assignees(first.id!)).map(row => row.id), [actor.id], 'union of team assignees')
105
+ check((await store.links!.list({ entityId: org, from: a.id, fromKind: PlanningResourceKind.Team })).total === 1, 'native relationship store owns membership')
106
+ await rejects(p.teams.remove(a.id!, { version: 1 }), WorkcardConflict, 'attached team cannot be removed')
107
+ await p.teams.detach(a.id!, first.id!); await p.teams.detach(a.id!, second.id!); await p.teams.remove(a.id!, { version: 1 })
108
+ same(await p.teams.load(a.id!), null, 'detached team removed')
109
+ same((await p.assignees.get(actor.id!)).id, actor.id, 'team deletion preserves identity')
110
+ same(await facade(organization()).teams.load(b.id!), null, 'team isolation')
111
+ },
112
+ },
113
+ {
114
+ name: 'card reporter, assignee and schema fields derive indexes and preserve retired references', needs: ['resources'],
115
+ run: async ({ facade, store }) => {
116
+ const org = organization(), p = facade(org), actor = await human(p), bot = await p.assignees.create({ nickname: 'bot', type: LIBRARY.bot, kind: AssigneeKind.NonHuman })
117
+ const branch = await conformanceFixturesOf(p).createBranch()
118
+ const card = await section(p, branch.id!, { reporter: actor.id, assignee: bot.id, fields: { requester: actor.id, reviewers: [actor.id, bot.id] } })
119
+ const edges = await store.links!.list({ entityId: org, from: card.id, toKind: PlanningResourceKind.Assignee }, { size: 0 })
120
+ sameSet(edges.items.map(row => [row.type, row.to]), [[PLANNING_REPORTER, actor.id], [PLANNING_ASSIGNEE, bot.id], ['requested-by', actor.id], ['reviewed-by', actor.id], ['reviewed-by', bot.id]], 'canonical indexes from top-level and typed fields')
121
+ same((await p.relationships.list({ from: actor.id, type: 'requests', fromKind: PlanningResourceKind.Assignee })).items.map(edge => edge.to), [card.id], 'inverse relationship is derived from canonical edge')
122
+ await rejects(section(p, branch.id!, { reporter: 'missing' }), RelationshipRefused, 'unknown assignee reference')
123
+ await rejects(section(p, branch.id!, { fields: { requester: bot.id } }), RelationshipRefused, 'assignee-type relationship restriction')
124
+ await rejects(p.execute({ card: card.id!, action: TransitionAction.Link, link: { type: 'requested-by', to: bot.id! } }), RelationshipRefused, 'field-derived link cannot be independently edited')
125
+ await rejects(p.assignees.update(actor.id!, { type: LIBRARY.guest }, { version: 1 }), FieldsInvalid, 'assignee type change cannot invalidate existing typed references')
126
+ same((await p.assignees.get(actor.id!)).version, 1, 'rejected type change leaves identity unchanged')
127
+ await p.assignees.retire(actor.id!, { version: 1 })
128
+ await p.execute({ card: card.id!, action: TransitionAction.Update, changes: { title: 'Kept after retirement' } }, { wait: true })
129
+ await rejects(section(p, branch.id!, { reporter: actor.id }), RelationshipRefused, 'new references to retired assignee refused')
130
+ await p.execute({ card: card.id!, action: TransitionAction.Update, unset: ['reporter', 'fields.requester', 'fields.reviewers'] }, { wait: true })
131
+ same((await store.links!.list({ entityId: org, from: card.id }, { size: 0 })).items.map(row => row.type), [PLANNING_ASSIGNEE], 'cleared fields remove their derived indexes')
132
+ },
133
+ },
134
+ {
135
+ name: 'purging a former project preserves reparented descendant documents, links and full history', needs: ['resources'],
136
+ run: async ({ facade, store }) => {
137
+ const org = organization(), p = facade(org), fixtures = conformanceFixturesOf(p)
138
+ const old = await fixtures.createBranch('Former'), next = await fixtures.createBranch('Destination')
139
+ const parent = await section(p, old.id!), leaf = await fixtures.createBook(parent.id!)
140
+ const document = (await (await p.model(leaf)).write('summary', '{"lines":["kept"]}', { wait: true })).card!
141
+ await p.execute({ card: leaf.id!, action: TransitionAction.Link, link: { type: 'shelved-near', to: parent.id! } }, { wait: true })
142
+ const history = (await store.transitions!.list({ entityId: org, card: leaf.id }, { size: 0 })).items.map(row => row.id)
143
+ await p.execute({ card: parent.id!, action: TransitionAction.Update, changes: { parent: next.id, parents: [next.id!] } }, { wait: true })
144
+ await p.execute({ card: old.id!, action: TransitionAction.Delete }, { wait: true })
145
+ same((await p.cards.get(leaf.id!)).id, leaf.id, 'reparented descendant survives')
146
+ same((await p.specifications.get(document.id!)).body, '{"lines":["kept"]}', 'descendant document survives stale project cache')
147
+ sameSet((await store.transitions!.list({ entityId: org, card: leaf.id }, { size: 0 })).items.map(row => row.id), history, 'complete history survives former project purge')
148
+ same((await p.relationships.list({ from: leaf.id })).items.length, 1, 'surviving edge kept')
149
+ await (await p.model(leaf.id!)).update({ title: 'Still writable' }, { wait: true })
150
+ },
151
+ },
152
+ {
153
+ name: 'nested hierarchies derive primary parent type, refuse cycles and permit safe reparenting',
154
+ run: async ({ facade }) => {
155
+ const p = facade(organization()), fixtures = conformanceFixturesOf(p)
156
+ const a = await fixtures.createBranch('A'), b = await fixtures.createBranch('B'), parent = await section(p, a.id!), child = await section(p, parent.id!), leaf = await fixtures.createBook(child.id!)
157
+ same([child.parent, child.parentType, child.parents, leaf.parentType], [parent.id, LIBRARY.section, [parent.id], LIBRARY.section], 'primary parent is direct and typed')
158
+ same((await p.model(leaf)).schema().type, LIBRARY.book, 'nested card resolves nearest project schemas')
159
+ await rejects(p.execute({ card: parent.id!, action: TransitionAction.Update, changes: { parent: child.id, parents: [child.id!] } }), PlanningError, 'cycle refused')
160
+ await rejects(p.execute({ card: parent.id!, action: TransitionAction.Delete }), PlanningError, 'live children refuse ordinary deletion')
161
+ await rejects(p.execute({ card: child.id!, action: TransitionAction.Update, changes: { parents: [] }, unset: ['parent'] }), ParentNotFound, 'required primary parent cannot orphan')
162
+ await p.execute({ card: parent.id!, action: TransitionAction.Update, changes: { parent: b.id, parents: [b.id!] } }, { wait: true })
163
+ same((await p.cards.get(parent.id!)).parent, b.id, 'required hierarchy permits safe reparent')
164
+ },
165
+ },
166
+ {
167
+ name: 'recursive project scopes filter nested cards, comments and relationships before pagination', needs: ['resources'],
168
+ run: async ({ facade, service }) => {
169
+ const org = organization(), p = facade(org), fixtures = conformanceFixturesOf(p), actor = await human(p)
170
+ const a = await fixtures.createBranch('Allowed'), b = await fixtures.createBranch('Other'), nest = await section(p, a.id!)
171
+ const visible = await fixtures.createBook(nest.id!), hidden = await fixtures.createBook(b.id!)
172
+ await p.execute({ card: visible.id!, action: TransitionAction.Link, link: { type: 'shelved-near', to: hidden.id! } }, { wait: true })
173
+ const author = service.for({ ...p.scope, assigneeId: actor.id })
174
+ await author.comments.create({ card: visible.id!, body: 'visible' }); await author.comments.create({ card: hidden.id!, body: 'hidden' })
175
+ const narrowed = service.for({ ...p.scope, projects: [a.id!] })
176
+ same((await narrowed.cards.get(visible.id!)).id, visible.id, 'recursive single read')
177
+ same(await narrowed.cards.load(hidden.id!), null, 'outside scope absent')
178
+ const list = await narrowed.cards.list({ size: 1, page: 1 })
179
+ same(list.total, 3, 'recursive total includes project and nested cards')
180
+ same((await narrowed.comments.list({ size: 1 })).total, 1, 'comment total excludes inaccessible cards')
181
+ same((await narrowed.relationships.list({ from: visible.id, size: 1 })).total, 0, 'cross-scope target edge concealed')
182
+ },
183
+ },
184
+ {
185
+ name: 'project modes are validated metadata and project purge removes recursive auxiliaries only', needs: ['resources'],
186
+ run: async ({ facade, service, store }) => {
187
+ const org = organization(), p = facade(org), fixtures = conformanceFixturesOf(p), actor = await human(p)
188
+ const project = await fixtures.createBranch(), nest = await section(p, project.id!), card = await fixtures.createBook(nest.id!)
189
+ same(project.mode, ProjectMode.Opened, 'default open mode')
190
+ await p.execute({ card: project.id!, action: TransitionAction.Update, changes: { mode: ProjectMode.Closed } }, { wait: true })
191
+ same((await p.cards.get(project.id!)).mode, ProjectMode.Closed, 'closed mode persists independently of flow status')
192
+ await rejects(p.execute({ card: card.id!, action: TransitionAction.Update, changes: { mode: ProjectMode.Closed } }), FieldsInvalid, 'mode belongs to projects')
193
+ const team = await p.teams.create({ name: 'Reusable' }); await p.teams.addMember(team.id!, actor.id!); await p.teams.attach(team.id!, project.id!)
194
+ const author = service.for({ ...p.scope, assigneeId: actor.id })
195
+ const comment = await author.comments.create({ card: card.id!, body: mentionHelper.encode(actor.id!, actor.nickname) })
196
+ await p.execute({ card: project.id!, action: TransitionAction.Delete }, { wait: true })
197
+ same(await p.cards.load(card.id!), null, 'recursive child purged')
198
+ same(await p.comments.load(comment.id!), null, 'comment purged')
199
+ same((await store.mentions!.list({ entityId: org, comment: comment.id })).total, 0, 'mention purged')
200
+ same((await p.teams.get(team.id!)).id, team.id, 'organization team survives project purge')
201
+ same((await p.assignees.get(actor.id!)).id, actor.id, 'organization assignee survives project purge')
202
+ same(await p.teams.projects(team.id!), [], 'project-team edge purged')
203
+ },
204
+ },
205
+ {
206
+ name: 'data-defined assignee schemas are organization-only, validated and revisioned', needs: ['schemas', 'resources'],
207
+ run: async ({ facade, service }) => {
208
+ const p = facade(organization()), defs = p.definitions!, type = `agent:${idHelper.uuid()}`
209
+ const schema = { type, version: 1, kind: AssigneeKind.NonHuman, fields: { type: 'object', properties: { model: { type: 'string' } }, required: ['model'], additionalProperties: false } }
210
+ await defs.putAssigneeType(schema)
211
+ await rejects(p.assignees.create({ nickname: 'agent', type, kind: AssigneeKind.NonHuman }), FieldsInvalid, 'data-defined fields enforced')
212
+ const actor = await p.assignees.create({ nickname: 'agent', type, kind: AssigneeKind.NonHuman, fields: { model: 'runner' } })
213
+ const bundle = await defs.bundle()
214
+ check(bundle.assigneeTypes?.some(row => row.type === type) === true, 'assignee definition roundtrips')
215
+ const root = await conformanceFixturesOf(p).createBranch('Schema root')
216
+ const nested = (await p.execute({ action: TransitionAction.Create, card: { kind: WorkcardKind.Project, type: LIBRARY.branch, title: 'Nested schema project', parent: root.id! } }, { wait: true })).card!
217
+ const nestedType = `${type}:nested-card`
218
+ await defs.putType({ type: nestedType, version: 1, kind: WorkcardKind.Card, fields: { type: 'object' }, flows: [LIBRARY.circulation], specifications: [] }, { project: nested.id })
219
+ const scoped = service.for({ ...p.scope, projects: [root.id!] })
220
+ check((await scoped.definitions!.records({ project: nested.id })).some(record => record.key === nestedType), 'ancestor project scope includes descendant schema records')
221
+ await rejects(defs.putAssigneeType({ ...schema, version: 2, fields: { type: 'invalid-json-schema' } }), SchemaInvalid, 'invalid assignee schema refused')
222
+ await defs.retire(PlanningSchemaKind.AssigneeType, type)
223
+ await rejects(p.assignees.create({ nickname: 'new-agent', type, kind: AssigneeKind.NonHuman, fields: actor.fields }), FieldsInvalid, 'retired type refuses new identities')
224
+ same((await p.assignees.get(actor.id!)).id, actor.id, 'retirement keeps identity readable')
225
+ },
226
+ },
227
+ ]
@@ -0,0 +1,20 @@
1
+ import type { PlanningFacade, PlanningService, PlanningStore } from '@owlmeans/planning'
2
+
3
+ /** What a case runs against: a planning service over the store under test. */
4
+ export interface ConformanceSubject {
5
+ /** A service with `planningConformancePlugin` registered and a strictly increasing clock. */
6
+ service: PlanningService
7
+ /** The store under test — the service's default store. */
8
+ store: PlanningStore
9
+ /** A facade of one organization, acting as a signed-in person. */
10
+ facade: (entityId: string) => PlanningFacade
11
+ }
12
+
13
+ /** A capability a store may implement; a case that needs one is skipped for a store without it. */
14
+ export type ConformanceCapability = 'schemas' | 'resources'
15
+
16
+ export interface ConformanceCase {
17
+ name: string
18
+ needs?: ConformanceCapability[]
19
+ run: (subject: ConformanceSubject) => Promise<void>
20
+ }
package/src/consts.ts CHANGED
@@ -20,3 +20,9 @@ export const DEFAULT_COMMIT_MEMORY = 1000
20
20
  * ladder is what answers when it folded somewhere the hub does not.
21
21
  */
22
22
  export const COMMIT_POLL_LADDER: readonly number[] = Object.freeze([250, 1000, 2000])
23
+
24
+ /**
25
+ * How many resolved schema layers (organization, project) a service keeps. A layer is re-resolved
26
+ * whenever its organization's schema revision moves, so the cap bounds memory, never staleness.
27
+ */
28
+ export const DEFAULT_SCHEMA_VIEWS = 500