@yemi33/minions 0.1.2423 → 0.1.2425

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 (184) hide show
  1. package/bin/install-internal-minions.js +1209 -0
  2. package/bin/minions.js +75 -9
  3. package/dashboard/docs/typography.md +27 -12
  4. package/dashboard/js/command-center.js +13 -11
  5. package/dashboard/js/live-stream.js +1 -1
  6. package/dashboard/js/memory-search.js +388 -43
  7. package/dashboard/js/modal-qa.js +5 -5
  8. package/dashboard/js/qa.js +39 -26
  9. package/dashboard/js/refresh.js +37 -18
  10. package/dashboard/js/render-agents.js +14 -18
  11. package/dashboard/js/render-dispatch.js +14 -74
  12. package/dashboard/js/render-inbox.js +3 -3
  13. package/dashboard/js/render-meetings.js +8 -8
  14. package/dashboard/js/render-other.js +45 -20
  15. package/dashboard/js/render-pipelines.js +6 -6
  16. package/dashboard/js/render-plans.js +14 -14
  17. package/dashboard/js/render-prd.js +46 -46
  18. package/dashboard/js/render-prs.js +9 -65
  19. package/dashboard/js/render-schedules.js +5 -5
  20. package/dashboard/js/render-skills.js +6 -6
  21. package/dashboard/js/render-utils.js +4 -4
  22. package/dashboard/js/render-watches.js +4 -16
  23. package/dashboard/js/render-work-items.js +95 -107
  24. package/dashboard/js/settings.js +175 -74
  25. package/dashboard/layout.html +3 -2
  26. package/dashboard/pages/inbox.html +1 -1
  27. package/dashboard/pages/work.html +1 -1
  28. package/dashboard/shared/model-display.js +9 -0
  29. package/dashboard/shared/watches-source.js +43 -0
  30. package/dashboard/shared/welcome-popup.js +259 -0
  31. package/dashboard/slim/body.html +6 -6
  32. package/dashboard/slim/js/chat.js +4 -2
  33. package/dashboard/slim/js/history.js +10 -3
  34. package/dashboard/slim/js/modals-tiles.js +85 -16
  35. package/dashboard/slim/js/status.js +12 -16
  36. package/dashboard/slim/styles.css +24 -4
  37. package/dashboard/styles.css +62 -27
  38. package/dashboard-build.js +8 -2
  39. package/dashboard.js +3854 -2514
  40. package/docs/README.md +6 -4
  41. package/docs/api-errors.md +144 -0
  42. package/docs/auto-discovery.md +83 -61
  43. package/docs/capture-demos.js +173 -30
  44. package/docs/command-center.md +5 -1
  45. package/docs/completion-reports.md +127 -11
  46. package/docs/constants.md +15 -3
  47. package/docs/constellation-style-telemetry.md +2 -2
  48. package/docs/copilot-cli-schema.md +32 -22
  49. package/docs/cross-repo-plans.md +30 -30
  50. package/docs/demo/memory-system.html +1 -1
  51. package/docs/deprecated.json +132 -18
  52. package/docs/diagnostics-memory.md +8 -4
  53. package/docs/human-vs-automated.md +2 -2
  54. package/docs/internal-install.md +190 -0
  55. package/docs/kb-pr3223-cascade-archiving.md +16 -0
  56. package/docs/kb-pr696-merge-conflict-docs.md +23 -0
  57. package/docs/kb-sweep.md +2 -2
  58. package/docs/keep-processes.md +8 -1
  59. package/docs/live-checkout-mode.md +12 -7
  60. package/docs/managed-spawn.md +21 -3
  61. package/docs/named-agents.md +3 -2
  62. package/docs/plan-lifecycle.md +32 -32
  63. package/docs/pr-auto-fix-dispatch.md +3 -3
  64. package/docs/pr-review-fix-loop.md +6 -6
  65. package/docs/pr-screenshots/pr-899/worker-pool-worktrees-AFTER.png +0 -0
  66. package/docs/pr-screenshots/pr-899/worker-pool-worktrees-BEFORE.png +0 -0
  67. package/docs/pr-screenshots/pr-979/auto-fix-pane-AFTER.png +0 -0
  68. package/docs/pr-screenshots/pr-979/auto-fix-pane-BEFORE.png +0 -0
  69. package/docs/pr-screenshots/pr-985/pr-column-em-dash-AFTER.png +0 -0
  70. package/docs/pr-screenshots/pr-985/pr-column-em-dash-BEFORE.png +0 -0
  71. package/docs/qa-runbook-lifecycle.md +67 -51
  72. package/docs/qa-runbooks.md +2 -2
  73. package/docs/runtime-adapters.md +101 -50
  74. package/docs/security.md +28 -0
  75. package/docs/self-improvement.md +47 -13
  76. package/docs/skills.md +29 -0
  77. package/docs/slim-ux/concepts.md +2 -2
  78. package/docs/specs/agent-configurability.md +43 -41
  79. package/docs/specs/agent-rename.md +13 -13
  80. package/docs/team-memory.md +283 -14
  81. package/docs/tutorials/03-plan-driven-feature.md +3 -3
  82. package/docs/tutorials/04-runtimes-and-harness.md +18 -21
  83. package/docs/tutorials/08-operations-and-recovery.md +3 -2
  84. package/docs/tutorials/README.md +4 -3
  85. package/docs/visual-evidence-ci.md +103 -0
  86. package/docs/watches.md +35 -30
  87. package/docs/worktree-lifecycle.md +151 -13
  88. package/engine/acp-transport.js +1 -0
  89. package/engine/ado.js +80 -33
  90. package/engine/agent-api-validation.js +571 -0
  91. package/engine/agent-worker-pool.js +7 -6
  92. package/engine/api-contracts/agent-content.js +761 -0
  93. package/engine/api-contracts/cc-ops.js +219 -0
  94. package/engine/api-contracts/config-runtime.js +419 -0
  95. package/engine/api-contracts/core.js +138 -0
  96. package/engine/api-contracts/index.js +506 -0
  97. package/engine/api-contracts/orchestration.js +602 -0
  98. package/engine/api-contracts/pull-requests.js +323 -0
  99. package/engine/api-contracts/qa-process.js +764 -0
  100. package/engine/api-contracts/work-plan-prd.js +494 -0
  101. package/engine/api-validation.js +543 -0
  102. package/engine/cc-api-validation.js +586 -0
  103. package/engine/cc-worker-pool.js +9 -1
  104. package/engine/cleanup.js +393 -271
  105. package/engine/cli.js +65 -7
  106. package/engine/comment-classifier.js +70 -5
  107. package/engine/consolidation.js +123 -5
  108. package/engine/content-api-validation.js +606 -0
  109. package/engine/create-pr-worktree.js +22 -1
  110. package/engine/db/migrations/027-review-learning-lifecycle.js +162 -0
  111. package/engine/dispatch.js +33 -0
  112. package/engine/distribution.js +189 -0
  113. package/engine/execution-model.js +59 -0
  114. package/engine/features.js +12 -0
  115. package/engine/github.js +67 -25
  116. package/engine/harness-context.js +313 -0
  117. package/engine/inbox-store.js +1 -1
  118. package/engine/kb-sweep.js +161 -38
  119. package/engine/keep-process-sweep.js +43 -3
  120. package/engine/lifecycle.js +1066 -165
  121. package/engine/llm.js +4 -0
  122. package/engine/managed-spawn.js +463 -48
  123. package/engine/meeting.js +197 -34
  124. package/engine/memory-retrieval.js +280 -0
  125. package/engine/memory-store.js +821 -38
  126. package/engine/model-discovery.js +17 -3
  127. package/engine/pipeline.js +802 -14
  128. package/engine/plan-prd-validation.js +745 -0
  129. package/engine/playbook.js +206 -17
  130. package/engine/pooled-agent-process.js +6 -1
  131. package/engine/pr-action.js +1 -1
  132. package/engine/pr-issue-validation.js +653 -0
  133. package/engine/pr-resolve.js +75 -13
  134. package/engine/prd-store.js +39 -6
  135. package/engine/preflight.js +7 -2
  136. package/engine/process-utils.js +79 -31
  137. package/engine/projects.js +198 -12
  138. package/engine/promotion.js +371 -0
  139. package/engine/qa-from-prd.js +3 -0
  140. package/engine/qa-process-validation.js +572 -0
  141. package/engine/qa-runbooks.js +41 -37
  142. package/engine/qa-runners/maestro.js +11 -7
  143. package/engine/qa-runners/playwright.js +37 -14
  144. package/engine/qa-runners.js +9 -10
  145. package/engine/qa-runs.js +124 -11
  146. package/engine/qa-sessions.js +139 -40
  147. package/engine/quarantine-refs.js +81 -11
  148. package/engine/queries.js +163 -9
  149. package/engine/review-learning-backfill.js +482 -0
  150. package/engine/review-learning.js +1236 -0
  151. package/engine/runtimes/claude.js +60 -2
  152. package/engine/runtimes/codex.js +57 -16
  153. package/engine/runtimes/contract.js +21 -0
  154. package/engine/runtimes/copilot.js +51 -3
  155. package/engine/runtimes/index.js +1 -0
  156. package/engine/scheduler.js +201 -32
  157. package/engine/settings-validation.js +909 -0
  158. package/engine/shared.js +705 -208
  159. package/engine/spawn-agent.js +48 -21
  160. package/engine/steering-constraints.js +31 -0
  161. package/engine/steering-store.js +10 -2
  162. package/engine/steering.js +14 -2
  163. package/engine/timeout.js +404 -111
  164. package/engine/untrusted-fence.js +24 -10
  165. package/engine/watch-actions.js +7 -2
  166. package/engine/watches.js +353 -51
  167. package/engine/work-item-validation.js +561 -0
  168. package/engine/work-items-store.js +61 -1
  169. package/engine/worktree-gc.js +656 -37
  170. package/engine/worktree-preflight.js +154 -0
  171. package/engine.js +2068 -342
  172. package/package.json +1 -1
  173. package/playbooks/_pr-description-audit.md +88 -41
  174. package/playbooks/build-fix-complex.md +3 -3
  175. package/playbooks/docs.md +2 -2
  176. package/playbooks/fix.md +18 -18
  177. package/playbooks/implement.md +18 -18
  178. package/playbooks/qa-session-draft.md +10 -12
  179. package/playbooks/qa-session-execute.md +16 -12
  180. package/playbooks/qa-validate.md +8 -7
  181. package/playbooks/shared-rules.md +45 -0
  182. package/playbooks/test.md +4 -0
  183. package/prompts/cc-system.md +1 -1
  184. package/skills/check-self-authored-review-comment/SKILL.md +2 -2
@@ -0,0 +1,764 @@
1
+ 'use strict';
2
+
3
+ const qaFromPrd = require('../qa-from-prd');
4
+ const keepProcesses = require('../keep-process-sweep');
5
+ const qaRunbooks = require('../qa-runbooks');
6
+ const qaRuns = require('../qa-runs');
7
+ const qaSessions = require('../qa-sessions');
8
+ const shared = require('../shared');
9
+
10
+ const sessionIdPath = {
11
+ policy: 'required',
12
+ fields: [{
13
+ name: 'id',
14
+ type: 'string',
15
+ required: true,
16
+ maxLength: qaSessions.LIMITS.idMax,
17
+ pattern: 'kebab-case',
18
+ validatorSource: 'qa-sessions._isSafeSessionId',
19
+ }],
20
+ };
21
+ const runIdPath = {
22
+ policy: 'required',
23
+ fields: [{
24
+ name: 'id',
25
+ type: 'string',
26
+ required: true,
27
+ maxLength: qaRuns.QA_RUN_ID_MAX,
28
+ validatorSource: 'qa-runs._isSafeRunId',
29
+ }],
30
+ };
31
+ const runbookIdPath = {
32
+ policy: 'required',
33
+ fields: [{
34
+ name: 'id',
35
+ type: 'string',
36
+ required: true,
37
+ maxLength: qaRunbooks.LIMITS.idMax,
38
+ pattern: 'kebab-case',
39
+ validatorSource: 'qa-runbooks._isSafeId',
40
+ }],
41
+ };
42
+
43
+ module.exports = {
44
+ owner: 'qa-process',
45
+ routes: [
46
+ 'GET /api/qa-runs-summary',
47
+ 'GET /api/qa-sessions-summary',
48
+ 'GET /api/meetings-total',
49
+ 'POST /api/qa/runbooks/run',
50
+ 'GET /api/qa/runs',
51
+ 'GET /api/qa/runs/<id>',
52
+ 'DELETE /api/qa/runs/<id>',
53
+ 'GET /api/qa/artifacts/<runId>/<path>',
54
+ 'GET /api/qa/runbooks',
55
+ 'GET /api/qa/runbooks/<id>',
56
+ 'POST /api/qa/runbooks',
57
+ 'DELETE /api/qa/runbooks/<id>',
58
+ 'POST /api/qa/session',
59
+ 'POST /api/qa/from-prd',
60
+ 'GET /api/qa/sessions',
61
+ 'POST /api/qa/sessions/<id>/approve',
62
+ 'POST /api/qa/sessions/<id>/edit',
63
+ 'POST /api/qa/sessions/<id>/cancel',
64
+ 'POST /api/qa/sessions/<id>/kill',
65
+ 'POST /api/qa/sessions/<id>/dismiss',
66
+ 'GET /api/qa/sessions/<id>/test',
67
+ 'GET /api/qa/sessions/<id>',
68
+ 'GET /api/qa/runners',
69
+ 'POST /api/qa/runners/reload',
70
+ 'POST /api/meetings',
71
+ 'GET /api/meetings',
72
+ 'GET /api/meetings/<id>',
73
+ 'POST /api/meetings/note',
74
+ 'POST /api/meetings/advance',
75
+ 'POST /api/meetings/end',
76
+ 'POST /api/meetings/archive',
77
+ 'POST /api/meetings/unarchive',
78
+ 'POST /api/meetings/delete',
79
+ ],
80
+ bodyless: [
81
+ 'DELETE /api/qa/runs/<id>',
82
+ 'DELETE /api/qa/runbooks/<id>',
83
+ 'POST /api/qa/sessions/<id>/approve',
84
+ 'POST /api/qa/runners/reload',
85
+ ],
86
+ overrides: {
87
+ 'GET /api/qa-runs-summary': { noInput: true },
88
+ 'GET /api/qa-sessions-summary': { noInput: true },
89
+ 'GET /api/meetings-total': { noInput: true },
90
+ 'GET /api/meetings': { noInput: true },
91
+ 'GET /api/meetings/<id>': {
92
+ audit: 'audited',
93
+ path: {
94
+ policy: 'required',
95
+ fields: [
96
+ {
97
+ name: 'id',
98
+ type: 'identifier',
99
+ required: true,
100
+ maxLength: 128,
101
+ pattern: '^MTG-[\\w]+$',
102
+ allowedValuesSource: 'meetings[].id',
103
+ },
104
+ ],
105
+ },
106
+ negativeTests: [
107
+ { strategy: 'return-not-found-for-valid-missing-meeting-id', expectedStatus: 404 },
108
+ ],
109
+ },
110
+ 'GET /api/qa/runners': { noInput: true },
111
+ 'POST /api/qa/runbooks/run': {
112
+ audit: 'audited',
113
+ body: {
114
+ policy: 'required',
115
+ fields: [
116
+ {
117
+ name: 'runbookId',
118
+ type: 'string',
119
+ required: true,
120
+ maxLength: qaRunbooks.LIMITS.idMax,
121
+ pattern: 'kebab-case',
122
+ validatorSource: 'qa-runbooks._isSafeId',
123
+ },
124
+ {
125
+ name: 'targetName',
126
+ type: 'string',
127
+ required: true,
128
+ maxLength: qaRunbooks.LIMITS.targetNameMax,
129
+ },
130
+ {
131
+ name: 'agent',
132
+ type: 'string',
133
+ required: false,
134
+ allowedValuesSource: 'config.agents keys',
135
+ },
136
+ ],
137
+ },
138
+ constraints: [
139
+ { kind: 'no-unknown-fields', location: 'body' },
140
+ { kind: 'resource-exists', field: 'runbookId', source: 'qa-runbooks' },
141
+ { kind: 'healthy-target', field: 'targetName', source: 'managed-processes or keep-processes' },
142
+ ],
143
+ negativeTests: [
144
+ { strategy: 'invalid-runbook-id-before-lookup', expectedStatus: 400 },
145
+ { strategy: 'missing-or-unhealthy-target-before-create', expectedStatus: 404 },
146
+ { strategy: 'snapshot-runs-and-work-items-on-rejection', expectedStatus: '4xx' },
147
+ ],
148
+ },
149
+ 'GET /api/qa/runs': {
150
+ audit: 'audited',
151
+ query: {
152
+ policy: 'optional',
153
+ fields: [
154
+ {
155
+ name: 'limit',
156
+ type: 'integer',
157
+ required: false,
158
+ min: 1,
159
+ max: qaRuns.QA_RUNS_MAX_RECORDS,
160
+ },
161
+ {
162
+ name: 'status',
163
+ type: 'string',
164
+ required: false,
165
+ allowedValues: Object.values(qaRuns.QA_RUN_STATUS),
166
+ },
167
+ ],
168
+ },
169
+ constraints: [{ kind: 'single-value-query-fields' }],
170
+ negativeTests: [
171
+ { strategy: 'invalid-fractional-or-out-of-range-limit', expectedStatus: 400 },
172
+ { strategy: 'invalid-or-duplicate-status', expectedStatus: 400 },
173
+ ],
174
+ },
175
+ 'GET /api/qa/runs/<id>': {
176
+ audit: 'audited',
177
+ path: runIdPath,
178
+ negativeTests: [
179
+ { strategy: 'unsafe-id', expectedStatus: 400 },
180
+ { strategy: 'well-formed-missing-id', expectedStatus: 404 },
181
+ ],
182
+ },
183
+ 'DELETE /api/qa/runs/<id>': {
184
+ audit: 'audited',
185
+ path: runIdPath,
186
+ constraints: [
187
+ { kind: 'body-forbidden' },
188
+ { kind: 'terminal-state-required', source: 'qa-runs.TERMINAL_STATUSES' },
189
+ ],
190
+ negativeTests: [
191
+ { strategy: 'non-empty-body-before-delete', expectedStatus: 400 },
192
+ { strategy: 'well-formed-missing-id', expectedStatus: 404 },
193
+ { strategy: 'non-terminal-run', expectedStatus: 409 },
194
+ { strategy: 'snapshot-run-and-artifact-directory-on-rejection', expectedStatus: '4xx' },
195
+ ],
196
+ },
197
+ 'GET /api/qa/artifacts/<runId>/<path>': {
198
+ audit: 'audited',
199
+ path: {
200
+ policy: 'required',
201
+ fields: [
202
+ {
203
+ name: 'runId',
204
+ type: 'string',
205
+ required: true,
206
+ maxLength: qaRuns.QA_RUN_ID_MAX,
207
+ validatorSource: 'qa-runs._isSafeRunId',
208
+ },
209
+ {
210
+ name: 'path',
211
+ type: 'path',
212
+ required: true,
213
+ maxLength: qaRuns.QA_ARTIFACT_PATH_MAX,
214
+ segmentMaxLength: qaRuns.QA_ARTIFACT_SEGMENT_MAX,
215
+ },
216
+ ],
217
+ },
218
+ constraints: [
219
+ { kind: 'relative-path-within-root', rootSource: 'qa-runs.qaArtifactsDirForRun' },
220
+ { kind: 'realpath-within-root', rejectStatus: 403 },
221
+ ],
222
+ negativeTests: [
223
+ { strategy: 'path-traversal-or-symlink-escape', expectedStatus: 403 },
224
+ { strategy: 'well-formed-missing-artifact', expectedStatus: 404 },
225
+ ],
226
+ },
227
+ 'GET /api/qa/runbooks': {
228
+ audit: 'audited',
229
+ query: {
230
+ policy: 'optional',
231
+ fields: [
232
+ {
233
+ name: 'project',
234
+ type: 'string',
235
+ required: false,
236
+ maxLength: qaSessions.LIMITS.projectMax,
237
+ allowedValuesSource: 'config.projects[].name',
238
+ },
239
+ ],
240
+ },
241
+ constraints: [{ kind: 'single-value-query-fields' }],
242
+ negativeTests: [
243
+ { strategy: 'invalid-project-shape', expectedStatus: 400 },
244
+ { strategy: 'unknown-project', expectedStatus: 404 },
245
+ ],
246
+ },
247
+ 'GET /api/qa/runbooks/<id>': {
248
+ audit: 'audited',
249
+ path: runbookIdPath,
250
+ negativeTests: [
251
+ { strategy: 'unsafe-id', expectedStatus: 400 },
252
+ { strategy: 'well-formed-missing-id', expectedStatus: 404 },
253
+ ],
254
+ },
255
+ 'POST /api/qa/runbooks': {
256
+ audit: 'audited',
257
+ body: {
258
+ policy: 'required',
259
+ fields: [
260
+ {
261
+ name: 'id',
262
+ type: 'string',
263
+ required: true,
264
+ maxLength: qaRunbooks.LIMITS.idMax,
265
+ pattern: 'kebab-case',
266
+ },
267
+ { name: 'name', type: 'string', required: true, maxLength: qaRunbooks.LIMITS.nameMax },
268
+ {
269
+ name: 'project',
270
+ type: 'string',
271
+ required: true,
272
+ maxLength: qaSessions.LIMITS.projectMax,
273
+ allowedValuesSource: 'config.projects[].name',
274
+ },
275
+ {
276
+ name: 'targetName',
277
+ type: 'string',
278
+ required: true,
279
+ maxLength: qaRunbooks.LIMITS.targetNameMax,
280
+ },
281
+ {
282
+ name: 'steps',
283
+ type: 'array',
284
+ required: true,
285
+ maxLength: qaRunbooks.LIMITS.stepsMax,
286
+ itemFields: [
287
+ {
288
+ name: 'description',
289
+ type: 'string',
290
+ required: true,
291
+ maxLength: qaRunbooks.LIMITS.stepDescriptionMax,
292
+ },
293
+ {
294
+ name: 'command',
295
+ type: 'string',
296
+ required: false,
297
+ maxLength: qaRunbooks.LIMITS.stepCommandMax,
298
+ },
299
+ ],
300
+ },
301
+ {
302
+ name: 'expectedArtifacts',
303
+ type: 'array',
304
+ required: true,
305
+ maxLength: qaRunbooks.LIMITS.artifactsMax,
306
+ itemFields: [
307
+ {
308
+ name: 'type',
309
+ type: 'string',
310
+ required: true,
311
+ allowedValues: qaRunbooks.ARTIFACT_TYPES,
312
+ },
313
+ {
314
+ name: 'label',
315
+ type: 'string',
316
+ required: true,
317
+ maxLength: qaRunbooks.LIMITS.artifactLabelMax,
318
+ },
319
+ {
320
+ name: 'path',
321
+ type: 'string',
322
+ required: false,
323
+ maxLength: qaRunbooks.LIMITS.artifactPathMax,
324
+ },
325
+ ],
326
+ },
327
+ ],
328
+ },
329
+ constraints: [
330
+ { kind: 'no-unknown-fields', location: 'body', recursive: true },
331
+ { kind: 'globally-unique-id', conflictStatus: 409 },
332
+ ],
333
+ negativeTests: [
334
+ { strategy: 'invalid-top-level-or-nested-field', expectedStatus: 400 },
335
+ { strategy: 'unknown-project-before-file-write', expectedStatus: 404 },
336
+ { strategy: 'cross-project-id-collision', expectedStatus: 409 },
337
+ { strategy: 'snapshot-runbook-files-on-rejection', expectedStatus: '4xx' },
338
+ ],
339
+ },
340
+ 'DELETE /api/qa/runbooks/<id>': {
341
+ audit: 'audited',
342
+ path: runbookIdPath,
343
+ constraints: [{ kind: 'body-forbidden' }],
344
+ negativeTests: [
345
+ { strategy: 'non-empty-body-before-delete', expectedStatus: 400 },
346
+ { strategy: 'well-formed-missing-id', expectedStatus: 404 },
347
+ { strategy: 'snapshot-runbook-files-on-rejection', expectedStatus: '4xx' },
348
+ ],
349
+ },
350
+ 'GET /api/qa/sessions': {
351
+ audit: 'audited',
352
+ query: {
353
+ policy: 'optional',
354
+ fields: [
355
+ {
356
+ name: 'limit',
357
+ type: 'integer',
358
+ required: false,
359
+ min: 1,
360
+ max: qaSessions.QA_SESSIONS_MAX_RECORDS,
361
+ },
362
+ {
363
+ name: 'state',
364
+ type: 'string',
365
+ required: false,
366
+ allowedValues: Object.values(qaSessions.QA_SESSION_STATE),
367
+ allowedValuesSource: 'qa-sessions.QA_SESSION_STATE',
368
+ },
369
+ ],
370
+ },
371
+ constraints: [{ kind: 'single-value-query-fields' }],
372
+ negativeTests: [
373
+ { strategy: 'invalid-fractional-or-out-of-range-limit', expectedStatus: 400 },
374
+ { strategy: 'invalid-or-duplicate-state', expectedStatus: 400 },
375
+ ],
376
+ },
377
+ 'POST /api/qa/session': {
378
+ audit: 'audited',
379
+ body: {
380
+ policy: 'required',
381
+ fields: [
382
+ {
383
+ name: 'target',
384
+ type: 'object',
385
+ required: true,
386
+ variants: [...qaSessions.VALID_TARGET_KINDS],
387
+ fieldMaxLength: qaSessions.LIMITS.targetFieldMax,
388
+ },
389
+ { name: 'flowsRaw', type: 'string', required: true, maxLength: qaSessions.LIMITS.flowsMax },
390
+ {
391
+ name: 'mode',
392
+ type: 'string',
393
+ required: false,
394
+ default: 'confirm',
395
+ allowedValues: [...qaSessions.VALID_MODES],
396
+ },
397
+ {
398
+ name: 'capture',
399
+ type: 'object',
400
+ required: false,
401
+ fields: ['video:boolean', 'screenshots:boolean', 'logs:boolean'],
402
+ },
403
+ {
404
+ name: 'runner',
405
+ type: 'string',
406
+ required: false,
407
+ maxLength: qaSessions.LIMITS.runnerNameMax,
408
+ allowedValuesSource: 'qa-runners registry',
409
+ },
410
+ {
411
+ name: 'project',
412
+ type: 'string',
413
+ required: false,
414
+ maxLength: qaSessions.LIMITS.projectMax,
415
+ allowedValuesSource: 'config.projects[].name',
416
+ },
417
+ {
418
+ name: 'projects',
419
+ type: 'array',
420
+ required: false,
421
+ maxLength: qaSessions.LIMITS.projectsMax,
422
+ itemType: 'string',
423
+ itemMaxLength: qaSessions.LIMITS.projectMax,
424
+ allowedValuesSource: 'config.projects[].name',
425
+ },
426
+ {
427
+ name: 'createdBy',
428
+ type: 'string',
429
+ required: false,
430
+ maxLength: keepProcesses.KEEP_PROCESS_AGENT_ID_MAX,
431
+ },
432
+ ],
433
+ },
434
+ headers: {
435
+ policy: 'optional',
436
+ fields: [
437
+ {
438
+ name: 'X-Minions-Agent',
439
+ type: 'string',
440
+ required: false,
441
+ maxLength: keepProcesses.KEEP_PROCESS_AGENT_ID_MAX,
442
+ pattern: '^[A-Za-z0-9][A-Za-z0-9_-]*$',
443
+ },
444
+ ],
445
+ },
446
+ constraints: [
447
+ { kind: 'no-unknown-fields', location: 'body', recursive: true },
448
+ { kind: 'mutually-exclusive', fields: ['project', 'projects'] },
449
+ { kind: 'resolve-projects-before-create' },
450
+ ],
451
+ negativeTests: [
452
+ { strategy: 'project-domain-string-errors-to-structured-details', expectedStatus: 400 },
453
+ { strategy: 'unknown-nested-field-or-runner-or-project', expectedStatus: '4xx' },
454
+ { strategy: 'snapshot-sessions-and-work-items-on-rejection', expectedStatus: '4xx' },
455
+ ],
456
+ },
457
+ 'POST /api/qa/from-prd': {
458
+ audit: 'audited',
459
+ body: {
460
+ policy: 'required',
461
+ fields: [
462
+ {
463
+ name: 'prd',
464
+ type: 'string',
465
+ required: true,
466
+ maxLength: qaFromPrd.QA_PRD_FILENAME_MAX,
467
+ allowedValuesSource: 'approved/completed PRDs',
468
+ },
469
+ {
470
+ name: 'mode',
471
+ type: 'string',
472
+ required: false,
473
+ default: 'confirm',
474
+ allowedValues: [...qaSessions.VALID_MODES],
475
+ },
476
+ {
477
+ name: 'runner',
478
+ type: 'string',
479
+ required: false,
480
+ maxLength: qaSessions.LIMITS.runnerNameMax,
481
+ allowedValuesSource: 'qa-runners registry',
482
+ },
483
+ ],
484
+ },
485
+ headers: {
486
+ policy: 'optional',
487
+ fields: [
488
+ {
489
+ name: 'X-Minions-Agent',
490
+ type: 'string',
491
+ required: false,
492
+ maxLength: keepProcesses.KEEP_PROCESS_AGENT_ID_MAX,
493
+ pattern: '^[A-Za-z0-9][A-Za-z0-9_-]*$',
494
+ },
495
+ {
496
+ name: 'X-CC-Turn-Id',
497
+ type: 'string',
498
+ required: false,
499
+ maxLength: shared.CC_TURN_ID_MAX,
500
+ validatorSource: 'api-validation.validateIdentifier',
501
+ },
502
+ ],
503
+ },
504
+ constraints: [
505
+ { kind: 'safe-filename', field: 'prd', root: 'prd/' },
506
+ { kind: 'no-unknown-fields', location: 'body' },
507
+ { kind: 'resolve-projects-before-create' },
508
+ ],
509
+ negativeTests: [
510
+ { strategy: 'invalid-mode-runner-or-filename-before-file-read', expectedStatus: 400 },
511
+ { strategy: 'non-ready-or-missing-prd', expectedStatus: 400 },
512
+ { strategy: 'snapshot-sessions-and-work-items-on-rejection', expectedStatus: 400 },
513
+ ],
514
+ },
515
+ 'GET /api/qa/sessions/<id>': {
516
+ audit: 'audited',
517
+ path: sessionIdPath,
518
+ negativeTests: [
519
+ { strategy: 'unsafe-id', expectedStatus: 400 },
520
+ { strategy: 'well-formed-missing-id', expectedStatus: 404 },
521
+ ],
522
+ },
523
+ 'GET /api/qa/sessions/<id>/test': {
524
+ audit: 'audited',
525
+ path: sessionIdPath,
526
+ constraints: [
527
+ { kind: 'sandboxed-session-test-file', source: 'qa-sessions.getSessionTestFile' },
528
+ ],
529
+ negativeTests: [
530
+ { strategy: 'unsafe-id', expectedStatus: 400 },
531
+ { strategy: 'missing-session-or-test-file', expectedStatus: 404 },
532
+ ],
533
+ },
534
+ 'POST /api/qa/sessions/<id>/approve': {
535
+ audit: 'audited',
536
+ path: sessionIdPath,
537
+ constraints: [{
538
+ kind: 'required-state-before-create-run',
539
+ field: 'state',
540
+ allowedValues: [qaSessions.QA_SESSION_STATE.AWAITING_APPROVAL],
541
+ }],
542
+ negativeTests: [
543
+ { strategy: 'request-body', expectedStatus: 400 },
544
+ { strategy: 'missing-session', expectedStatus: 404 },
545
+ { strategy: 'wrong-state-before-run-or-work-item-create', expectedStatus: 409 },
546
+ ],
547
+ },
548
+ 'POST /api/qa/runners/reload': {
549
+ audit: 'audited',
550
+ constraints: [{ kind: 'body-forbidden' }],
551
+ negativeTests: [
552
+ { strategy: 'request-body-before-plugin-load', expectedStatus: 400 },
553
+ { strategy: 'plugin-spy-zero-calls-on-rejection', expectedStatus: 400 },
554
+ ],
555
+ },
556
+ 'POST /api/qa/sessions/<id>/edit': {
557
+ audit: 'audited',
558
+ path: sessionIdPath,
559
+ body: {
560
+ policy: 'required',
561
+ fields: [{
562
+ name: 'feedback',
563
+ type: 'string',
564
+ required: true,
565
+ maxLength: qaSessions.LIMITS.feedbackMax,
566
+ }],
567
+ },
568
+ constraints: [{
569
+ kind: 'required-state-before-queue',
570
+ field: 'state',
571
+ allowedValues: [qaSessions.QA_SESSION_STATE.AWAITING_APPROVAL],
572
+ }],
573
+ negativeTests: [
574
+ { strategy: 'invalid-feedback', expectedStatus: 400 },
575
+ { strategy: 'missing-session', expectedStatus: 404 },
576
+ { strategy: 'wrong-state-before-work-item-create', expectedStatus: 409 },
577
+ ],
578
+ },
579
+ 'POST /api/qa/sessions/<id>/cancel': {
580
+ audit: 'audited',
581
+ path: sessionIdPath,
582
+ body: {
583
+ policy: 'optional',
584
+ fields: [{
585
+ name: 'reason',
586
+ type: 'string',
587
+ required: false,
588
+ maxLength: qaSessions.LIMITS.summaryMax,
589
+ }],
590
+ },
591
+ constraints: [{
592
+ kind: 'allowed-transition',
593
+ source: 'qa-sessions.ALLOWED_TRANSITIONS',
594
+ target: qaSessions.QA_SESSION_STATE.KILLED,
595
+ }],
596
+ negativeTests: [
597
+ { strategy: 'invalid-reason', expectedStatus: 400 },
598
+ { strategy: 'terminal-state', expectedStatus: 409 },
599
+ ],
600
+ },
601
+ 'POST /api/qa/sessions/<id>/kill': {
602
+ audit: 'audited',
603
+ path: sessionIdPath,
604
+ body: {
605
+ policy: 'optional',
606
+ fields: [{
607
+ name: 'reason',
608
+ type: 'string',
609
+ required: false,
610
+ maxLength: qaSessions.LIMITS.summaryMax,
611
+ }],
612
+ },
613
+ constraints: [{
614
+ kind: 'allowed-transition-before-process-kill',
615
+ source: 'qa-sessions.ALLOWED_TRANSITIONS',
616
+ target: qaSessions.QA_SESSION_STATE.KILLED,
617
+ }],
618
+ negativeTests: [
619
+ { strategy: 'invalid-reason', expectedStatus: 400 },
620
+ { strategy: 'terminal-state-before-managed-process-kill', expectedStatus: 409 },
621
+ { strategy: 'process-spy-zero-calls-on-rejection', expectedStatus: '4xx' },
622
+ ],
623
+ },
624
+ 'POST /api/qa/sessions/<id>/dismiss': {
625
+ audit: 'audited',
626
+ path: sessionIdPath,
627
+ body: {
628
+ policy: 'optional',
629
+ fields: [{
630
+ name: 'summary',
631
+ type: 'string',
632
+ required: false,
633
+ maxLength: qaSessions.LIMITS.summaryMax,
634
+ }],
635
+ },
636
+ constraints: [{
637
+ kind: 'allowed-transition',
638
+ source: 'qa-sessions.ALLOWED_TRANSITIONS',
639
+ target: qaSessions.QA_SESSION_STATE.DONE,
640
+ }],
641
+ negativeTests: [
642
+ { strategy: 'invalid-summary', expectedStatus: 400 },
643
+ { strategy: 'terminal-or-wrong-state', expectedStatus: 409 },
644
+ ],
645
+ },
646
+ 'POST /api/meetings': {
647
+ audit: 'audited',
648
+ body: {
649
+ policy: 'required',
650
+ fields: [
651
+ { name: 'title', type: 'string', required: true, minLength: 1, maxLength: 200 },
652
+ { name: 'agenda', type: 'string', required: true, minLength: 1, maxLength: 20000 },
653
+ {
654
+ name: 'participants',
655
+ type: 'array<string>',
656
+ required: true,
657
+ minLength: 2,
658
+ maxLength: 50,
659
+ unique: true,
660
+ allowedValuesSource: 'config.agents keys',
661
+ },
662
+ ],
663
+ },
664
+ constraints: [
665
+ { kind: 'unique-configured-agents', field: 'participants', minItems: 2, maxItems: 50 },
666
+ ],
667
+ negativeTests: [
668
+ { strategy: 'reject-unbounded-title-or-agenda', expectedStatus: 400 },
669
+ { strategy: 'reject-malformed-duplicate-or-unconfigured-participants', expectedStatus: 400 },
670
+ { strategy: 'snapshot-meeting-dispatch-and-inbox-state-on-rejection', expectedStatus: 400 },
671
+ ],
672
+ },
673
+ 'POST /api/meetings/note': {
674
+ audit: 'audited',
675
+ body: {
676
+ policy: 'required',
677
+ fields: [
678
+ { name: 'id', type: 'string', required: true },
679
+ { name: 'note', type: 'string', required: true, minLength: 1, maxLength: 4000 },
680
+ ],
681
+ },
682
+ constraints: [
683
+ { kind: 'meeting-status', field: 'id', allowedValuesSource: 'shared.MEETING_STATUS active-round values' },
684
+ ],
685
+ negativeTests: [
686
+ { strategy: 'reject-malformed-id-or-note-before-mutation', expectedStatus: 400 },
687
+ { strategy: 'reject-terminal-meeting-note', expectedStatus: 409 },
688
+ { strategy: 'return-not-found-for-valid-missing-meeting-id', expectedStatus: 404 },
689
+ ],
690
+ },
691
+ 'POST /api/meetings/advance': {
692
+ audit: 'audited',
693
+ body: {
694
+ policy: 'required',
695
+ fields: [{ name: 'id', type: 'string', required: true }],
696
+ },
697
+ constraints: [
698
+ { kind: 'meeting-status', field: 'id', allowedValuesSource: 'shared.MEETING_STATUS active-round values' },
699
+ { kind: 'round-transition', allowedValuesSource: 'meeting round-transition helpers' },
700
+ ],
701
+ negativeTests: [
702
+ { strategy: 'reject-malformed-id-before-dispatch-teardown', expectedStatus: 400 },
703
+ { strategy: 'reject-terminal-meeting-transition', expectedStatus: 409 },
704
+ { strategy: 'return-not-found-for-valid-missing-meeting-id', expectedStatus: 404 },
705
+ ],
706
+ },
707
+ 'POST /api/meetings/end': {
708
+ audit: 'audited',
709
+ body: {
710
+ policy: 'required',
711
+ fields: [{ name: 'id', type: 'string', required: true }],
712
+ },
713
+ constraints: [
714
+ { kind: 'meeting-status', field: 'id', allowedValuesSource: 'shared.MEETING_STATUS active-round values' },
715
+ ],
716
+ negativeTests: [
717
+ { strategy: 'reject-malformed-id-before-dispatch-teardown', expectedStatus: 400 },
718
+ { strategy: 'reject-terminal-meeting-transition', expectedStatus: 409 },
719
+ { strategy: 'return-not-found-for-valid-missing-meeting-id', expectedStatus: 404 },
720
+ ],
721
+ },
722
+ 'POST /api/meetings/archive': {
723
+ audit: 'audited',
724
+ body: {
725
+ policy: 'required',
726
+ fields: [{ name: 'id', type: 'string', required: true }],
727
+ },
728
+ constraints: [
729
+ { kind: 'meeting-status', field: 'id', allowedValues: ['completed'] },
730
+ ],
731
+ negativeTests: [
732
+ { strategy: 'reject-malformed-id', expectedStatus: 400 },
733
+ { strategy: 'reject-non-completed-meeting', expectedStatus: 409 },
734
+ { strategy: 'return-not-found-for-valid-missing-meeting-id', expectedStatus: 404 },
735
+ ],
736
+ },
737
+ 'POST /api/meetings/unarchive': {
738
+ audit: 'audited',
739
+ body: {
740
+ policy: 'required',
741
+ fields: [{ name: 'id', type: 'string', required: true }],
742
+ },
743
+ constraints: [
744
+ { kind: 'meeting-status', field: 'id', allowedValues: ['archived'] },
745
+ ],
746
+ negativeTests: [
747
+ { strategy: 'reject-malformed-id', expectedStatus: 400 },
748
+ { strategy: 'reject-non-archived-meeting', expectedStatus: 409 },
749
+ { strategy: 'return-not-found-for-valid-missing-meeting-id', expectedStatus: 404 },
750
+ ],
751
+ },
752
+ 'POST /api/meetings/delete': {
753
+ audit: 'audited',
754
+ body: {
755
+ policy: 'required',
756
+ fields: [{ name: 'id', type: 'string', required: true }],
757
+ },
758
+ negativeTests: [
759
+ { strategy: 'reject-malformed-id-before-dispatch-teardown', expectedStatus: 400 },
760
+ { strategy: 'return-not-found-for-valid-missing-meeting-id', expectedStatus: 404 },
761
+ ],
762
+ },
763
+ },
764
+ };