@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
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: planning
3
- description: How to use @owlmeans/planning — the runtime-free workcard model (cards, projects, specifications), typed relationships, shareable status flows with three intrinsic states, the Transition event and its pure fold, the schema registry, the query language and its wire encoding, the protocol tree, and the models that work identically on a server and in a browser. Auto-invoked when importing a Workcard type, a status flow, applyTransition, computeChanges, criteriaOf, encodeWorkcardQuery, makePlanningProtocols or a planning model.
3
+ description: How to use @owlmeans/planning — the runtime-free workcard model (cards, projects, specifications), typed relationships, shareable status flows with three intrinsic states, the Transition event and its pure fold, the schema registry, the query language and its wire encoding, the protocol tree, and the models that work identically on a server and in a browser. Auto-invoked when importing a Workcard type, a status flow, applyHelper, changesHelper, queryHelper, wireHelper, makePlanningProtocols or a planning model.
4
4
  user-invocable: false
5
5
  ---
6
6
  <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
@@ -8,7 +8,7 @@ user-invocable: false
8
8
  # @owlmeans/planning
9
9
 
10
10
  **Layer:** Cross-cutting domain
11
- **Install:** `"@owlmeans/planning": "^0.1.18-rc.9"` in `dependencies` (`ajv` and `ajv-formats` are peers)
11
+ **Install:** `"@owlmeans/planning": "^0.1.18"` in `dependencies` (`ajv` and `ajv-formats` are peers)
12
12
 
13
13
  The contracts of project planning: record shapes, schemas, refusals, the protocol tree, the pure
14
14
  fold and the models. No database, no fastify, no React. The executor, the plugin registry, the
@@ -17,32 +17,54 @@ mirror are `@owlmeans/client-planning`; a durable store implements the ports dec
17
17
 
18
18
  ## The one idea
19
19
 
20
- Every change is a **transition** — an append-only event — and a card IS the fold of its
20
+ Every workcard change is a **transition** — an append-only event — and a card IS the fold of its
21
21
  transitions in `seq` order. There is one write path (`facade.execute(exec)`), one fold
22
- (`applyTransition`) and one query translation (`criteriaOf`), shared by every store and both sides
22
+ (`applyHelper.applyTransition`) and one query translation (`queryHelper.criteriaOf`), shared by every store and both sides
23
23
  of the wire, so one log always means one card.
24
24
 
25
+ ## The write call
26
+
27
+ `facade.execute(execution, options?)` — the same signature on the server facade
28
+ (`@owlmeans/server-planning`, reached as `ctx.service<PlanningHostService>(PLANNING_SERVICE).for({
29
+ entityId, profileId })`) and the remote one (`@owlmeans/client-planning`).
30
+
31
+ - The **execution** has exactly these keys — `TransitionExecutionSchema` is closed, but only the
32
+ wire validates against it; in process an unknown key is silently ignored: `card` — the `WorkcardDraft` of a
33
+ create (`kind`, `type`, `title` required; `parent`/`parents`, `description`, `code`, `labels`,
34
+ `order`, `fields`, `status`, and a specification's `category`/`format`/`body`), the card id
35
+ otherwise — and `action` (`TransitionAction`), then per action `transition`, `flow`, `changes`,
36
+ `unset`, `link` / `links`, and `cause`, `key`, `expectSeq`.
37
+ - The **options** are `{ wait?, timeout? }`, always the second argument. Only the wire body
38
+ (`ExecuteRequest`) carries them beside the execution.
39
+
40
+ The worked example (facade, create, read back, transit, a data-defined type and flow) and the wrong
41
+ forms generated code keeps writing are `server-planning` → Creating and reading a card.
42
+
25
43
  ## Key exports
26
44
 
27
45
  | Export | What it is |
28
46
  |---|---|
29
47
  | `Workcard`, `Card`, `Project`, `Specification`, `Relationship`, `Transition` | The records |
30
48
  | `StatusFlowSchema`, `WorkcardTypeSchema`, `ProjectTypeSchema`, `SpecificationSlot`, `CodePolicy`, `RelationshipType`, `PlanningSchemaBundle` | Type declarations — data, not code |
49
+ | `PlanningAppDeclarations`, `PlanningAppSchema`, `PlanningAppBagKind` | The application's own type/flow properties (`app` bags) and base card fields, declared in code |
31
50
  | `TransitionExecution`, `WorkcardDraft`, `TransitionReceipt`, `CommitEvent`, `CommitSource` | The write path and its commit feed |
32
- | `TransitionStore`, `ProjectionStore`, `SpecificationStore`, `RelationshipStore`, `PlanningStore` | Ports a store implements (only `cards` is required) |
33
- | `PlanningPlugin`, `PlanningScope`, `PlanningFacade`, `PlanningService` | The plugin seam and the facade, typed here so a plugin and a client can name them |
34
- | `WorkcardKind`, `IntrinsicStatus`, `IntrinsicPolicy`, `TransitionAction`, `CommitState`, `SpecificationFormat`, `CodeStyle`, `CodeScope` | Enums |
35
- | `PLANNING_SERVICE` (`'planning'`), `PLANNING_PATH`, `PLANNING_COMMIT_EVENT` (`'planning-commit'`), `planningAliases(base)` | Names both sides share |
51
+ | `TransitionStore`, `ProjectionStore`, `SpecificationStore`, `RelationshipStore`, `SchemaStore`, `PlanningStore` | Ports a store implements (only `cards` is required) |
52
+ | `PlanningPlugin`, `PlanningScope`, `PlanningFacade`, `PlanningService`, `PlanningDefinitions` | The plugin seam and the facade, typed here so a plugin and a client can name them |
53
+ | `ScopedSchemaRecord`, `ScopedSchemaWhere`, `ScopedSchemaBundle`, `ScopedSchemaRegistry`, `SchemaDeclarations`, `SchemaWriteOptions`, `SchemaScope`, `SchemaKey`, `SchemaListQuery`, `SchemaDefineRequest`, `SchemaDefineReply` | Data-defined types and flows |
54
+ | `WorkcardKind`, `IntrinsicStatus`, `IntrinsicPolicy`, `TransitionAction`, `CommitState`, `SpecificationFormat`, `CodeStyle`, `CodeScope`, `PlanningSchemaKind`, `SchemaOrigin`, `SchemaWriteMode` | Enums |
55
+ | `PLANNING_SERVICE` (`'planning'`), `PLANNING_PATH`, `PLANNING_COMMIT_EVENT` (`'planning-commit'`), `planningAliasHelper.planningAliases(base)`, `planningAliasHelper.planningDefinitionAliases(base)` | Names both sides share |
36
56
  | `TITLE_MAX` 2048, `DESCRIPTION_MAX` 16384, `CODE_MAX` 64, `BODY_MAX` 1048576, `MAX_LABELS` 32, `MAX_PARENTS` 32, `DEFAULT_COMMIT_TIMEOUT` 30 s, `DEFAULT_COMMIT_POLL` 20 s, `MAX_COMMIT_POLL` 55 s, `CODE_MINT_ATTEMPTS` 8 | Limits |
37
- | `applyTransition`, `applyRelationship` | The fold |
38
- | `computeChanges`, `isEmptyChange`, `assertMutable`, `mergeFields`, `applyUnset`, `sameValue` | What a transition records |
39
- | `intrinsicOf`, `initialStatusOf`, `ruleOf`, `canTransit`, `transitionsFrom`, `initialFlowsOf`, `resolveIntrinsic`, `primaryFlowOf`, `isTerminal` | Status helpers |
40
- | `criteriaOf`, `listOptionsOf`, `specCriteriaOf`, `linkWhereOf`, `transitionWhereOf`, `summaryOf` | Querying |
41
- | `encode*Query` / `decode*Query` (Workcard, Summary, Transition, Specification, Relationship), `encodeSort`, `decodeSort`, `encodeList`, `decodeList` | The wire form of a query |
42
- | `mintCode`, `codeScopeOf`, `slugOf` | Codes |
43
- | `slotOf`, `currentSpecification`, `nextRevision`, `specificationTypeOf`, `bodyCharsOf` | Specifications |
44
- | `makeAjv`, `validateFields`, `validateCard`, `validateSpecificationBody`, `invalidFieldKeys`, `ajvErrorText` | Validation |
45
- | `makeSchemaRegistry(bundle?)` | Types, flows, cached field validators; `bundle()` / `load()` |
57
+ | `applyHelper` — `applyTransition`, `applyRelationship` | The fold |
58
+ | `changesHelper` — `computeChanges`, `isEmptyChange`, `assertMutable`, `mergeFields`, `applyUnset`, `sameValue` | What a transition records |
59
+ | `statusHelper` — `intrinsicOf`, `initialStatusOf`, `ruleOf`, `canTransit`, `transitionsFrom`, `initialFlowsOf`, `resolveIntrinsic`, `primaryFlowOf`, `isTerminal` | Status helpers |
60
+ | `queryHelper` — `criteriaOf`, `listOptionsOf`, `specCriteriaOf`, `linkWhereOf`, `transitionWhereOf`, `summaryOf` | Querying |
61
+ | `wireHelper` — `encode*Query` / `decode*Query` (Workcard, Summary, Transition, Specification, Relationship), `encodeSort`, `decodeSort`, `encodeList`, `decodeList` | The wire form of a query |
62
+ | `codeHelper` — `mintCode`, `codeScopeOf`, `slugOf` | Codes |
63
+ | `specificationHelper` — `slotOf`, `currentSpecification`, `nextRevision`, `specificationTypeOf`, `bodyCharsOf` | Specifications |
64
+ | `makeAjv`, `validateHelper` — `validateFields`, `validateCard`, `validateSpecificationBody`, `invalidFieldKeys`, `ajvErrorText` | Validation |
65
+ | `makeSchemaRegistry(bundle?)` | Types, flows, the app declarations (`registerApp`, `app`), `fieldsSchema(type)` and its cached validator; `bundle()` / `load()` |
66
+ | `appSchemaHelper` — `merge`, `assertDeclarations`, `bagSchemaOf`, `assertBag`, `fieldsSchemaOf` | The application's declarations: merge order, closed forms, a definition's `app` bag, a type's effective fields schema |
67
+ | `scopedSchemaHelper` — `resolveScopedBundle`, `scopedRegistryOf`, `assertTypeSchema`, `assertFlowSchema`, `assertOverridable`, `flowInUse`, `schemaRecordKey`, `schemaKeyOf` | Data-defined layers: resolution, the read-only view, the closed-form checks |
46
68
  | `makePlanningProtocols(opts)` | The protocol tree |
47
69
  | `makeWorkcardModel`, `makeProjectModel`, `makeSpecificationModel`, `modelOf`, `executeFor` | Models |
48
70
  | `*Schema` | AJV schemas of every record, declaration, request and view |
@@ -51,18 +73,31 @@ of the wire, so one log always means one card.
51
73
 
52
74
  - **Base fields at the top level, provider fields under `fields`.** `title`, `description`, `code`,
53
75
  `status`, `labels`, `order`, `parent` are generic and indexable; everything a type adds lives in
54
- `fields` and is validated by the type's `fields` JSON schema. Generic code reads only the top.
55
- A `fields` key never holds `.` or `$` at any depth (`invalidFieldKeys`).
56
- - **`entityId` is the organization's stable id**, never a slug. Every read is scoped by it.
57
- - **Membership is `parent` + `parents`, never a relationship.** `parents` always contains `parent`
58
- (first), so many-to-many membership needs no junction row. `within` queries `parents`.
76
+ `fields` and is validated by the type's `fields` JSON schema — beside the application's base card
77
+ fields (`schemas.app.card`, the `types` part). Generic code reads only the top.
78
+ A `fields` key never holds `.` or `$` at any depth (`validateHelper.invalidFieldKeys`).
79
+ - **`entityId` is the organization's stable record id** — never the wire `entitySlug`, never a body
80
+ value. Every read is scoped by it; where a request's comes from is `server-planning` → Scope and
81
+ security.
82
+ - **Card containment is `parent` + `parents`.** `parents` always contains `parent` (first), so
83
+ multiple direct parents need no junction row. `within` queries those direct parents. Team
84
+ membership and project-team assignment use canonical typed relationships instead.
59
85
  - **Specification is a workcard** (`kind: specification`) with `category`, `format`, `body`, `ref`,
60
86
  `revision`, `version`, `bodyChars`. The SLOT — category, format, `multiple`, `revisioned`,
61
87
  `keepRevisions`, a JSON body `schema` — is declared on the PARENT's type. One record per
62
88
  `(parent, category)` unless the slot is `multiple`; a revisioned slot increments `revision` on the
63
- same record, and earlier bodies come back from the transition log.
89
+ same record, and earlier bodies come back from the transition log. A document belongs to its
90
+ primary parent: the parent's `delete` removes it (and the documents under it) with their edges,
91
+ comments, mentions and log, and never waits for it to be deleted first.
92
+ - **`createdBy` is written once, from a create's draft, and never moves.** Named in `changes` or
93
+ `unset` it is refused on every action (`planning:immutable:createdBy`), so an ownership check
94
+ (`card.createdBy === profileId`) can trust it. Responsibility uses the optional top-level
95
+ `assignee` planning ID; additional participant fields belong in the type's `fields` schema.
96
+ The optional `reporter` also references an assignee ID. Who made each later write is the
97
+ transition's `actor`.
64
98
  - **`seq` is the last folded transition, `head` the highest allocated.** `head > seq` means a write
65
- is in flight (`isPending`); optimistic concurrency (`expectSeq`) compares against the head.
99
+ is in flight (`cardHelper.isPending`); optimistic concurrency (`expectSeq`) compares against the
100
+ head.
66
101
  - `order` is a double: a card may sit between two others (`3.5`).
67
102
  - Timestamps are ISO-8601 UTC strings (`toISOString()`), so lexicographic order is chronological.
68
103
 
@@ -73,112 +108,17 @@ of the wire, so one log always means one card.
73
108
  - A flow is shareable across types, and a type may run several (`flows[0]` is primary).
74
109
  `flows` on the record is authoritative for every flow; `status`/`intrinsic` mirror the primary.
75
110
  - A rule name may repeat with different `from` sets (`start` from `planned`, `start` from
76
- `failed`). `ruleOf` picks the rule naming the current status; a `'*'` rule of that name answers
111
+ `failed`). `statusHelper.ruleOf` picks the rule naming the current status; a `'*'` rule of that name answers
77
112
  only when none does, wherever it is declared. `'*'` includes the target status itself, so
78
- `reset` from `planned` is legal.
79
- - `transitionsFrom(flow, status)` answers one rule per name in declaration order;
113
+ `reset` from `planned` is legal. A status the flow does NOT declare (a card whose flow changed
114
+ under it) matches every rule of the name, the first declared answering — such a card can always
115
+ move back onto the flow it now runs.
116
+ - `statusHelper.transitionsFrom(flow, status)` answers one rule per name in declaration order;
80
117
  `{ explicit: true }` keeps the ones offered to a person.
81
118
  - `IntrinsicPolicy.Primary` (default) reads the primary flow; `IntrinsicPolicy.All` takes the least
82
119
  advanced flow — closed only when every flow is closed. A status a flow does not declare reads as
83
120
  planned.
84
121
 
85
- ## Declaring a type
86
-
87
- A `WorkcardTypeSchema` / `ProjectTypeSchema` is plain data registered through a plugin's `schemas`
88
- or `makeSchemaRegistry({ types, flows })`:
89
-
90
- - `fields` — the AJV schema of `fields`; the registry compiles and caches it (`validator(type)`).
91
- - `specifications` — the slots this type's cards carry as children. A slot may name its
92
- specification `type`; otherwise `specificationTypeOf` takes the only registered specification
93
- type, then the one sharing the parent type's prefix (`viable:project` → `viable:spec`).
94
- - `code` — `CodePolicy { prefix?, style: random|sequential|slug, length?, uppercase?, uniqueWithin:
95
- parent|entity, mutable? }`. A code is fixed once minted unless `mutable`.
96
- - `relationships` — the edge types a card may start (`single` = at most one per card).
97
- - `labels` — the allowed labels (any when omitted).
98
- - A project type lists `cardTypes` (and `projectTypes` for nesting) its children may have.
99
-
100
- ## The fold
101
-
102
- `applyTransition(card | undefined, transition)` is pure and total:
103
-
104
- - an already-applied seq (`seq <= card.seq`) returns the same card — re-folding is safe;
105
- - anything but `card.seq + 1` (or seq 1 for a create with no card) throws
106
- `PlanningError('fold:out-of-order')`;
107
- - `create` builds the whole record from `changes` + `id`/`kind`/`type`/`entityId`/`createdAt`/`seq`;
108
- - `update`/`transit` write `changes` — **`fields` and `flows` merge shallowly, every other key
109
- replaces** — and clear `unset` (`key`, `fields.key`, `flows.id`); identity and required keys are
110
- never written or cleared;
111
- - `link`/`unlink` move only `seq`/`head`/`updatedAt`; `delete` answers `null`;
112
- - every result carries `seq = transition.seq`, `head = max(head, seq)`, `updatedAt = transition.at`.
113
-
114
- `applyRelationship(links, transition)` folds the edges: a create's `links`, `link`, `unlink`, and a
115
- `delete` dropping every edge touching the card. An existing edge is kept, so re-folding adds nothing.
116
-
117
- `computeChanges(card, exec, type, registry, at, { slot? })` is what the executor records:
118
-
119
- - create — the whole initial record: normalized `parents`, every flow at its initial status (the
120
- draft's `status` for the primary), mirrored `status`/`intrinsic`, `closedAt` when it starts
121
- closed; a specification gets its format, `bodyChars` and revision 1 when the slot is revisioned.
122
- The code must already be on the draft (the executor mints first);
123
- - update — only differing values; `null` or `exec.unset` clears a field that has one; a moved
124
- `parent` replaces the old one in `parents`; changed content of a revisioned document sets
125
- `revision + 1`. An update that changes nothing is `isEmptyChange` and must append nothing;
126
- - transit — `flows[flow] = rule.to`, `status` on the primary flow, `intrinsic` when it moves,
127
- `closedAt` set on entering closed and cleared on leaving; throws `IllegalTransition`;
128
- - link/unlink/delete — nothing.
129
-
130
- `assertMutable(exec, type)` refuses (`planning:immutable:<field>`) identity keys, `revision` /
131
- `bodyChars`, `status`/`intrinsic`/`flows`/`closedAt` outside a create (they move only through
132
- `transit`), `category`, a fixed `code`, and clearing a required field.
133
-
134
- ## Querying
135
-
136
- `criteriaOf(query, scope)` is the ONE translation from `WorkcardQuery` to `Criteria<Workcard>`:
137
- bare values and arrays pass through (`kind`, `type`, `status`, `intrinsic`, `code`, `category`),
138
- `parent` is direct children, `within` → `parents: { $contains }`, `labels` → `$overlaps`, `ids` →
139
- `id: { $in }`, `flow` → `flows.<id>`, `fields` → `fields.<key>` equality, `q` → `$or` of title /
140
- description `$ilike` (wildcards escaped) and code `$startsWith`, `updatedSince` → `updatedAt: {
141
- $gte }`. `entityId` always comes from the scope; `undefined` is omitted. Paging and sort are
142
- `listOptionsOf` (`size: 0` = no limit). `summaryOf(cards, parents)` counts DIRECT children by
143
- intrinsic state and gives no key to a parent with none.
144
-
145
- **A query crosses HTTP in its wire form.** The OwlMeans transport cannot carry arrays or nested
146
- objects in a query string (the client writes `key[]=`, the server reads that key literally), so
147
- the list, summary, transitions, specifications and links protocols take `*QueryWire` shapes: lists
148
- comma-joined (a JSON array when an entry holds a comma), objects as JSON, sort as `field,-field`.
149
- A client calls `encodeWorkcardQuery(query)` before `call({ query })`; a handler calls
150
- `decodeWorkcardQuery(req.query)` — tolerant of arrays and of the rich form — and then
151
- `criteriaOf`. A value that does not decode throws `PlanningError('malformed:query:<key>')`.
152
-
153
- ## The protocol tree and mounting
154
-
155
- `makePlanningProtocols({ base: { alias, path?, parent?, service? }, guards, gate?, socketBase? })`:
156
-
157
- | Leaf | Route | Request → reply |
158
- |---|---|---|
159
- | `schema.list` | GET `/schemas` | → `PlanningSchemaBundle` |
160
- | `card.list` | GET `/cards` | query `WorkcardQueryWire` → `ListResult<Workcard>` |
161
- | `card.summary` | GET `/cards/summary` | query `SummaryQueryWire` → `SummaryView` |
162
- | `card.get` | GET `/cards/:id` | → `Workcard` |
163
- | `card.transitions` | GET `/cards/:id/transitions` | query `TransitionQueryWire` |
164
- | `card.specifications` | GET `/cards/:id/specifications` | query `SpecificationQueryWire` |
165
- | `spec.get` / `spec.revisions` | GET `/specifications/:id` / `…/revisions` | query `RevisionsQuery` |
166
- | `link.list` | GET `/links` | query `RelationshipQueryWire` |
167
- | `transition.get` | GET `/transitions/:transition` | → `Transition` |
168
- | `execute` | POST `/execute` | body `ExecuteRequest` → `TransitionReceiptView` |
169
- | `commit.get` | GET `/commits/:transition` | query `CommitQuery { wait? }` → `CommitStatus` |
170
- | `commit.events` | SOCKET `/commits` | query `CommitFeedQuery` → `CommitEvent` frames |
171
-
172
- - Aliases derive from the base alias (`planningAliases(base)` → `<base>:card:list`, …); two mounts
173
- are two base aliases.
174
- - The guards and the gate sit on the base alone; every HTTP leaf inherits them. `path` defaults to
175
- `/planning`.
176
- - Static `/cards/summary` is declared before parametric `/cards/:id`.
177
- - The commit socket hangs under `socketBase` when given and then inherits THAT base's guards —
178
- pass an update/socket base that authenticates; otherwise it hangs under the planning base.
179
- - `actor` in an execute body is ignored on the wire: the server fills it from the request.
180
- - Responses carry no runtime schema (typed only), so a serializer never strips `fields`.
181
-
182
122
  ## Models
183
123
 
184
124
  `makeWorkcardModel(record, facade)` (and `makeProjectModel`, `makeSpecificationModel`, `modelOf`
@@ -196,51 +136,6 @@ by kind) is the same object on a server and in a browser. `schema`, `flow`, `sta
196
136
  `purge` (a delete of the project — the store removes everything under it).
197
137
  - `SpecificationModel` adds `body`, `revise`, `history` (from the log).
198
138
 
199
- ## Errors
200
-
201
- Every refusal is a registered `ResilientError` whose message is `planning:<marker>:<detail>` —
202
- the marker survives a hop where the class is unknown, so match on it there:
203
-
204
- | Class | Marker | HTTP |
205
- |---|---|---|
206
- | `PlanningError` | `planning:` (`fold:out-of-order`, `immutable:<field>`, `malformed:<what>`) | 500 |
207
- | `WorkcardNotFound` | `workcard-not-found:` — also the answer for another entity's card | 404 |
208
- | `UnknownWorkcardType` / `UnknownStatusFlow` | `unknown-type:` / `unknown-flow:` | 422 |
209
- | `ParentNotFound` / `CardTypeNotAllowed` | `parent-not-found:` / `card-type-not-allowed:` | 422 |
210
- | `PlanningRefused` | `refused:` — a plugin middleware said no | 422 |
211
- | `IllegalTransition` | `illegal-transition:<flow>:<transition>:<from>` | 409 |
212
- | `FieldsInvalid` / `LabelNotAllowed` | `fields-invalid:` / `label-not-allowed:` | 422 |
213
- | `SpecificationSlotUnknown` / `SpecificationRevisionConflict` | `specification-slot-unknown:` / `specification-revision-conflict:` | 422 / 409 |
214
- | `RelationshipRefused` / `CodeTaken` | `relationship-refused:` / `code-taken:` | 422 / 409 |
215
- | `WorkcardConflict` | `workcard-conflict:` — stale `expectSeq` | 409 |
216
- | `CommitTimeout` / `CommitFailed` | `commit-timeout:` (still pending, nothing undone) / `commit-failed:` | 500 |
217
- | `PlanningScopeMismatch` / `PlanningUnsupported` | `scope-mismatch:` / `unsupported:` | 404 / 500 |
218
-
219
- Type names are `PlanningError<Suffix>` (`PlanningErrorWorkcardNotFound`).
220
-
221
- **The HTTP column is each class's `static httpStatus`** (the `error` skill's principle; 500 = none
222
- declared): an addressed card that is absent or another entity's is 404, a card whose state or head
223
- refuses the write is 409, a body naming what the registry, the parent or a schema refuses is 422.
224
- Faults declare nothing, and so does `PlanningError` itself — its three causes share one class, and
225
- a base's static would reach every subclass. A new refusal declares on its own leaf class, and
226
- `tests/error-status.spec.ts` refuses an exported refusal whose status was not decided. A plugin's
227
- plain `throw` inside `before` becomes `PlanningRefused` and answers 422, so a plugin FAULT must
228
- throw a `ResilientError` that declares nothing.
229
-
230
- ## i18n
231
-
232
- Importing the package registers, in all seven languages, `lib:planning.*` labels (`kind.*`,
233
- `intrinsic.*`, `action.*`, `commit.*`, `format.*`) and one text per refusal type under the shared
234
- `errors` resource (`errors.<TypeName>`), where a panel's error lookup finds it.
235
-
236
- ## Schema rules
237
-
238
- A record schema may become a Mongo `$jsonSchema`, so every exported schema keeps two rules: **no
239
- `integer`** (`number` only) and **every optional property `nullable: true`** (an enum then admits
240
- `null` too). No `oneOf`/`anyOf` inside a record schema. Build type arrays per schema, never share
241
- one object between schemas — ajv may extend a `type` array while compiling. The walk that pins this
242
- is part of the package's own tests.
243
-
244
139
  ## Depends On
245
140
 
246
141
  - `@owlmeans/resource` — `Criteria`, `ListOptions`, `ListResult`, `createListSchema`
@@ -251,5 +146,17 @@ is part of the package's own tests.
251
146
 
252
147
  - [[server-planning]] — the executor, the plugin registry, the memory store, the handlers
253
148
  - [[client-planning]] — the remote facade, the state mirror, waiting for a commit
254
- - [[resource]] — the criteria language `criteriaOf` targets
255
- - [[server-planning]] — executor, projection implementations, plugin registry and memory store
149
+ - [[resource]] — the criteria language `queryHelper.criteriaOf` targets
150
+ - [[planning-postgres]] — a durable store implementing every port, the schema port included
151
+
152
+ ## Parts
153
+
154
+ Load a part on top of this core when its job is in play.
155
+
156
+ | Part | Load it when |
157
+ |---|---|
158
+ | [types](parts/types.md) | Declaring a type or a status flow, the application's own properties and base card fields (`app`, `appSchemaHelper`), data-defined types and flows (`scopedSchemaHelper`), or a new record schema |
159
+ | [fold](parts/fold.md) | Folding transitions (`applyHelper`, `changesHelper`) or translating a `WorkcardQuery` (`queryHelper`, `wireHelper`) |
160
+ | [mounting](parts/mounting.md) | Declaring or mounting the protocol tree (`makePlanningProtocols`), or serving it in a generated target |
161
+ | [errors](parts/errors.md) | Throwing, matching or phrasing a planning refusal (marker, HTTP status, i18n text) |
162
+ | [collaboration](parts/collaboration.md) | Assignees, reporters, comments and mentions, teams, card hierarchy, the `resources` branches or `PlanningReply` |
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: collaboration
3
+ description: Assignees, comments with stable mentions, teams across projects, card hierarchy rules, the resources protocol branches, and PlanningReply's organization-free records.
4
+ metadata:
5
+ purposes: detail, view
6
+ mentions: Assignee, Team, Comment, CommentMention, mentionHelper, PlanningResourceKind, PLANNING_PROJECT_TEAM, planningResourceAliases, planningReplyHelper, PlanningReply, teams.addMember, reporter, assignee
7
+ ---
8
+ <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
9
+
10
+ # @owlmeans/planning — Assignees, comments, teams and hierarchy
11
+
12
+ ## Assignees, comments, teams and hierarchy
13
+
14
+ Cards retain the transition log; auxiliary records use separate versioned resources. `Assignee`,
15
+ `Team`, `Comment` and `CommentMention` have organization scope and `version` compare-and-set.
16
+ `PlanningResourceKind` qualifies relationship endpoints so ids from different resources cannot
17
+ collide. Omitted endpoint kinds mean `workcard` for stored legacy edges.
18
+
19
+ Assignees have stable ids, normalized organization-unique nicknames (`mentionHelper.nicknameKey`),
20
+ `kind: human | non-human`, `type`, flexible `fields`, optional `authentication: { provider,
21
+ externalId }` and retirement. Human types require authentication by default; declare
22
+ `authentication: 'optional'` to allow a human without it. Non-human types default to optional.
23
+ Authentication is provider-neutral; `@owlmeans/planning-auth` adds the optional OwlMeans adapter.
24
+ Code plugins declare `schemas.assigneeTypes`; data definitions use `putAssigneeType` or
25
+ `define({ assigneeTypes })`. Assignee schemas are organization-wide, never project-local.
26
+
27
+ `reporter` and `assignee` are optional top-level single assignee ids. Additional schema fields use
28
+ `relationships: [{ name, field, toKind: PlanningResourceKind.Assignee, multiple?, to?, inverse? }]`.
29
+ `to` restricts target assignee types. The field is authoritative; the store maintains its canonical
30
+ edge and the facade exposes named inverse views. Do not mirror base fields in `fields`, invent
31
+ join collections or manually write these derived edges. Retired assignees retain existing links
32
+ and comments but cannot receive new assignments or team memberships.
33
+
34
+ Comments belong to a card and a trusted author assignee. Encode stable mentions with
35
+ `mentionHelper.encode(id, nickname)`, producing `[@nickname](assignee:<encoded-id>)`; renaming a
36
+ nickname never retargets a mention. `CommentMention` is a separate derived cache with the source
37
+ comment `revision`; reads repair interrupted cache updates. Never accept author ids from an
38
+ untrusted request or parse ordinary prose as identity.
39
+
40
+ Teams are organization resources, reusable across projects. `teams.addMember/removeMember` and
41
+ `teams.attach/detach` use the existing relationship store, with canonical team-member and
42
+ project-team edges. `teams.members(team)` and `teams.projects(team)` read membership and
43
+ attachment; `teams.assignees(project)` reads effective assignees. Read project teams through
44
+ `relationships.list({ from: project, type: PLANNING_PROJECT_TEAM, toKind: PlanningResourceKind.Team })`;
45
+ project assignees are the deduplicated union of attached teams. Team `externalId` may name an
46
+ external permission group; links do not mutate authentication group membership.
47
+
48
+ Hierarchy uses existing `parent` and direct `parents`, with optional derived `parentType`.
49
+ Declare compatible `parents: { types?, required? }` and `children: { types }` on card schemas.
50
+ Required primary parents cannot be cleared; safe reparenting is supported. Cycles, cross-organization
51
+ links, incompatible descendants and deleting an ordinary card with child cards
52
+ (`hierarchy:parent-in-use`) are refused. A card's own documents — the specifications whose primary
53
+ parent is it, and theirs in turn — never block its delete: they go with it. The
54
+ nearest primary project selects schemas; all project ancestors participate in access checks.
55
+ Project `mode` is `opened` (default) or `closed`, separate from flow status; host policy may use it
56
+ for access decisions.
57
+
58
+ Declare `resources: true` on `makePlanningProtocols` to add assignee/team/comment/mention
59
+ `get`, `list`, `write` branches and use `planningResourceAliases`. Enable `definitions: true`
60
+ for assignee type schema writes. Use stock facade methods, handlers, stores and feeds on both
61
+ sides; resource commands and versions are validated by the shared wire contract.
62
+
63
+ Protocol responses use `PlanningReply<T>`: storage organization metadata (`entityId`) is absent
64
+ from records, schema scopes, receipts and commit frames. `planningReplyHelper.project` handles
65
+ only known record/envelope positions; custom fields, transition changes and schema definitions
66
+ remain opaque. Native facades retain their trusted storage scope. Remote facades hydrate the
67
+ shared record shape with empty advisory metadata; never derive organization identity from it.
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: errors
3
+ description: Every refusal class with its planning marker and HTTP status, the httpStatus rules for a new refusal, and the eight-language i18n the package registers.
4
+ metadata:
5
+ purposes: feedback
6
+ mentions: PlanningError, WorkcardNotFound, IllegalTransition, WorkcardConflict, PlanningRefused, FieldsInvalid, SchemaConflict, SchemaSealed, SchemaInvalid, PlanningForbidden, CommitTimeout, CommitFailed, lib:planning
7
+ ---
8
+ <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
9
+
10
+ # @owlmeans/planning — Errors and i18n
11
+
12
+ ## Errors
13
+
14
+ Every refusal is a registered `ResilientError` whose message is `planning:<marker>:<detail>` —
15
+ the marker survives a hop where the class is unknown, so match on it there:
16
+
17
+ | Class | Marker | HTTP |
18
+ |---|---|---|
19
+ | `PlanningError` | `planning:` (`fold:out-of-order`, `immutable:<field>`, `malformed:<what>`) | 500 |
20
+ | `WorkcardNotFound` | `workcard-not-found:` — also the answer for another entity's card | 404 |
21
+ | `UnknownWorkcardType` / `UnknownStatusFlow` | `unknown-type:` / `unknown-flow:` | 422 |
22
+ | `ParentNotFound` / `CardTypeNotAllowed` | `parent-not-found:` / `card-type-not-allowed:` | 422 |
23
+ | `PlanningRefused` | `refused:` — a plugin middleware said no | 422 |
24
+ | `IllegalTransition` | `illegal-transition:<flow>:<transition>:<from>` | 409 |
25
+ | `FieldsInvalid` / `LabelNotAllowed` | `fields-invalid:` / `label-not-allowed:` | 422 |
26
+ | `SpecificationSlotUnknown` / `SpecificationRevisionConflict` | `specification-slot-unknown:` / `specification-revision-conflict:` | 422 / 409 |
27
+ | `RelationshipRefused` / `CodeTaken` | `relationship-refused:` / `code-taken:` | 422 / 409 |
28
+ | `WorkcardConflict` | `workcard-conflict:` — stale `expectSeq` | 409 |
29
+ | `SchemaConflict` / `SchemaInUse` | `schema-conflict:` (lost version CAS) / `schema-in-use:` (retiring a flow a live type runs) | 409 |
30
+ | `SchemaSealed` / `SchemaInvalid` | `schema-sealed:` / `schema-invalid:` | 422 |
31
+ | `PlanningForbidden` | `forbidden:<grant>:<target>` — the request's access lacks a grant; `forbidden:writes:<id>` — the card or project is outside its `writes` | 403 |
32
+ | `CommitTimeout` / `CommitFailed` | `commit-timeout:` (still pending, nothing undone) / `commit-failed:` | 500 |
33
+ | `PlanningScopeMismatch` / `PlanningUnsupported` | `scope-mismatch:` / `unsupported:` | 404 / 500 |
34
+
35
+ Type names are `PlanningError<Suffix>` (`PlanningErrorWorkcardNotFound`).
36
+
37
+ **The HTTP column is each class's `static httpStatus`** (the `error` skill's principle; 500 = none
38
+ declared): an addressed card that is absent or another entity's is 404, a write the request's
39
+ access does not grant is 403, a card whose state or head (or a schema record whose version)
40
+ refuses the write is 409, a body naming what the registry, the parent or a schema refuses is 422.
41
+ Faults declare nothing, and so does `PlanningError` itself — its three causes share one class, and
42
+ a base's static would reach every subclass. A new refusal declares on its own leaf class, and
43
+ `tests/error-status.spec.ts` refuses an exported refusal whose status was not decided. A plugin's
44
+ plain `throw` inside `before` becomes `PlanningRefused` and answers 422, so a plugin FAULT must
45
+ throw a `ResilientError` that declares nothing.
46
+
47
+ ## i18n
48
+
49
+ Importing the package registers, in eight languages (en, pl, ru, be, uk, es, de, fr), `lib:planning.*` labels (`kind.*`,
50
+ `intrinsic.*`, `action.*`, `commit.*`, `format.*`) and one text per refusal type under the shared
51
+ `errors` resource (`errors.<TypeName>`), where a panel's error lookup finds it.
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: fold
3
+ description: The pure fold (applyTransition, applyRelationship), what computeChanges records and assertMutable refuses, and the one query translation with its wire form.
4
+ metadata:
5
+ purposes: query, state
6
+ mentions: applyHelper, applyTransition, applyRelationship, changesHelper, computeChanges, assertMutable, isEmptyChange, queryHelper, criteriaOf, listOptionsOf, summaryOf, wireHelper, encodeWorkcardQuery, decodeWorkcardQuery, WorkcardQuery
7
+ ---
8
+ <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
9
+
10
+ # @owlmeans/planning — The fold and querying
11
+
12
+ ## The fold
13
+
14
+ `applyHelper.applyTransition(card | undefined, transition)` is pure and total:
15
+
16
+ - an already-applied seq (`seq <= card.seq`) returns the same card — re-folding is safe;
17
+ - anything but `card.seq + 1` (or seq 1 for a create with no card) throws
18
+ `PlanningError('fold:out-of-order')`;
19
+ - `create` builds the whole record from `changes` + `id`/`kind`/`type`/`entityId`/`createdAt`/`seq`;
20
+ - `update`/`transit` write `changes` — **`fields` and `flows` merge shallowly, every other key
21
+ replaces** — and clear `unset` (`key`, `fields.key`, `flows.id`); identity and required keys are
22
+ never written or cleared;
23
+ - `link`/`unlink` move only `seq`/`head`/`updatedAt`; `delete` answers `null`;
24
+ - every result carries `seq = transition.seq`, `head = max(head, seq)`, `updatedAt = transition.at`;
25
+ - the log is replayed AS WRITTEN: a stored row admission would refuse (an update naming
26
+ `createdBy`) still folds. Guards live at admission (`changesHelper.assertMutable`, the executor), never in the
27
+ fold, so a stored log never stops projecting.
28
+
29
+ `applyHelper.applyRelationship(links, transition)` folds the edges: a create's `links`, `link`, `unlink`, and a
30
+ `delete` dropping every edge touching the card. An existing edge is kept, so re-folding adds nothing.
31
+
32
+ `changesHelper.computeChanges(card, exec, type, registry, at, { slot? })` is what the executor records:
33
+
34
+ - create — the whole initial record: normalized `parents`, every flow at its initial status (the
35
+ draft's `status` for the primary), mirrored `status`/`intrinsic`, `closedAt` when it starts
36
+ closed; a specification gets its format, `bodyChars` and revision 1 when the slot is revisioned.
37
+ The code must already be on the draft (the executor mints first);
38
+ - update — only differing values; `null` or `exec.unset` clears a field that has one; a moved
39
+ `parent` replaces the old one in `parents`; changed content of a revisioned document sets
40
+ `revision + 1`. An update that changes nothing is `changesHelper.isEmptyChange` and must append nothing;
41
+ - transit — `flows[flow] = rule.to`, `status` on the primary flow, `intrinsic` when it moves,
42
+ `closedAt` set on entering closed and cleared on leaving; throws `IllegalTransition`;
43
+ - link/unlink/delete — nothing.
44
+
45
+ `changesHelper.assertMutable(exec, type)` refuses (`planning:immutable:<field>`) identity keys (`createdAt`
46
+ among them), `createdBy` in `changes` or `unset` on any action (a create included),
47
+ `revision` / `bodyChars`, `status`/`intrinsic`/`flows`/`closedAt` outside a create (they move only
48
+ through `transit`), `category`, a fixed `code`, and clearing a required field.
49
+
50
+ ## Querying
51
+
52
+ `queryHelper.criteriaOf(query, scope)` is the ONE translation from `WorkcardQuery` to `Criteria<Workcard>`:
53
+ bare values and arrays pass through (`kind`, `type`, `status`, `intrinsic`, `code`, `category`),
54
+ `parent` is direct children, `within` → `parents: { $contains }`, `labels` → `$overlaps`, `ids` →
55
+ `id: { $in }`, `flow` → `flows.<id>`, `fields` → `fields.<key>` equality, `q` → `$or` of title /
56
+ description `$ilike` (wildcards escaped) and code `$startsWith`, `updatedSince` → `updatedAt: {
57
+ $gte }`. `entityId` always comes from the scope; `undefined` is omitted. Paging and sort are
58
+ `queryHelper.listOptionsOf` (`size: 0` = no limit). `queryHelper.summaryOf(cards, parents)` counts DIRECT children by
59
+ intrinsic state and gives no key to a parent with none. A scope naming `projects` is narrowed by
60
+ the SERVER facade on top of this translation (`server-planning` → The access resolver);
61
+ `TransitionWhere.project` and `RelationshipWhere.project` take one id or a list.
62
+
63
+ **A query crosses HTTP in its wire form.** The OwlMeans transport cannot carry arrays or nested
64
+ objects in a query string (the client writes `key[]=`, the server reads that key literally), so
65
+ the list, summary, transitions, specifications and links protocols take `*QueryWire` shapes: lists
66
+ comma-joined (a JSON array when an entry holds a comma), objects as JSON, sort as `field,-field`.
67
+ A client calls `wireHelper.encodeWorkcardQuery(query)` before `call({ query })`; a handler calls
68
+ `wireHelper.decodeWorkcardQuery(req.query)` — tolerant of arrays and of the rich form — and then
69
+ `queryHelper.criteriaOf`. A value that does not decode throws `PlanningError('malformed:query:<key>')`.
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: mounting
3
+ description: The protocol tree makePlanningProtocols declares (leaves, routes, aliases, guards, the commit socket) and how a generated target mounts it.
4
+ metadata:
5
+ purposes: server-api, config
6
+ mentions: makePlanningProtocols, planningAliasHelper, planningAliases, planningDefinitionAliases, PLANNING_PATH, socketBase, ExecuteRequest, CommitQuery, CommitFeedQuery, schema.define
7
+ ---
8
+ <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
9
+
10
+ # @owlmeans/planning — The protocol tree and mounting
11
+
12
+ ## The protocol tree and mounting
13
+
14
+ `makePlanningProtocols({ base: { alias, path?, parent?, service? }, guards, gate?, socketBase?, definitions? })`:
15
+
16
+ | Leaf | Route | Request → reply |
17
+ |---|---|---|
18
+ | `schema.list` | GET `/schemas` | → `PlanningSchemaBundle` |
19
+ | `card.list` | GET `/cards` | query `WorkcardQueryWire` → `ListResult<Workcard>` |
20
+ | `card.summary` | GET `/cards/summary` | query `SummaryQueryWire` → `SummaryView` |
21
+ | `card.get` | GET `/cards/:id` | → `Workcard` |
22
+ | `card.transitions` | GET `/cards/:id/transitions` | query `TransitionQueryWire` |
23
+ | `card.specifications` | GET `/cards/:id/specifications` | query `SpecificationQueryWire` |
24
+ | `spec.get` / `spec.revisions` | GET `/specifications/:id` / `…/revisions` | query `RevisionsQuery` |
25
+ | `link.list` | GET `/links` | query `RelationshipQueryWire` |
26
+ | `transition.get` | GET `/transitions/:transition` | → `Transition` |
27
+ | `execute` | POST `/execute` | body `ExecuteRequest` → `TransitionReceiptView` |
28
+ | `commit.get` | GET `/commits/:transition` | query `CommitQuery { wait? }` → `CommitStatus` |
29
+ | `commit.events` | SOCKET `/commits` | query `CommitFeedQuery` → `CommitEvent` frames |
30
+ | `schema.define` (with `definitions: true`) | POST `/schemas` | body `SchemaDefineRequest { project?, mode?, types?, flows?, retire? }` → `SchemaDefineReply { records, bundle }` |
31
+
32
+ - Aliases derive from the base alias (`planningAliasHelper.planningAliases(base)` → `<base>:card:list`, …); two mounts
33
+ are two base aliases. `definitions: true` adds `schema.define` (its alias from
34
+ `planningAliasHelper.planningDefinitionAliases(base)`, kept apart so a tree without it names no undeclared alias)
35
+ and gives `schema.list` a `SchemaListQuery { project? }` answered with that layer's scoped bundle.
36
+ Without it the tree declares exactly the other leaves.
37
+ - The guards and the gate sit on the base alone; every HTTP leaf inherits them. `path` defaults to
38
+ `/planning`.
39
+ - Static `/cards/summary` is declared before parametric `/cards/:id`.
40
+ - The commit socket hangs under `socketBase` when given and then inherits THAT base's guards —
41
+ pass an update/socket base that authenticates; otherwise it hangs under the planning base.
42
+ - `actor` in an execute body is ignored on the wire: the server fills it from the request.
43
+ - Responses carry no runtime schema (typed only), so a serializer never strips `fields`.
44
+
45
+ ## Mounting in a target
46
+
47
+ A generated target serves this tree as it is — no hand-written card, transition, schema or document
48
+ routes; domain rules are `before` plugins. This section is the hub; each package skill has its half.
49
+
50
+ - **common** declares the one tree the api and the web share: `makePlanningProtocols({ base: {
51
+ alias: 'api:planning' }, guards: DEFAULT_GUARD, definitions: true, resources: true })`, in its own module beside
52
+ the `PLANNING` plugin (`sources/common/src/planning.ts`).
53
+ - **api** — `server-planning` → Mounting in a target (stock handlers, no commit socket, the
54
+ resolver answering `writes`). **web** — `client-planning` → Mounting in a target. **store** —
55
+ `planning-postgres`.
56
+ - **Kit literals**: card types and flows are LITERAL objects in `PLANNING.schemas.flows` / `.types`,
57
+ above the `// owlmeans: add planning flows|types above this line` sentinels — a spread or an
58
+ imported constant hides them from the library's registration checks. Card types and flows carry
59
+ `overridable: true` with open `fields` (`additionalProperties: true`); the container project type
60
+ (always sealed) carries `scopedCardTypes: true`, so the app's people override and extend them as
61
+ data through `schema.define`.
62
+ - What the app itself keeps about every type, flow or card (a type's `description`, an origin's
63
+ attribute planning has no key for) is `PLANNING.schemas.app` — never an extra key on a type or a
64
+ flow (the `types` part, Application-declared properties).