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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/README.md +40 -12
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/server-planning/SKILL.md +193 -18
  4. package/build/actions/cards.d.ts +3 -5
  5. package/build/actions/cards.d.ts.map +1 -1
  6. package/build/actions/cards.js +1 -1
  7. package/build/actions/cards.js.map +1 -1
  8. package/build/actions/execute.d.ts +24 -3
  9. package/build/actions/execute.d.ts.map +1 -1
  10. package/build/actions/execute.js +82 -8
  11. package/build/actions/execute.js.map +1 -1
  12. package/build/actions/schemas.d.ts +27 -5
  13. package/build/actions/schemas.d.ts.map +1 -1
  14. package/build/actions/schemas.js +67 -7
  15. package/build/actions/schemas.js.map +1 -1
  16. package/build/actions/specs.d.ts +2 -4
  17. package/build/actions/specs.d.ts.map +1 -1
  18. package/build/actions/specs.js.map +1 -1
  19. package/build/actions/types.d.ts +4 -0
  20. package/build/actions/types.d.ts.map +1 -0
  21. package/build/actions/types.js +2 -0
  22. package/build/actions/types.js.map +1 -0
  23. package/build/actions/watch.d.ts.map +1 -1
  24. package/build/actions/watch.js +4 -2
  25. package/build/actions/watch.js.map +1 -1
  26. package/build/conformance/assert.d.ts +20 -0
  27. package/build/conformance/assert.d.ts.map +1 -0
  28. package/build/conformance/assert.js +54 -0
  29. package/build/conformance/assert.js.map +1 -0
  30. package/build/conformance/cases.d.ts +7 -0
  31. package/build/conformance/cases.d.ts.map +1 -0
  32. package/build/conformance/cases.js +447 -0
  33. package/build/conformance/cases.js.map +1 -0
  34. package/build/conformance/consts.d.ts +19 -0
  35. package/build/conformance/consts.d.ts.map +1 -0
  36. package/build/conformance/consts.js +42 -0
  37. package/build/conformance/consts.js.map +1 -0
  38. package/build/conformance/fixtures.d.ts +14 -0
  39. package/build/conformance/fixtures.d.ts.map +1 -0
  40. package/build/conformance/fixtures.js +109 -0
  41. package/build/conformance/fixtures.js.map +1 -0
  42. package/build/conformance/index.d.ts +12 -0
  43. package/build/conformance/index.d.ts.map +1 -0
  44. package/build/conformance/index.js +11 -0
  45. package/build/conformance/index.js.map +1 -0
  46. package/build/conformance/types.d.ts +18 -0
  47. package/build/conformance/types.d.ts.map +1 -0
  48. package/build/conformance/types.js +2 -0
  49. package/build/conformance/types.js.map +1 -0
  50. package/build/consts.d.ts +5 -0
  51. package/build/consts.d.ts.map +1 -1
  52. package/build/consts.js +5 -0
  53. package/build/consts.js.map +1 -1
  54. package/build/definitions.d.ts +17 -0
  55. package/build/definitions.d.ts.map +1 -0
  56. package/build/definitions.js +211 -0
  57. package/build/definitions.js.map +1 -0
  58. package/build/executor/changes.d.ts +2 -2
  59. package/build/executor/changes.d.ts.map +1 -1
  60. package/build/executor/changes.js.map +1 -1
  61. package/build/executor/code.d.ts +3 -3
  62. package/build/executor/code.d.ts.map +1 -1
  63. package/build/executor/code.js +1 -1
  64. package/build/executor/code.js.map +1 -1
  65. package/build/executor/consts.local.d.ts +5 -0
  66. package/build/executor/consts.local.d.ts.map +1 -0
  67. package/build/executor/consts.local.js +6 -0
  68. package/build/executor/consts.local.js.map +1 -0
  69. package/build/executor/creator.d.ts +30 -0
  70. package/build/executor/creator.d.ts.map +1 -0
  71. package/build/executor/creator.js +46 -0
  72. package/build/executor/creator.js.map +1 -0
  73. package/build/executor/resolve.d.ts +31 -23
  74. package/build/executor/resolve.d.ts.map +1 -1
  75. package/build/executor/resolve.js +74 -15
  76. package/build/executor/resolve.js.map +1 -1
  77. package/build/executor/types.d.ts +24 -0
  78. package/build/executor/types.d.ts.map +1 -0
  79. package/build/executor/types.js +2 -0
  80. package/build/executor/types.js.map +1 -0
  81. package/build/executor/validate.d.ts +6 -5
  82. package/build/executor/validate.d.ts.map +1 -1
  83. package/build/executor/validate.js +18 -15
  84. package/build/executor/validate.js.map +1 -1
  85. package/build/executor.d.ts +10 -7
  86. package/build/executor.d.ts.map +1 -1
  87. package/build/executor.js +17 -14
  88. package/build/executor.js.map +1 -1
  89. package/build/facade.d.ts +20 -2
  90. package/build/facade.d.ts.map +1 -1
  91. package/build/facade.js +128 -18
  92. package/build/facade.js.map +1 -1
  93. package/build/helper.d.ts +3 -2
  94. package/build/helper.d.ts.map +1 -1
  95. package/build/helper.js +7 -3
  96. package/build/helper.js.map +1 -1
  97. package/build/index.d.ts +1 -0
  98. package/build/index.d.ts.map +1 -1
  99. package/build/index.js +1 -0
  100. package/build/index.js.map +1 -1
  101. package/build/projection.d.ts +2 -10
  102. package/build/projection.d.ts.map +1 -1
  103. package/build/projection.js +1 -1
  104. package/build/projection.js.map +1 -1
  105. package/build/queue/consts.d.ts +5 -0
  106. package/build/queue/consts.d.ts.map +1 -0
  107. package/build/queue/consts.js +5 -0
  108. package/build/queue/consts.js.map +1 -0
  109. package/build/queue/index.d.ts +4 -16
  110. package/build/queue/index.d.ts.map +1 -1
  111. package/build/queue/index.js +2 -4
  112. package/build/queue/index.js.map +1 -1
  113. package/build/queue/types.d.ts +13 -0
  114. package/build/queue/types.d.ts.map +1 -0
  115. package/build/queue/types.js +2 -0
  116. package/build/queue/types.js.map +1 -0
  117. package/build/registry.d.ts +2 -21
  118. package/build/registry.d.ts.map +1 -1
  119. package/build/registry.js +3 -1
  120. package/build/registry.js.map +1 -1
  121. package/build/service.d.ts +3 -16
  122. package/build/service.d.ts.map +1 -1
  123. package/build/service.js +9 -0
  124. package/build/service.js.map +1 -1
  125. package/build/store/commits.d.ts.map +1 -1
  126. package/build/store/commits.js +3 -1
  127. package/build/store/commits.js.map +1 -1
  128. package/build/store/composite.d.ts +1 -0
  129. package/build/store/composite.d.ts.map +1 -1
  130. package/build/store/composite.js +3 -0
  131. package/build/store/composite.js.map +1 -1
  132. package/build/store/consts.local.d.ts +2 -0
  133. package/build/store/consts.local.d.ts.map +1 -0
  134. package/build/store/consts.local.js +2 -0
  135. package/build/store/consts.local.js.map +1 -0
  136. package/build/store/fold.d.ts +4 -3
  137. package/build/store/fold.d.ts.map +1 -1
  138. package/build/store/fold.js +14 -6
  139. package/build/store/fold.js.map +1 -1
  140. package/build/store/memory.d.ts +12 -0
  141. package/build/store/memory.d.ts.map +1 -1
  142. package/build/store/memory.js +0 -0
  143. package/build/store/memory.js.map +1 -1
  144. package/build/store/types.d.ts +15 -1
  145. package/build/store/types.d.ts.map +1 -1
  146. package/build/store/types.local.d.ts +3 -0
  147. package/build/store/types.local.d.ts.map +1 -0
  148. package/build/store/types.local.js +2 -0
  149. package/build/store/types.local.js.map +1 -0
  150. package/build/types.d.ts +111 -1
  151. package/build/types.d.ts.map +1 -1
  152. package/build/types.local.d.ts +7 -0
  153. package/build/types.local.d.ts.map +1 -0
  154. package/build/types.local.js +2 -0
  155. package/build/types.local.js.map +1 -0
  156. package/build/utils/index.d.ts +1 -0
  157. package/build/utils/index.d.ts.map +1 -1
  158. package/build/utils/scope.d.ts +39 -2
  159. package/build/utils/scope.d.ts.map +1 -1
  160. package/build/utils/scope.js +79 -15
  161. package/build/utils/scope.js.map +1 -1
  162. package/build/utils/types.d.ts +8 -0
  163. package/build/utils/types.d.ts.map +1 -0
  164. package/build/utils/types.js +2 -0
  165. package/build/utils/types.js.map +1 -0
  166. package/package.json +28 -20
  167. package/src/actions/cards.ts +2 -6
  168. package/src/actions/execute.ts +95 -12
  169. package/src/actions/schemas.ts +81 -9
  170. package/src/actions/specs.ts +1 -2
  171. package/src/actions/types.ts +4 -0
  172. package/src/actions/watch.ts +5 -2
  173. package/src/conformance/assert.ts +59 -0
  174. package/src/conformance/cases.ts +522 -0
  175. package/src/conformance/consts.ts +44 -0
  176. package/src/conformance/fixtures.ts +121 -0
  177. package/src/conformance/index.ts +11 -0
  178. package/src/conformance/types.ts +20 -0
  179. package/src/consts.ts +6 -0
  180. package/src/definitions.ts +250 -0
  181. package/src/executor/changes.ts +2 -6
  182. package/src/executor/code.ts +3 -9
  183. package/src/executor/consts.local.ts +8 -0
  184. package/src/executor/creator.ts +51 -0
  185. package/src/executor/resolve.ts +84 -40
  186. package/src/executor/types.ts +25 -0
  187. package/src/executor/validate.ts +22 -26
  188. package/src/executor.ts +21 -21
  189. package/src/facade.ts +143 -25
  190. package/src/helper.ts +7 -3
  191. package/src/index.ts +1 -0
  192. package/src/projection.ts +4 -14
  193. package/src/queue/consts.ts +5 -0
  194. package/src/queue/index.ts +6 -21
  195. package/src/queue/types.ts +14 -0
  196. package/src/registry.ts +5 -29
  197. package/src/service.ts +13 -20
  198. package/src/store/commits.ts +5 -4
  199. package/src/store/composite.ts +3 -0
  200. package/src/store/consts.local.ts +1 -0
  201. package/src/store/fold.ts +15 -12
  202. package/src/store/memory.ts +0 -0
  203. package/src/store/types.local.ts +3 -0
  204. package/src/store/types.ts +16 -1
  205. package/src/types.local.ts +7 -0
  206. package/src/types.ts +126 -4
  207. package/src/utils/index.ts +1 -0
  208. package/src/utils/scope.ts +97 -17
  209. package/src/utils/types.ts +8 -0
  210. package/tests/access.spec.ts +160 -0
  211. package/tests/conformance.spec.ts +47 -0
  212. package/tests/create-example.spec.ts +202 -0
  213. package/tests/creator.spec.ts +257 -0
  214. package/tests/entrypoints.spec.ts +5 -0
  215. package/tests/narrowing.spec.ts +94 -0
  216. package/tests/plugins.spec.ts +4 -3
  217. package/tests/writes.spec.ts +140 -0
@@ -0,0 +1,140 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import {
3
+ makePlanningProtocols, PlanningForbidden, SchemaOrigin, SpecificationFormat, TransitionAction, WorkcardKind,
4
+ } from '@owlmeans/planning'
5
+ import type { SchemaDefineReply, ScopedSchemaBundle, Specification } from '@owlmeans/planning'
6
+ import { defineSchemas, executePlanning, getCard, listCards, listSchemas } from '../src/actions/index.js'
7
+ import type { PlanningAccess, PlanningHandlerOptions } from '../src/types.js'
8
+ import { createProject, createTask, ENTITY, invoke, makeTestPlanning, PROJECT, session, TASK } from './context.js'
9
+
10
+ const tree = makePlanningProtocols({ base: { alias: 'test:planning' }, guards: 'test-guard', definitions: true })
11
+
12
+ const withAccess = (access: PlanningAccess): PlanningHandlerOptions => ({ access: async () => access })
13
+
14
+ const flow = { id: 'test:rota', version: 1, statuses: [{ key: 'due', intrinsic: 'planned' }], transitions: [] }
15
+
16
+ /** Two projects: the caller views both and writes only in `written`. */
17
+ const setup = async () => {
18
+ const planning = await makeTestPlanning({}, { schemas: true })
19
+ const { facade } = planning
20
+ const viewed = await createProject(facade(), 'Viewed')
21
+ const written = await createProject(facade(), 'Written')
22
+ const viewedTask = (await createTask(facade(), viewed.id!, 'Viewed task')).card!
23
+ const writtenTask = (await createTask(facade(), written.id!, 'Written task')).card!
24
+ const access: PlanningAccess = { entityId: ENTITY, projects: [viewed.id!, written.id!], writes: [written.id!] }
25
+
26
+ return { ...planning, viewed, written, viewedTask, writtenTask, access, opts: withAccess(access) }
27
+ }
28
+
29
+ describe('@owlmeans/server-planning — per-project writes', () => {
30
+ test('a viewer reads a project it may not write in', async () => {
31
+ const { context, viewed, viewedTask, opts } = await setup()
32
+
33
+ expect((await invoke(getCard(tree.card.get, opts), context, { ...session(), params: { id: viewedTask.id } })).id).toBe(viewedTask.id)
34
+ const listed = await invoke(listCards(tree.card.list, opts), context, { ...session(), query: { parent: viewed.id } })
35
+ expect(listed.items.map((card: { id: string }) => card.id)).toEqual([viewedTask.id])
36
+ const bundle: ScopedSchemaBundle = await invoke(listSchemas(tree.schema.list), context, { ...session(), query: { project: viewed.id } })
37
+ expect(bundle.types.some(type => type.type === TASK)).toBe(true)
38
+ })
39
+
40
+ test('a viewer\'s execute answers 403 on a card, a project, a create and a link; nothing is appended', async () => {
41
+ const { context, facade, viewed, viewedTask, writtenTask, opts } = await setup()
42
+ const execute = executePlanning(tree.execute, opts)
43
+ const refused = async (body: Record<string, unknown>) =>
44
+ await expect(invoke(execute, context, { ...session(), body })).rejects.toBeInstanceOf(PlanningForbidden)
45
+
46
+ expect(PlanningForbidden.httpStatus).toBe(403)
47
+ await refused({ card: viewedTask.id, action: TransitionAction.Update, changes: { title: 'Renamed' } })
48
+ await refused({ card: viewedTask.id, action: TransitionAction.Transit, transition: 'start' })
49
+ await refused({ card: viewed.id, action: TransitionAction.Update, changes: { title: 'Renamed' } })
50
+ await refused({ card: viewed.id, action: TransitionAction.Delete })
51
+ await refused({ card: { kind: WorkcardKind.Card, type: TASK, parent: viewed.id, title: 'Sneaky' }, action: TransitionAction.Create })
52
+ await refused({ card: { kind: WorkcardKind.Card, type: TASK, parent: writtenTask.id, parents: [viewed.id], title: 'Sneaky' }, action: TransitionAction.Create })
53
+ await refused({ card: viewedTask.id, action: TransitionAction.Link, link: { type: 'blocks', to: writtenTask.id } })
54
+
55
+ expect((await facade().cards.get(viewedTask.id!)).title).toBe('Viewed task')
56
+ expect((await facade().cards.get(viewed.id!)).title).toBe('Viewed')
57
+ expect((await facade().cards.list({ parent: viewed.id })).items.map(card => card.id)).toEqual([viewedTask.id])
58
+ })
59
+
60
+ test('a specification is written through its parent card', async () => {
61
+ const { context, facade, viewedTask, writtenTask, opts } = await setup()
62
+ const execute = executePlanning(tree.execute, opts)
63
+ const design = async (task: string) => ((await (await facade().model(await facade().cards.get(task)))
64
+ .write('design', '{"v":1}', { wait: true })).card as Specification)
65
+ const viewedDesign = await design(viewedTask.id!)
66
+ const writtenDesign = await design(writtenTask.id!)
67
+
68
+ await expect(invoke(execute, context, {
69
+ ...session(), body: { card: viewedDesign.id, action: TransitionAction.Update, changes: { body: '{"v":2}' } },
70
+ })).rejects.toBeInstanceOf(PlanningForbidden)
71
+ await expect(invoke(execute, context, {
72
+ ...session(), body: {
73
+ card: { kind: WorkcardKind.Specification, type: viewedDesign.type, parent: viewedTask.id, category: 'design', format: SpecificationFormat.Json, title: 'd', body: '{}' },
74
+ action: TransitionAction.Create,
75
+ },
76
+ })).rejects.toBeInstanceOf(PlanningForbidden)
77
+ const updated = await invoke(execute, context, {
78
+ ...session(), body: { card: writtenDesign.id, action: TransitionAction.Update, changes: { body: '{"v":2}' }, wait: true },
79
+ })
80
+ expect(updated.card.body).toBe('{"v":2}')
81
+ })
82
+
83
+ test('a writer writes in its project: create, transit, link, and its project layer', async () => {
84
+ const { context, facade, written, writtenTask, viewedTask, opts } = await setup()
85
+ const execute = executePlanning(tree.execute, opts)
86
+
87
+ const made = await invoke(execute, context, {
88
+ ...session(), body: { card: { kind: WorkcardKind.Card, type: TASK, parent: written.id, title: 'Allowed' }, action: TransitionAction.Create, wait: true },
89
+ })
90
+ expect(made.card.parent).toBe(written.id)
91
+ await invoke(execute, context, { ...session(), body: { card: writtenTask.id, action: TransitionAction.Transit, transition: 'start', wait: true } })
92
+ expect((await facade().cards.get(writtenTask.id!)).status).toBe('doing')
93
+ // A link FROM a writable card may point at a card the caller only views.
94
+ await invoke(execute, context, {
95
+ ...session(), body: { card: writtenTask.id, action: TransitionAction.Link, link: { type: 'blocks', to: viewedTask.id }, wait: true },
96
+ })
97
+ await invoke(execute, context, { ...session(), body: { card: written.id, action: TransitionAction.Update, changes: { title: 'Renamed' }, wait: true } })
98
+ expect((await facade().cards.get(written.id!)).title).toBe('Renamed')
99
+
100
+ const layer: SchemaDefineReply = await invoke(defineSchemas(tree.schema.define!, opts), context, {
101
+ ...session(), body: { project: written.id, flows: [flow] },
102
+ })
103
+ expect(layer.bundle.origins?.flows['test:rota']).toBe(SchemaOrigin.Project)
104
+ })
105
+
106
+ test('a viewer\'s project-layer schema define answers 403; writes never reach the root', async () => {
107
+ const { context, viewed, access, opts } = await setup()
108
+
109
+ await expect(invoke(defineSchemas(tree.schema.define!, opts), context, {
110
+ ...session(), body: { project: viewed.id, flows: [flow] },
111
+ })).rejects.toBeInstanceOf(PlanningForbidden)
112
+ // `writes` does not gate the organization layer (that is `grants.defineSchemas`), nor a project create.
113
+ const project = await invoke(executePlanning(tree.execute, opts), context, {
114
+ ...session(), body: { card: { kind: WorkcardKind.Project, type: PROJECT, title: 'New' }, action: TransitionAction.Create, wait: true },
115
+ })
116
+ expect(project.card.kind).toBe(WorkcardKind.Project)
117
+ await expect(invoke(defineSchemas(tree.schema.define!, withAccess({ ...access, grants: { defineSchemas: [viewed.id!] } })), context, {
118
+ ...session(), body: { project: viewed.id, flows: [flow] },
119
+ })).rejects.toBeInstanceOf(PlanningForbidden)
120
+ // A project create stays `createProjects`' decision when grants are present.
121
+ await expect(invoke(executePlanning(tree.execute, withAccess({ ...access, grants: {} })), context, {
122
+ ...session(), body: { card: { kind: WorkcardKind.Project, type: PROJECT, title: 'Refused' }, action: TransitionAction.Create },
123
+ })).rejects.toBeInstanceOf(PlanningForbidden)
124
+ })
125
+
126
+ test('absent writes keep the old behaviour: every visible project is writable', async () => {
127
+ const { context, facade, viewed, viewedTask, access } = await setup()
128
+ const { writes: _writes, ...open } = access
129
+ const opts = withAccess(open)
130
+
131
+ await invoke(executePlanning(tree.execute, opts), context, {
132
+ ...session(), body: { card: viewedTask.id, action: TransitionAction.Update, changes: { title: 'Renamed' }, wait: true },
133
+ })
134
+ expect((await facade().cards.get(viewedTask.id!)).title).toBe('Renamed')
135
+ const layer: SchemaDefineReply = await invoke(defineSchemas(tree.schema.define!, opts), context, {
136
+ ...session(), body: { project: viewed.id, flows: [flow] },
137
+ })
138
+ expect(layer.records.map(record => record.key)).toEqual(['test:rota'])
139
+ })
140
+ })