@nanobpm/nano-workforce 0.37.0 → 0.38.0

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.
package/openapi.yaml ADDED
@@ -0,0 +1,649 @@
1
+ # Nano Workforce control API — OpenAPI (authoring source, ADR 0059).
2
+ #
3
+ # This YAML is the single source of truth for the app HTTP surface. The Urban toolkit
4
+ # derives typed request/response contracts + runtime validators from it (urban gen), and
5
+ # each operationId is implemented by a delegate in operations/<operationId>.ts. Mounted
6
+ # under /app/api (off the framework-reserved /app page-runtime namespace). Swagger UI is
7
+ # served at /app/api-docs. Prefer editing this file over the generated artifacts.
8
+ openapi: 3.0.3
9
+ info:
10
+ title: Nano Workforce control API
11
+ version: 1.0.0
12
+ description: "The externally-facing control surface an operator, an automation harness, or an LLM
13
+ uses to observe and steer PR-convergence and planning runs. Contract-first (ADR 0058/0059): the
14
+ toolkit derives typed request/response contracts + runtime validators from this document and
15
+ each `operationId` is implemented by a delegate module in `operations/`. Mounted under base
16
+ `/app/api`, kept off the framework-reserved `/app` page-runtime namespace (which owns
17
+ `/app/runtime.js`, `/app/pages/*`, `/app/data/*`). The webhook endpoints are ordinary operations
18
+ too (ADR 0059 — the `actions[]` array is retired): their `/hooks/*` paths in this document are
19
+ relative to the `/app/api` base, so they are served at `/app/api/hooks/*`. Shared-secret and
20
+ capability-token side-channels enforce their credential in the delegate."
21
+ components:
22
+ securitySchemes:
23
+ hookSecret:
24
+ type: apiKey
25
+ in: header
26
+ name: x-hook-secret
27
+ description: Optional shared secret. Enforced by the delegate (NOT the runtime) only when
28
+ NANO_PR_WEBHOOK_SECRET is set; unset means the endpoint is open. Declared here for
29
+ documentation.
30
+ schemas:
31
+ ErrorBody:
32
+ type: object
33
+ required:
34
+ - error
35
+ properties:
36
+ error:
37
+ type: string
38
+ ActivePr:
39
+ type: object
40
+ description: A tracked PR that is not in a terminal (converged/abandoned) state.
41
+ required:
42
+ - prKey
43
+ - repo
44
+ - number
45
+ - url
46
+ - title
47
+ - status
48
+ - round
49
+ - processKey
50
+ - waitingSince
51
+ - openEscalation
52
+ - updatedAt
53
+ - activeWorker
54
+ - leaseUntil
55
+ properties:
56
+ prKey:
57
+ type: string
58
+ repo:
59
+ type: string
60
+ number:
61
+ type: integer
62
+ url:
63
+ type: string
64
+ title:
65
+ type:
66
+ - string
67
+ - "null"
68
+ status:
69
+ type: string
70
+ round:
71
+ type: integer
72
+ processKey:
73
+ type:
74
+ - string
75
+ - "null"
76
+ description: The engine process instance key; also the keyField the pages processExplorer link uses.
77
+ waitingSince:
78
+ type:
79
+ - string
80
+ - "null"
81
+ openEscalation:
82
+ type:
83
+ - string
84
+ - "null"
85
+ updatedAt:
86
+ type: string
87
+ activeWorker:
88
+ type:
89
+ - string
90
+ - "null"
91
+ leaseUntil:
92
+ type:
93
+ - string
94
+ - "null"
95
+ ActivePrList:
96
+ type: object
97
+ required:
98
+ - count
99
+ - prs
100
+ properties:
101
+ count:
102
+ type: integer
103
+ prs:
104
+ type: array
105
+ items:
106
+ $ref: "#/components/schemas/ActivePr"
107
+ VersionInfo:
108
+ type: object
109
+ description: The running app's identity (which code is actually live).
110
+ required:
111
+ - name
112
+ - version
113
+ - urbanVersion
114
+ - gitSha
115
+ - gitBranch
116
+ - runtime
117
+ - pid
118
+ - startedAt
119
+ - uptimeSeconds
120
+ properties:
121
+ name:
122
+ type: string
123
+ version:
124
+ type:
125
+ - string
126
+ - "null"
127
+ urbanVersion:
128
+ type:
129
+ - string
130
+ - "null"
131
+ gitSha:
132
+ type:
133
+ - string
134
+ - "null"
135
+ gitBranch:
136
+ type:
137
+ - string
138
+ - "null"
139
+ runtime:
140
+ type: string
141
+ pid:
142
+ type:
143
+ - integer
144
+ - "null"
145
+ startedAt:
146
+ type: string
147
+ uptimeSeconds:
148
+ type: integer
149
+ SubmitResult:
150
+ type: object
151
+ required:
152
+ - prKey
153
+ properties:
154
+ prKey:
155
+ type: string
156
+ processKey:
157
+ type:
158
+ - string
159
+ - "null"
160
+ description: The started convergence-loop instance key (null if the engine did not return one).
161
+ alreadyRunning:
162
+ type: boolean
163
+ description: True when a non-terminal convergence loop for this PR already exists; the aggregate was
164
+ refreshed and no new instance was started.
165
+ StartPlanResult:
166
+ type: object
167
+ required:
168
+ - planKey
169
+ properties:
170
+ planKey:
171
+ type: string
172
+ processKey:
173
+ type:
174
+ - string
175
+ - "null"
176
+ alreadyRunning:
177
+ type: boolean
178
+ description: True when a non-terminal plan for this issue already exists; no new instance was started.
179
+ StartVariables:
180
+ type: object
181
+ description: The start request body. `pr`/`url` (convergence) or `issue`/`url` (planning) name the
182
+ target. Only the fields below are read by the delegate; any extra keys are accepted but
183
+ ignored (they are NOT forwarded to the engine as process variables).
184
+ additionalProperties: true
185
+ properties:
186
+ pr:
187
+ type: string
188
+ description: "PR reference: owner/repo#123 or a PR URL."
189
+ issue:
190
+ type: string
191
+ description: "Issue reference: owner/repo#123 or an issue URL."
192
+ url:
193
+ type: string
194
+ description: Alias for pr/issue when a bare URL is supplied.
195
+ dependsOn:
196
+ type: array
197
+ items:
198
+ type: string
199
+ maxRounds:
200
+ type: integer
201
+ minimum: 1
202
+ description: Values above 100 are accepted and clamped to 100 by the delegate.
203
+ MessageResult:
204
+ type: object
205
+ description: The result of publishing a message / answering an escalation. Shape varies by message
206
+ name; `ok` is always present.
207
+ additionalProperties: true
208
+ required:
209
+ - ok
210
+ properties:
211
+ ok:
212
+ type: boolean
213
+ FeatureAnswerRequest:
214
+ type: object
215
+ description: "Answer an implementation-phase task escalation (issue #25). Supply either `corrKey`,
216
+ or both `plan` and `task`; `answer` is always required."
217
+ required:
218
+ - answer
219
+ properties:
220
+ corrKey:
221
+ type: string
222
+ description: The task correlation key, `<plan_key>:<task_id>` (e.g. owner/repo#12:task-3).
223
+ plan:
224
+ type: string
225
+ description: Plan reference (owner/repo#N); combined with `task` to derive the corrKey.
226
+ task:
227
+ type: string
228
+ description: Task id within the plan; combined with `plan` to derive the corrKey.
229
+ answer:
230
+ type: string
231
+ minLength: 1
232
+ description: The operator's answer that resumes the parked implementation agent.
233
+ BlackboardEntry:
234
+ type: object
235
+ required:
236
+ - id
237
+ - author_task
238
+ - kind
239
+ - files
240
+ - body
241
+ - wave
242
+ - created_at
243
+ properties:
244
+ id:
245
+ type: integer
246
+ author_task:
247
+ type: string
248
+ kind:
249
+ type: string
250
+ files:
251
+ type: array
252
+ items:
253
+ type: string
254
+ body:
255
+ type: string
256
+ wave:
257
+ type:
258
+ - integer
259
+ - "null"
260
+ created_at:
261
+ type: string
262
+ BlackboardPage:
263
+ type: object
264
+ required:
265
+ - planKey
266
+ - entries
267
+ - cursor
268
+ properties:
269
+ planKey:
270
+ type: string
271
+ entries:
272
+ type: array
273
+ items:
274
+ $ref: "#/components/schemas/BlackboardEntry"
275
+ cursor:
276
+ type:
277
+ - integer
278
+ - "null"
279
+ description: The plan's current head id; pass it back as `since` on the next poll (Tier 2).
280
+ BlackboardAppendRequest:
281
+ type: object
282
+ description: Append one blackboard entry. Idempotent on (plan, dedupe_key).
283
+ required:
284
+ - body
285
+ properties:
286
+ author_task:
287
+ type: string
288
+ description: The authoring task id; defaults to "system" when omitted/blank.
289
+ kind:
290
+ type: string
291
+ description: Entry kind (e.g. note, file-claim); normalized by the delegate.
292
+ files:
293
+ type: array
294
+ items:
295
+ type: string
296
+ description: Files this entry concerns (used by advisory file-claim conflict detection).
297
+ body:
298
+ type: string
299
+ minLength: 1
300
+ description: The note text (required, non-blank).
301
+ wave:
302
+ type: integer
303
+ description: Optional plan wave the entry belongs to.
304
+ dedupe_key:
305
+ type: string
306
+ description: "Idempotency key: a retry with the same (plan, dedupe_key) returns the existing entry."
307
+ BlackboardAppendResult:
308
+ type: object
309
+ required:
310
+ - id
311
+ - inserted
312
+ - conflicts
313
+ properties:
314
+ id:
315
+ type: integer
316
+ inserted:
317
+ type: boolean
318
+ description: False when a prior entry with the same dedupe_key already existed (idempotent retry).
319
+ conflicts:
320
+ type: array
321
+ description: Prior sibling file-claims on the same file(s) (advisory first-writer-wins; never a lock).
322
+ items:
323
+ type: object
324
+ required:
325
+ - file
326
+ - author_task
327
+ - id
328
+ - body
329
+ - created_at
330
+ properties:
331
+ file:
332
+ type: string
333
+ author_task:
334
+ type: string
335
+ id:
336
+ type: integer
337
+ body:
338
+ type: string
339
+ created_at:
340
+ type: string
341
+ AbandonStatus:
342
+ type: object
343
+ required:
344
+ - prKey
345
+ - status
346
+ - abandoned
347
+ properties:
348
+ prKey:
349
+ type: string
350
+ status:
351
+ type: string
352
+ abandoned:
353
+ type: boolean
354
+ description: Derived from pull_requests.status; true ⇒ the run was cancelled and the agent must stop.
355
+ paths:
356
+ /status:
357
+ get:
358
+ operationId: listActivePrs
359
+ summary: List every tracked PR currently in flight (not converged/abandoned), newest-updated first.
360
+ security:
361
+ - hookSecret: []
362
+ - {}
363
+ responses:
364
+ "200":
365
+ description: The active PRs.
366
+ content:
367
+ application/json:
368
+ schema:
369
+ $ref: "#/components/schemas/ActivePrList"
370
+ "401":
371
+ description: Missing/invalid shared secret (only when NANO_PR_WEBHOOK_SECRET is set).
372
+ content:
373
+ application/json:
374
+ schema:
375
+ $ref: "#/components/schemas/ErrorBody"
376
+ /version:
377
+ get:
378
+ operationId: getVersion
379
+ summary: The running app's identity (app/urban versions, git sha/branch, runtime, pid, uptime).
380
+ security:
381
+ - hookSecret: []
382
+ - {}
383
+ responses:
384
+ "200":
385
+ description: The version/identity payload.
386
+ content:
387
+ application/json:
388
+ schema:
389
+ $ref: "#/components/schemas/VersionInfo"
390
+ "401":
391
+ description: Missing/invalid shared secret (only when NANO_PR_WEBHOOK_SECRET is set).
392
+ content:
393
+ application/json:
394
+ schema:
395
+ $ref: "#/components/schemas/ErrorBody"
396
+ /actions/start/convergence-loop:
397
+ post:
398
+ operationId: startConvergenceLoop
399
+ summary: Register/refresh a PR aggregate (idempotent on prKey) and start its convergence loop.
400
+ requestBody:
401
+ required: true
402
+ content:
403
+ application/json:
404
+ schema:
405
+ $ref: "#/components/schemas/StartVariables"
406
+ responses:
407
+ "202":
408
+ description: The loop was started (or refreshed).
409
+ content:
410
+ application/json:
411
+ schema:
412
+ $ref: "#/components/schemas/SubmitResult"
413
+ "400":
414
+ description: The PR reference could not be parsed.
415
+ content:
416
+ application/json:
417
+ schema:
418
+ $ref: "#/components/schemas/ErrorBody"
419
+ /actions/start/plan-fanout:
420
+ post:
421
+ operationId: startPlanFanout
422
+ summary: Register/refresh a plan aggregate (idempotent on planKey) and start the planning fan-out.
423
+ requestBody:
424
+ required: true
425
+ content:
426
+ application/json:
427
+ schema:
428
+ $ref: "#/components/schemas/StartVariables"
429
+ responses:
430
+ "202":
431
+ description: The plan fan-out was started (or was already running).
432
+ content:
433
+ application/json:
434
+ schema:
435
+ $ref: "#/components/schemas/StartPlanResult"
436
+ "400":
437
+ description: The issue reference could not be parsed.
438
+ content:
439
+ application/json:
440
+ schema:
441
+ $ref: "#/components/schemas/ErrorBody"
442
+ /actions/message:
443
+ post:
444
+ operationId: postMessage
445
+ summary: Publish a message / answer an escalation. For escalation-answered and
446
+ feature-escalation-answered names, runs the corresponding answer flow; otherwise a plain
447
+ publishMessage.
448
+ requestBody:
449
+ required: true
450
+ content:
451
+ application/json:
452
+ schema:
453
+ type: object
454
+ additionalProperties: true
455
+ required:
456
+ - name
457
+ properties:
458
+ name:
459
+ type: string
460
+ minLength: 1
461
+ description: The message name (correlates a waiting event).
462
+ correlationKey:
463
+ type: string
464
+ variables:
465
+ type: object
466
+ additionalProperties: true
467
+ properties:
468
+ answer:
469
+ type: string
470
+ responses:
471
+ "200":
472
+ description: The message was published (or the escalation answered).
473
+ content:
474
+ application/json:
475
+ schema:
476
+ $ref: "#/components/schemas/MessageResult"
477
+ "400":
478
+ description: A required field was missing/blank.
479
+ content:
480
+ application/json:
481
+ schema:
482
+ $ref: "#/components/schemas/ErrorBody"
483
+ "404":
484
+ description: No matching open escalation / parked token to answer.
485
+ content:
486
+ application/json:
487
+ schema:
488
+ $ref: "#/components/schemas/MessageResult"
489
+ /hooks/feature-answer:
490
+ post:
491
+ operationId: answerFeatureEscalation
492
+ summary: "Answer an implementation-phase task escalation out of band (issue #25). Optional
493
+ shared-secret guard (x-hook-secret), enforced only when NANO_PR_WEBHOOK_SECRET is set."
494
+ security:
495
+ - hookSecret: []
496
+ - {}
497
+ requestBody:
498
+ required: true
499
+ content:
500
+ application/json:
501
+ schema:
502
+ $ref: "#/components/schemas/FeatureAnswerRequest"
503
+ responses:
504
+ "200":
505
+ description: The escalation was answered and the parked agent resumed.
506
+ content:
507
+ application/json:
508
+ schema:
509
+ $ref: "#/components/schemas/MessageResult"
510
+ "400":
511
+ description: A required field was missing (answer, or a resolvable correlation key).
512
+ content:
513
+ application/json:
514
+ schema:
515
+ $ref: "#/components/schemas/MessageResult"
516
+ "401":
517
+ description: Missing/invalid shared secret (only when NANO_PR_WEBHOOK_SECRET is set).
518
+ content:
519
+ application/json:
520
+ schema:
521
+ $ref: "#/components/schemas/MessageResult"
522
+ "404":
523
+ description: No matching open task escalation for the correlation key.
524
+ content:
525
+ application/json:
526
+ schema:
527
+ $ref: "#/components/schemas/MessageResult"
528
+ /hooks/blackboard:
529
+ get:
530
+ operationId: readBlackboard
531
+ summary: Read a plan's blackboard, scoped by the capability token (Tier 1). Optional ?since=<id>
532
+ returns only newer entries.
533
+ parameters:
534
+ - name: token
535
+ in: query
536
+ required: false
537
+ schema:
538
+ type: string
539
+ description: "Per-plan capability token (issues #51 / #49) — the token IS the credential and
540
+ scopes the read to exactly one plan. Required in practice, but declared optional here
541
+ because it may instead be sent as the `x-blackboard-token` header; the delegate enforces
542
+ it (400 when absent from both, 404 when unknown). Not an OpenAPI `security` scheme: it is
543
+ a per-plan DB-issued capability, not a fixed shared secret the runtime could verify."
544
+ - name: since
545
+ in: query
546
+ required: false
547
+ schema:
548
+ type: integer
549
+ description: Return only entries with id > since (incremental poll).
550
+ responses:
551
+ "200":
552
+ description: The blackboard page for this plan.
553
+ content:
554
+ application/json:
555
+ schema:
556
+ $ref: "#/components/schemas/BlackboardPage"
557
+ "400":
558
+ description: Missing blackboard token.
559
+ content:
560
+ application/json:
561
+ schema:
562
+ $ref: "#/components/schemas/ErrorBody"
563
+ "404":
564
+ description: Unknown blackboard token (never reveals which plans exist).
565
+ content:
566
+ application/json:
567
+ schema:
568
+ $ref: "#/components/schemas/ErrorBody"
569
+ post:
570
+ operationId: appendBlackboard
571
+ summary: Append one entry to a plan's blackboard, scoped by the capability token (Tier 1).
572
+ Idempotent on (plan, dedupe_key).
573
+ parameters:
574
+ - name: token
575
+ in: query
576
+ required: false
577
+ schema:
578
+ type: string
579
+ description: "Per-plan capability token (issues #51 / #49) — the token IS the credential and
580
+ scopes the write to exactly one plan. Required in practice, but declared optional here
581
+ because it may instead be sent as the `x-blackboard-token` header; the delegate enforces
582
+ it (400 when absent from both, 404 when unknown). Not an OpenAPI `security` scheme: it is
583
+ a per-plan DB-issued capability, not a fixed shared secret the runtime could verify."
584
+ requestBody:
585
+ required: true
586
+ content:
587
+ application/json:
588
+ schema:
589
+ $ref: "#/components/schemas/BlackboardAppendRequest"
590
+ responses:
591
+ "200":
592
+ description: "An idempotent retry: the entry already existed (inserted=false)."
593
+ content:
594
+ application/json:
595
+ schema:
596
+ $ref: "#/components/schemas/BlackboardAppendResult"
597
+ "201":
598
+ description: The entry was appended.
599
+ content:
600
+ application/json:
601
+ schema:
602
+ $ref: "#/components/schemas/BlackboardAppendResult"
603
+ "400":
604
+ description: Missing blackboard token or blank body.
605
+ content:
606
+ application/json:
607
+ schema:
608
+ $ref: "#/components/schemas/ErrorBody"
609
+ "404":
610
+ description: Unknown blackboard token (never reveals which plans exist).
611
+ content:
612
+ application/json:
613
+ schema:
614
+ $ref: "#/components/schemas/ErrorBody"
615
+ /hooks/abandon:
616
+ get:
617
+ operationId: checkAbandon
618
+ summary: "Cooperative abandon check (issue #76): tell a running agent whether its run was cancelled.
619
+ Scoped by the per-PR capability token."
620
+ parameters:
621
+ - name: token
622
+ in: query
623
+ required: false
624
+ schema:
625
+ type: string
626
+ description: "Per-PR capability token (issue #76) — the token IS the credential and scopes the
627
+ read to exactly one PR. Required in practice, but declared optional here because it may
628
+ instead be sent as the `x-abandon-token` header; the delegate enforces it (400 when absent
629
+ from both, 404 when unknown). Not an OpenAPI `security` scheme: it is a per-PR DB-issued
630
+ capability, not a fixed shared secret the runtime could verify."
631
+ responses:
632
+ "200":
633
+ description: The PR's abandon status.
634
+ content:
635
+ application/json:
636
+ schema:
637
+ $ref: "#/components/schemas/AbandonStatus"
638
+ "400":
639
+ description: Missing abandon token.
640
+ content:
641
+ application/json:
642
+ schema:
643
+ $ref: "#/components/schemas/ErrorBody"
644
+ "404":
645
+ description: Unknown abandon token (never reveals which PRs exist).
646
+ content:
647
+ application/json:
648
+ schema:
649
+ $ref: "#/components/schemas/ErrorBody"