@yemi33/minions 0.1.2424 → 0.1.2426

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 (185) 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 +7 -5
  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/index.html +9 -2
  55. package/docs/internal-install.md +212 -0
  56. package/docs/kb-pr3223-cascade-archiving.md +16 -0
  57. package/docs/kb-pr696-merge-conflict-docs.md +23 -0
  58. package/docs/kb-sweep.md +2 -2
  59. package/docs/keep-processes.md +8 -1
  60. package/docs/live-checkout-mode.md +1 -1
  61. package/docs/managed-spawn.md +21 -3
  62. package/docs/named-agents.md +3 -2
  63. package/docs/onboarding.md +26 -0
  64. package/docs/plan-lifecycle.md +32 -32
  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/01-install-and-connect.md +23 -0
  82. package/docs/tutorials/03-plan-driven-feature.md +3 -3
  83. package/docs/tutorials/04-runtimes-and-harness.md +18 -21
  84. package/docs/tutorials/08-operations-and-recovery.md +3 -2
  85. package/docs/tutorials/README.md +4 -3
  86. package/docs/visual-evidence-ci.md +103 -0
  87. package/docs/watches.md +35 -30
  88. package/docs/worktree-lifecycle.md +151 -13
  89. package/engine/acp-transport.js +1 -0
  90. package/engine/ado.js +80 -33
  91. package/engine/agent-api-validation.js +571 -0
  92. package/engine/agent-worker-pool.js +7 -6
  93. package/engine/api-contracts/agent-content.js +761 -0
  94. package/engine/api-contracts/cc-ops.js +219 -0
  95. package/engine/api-contracts/config-runtime.js +419 -0
  96. package/engine/api-contracts/core.js +138 -0
  97. package/engine/api-contracts/index.js +506 -0
  98. package/engine/api-contracts/orchestration.js +602 -0
  99. package/engine/api-contracts/pull-requests.js +323 -0
  100. package/engine/api-contracts/qa-process.js +764 -0
  101. package/engine/api-contracts/work-plan-prd.js +494 -0
  102. package/engine/api-validation.js +543 -0
  103. package/engine/cc-api-validation.js +586 -0
  104. package/engine/cc-worker-pool.js +9 -1
  105. package/engine/cleanup.js +393 -271
  106. package/engine/cli.js +65 -7
  107. package/engine/comment-classifier.js +70 -5
  108. package/engine/consolidation.js +123 -5
  109. package/engine/content-api-validation.js +606 -0
  110. package/engine/create-pr-worktree.js +22 -1
  111. package/engine/db/migrations/027-review-learning-lifecycle.js +162 -0
  112. package/engine/dispatch.js +33 -0
  113. package/engine/distribution.js +189 -0
  114. package/engine/execution-model.js +59 -0
  115. package/engine/features.js +12 -0
  116. package/engine/github.js +67 -25
  117. package/engine/harness-context.js +313 -0
  118. package/engine/inbox-store.js +1 -1
  119. package/engine/kb-sweep.js +161 -38
  120. package/engine/keep-process-sweep.js +43 -3
  121. package/engine/lifecycle.js +1066 -165
  122. package/engine/llm.js +4 -0
  123. package/engine/managed-spawn.js +463 -48
  124. package/engine/meeting.js +197 -34
  125. package/engine/memory-retrieval.js +280 -0
  126. package/engine/memory-store.js +821 -38
  127. package/engine/model-discovery.js +17 -3
  128. package/engine/pipeline.js +802 -14
  129. package/engine/plan-prd-validation.js +745 -0
  130. package/engine/playbook.js +206 -17
  131. package/engine/pooled-agent-process.js +6 -1
  132. package/engine/pr-action.js +1 -1
  133. package/engine/pr-issue-validation.js +653 -0
  134. package/engine/pr-resolve.js +75 -13
  135. package/engine/prd-store.js +39 -6
  136. package/engine/preflight.js +7 -2
  137. package/engine/process-utils.js +79 -31
  138. package/engine/projects.js +198 -12
  139. package/engine/promotion.js +371 -0
  140. package/engine/qa-from-prd.js +3 -0
  141. package/engine/qa-process-validation.js +572 -0
  142. package/engine/qa-runbooks.js +41 -37
  143. package/engine/qa-runners/maestro.js +11 -7
  144. package/engine/qa-runners/playwright.js +37 -14
  145. package/engine/qa-runners.js +9 -10
  146. package/engine/qa-runs.js +124 -11
  147. package/engine/qa-sessions.js +139 -40
  148. package/engine/quarantine-refs.js +81 -11
  149. package/engine/queries.js +163 -9
  150. package/engine/review-learning-backfill.js +482 -0
  151. package/engine/review-learning.js +1236 -0
  152. package/engine/runtimes/claude.js +60 -2
  153. package/engine/runtimes/codex.js +57 -16
  154. package/engine/runtimes/contract.js +21 -0
  155. package/engine/runtimes/copilot.js +51 -3
  156. package/engine/runtimes/index.js +1 -0
  157. package/engine/scheduler.js +201 -32
  158. package/engine/settings-validation.js +909 -0
  159. package/engine/shared.js +705 -208
  160. package/engine/spawn-agent.js +48 -21
  161. package/engine/steering-constraints.js +31 -0
  162. package/engine/steering-store.js +10 -2
  163. package/engine/steering.js +14 -2
  164. package/engine/timeout.js +404 -111
  165. package/engine/untrusted-fence.js +24 -10
  166. package/engine/watch-actions.js +7 -2
  167. package/engine/watches.js +353 -51
  168. package/engine/work-item-validation.js +561 -0
  169. package/engine/work-items-store.js +61 -1
  170. package/engine/worktree-gc.js +656 -37
  171. package/engine/worktree-preflight.js +154 -0
  172. package/engine.js +2068 -342
  173. package/package.json +1 -1
  174. package/playbooks/_pr-description-audit.md +88 -41
  175. package/playbooks/build-fix-complex.md +3 -3
  176. package/playbooks/docs.md +2 -2
  177. package/playbooks/fix.md +18 -18
  178. package/playbooks/implement.md +18 -18
  179. package/playbooks/qa-session-draft.md +10 -12
  180. package/playbooks/qa-session-execute.md +16 -12
  181. package/playbooks/qa-validate.md +8 -7
  182. package/playbooks/shared-rules.md +45 -0
  183. package/playbooks/test.md +4 -0
  184. package/prompts/cc-system.md +1 -1
  185. package/skills/check-self-authored-review-comment/SKILL.md +2 -2
@@ -0,0 +1,761 @@
1
+ 'use strict';
2
+
3
+ const agentApiValidation = require('../agent-api-validation');
4
+ const steeringConstraints = require('../steering-constraints');
5
+ const steeringStore = require('../steering-store');
6
+ const { LIMITS, PIN_LEVELS } = require('../content-api-validation');
7
+
8
+ function rejectsInvalid(...fields) {
9
+ return [{
10
+ strategy: 'reject-invalid-input-before-side-effects',
11
+ expectedStatus: '4xx',
12
+ fields,
13
+ }];
14
+ }
15
+
16
+ const AGENT_VALUES_SOURCE = 'config.agents keys + active temp dispatch agents';
17
+ // Read-only output/detail routes additionally accept historical agents whose
18
+ // dispatch has completed (or whose agents/<id>/ artifacts still persist), so
19
+ // their path identifier advertises a broader source than the mutating routes.
20
+ const READABLE_AGENT_VALUES_SOURCE = 'config.agents keys + active/completed dispatch agents + persisted agent output directories';
21
+
22
+ function agentIdentifierField(name, valuesSource = AGENT_VALUES_SOURCE) {
23
+ return {
24
+ name,
25
+ type: 'identifier',
26
+ required: true,
27
+ maxLength: agentApiValidation.AGENT_ID_MAX_LENGTH,
28
+ allowedValuesSource: valuesSource,
29
+ };
30
+ }
31
+
32
+ function agentPath(name = 'id') {
33
+ return {
34
+ policy: 'required',
35
+ fields: [agentIdentifierField(name)],
36
+ };
37
+ }
38
+
39
+ // Path declaration for read-only output/detail routes (validateReadableAgent).
40
+ function readableAgentPath(name = 'id') {
41
+ return {
42
+ policy: 'required',
43
+ fields: [agentIdentifierField(name, READABLE_AGENT_VALUES_SOURCE)],
44
+ };
45
+ }
46
+
47
+ function liveOutputOverride() {
48
+ return {
49
+ audit: 'audited',
50
+ path: readableAgentPath(),
51
+ query: {
52
+ policy: 'optional',
53
+ fields: [
54
+ {
55
+ name: 'tail',
56
+ type: 'integer',
57
+ required: false,
58
+ default: 8192,
59
+ min: 1,
60
+ max: agentApiValidation.LIVE_TAIL_MAX_BYTES,
61
+ },
62
+ { name: 'from', type: 'integer', required: false, min: 0 },
63
+ { name: 'to', type: 'integer', required: false, min: 1 },
64
+ { name: 'prev', type: 'boolean-string', required: false, allowedValues: ['0', '1'] },
65
+ ],
66
+ },
67
+ constraints: [
68
+ {
69
+ kind: 'mutually-exclusive-query-modes',
70
+ modes: [['tail'], ['from', 'to', 'prev']],
71
+ },
72
+ {
73
+ kind: 'paired-fields',
74
+ fields: ['from', 'to'],
75
+ },
76
+ {
77
+ kind: 'max-range-width',
78
+ fields: ['from', 'to'],
79
+ max: agentApiValidation.LIVE_RANGE_MAX_BYTES,
80
+ },
81
+ ],
82
+ negativeTests: [{
83
+ strategy: 'reject-before-output-open',
84
+ expectedStatus: '4xx',
85
+ audit: 'audited',
86
+ }],
87
+ };
88
+ }
89
+
90
+ const SIDE_EFFECT_NEGATIVE_TEST = {
91
+ strategy: 'in-process-http-reject-before-side-effects',
92
+ expectedStatus: '4xx',
93
+ audit: 'audited',
94
+ };
95
+
96
+ module.exports = {
97
+ owner: 'agent-content',
98
+ routes: [
99
+ 'GET /api/agents',
100
+ 'GET /api/pinned',
101
+ 'POST /api/pinned',
102
+ 'POST /api/pinned/remove',
103
+ 'POST /api/pinned/update',
104
+ 'GET /api/kb-pins',
105
+ 'POST /api/kb-pins',
106
+ 'POST /api/kb-pins/toggle',
107
+ 'POST /api/notes',
108
+ 'GET /api/notes-full',
109
+ 'POST /api/notes-save',
110
+ 'POST /api/agents/charter',
111
+ 'POST /api/agents/steer',
112
+ 'GET /api/agents/<agentId>/steering',
113
+ 'GET /api/steering/<id>',
114
+ 'POST /api/agents/cancel',
115
+ 'POST /api/agent/<id>/kill',
116
+ 'GET /api/agent/<id>/live-stream',
117
+ 'GET /api/agent/<id>/live',
118
+ 'GET /api/agent/<id>/live-output',
119
+ 'GET /api/agent/<id>/output',
120
+ 'GET /api/agent/<id>',
121
+ 'GET /api/agent-output',
122
+ 'GET /assets/memory-search.js',
123
+ 'GET /api/knowledge',
124
+ 'GET /api/memory-records/search',
125
+ 'GET /api/memory-records/retrieval-decision',
126
+ 'GET /api/memory-records/lifecycle',
127
+ 'GET /api/memory-records/rollout',
128
+ 'GET /api/memory-records/rollout-comparison',
129
+ 'POST /api/memory-records/<id>/action',
130
+ 'POST /api/memory-records/<id>/promote',
131
+ 'POST /api/knowledge',
132
+ 'POST /api/knowledge/sweep',
133
+ 'GET /api/knowledge/sweep/status',
134
+ 'GET /api/knowledge/<category>/<file>',
135
+ 'POST /api/inbox/persist',
136
+ 'POST /api/inbox/promote-kb',
137
+ 'POST /api/inbox/open',
138
+ 'POST /api/inbox/delete',
139
+ 'GET /api/skill',
140
+ 'GET /api/harness/diagnostics',
141
+ ],
142
+ bodyless: [
143
+ 'POST /api/agent/<id>/kill',
144
+ ],
145
+ overrides: {
146
+ 'GET /api/pinned': { noInput: true },
147
+ 'GET /api/kb-pins': { noInput: true },
148
+ 'GET /api/notes-full': { noInput: true },
149
+ 'GET /api/knowledge': { noInput: true },
150
+ 'GET /api/knowledge/sweep/status': { noInput: true },
151
+ 'POST /api/pinned': {
152
+ audit: 'audited',
153
+ body: {
154
+ policy: 'required',
155
+ fields: [
156
+ { name: 'title', type: 'string', required: true, minLength: 1, maxLength: LIMITS.title },
157
+ { name: 'content', type: 'string', required: true, minLength: 1, maxLength: LIMITS.content },
158
+ {
159
+ name: 'level',
160
+ type: 'string',
161
+ required: false,
162
+ default: 'info',
163
+ allowedValues: PIN_LEVELS,
164
+ },
165
+ ],
166
+ },
167
+ negativeTests: rejectsInvalid('body', 'title', 'content', 'level'),
168
+ },
169
+ 'POST /api/pinned/remove': {
170
+ audit: 'audited',
171
+ body: {
172
+ policy: 'required',
173
+ fields: [
174
+ { name: 'title', type: 'string', required: true, minLength: 1, maxLength: LIMITS.title },
175
+ ],
176
+ },
177
+ negativeTests: rejectsInvalid('body', 'title'),
178
+ },
179
+ 'POST /api/pinned/update': {
180
+ audit: 'audited',
181
+ body: {
182
+ policy: 'required',
183
+ fields: [
184
+ { name: 'originalTitle', type: 'string', required: true, minLength: 1, maxLength: LIMITS.title },
185
+ { name: 'title', type: 'string', required: true, minLength: 1, maxLength: LIMITS.title },
186
+ { name: 'content', type: 'string', required: true, minLength: 1, maxLength: LIMITS.content },
187
+ {
188
+ name: 'level',
189
+ type: 'string',
190
+ required: false,
191
+ default: 'info',
192
+ allowedValues: PIN_LEVELS,
193
+ },
194
+ ],
195
+ },
196
+ negativeTests: rejectsInvalid('body', 'originalTitle', 'title', 'content', 'level'),
197
+ },
198
+ 'POST /api/kb-pins': {
199
+ audit: 'audited',
200
+ body: {
201
+ policy: 'required',
202
+ fields: [{
203
+ name: 'pins',
204
+ type: 'array<string>',
205
+ required: true,
206
+ maxLength: LIMITS.pinCount,
207
+ itemMaxLength: LIMITS.pinKey,
208
+ }],
209
+ },
210
+ negativeTests: rejectsInvalid('body', 'pins', 'pins[]'),
211
+ },
212
+ 'POST /api/kb-pins/toggle': {
213
+ audit: 'audited',
214
+ body: {
215
+ policy: 'required',
216
+ fields: [
217
+ { name: 'key', type: 'string', required: true, minLength: 1, maxLength: LIMITS.pinKey },
218
+ ],
219
+ },
220
+ negativeTests: rejectsInvalid('body', 'key'),
221
+ },
222
+ 'POST /api/notes': {
223
+ audit: 'audited',
224
+ body: {
225
+ policy: 'required',
226
+ fields: [
227
+ { name: 'title', type: 'string', required: true, minLength: 1, maxLength: LIMITS.title },
228
+ { name: 'what', type: 'string', required: false, maxLength: LIMITS.content },
229
+ { name: 'why', type: 'string', required: false, maxLength: LIMITS.reason },
230
+ { name: 'author', type: 'string', required: false, maxLength: LIMITS.author },
231
+ ],
232
+ },
233
+ aliases: [
234
+ { name: 'content', canonical: 'what', location: 'body' },
235
+ { name: 'description', canonical: 'what', location: 'body' },
236
+ ],
237
+ constraints: [{
238
+ rule: 'what takes precedence over content, then description; unknown fields remain allowed',
239
+ }],
240
+ negativeTests: rejectsInvalid('body', 'title', 'what', 'content', 'description', 'why', 'author'),
241
+ },
242
+ 'POST /api/notes-save': {
243
+ audit: 'audited',
244
+ body: {
245
+ policy: 'required',
246
+ fields: [
247
+ { name: 'content', type: 'string', required: true, maxLength: LIMITS.content },
248
+ { name: 'file', type: 'string', required: false, default: 'notes.md', allowedValues: ['notes.md'] },
249
+ ],
250
+ },
251
+ negativeTests: rejectsInvalid('body', 'content', 'file'),
252
+ },
253
+ 'POST /api/memory-records/<id>/action': {
254
+ audit: 'audited',
255
+ path: {
256
+ policy: 'required',
257
+ fields: [
258
+ { name: 'id', type: 'identifier', required: true, minLength: 1, maxLength: 128 },
259
+ ],
260
+ },
261
+ body: {
262
+ policy: 'required',
263
+ fields: [{
264
+ name: 'action',
265
+ type: 'string',
266
+ required: true,
267
+ allowedValuesSource: 'memory-store.ACTIONS',
268
+ }],
269
+ },
270
+ negativeTests: rejectsInvalid('body', 'id', 'action'),
271
+ },
272
+ 'POST /api/knowledge': {
273
+ audit: 'audited',
274
+ body: {
275
+ policy: 'required',
276
+ fields: [
277
+ {
278
+ name: 'category',
279
+ type: 'string',
280
+ required: true,
281
+ allowedValuesSource: 'shared.KB_CATEGORIES',
282
+ },
283
+ { name: 'title', type: 'string', required: true, minLength: 1, maxLength: LIMITS.title },
284
+ { name: 'content', type: 'string', required: true, minLength: 1, maxLength: LIMITS.content },
285
+ ],
286
+ },
287
+ constraints: [{
288
+ rule: 'title must produce a non-empty safe slug; unknown fields remain allowed',
289
+ }],
290
+ negativeTests: rejectsInvalid('body', 'category', 'title', 'content'),
291
+ },
292
+ 'GET /api/knowledge/<category>/<file>': {
293
+ audit: 'audited',
294
+ path: {
295
+ policy: 'required',
296
+ fields: [
297
+ {
298
+ name: 'category',
299
+ type: 'string',
300
+ required: true,
301
+ allowedValuesSource: 'shared.KB_READABLE_CATEGORIES',
302
+ },
303
+ {
304
+ name: 'file',
305
+ type: 'basename',
306
+ required: true,
307
+ maxLength: LIMITS.basename,
308
+ suffix: '.md',
309
+ },
310
+ ],
311
+ },
312
+ },
313
+ 'POST /api/inbox/persist': {
314
+ audit: 'audited',
315
+ body: {
316
+ policy: 'required',
317
+ fields: [{
318
+ name: 'name', type: 'basename', required: true, maxLength: LIMITS.basename, suffix: '.md',
319
+ }],
320
+ },
321
+ negativeTests: rejectsInvalid('body', 'name'),
322
+ },
323
+ 'POST /api/inbox/promote-kb': {
324
+ audit: 'audited',
325
+ body: {
326
+ policy: 'required',
327
+ fields: [
328
+ { name: 'name', type: 'basename', required: true, maxLength: LIMITS.basename, suffix: '.md' },
329
+ {
330
+ name: 'category',
331
+ type: 'string',
332
+ required: true,
333
+ allowedValuesSource: 'shared.KB_CATEGORIES',
334
+ },
335
+ ],
336
+ },
337
+ negativeTests: rejectsInvalid('body', 'name', 'category'),
338
+ },
339
+ 'POST /api/inbox/open': {
340
+ audit: 'audited',
341
+ body: {
342
+ policy: 'required',
343
+ fields: [{
344
+ name: 'name', type: 'basename', required: true, maxLength: LIMITS.basename, suffix: '.md',
345
+ }],
346
+ },
347
+ negativeTests: rejectsInvalid('body', 'name'),
348
+ },
349
+ 'POST /api/inbox/delete': {
350
+ audit: 'audited',
351
+ body: {
352
+ policy: 'required',
353
+ fields: [{
354
+ name: 'name', type: 'basename', required: true, maxLength: LIMITS.basename, suffix: '.md',
355
+ }],
356
+ },
357
+ negativeTests: rejectsInvalid('body', 'name'),
358
+ },
359
+ 'POST /api/agents/charter': {
360
+ audit: 'audited',
361
+ body: {
362
+ policy: 'required',
363
+ fields: [
364
+ agentIdentifierField('agent'),
365
+ {
366
+ name: 'content',
367
+ type: 'string',
368
+ required: true,
369
+ maxLength: agentApiValidation.AGENT_CHARTER_MAX_LENGTH,
370
+ },
371
+ ],
372
+ },
373
+ negativeTests: [SIDE_EFFECT_NEGATIVE_TEST],
374
+ },
375
+ 'POST /api/agents/steer': {
376
+ audit: 'audited',
377
+ body: {
378
+ policy: 'required',
379
+ fields: [
380
+ agentIdentifierField('agent'),
381
+ {
382
+ name: 'message',
383
+ type: 'string',
384
+ required: true,
385
+ minLength: 1,
386
+ maxLength: agentApiValidation.STEERING_MESSAGE_MAX_LENGTH,
387
+ },
388
+ {
389
+ name: 'scope',
390
+ type: 'string',
391
+ required: false,
392
+ default: steeringConstraints.SCOPE.AGENT,
393
+ allowedValues: Object.values(steeringConstraints.SCOPE),
394
+ },
395
+ {
396
+ name: 'supersede',
397
+ type: 'string',
398
+ required: false,
399
+ allowedValuesSource: 'steeringConstraints.SUPERSEDE_MODE or steeringConstraints.isSteerId',
400
+ },
401
+ ],
402
+ },
403
+ constraints: [
404
+ {
405
+ kind: 'validate-before-supersede-or-write',
406
+ fields: ['agent', 'message', 'scope', 'supersede'],
407
+ },
408
+ ],
409
+ negativeTests: [SIDE_EFFECT_NEGATIVE_TEST],
410
+ },
411
+ 'POST /api/knowledge/sweep': {
412
+ audit: 'audited',
413
+ body: {
414
+ policy: 'optional',
415
+ fields: [
416
+ {
417
+ name: 'pinnedKeys',
418
+ type: 'array<string>',
419
+ required: false,
420
+ maxLength: LIMITS.pinCount,
421
+ itemMaxLength: LIMITS.pinKey,
422
+ itemFormat: 'knowledge/<category>/<file>.md',
423
+ },
424
+ { name: 'dryRun', type: 'boolean', required: false },
425
+ ],
426
+ },
427
+ negativeTests: rejectsInvalid('body', 'pinnedKeys', 'pinnedKeys[]', 'dryRun'),
428
+ },
429
+ 'GET /api/agents': {
430
+ audit: 'audited',
431
+ query: { policy: 'none', fields: [] },
432
+ noInputReason: 'The agent roster is derived from config, dispatch, and the agents directory; it consumes no caller-supplied body, path, query, or header input.',
433
+ },
434
+ 'GET /api/agents/<agentId>/steering': {
435
+ audit: 'audited',
436
+ path: agentPath('agentId'),
437
+ query: {
438
+ policy: 'optional',
439
+ fields: [
440
+ {
441
+ name: 'limit',
442
+ type: 'integer',
443
+ required: false,
444
+ default: 50,
445
+ min: 1,
446
+ max: agentApiValidation.STEERING_LIST_MAX_LIMIT,
447
+ },
448
+ {
449
+ name: 'status',
450
+ type: 'string',
451
+ required: false,
452
+ allowedValues: [...steeringStore.VALID_STATUSES],
453
+ allowedValuesSource: 'steeringStore.VALID_STATUSES',
454
+ },
455
+ ],
456
+ },
457
+ negativeTests: [{
458
+ strategy: 'reject-before-steering-store-read',
459
+ expectedStatus: '4xx',
460
+ audit: 'audited',
461
+ }],
462
+ },
463
+ 'GET /api/steering/<id>': {
464
+ audit: 'audited',
465
+ path: {
466
+ policy: 'required',
467
+ fields: [{
468
+ name: 'id',
469
+ type: 'identifier',
470
+ required: true,
471
+ pattern: 'steer-<10 hex characters>',
472
+ allowedValuesSource: 'steering.isSteerId',
473
+ }],
474
+ },
475
+ negativeTests: [{
476
+ strategy: 'reject-before-steering-store-read',
477
+ expectedStatus: '4xx',
478
+ audit: 'audited',
479
+ }],
480
+ },
481
+ 'POST /api/agents/cancel': {
482
+ audit: 'audited',
483
+ body: {
484
+ policy: 'required',
485
+ fields: [
486
+ { ...agentIdentifierField('agent'), required: false },
487
+ { ...agentIdentifierField('agentId'), required: false },
488
+ {
489
+ name: 'task',
490
+ type: 'string',
491
+ required: false,
492
+ minLength: 1,
493
+ maxLength: agentApiValidation.CANCEL_TASK_MAX_LENGTH,
494
+ },
495
+ {
496
+ name: 'cancelTask',
497
+ type: 'string',
498
+ required: false,
499
+ minLength: 1,
500
+ maxLength: agentApiValidation.CANCEL_TASK_MAX_LENGTH,
501
+ },
502
+ ],
503
+ },
504
+ aliases: [
505
+ { name: 'agentId', canonical: 'agent' },
506
+ { name: 'cancelTask', canonical: 'task' },
507
+ ],
508
+ constraints: [
509
+ {
510
+ kind: 'exactly-one-target',
511
+ fields: ['agent', 'task'],
512
+ aliases: { agent: ['agentId'], task: ['cancelTask'] },
513
+ },
514
+ {
515
+ kind: 'selector-matches-exactly-one-active-dispatch',
516
+ fields: ['agent', 'task'],
517
+ },
518
+ ],
519
+ negativeTests: [SIDE_EFFECT_NEGATIVE_TEST],
520
+ },
521
+ 'POST /api/agent/<id>/kill': {
522
+ audit: 'audited',
523
+ path: agentPath(),
524
+ negativeTests: [SIDE_EFFECT_NEGATIVE_TEST],
525
+ },
526
+ 'GET /api/agent/<id>/live-stream': {
527
+ audit: 'audited',
528
+ path: readableAgentPath(),
529
+ query: {
530
+ policy: 'optional',
531
+ fields: [{
532
+ name: 'tail',
533
+ type: 'integer',
534
+ required: false,
535
+ default: agentApiValidation.LIVE_TAIL_MAX_BYTES,
536
+ min: 0,
537
+ max: agentApiValidation.LIVE_TAIL_MAX_BYTES,
538
+ }],
539
+ },
540
+ negativeTests: [{
541
+ strategy: 'reject-before-sse-headers-or-output-open',
542
+ expectedStatus: '4xx',
543
+ audit: 'audited',
544
+ }],
545
+ },
546
+ 'GET /api/agent/<id>/live': liveOutputOverride(),
547
+ 'GET /api/agent/<id>/live-output': liveOutputOverride(),
548
+ 'GET /api/agent/<id>/output': {
549
+ audit: 'audited',
550
+ path: readableAgentPath(),
551
+ negativeTests: [{
552
+ strategy: 'reject-before-output-read',
553
+ expectedStatus: '4xx',
554
+ audit: 'audited',
555
+ }],
556
+ },
557
+ 'GET /api/agent/<id>': {
558
+ audit: 'audited',
559
+ path: readableAgentPath(),
560
+ negativeTests: [{
561
+ strategy: 'reject-before-agent-file-reads',
562
+ expectedStatus: '4xx',
563
+ audit: 'audited',
564
+ }],
565
+ },
566
+ 'GET /api/agent-output': {
567
+ audit: 'audited',
568
+ query: {
569
+ policy: 'required',
570
+ fields: [
571
+ {
572
+ name: 'file',
573
+ type: 'path',
574
+ required: true,
575
+ maxLength: agentApiValidation.OUTPUT_FILE_MAX_LENGTH,
576
+ allowedBasenames: agentApiValidation.OUTPUT_FILENAMES,
577
+ allowedValuesSource: 'agentApiValidation.OUTPUT_FILENAMES',
578
+ },
579
+ ],
580
+ },
581
+ constraints: [{
582
+ kind: 'contained-agent-output-path',
583
+ root: 'agents/<agent-id>',
584
+ filenames: agentApiValidation.OUTPUT_FILENAMES,
585
+ }],
586
+ negativeTests: [{
587
+ strategy: 'reject-traversal-or-unsupported-filename-before-open',
588
+ expectedStatus: '4xx',
589
+ audit: 'audited',
590
+ }],
591
+ },
592
+ 'GET /api/memory-records/search': {
593
+ audit: 'audited',
594
+ query: {
595
+ policy: 'required',
596
+ fields: [
597
+ { name: 'q', type: 'string', required: true, minLength: 1, maxLength: LIMITS.memoryQuery },
598
+ {
599
+ name: 'status',
600
+ type: 'string',
601
+ required: false,
602
+ default: 'active',
603
+ allowedValuesSource: 'memory-store.STATUSES',
604
+ },
605
+ {
606
+ name: 'limit',
607
+ type: 'integer',
608
+ required: false,
609
+ default: 20,
610
+ min: 1,
611
+ max: LIMITS.memoryLimit,
612
+ },
613
+ ],
614
+ },
615
+ },
616
+ 'GET /api/memory-records/retrieval-decision': {
617
+ // Read-only diagnostic: at least one of dispatchId/workItemId must be
618
+ // supplied; the handler 400s when both are absent. Values are bounded and
619
+ // redacted before use.
620
+ audit: 'audited',
621
+ query: {
622
+ policy: 'optional',
623
+ fields: [
624
+ { name: 'dispatchId', type: 'string', required: false, maxLength: 320 },
625
+ { name: 'workItemId', type: 'string', required: false, maxLength: 320 },
626
+ { name: 'project', type: 'string', required: false, allowedValuesSource: 'config.projects[].name' },
627
+ { name: 'retrievalKind', type: 'string', required: false },
628
+ ],
629
+ },
630
+ constraints: [
631
+ { kind: 'one-of', fields: ['dispatchId', 'workItemId'], minPresent: 1 },
632
+ { kind: 'bounded-redacted-query', fields: ['dispatchId', 'workItemId', 'project', 'retrievalKind'] },
633
+ ],
634
+ negativeTests: [
635
+ { strategy: 'omit-both-dispatch-and-work-item-id', expectedStatus: 400 },
636
+ { strategy: 'reject-oversized-or-malformed-identifier', expectedStatus: 400 },
637
+ ],
638
+ },
639
+ 'GET /api/memory-records/lifecycle': {
640
+ audit: 'audited',
641
+ query: {
642
+ policy: 'optional',
643
+ fields: [
644
+ { name: 'project', type: 'string', required: false, allowedValuesSource: 'config.projects[].name' },
645
+ { name: 'eventType', type: 'string', required: false },
646
+ { name: 'findingKey', type: 'string', required: false },
647
+ { name: 'memoryId', type: 'string', required: false, maxLength: 320 },
648
+ { name: 'agent', type: 'string', required: false, allowedValuesSource: 'config.agents keys' },
649
+ { name: 'workItemId', type: 'string', required: false, maxLength: 320 },
650
+ { name: 'dispatchId', type: 'string', required: false, maxLength: 320 },
651
+ { name: 'limit', type: 'integer', required: false, default: 100, max: 200 },
652
+ ],
653
+ },
654
+ constraints: [
655
+ { kind: 'bounded-redacted-query', fields: ['project', 'eventType', 'findingKey', 'memoryId', 'agent', 'workItemId', 'dispatchId'] },
656
+ { kind: 'clamped-limit', field: 'limit', min: 1, max: 200, default: 100 },
657
+ ],
658
+ negativeTests: [
659
+ { strategy: 'reject-invalid-event-type-finding-or-agent-filter', expectedStatus: 400 },
660
+ ],
661
+ },
662
+ 'GET /api/memory-records/rollout': {
663
+ audit: 'audited',
664
+ query: {
665
+ policy: 'optional',
666
+ fields: [
667
+ { name: 'project', type: 'string', required: false, allowedValuesSource: 'config.projects[].name' },
668
+ { name: 'area', type: 'string', required: false },
669
+ { name: 'from', type: 'number', required: false },
670
+ { name: 'to', type: 'number', required: false },
671
+ { name: 'minimumSample', type: 'number', required: false },
672
+ ],
673
+ },
674
+ constraints: [
675
+ { kind: 'numeric-window-fields', fields: ['from', 'to', 'minimumSample'] },
676
+ ],
677
+ negativeTests: [
678
+ { strategy: 'reject-non-numeric-window-field', expectedStatus: 400 },
679
+ ],
680
+ },
681
+ 'GET /api/memory-records/rollout-comparison': {
682
+ // The handler treats every window/gate parameter as optional (empty values
683
+ // are skipped) and parses them with Number(), so `minReduction` is a numeric
684
+ // ratio rather than an integer.
685
+ audit: 'audited',
686
+ query: {
687
+ policy: 'optional',
688
+ fields: [
689
+ { name: 'baselineFrom', type: 'number', required: false },
690
+ { name: 'baselineTo', type: 'number', required: false },
691
+ { name: 'activeFrom', type: 'number', required: false },
692
+ { name: 'activeTo', type: 'number', required: false },
693
+ { name: 'project', type: 'string', required: false, allowedValuesSource: 'config.projects[].name' },
694
+ { name: 'area', type: 'string', required: false },
695
+ { name: 'minimumSample', type: 'number', required: false },
696
+ { name: 'minReduction', type: 'number', required: false },
697
+ ],
698
+ },
699
+ constraints: [
700
+ { kind: 'numeric-window-fields', fields: ['baselineFrom', 'baselineTo', 'activeFrom', 'activeTo', 'minimumSample', 'minReduction'] },
701
+ ],
702
+ negativeTests: [
703
+ { strategy: 'reject-non-numeric-window-field', expectedStatus: 400 },
704
+ ],
705
+ },
706
+ 'POST /api/memory-records/<id>/promote': {
707
+ audit: 'audited',
708
+ path: {
709
+ policy: 'required',
710
+ fields: [
711
+ { name: 'id', type: 'identifier', required: true, minLength: 1, maxLength: 128 },
712
+ ],
713
+ },
714
+ body: {
715
+ policy: 'required',
716
+ fields: [
717
+ { name: 'targetKind', type: 'string', required: true, allowedValues: ['project-skill', 'repository-instruction'] },
718
+ { name: 'project', type: 'string', required: true, allowedValuesSource: 'config.projects[].name' },
719
+ { name: 'path', type: 'string', required: true },
720
+ ],
721
+ },
722
+ constraints: [
723
+ { kind: 'promotion-eligibility', description: 'The lesson must exist and satisfy verified-support promotion eligibility before a work item is queued.' },
724
+ { kind: 'never-edits-checkout', description: 'Promotion queues a project-bound work item; it never writes the operator checkout.' },
725
+ ],
726
+ negativeTests: [
727
+ { strategy: 'reject-missing-or-invalid-target-kind', expectedStatus: 400 },
728
+ { strategy: 'reject-unknown-project', expectedStatus: 400 },
729
+ { strategy: 'return-not-found-for-missing-lesson', expectedStatus: 404 },
730
+ ],
731
+ },
732
+ 'GET /api/skill': {
733
+ audit: 'audited',
734
+ query: {
735
+ policy: 'required',
736
+ fields: [
737
+ { name: 'file', type: 'basename', required: true, maxLength: LIMITS.basename },
738
+ { name: 'source', type: 'string', required: false, maxLength: LIMITS.skillSource },
739
+ { name: 'dir', type: 'path', required: false, maxLength: LIMITS.skillDir },
740
+ ],
741
+ },
742
+ constraints: [{
743
+ rule: 'file must be a cross-platform safe basename; dir may be absolute but cannot contain traversal segments',
744
+ }],
745
+ },
746
+ 'GET /assets/memory-search.js': {
747
+ audit: 'audited',
748
+ noInputReason: 'Serves one fixed bundled JavaScript asset from the dashboard source tree.',
749
+ constraints: [
750
+ {
751
+ kind: 'fixed-sandboxed-asset',
752
+ source: 'dashboard/js/memory-search.js',
753
+ },
754
+ ],
755
+ },
756
+ 'GET /api/harness/diagnostics': {
757
+ audit: 'audited',
758
+ noInputReason: 'Inventories configured projects and canonical runtime adapters; accepts no caller input.',
759
+ },
760
+ },
761
+ };