@nanobpm/nano-workforce 0.201.5 → 0.203.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/CHANGELOG.md +12 -0
- package/SPEC.md +48 -0
- package/app/admittedStaging.test.ts +71 -0
- package/app/agentActivationLedger.ts +5 -0
- package/app/agentCompletion.test.ts +33 -0
- package/app/agentCompletion.ts +108 -2
- package/app/agentic/README.md +48 -0
- package/app/agentic/claim-registry.test.ts +61 -0
- package/app/agentic/claim-registry.ts +35 -1
- package/app/agentic/families/claim.family.test.ts +216 -0
- package/app/agentic/families/claim.family.ts +100 -4
- package/app/agentic/vocab/agent-sla-boundary.test.ts +6 -1
- package/app/contracts.ts +31 -0
- package/app/deliveryGraphDispatch.ts +6 -0
- package/app/deliveryGraphReadModel.test.ts +2 -2
- package/app/deliveryGraphRun.ts +18 -0
- package/app/deliveryRunner.ts +9 -1
- package/app/escalationSteward.test.ts +452 -0
- package/app/escalationSteward.ts +537 -0
- package/app/escalationStewardPoll.test.ts +123 -0
- package/app/feature.test.ts +16 -0
- package/app/feature.ts +11 -0
- package/app/featureDelivery.test.ts +23 -0
- package/app/mcpToolSurface.ts +17 -1
- package/app/plan.ts +49 -6
- package/app/planLowering.test.ts +26 -1
- package/app/planLowering.ts +5 -0
- package/app/promotionPoll.test.ts +19 -0
- package/app/pullRequestReadModel.test.ts +1 -1
- package/app/service.test.ts +36 -0
- package/app/service.ts +139 -5
- package/db/migrations/120_escalation_auto_resolutions.sql +229 -0
- package/db/migrations/121_delivery_graph_read_model_auto_resolve.sql +54 -0
- package/db/migrations/122_pull_requests_read_model_auto_resolve.sql +56 -0
- package/db/migrations/123_escalation_auto_resolutions_selected_option.sql +47 -0
- package/db/migrations/124_admitted_epics_auto_resolve.sql +21 -0
- package/docs/agent-guide.md +69 -30
- package/docs/agentic-cockpit.md +34 -0
- package/docs/mcp-runbook.md +2 -0
- package/e2e/escalation-steward.e2e.ts +211 -0
- package/nano.app.json +10 -1
- package/openapi.yaml +365 -0
- package/operations/dispatchDeliveryGraph.ts +6 -1
- package/operations/listActivePrs.test.ts +60 -1
- package/operations/listActivePrs.ts +3 -3
- package/operations/listAutoResolutions.ts +83 -0
- package/operations/reviewAutoResolution.test.ts +137 -0
- package/operations/reviewAutoResolution.ts +42 -0
- package/operations/startConvergenceLoop.ts +5 -1
- package/operations/startEpicSet.admission.integration.test.ts +2 -2
- package/operations/startEpicSet.ts +8 -2
- package/operations/startFeature.ts +5 -0
- package/operations/startPlanFanout.ts +4 -1
- package/package.json +1 -1
- package/pages/_nav.json +12 -0
- package/pages/auto-resolutions.page.json +191 -0
- package/pages/board.page.json +17 -0
- package/pages/cockpit.page.json +17 -0
- package/pages/delivery-graph-detail.page.json +17 -0
- package/pages/delivery-graphs/staged.mount.js +17 -5
- package/pages/delivery-graphs.page.json +17 -0
- package/pages/epic-detail.page.json +17 -0
- package/pages/epic.page.json +19 -1
- package/pages/feature.page.json +19 -1
- package/pages/home.page.json +22 -0
- package/pages/lineage.page.json +17 -0
- package/pages/mcp.page.json +17 -0
- package/pages/overview.page.json +17 -0
- package/pages/tasks.page.json +17 -0
- package/pages/velocity.page.json +17 -0
- package/resources/processes/escalation-steward.bpmn +162 -0
- package/resources/prompts/escalation-steward.md +87 -0
- package/test/delivery-graphs-staged-embed.test.ts +13 -0
- package/test/derivation-parity/derivation-parity.test.ts +1 -0
- package/test/derivation-parity/flows.ts +12 -0
- package/workers/converge-feature/worker.ts +6 -1
- package/workers/delivery-connector/worker.test.ts +72 -1
- package/workers/delivery-connector/worker.ts +53 -2
- package/workers/escalation-steward-collect/worker.test.ts +180 -0
- package/workers/escalation-steward-collect/worker.ts +99 -0
- package/workers/escalation-steward-record/worker.test.ts +740 -0
- package/workers/escalation-steward-record/worker.ts +428 -0
- package/workers/record-wave/worker.ts +5 -1
package/openapi.yaml
CHANGED
|
@@ -91,6 +91,7 @@ components:
|
|
|
91
91
|
- updatedAt
|
|
92
92
|
- activeWorker
|
|
93
93
|
- leaseUntil
|
|
94
|
+
- autoResolveEscalations
|
|
94
95
|
properties:
|
|
95
96
|
prKey:
|
|
96
97
|
type: string
|
|
@@ -148,11 +149,18 @@ components:
|
|
|
148
149
|
leaseUntil:
|
|
149
150
|
type: string
|
|
150
151
|
nullable: true
|
|
152
|
+
autoResolveEscalations:
|
|
153
|
+
type: boolean
|
|
154
|
+
description: >-
|
|
155
|
+
Whether this PR's run opted into automatic escalation resolution by the escalation
|
|
156
|
+
steward (issue #889). Derived from the subject row's durable `auto_resolve_escalations`
|
|
157
|
+
flag, never a clobbered process variable.
|
|
151
158
|
ActivePrList:
|
|
152
159
|
type: object
|
|
153
160
|
required:
|
|
154
161
|
- count
|
|
155
162
|
- prs
|
|
163
|
+
- deliveries
|
|
156
164
|
properties:
|
|
157
165
|
count:
|
|
158
166
|
type: integer
|
|
@@ -160,6 +168,68 @@ components:
|
|
|
160
168
|
type: array
|
|
161
169
|
items:
|
|
162
170
|
$ref: "#/components/schemas/ActivePr"
|
|
171
|
+
deliveries:
|
|
172
|
+
type: array
|
|
173
|
+
description: >-
|
|
174
|
+
Every delivery-graph run currently in flight (not done/failed/abandoned), newest-updated
|
|
175
|
+
first (issue #889 §1 — the opt-in is shown for each PR AND each delivery).
|
|
176
|
+
items:
|
|
177
|
+
$ref: "#/components/schemas/ActiveDelivery"
|
|
178
|
+
ActiveDelivery:
|
|
179
|
+
type: object
|
|
180
|
+
description: A delivery-graph run currently in flight, as reported by the status endpoint.
|
|
181
|
+
additionalProperties: false
|
|
182
|
+
required:
|
|
183
|
+
- runKey
|
|
184
|
+
- title
|
|
185
|
+
- status
|
|
186
|
+
- phase
|
|
187
|
+
- processKey
|
|
188
|
+
- openEscalation
|
|
189
|
+
- updatedAt
|
|
190
|
+
- autoResolveEscalations
|
|
191
|
+
properties:
|
|
192
|
+
runKey:
|
|
193
|
+
type: string
|
|
194
|
+
title:
|
|
195
|
+
type: string
|
|
196
|
+
nullable: true
|
|
197
|
+
status:
|
|
198
|
+
type: string
|
|
199
|
+
phase:
|
|
200
|
+
type: string
|
|
201
|
+
nullable: true
|
|
202
|
+
processKey:
|
|
203
|
+
type: string
|
|
204
|
+
nullable: true
|
|
205
|
+
description: The engine process instance key; null while the launch claim has not been stamped yet.
|
|
206
|
+
openEscalation:
|
|
207
|
+
type: object
|
|
208
|
+
nullable: true
|
|
209
|
+
additionalProperties: false
|
|
210
|
+
description: >-
|
|
211
|
+
The structured pointer to this run's OPEN escalation/human task when parked awaiting an
|
|
212
|
+
answer (derived from the same `user_tasks` read model as `listEscalations`), else null.
|
|
213
|
+
required:
|
|
214
|
+
- userTaskKey
|
|
215
|
+
- kind
|
|
216
|
+
- summary
|
|
217
|
+
properties:
|
|
218
|
+
userTaskKey:
|
|
219
|
+
type: string
|
|
220
|
+
kind:
|
|
221
|
+
type: string
|
|
222
|
+
summary:
|
|
223
|
+
type: string
|
|
224
|
+
nullable: true
|
|
225
|
+
updatedAt:
|
|
226
|
+
type: string
|
|
227
|
+
autoResolveEscalations:
|
|
228
|
+
type: boolean
|
|
229
|
+
description: >-
|
|
230
|
+
Whether this delivery run opted into automatic escalation resolution by the escalation
|
|
231
|
+
steward (issue #889). Derived from the durable `auto_resolve_escalations` flag, never a
|
|
232
|
+
clobbered process variable.
|
|
163
233
|
Escalation:
|
|
164
234
|
type: object
|
|
165
235
|
description: >-
|
|
@@ -239,6 +309,141 @@ components:
|
|
|
239
309
|
type: array
|
|
240
310
|
items:
|
|
241
311
|
$ref: "#/components/schemas/Escalation"
|
|
312
|
+
AutoResolution:
|
|
313
|
+
type: object
|
|
314
|
+
description: "One escalation-steward auto-resolution record (issue #889) — a reviewable record of an escalation the steward resolved or abstained from."
|
|
315
|
+
additionalProperties: false
|
|
316
|
+
required:
|
|
317
|
+
- id
|
|
318
|
+
- userTaskKey
|
|
319
|
+
- outcome
|
|
320
|
+
- reviewState
|
|
321
|
+
- createdAt
|
|
322
|
+
properties:
|
|
323
|
+
id:
|
|
324
|
+
type: integer
|
|
325
|
+
userTaskKey:
|
|
326
|
+
type: string
|
|
327
|
+
kind:
|
|
328
|
+
type: string
|
|
329
|
+
nullable: true
|
|
330
|
+
elementId:
|
|
331
|
+
type: string
|
|
332
|
+
nullable: true
|
|
333
|
+
subjectType:
|
|
334
|
+
type: string
|
|
335
|
+
nullable: true
|
|
336
|
+
subjectKey:
|
|
337
|
+
type: string
|
|
338
|
+
nullable: true
|
|
339
|
+
subjectUrl:
|
|
340
|
+
type: string
|
|
341
|
+
nullable: true
|
|
342
|
+
processInstanceKey:
|
|
343
|
+
type: string
|
|
344
|
+
nullable: true
|
|
345
|
+
question:
|
|
346
|
+
type: string
|
|
347
|
+
nullable: true
|
|
348
|
+
options:
|
|
349
|
+
type: array
|
|
350
|
+
nullable: true
|
|
351
|
+
items: {}
|
|
352
|
+
outcome:
|
|
353
|
+
type: string
|
|
354
|
+
enum: [resolved, abstained]
|
|
355
|
+
submittedVariables:
|
|
356
|
+
type: object
|
|
357
|
+
nullable: true
|
|
358
|
+
additionalProperties: true
|
|
359
|
+
selectedOption:
|
|
360
|
+
type: string
|
|
361
|
+
nullable: true
|
|
362
|
+
description: >-
|
|
363
|
+
The option value the steward actually selected (derived host-side at record time), so a
|
|
364
|
+
consumer sees WHAT was chosen without decoding `submittedVariables`. Null when abstained /
|
|
365
|
+
not derivable.
|
|
366
|
+
rationale:
|
|
367
|
+
type: string
|
|
368
|
+
nullable: true
|
|
369
|
+
evidence:
|
|
370
|
+
type: string
|
|
371
|
+
nullable: true
|
|
372
|
+
stewardInstanceKey:
|
|
373
|
+
type: string
|
|
374
|
+
nullable: true
|
|
375
|
+
agentJobKey:
|
|
376
|
+
type: string
|
|
377
|
+
nullable: true
|
|
378
|
+
worker:
|
|
379
|
+
type: string
|
|
380
|
+
nullable: true
|
|
381
|
+
model:
|
|
382
|
+
type: string
|
|
383
|
+
nullable: true
|
|
384
|
+
createdAt:
|
|
385
|
+
type: string
|
|
386
|
+
reviewState:
|
|
387
|
+
type: string
|
|
388
|
+
enum: [pending, dismissed, intervened]
|
|
389
|
+
reviewedBy:
|
|
390
|
+
type: string
|
|
391
|
+
nullable: true
|
|
392
|
+
reviewedAt:
|
|
393
|
+
type: string
|
|
394
|
+
nullable: true
|
|
395
|
+
reviewNote:
|
|
396
|
+
type: string
|
|
397
|
+
nullable: true
|
|
398
|
+
AutoResolutionList:
|
|
399
|
+
type: object
|
|
400
|
+
additionalProperties: false
|
|
401
|
+
required:
|
|
402
|
+
- count
|
|
403
|
+
- pendingCount
|
|
404
|
+
- records
|
|
405
|
+
properties:
|
|
406
|
+
count:
|
|
407
|
+
type: integer
|
|
408
|
+
pendingCount:
|
|
409
|
+
type: integer
|
|
410
|
+
description: The number of `pending` (not-yet-reviewed) records — the nav badge count.
|
|
411
|
+
records:
|
|
412
|
+
type: array
|
|
413
|
+
items:
|
|
414
|
+
$ref: "#/components/schemas/AutoResolution"
|
|
415
|
+
AutoResolutionReview:
|
|
416
|
+
type: object
|
|
417
|
+
additionalProperties: false
|
|
418
|
+
required:
|
|
419
|
+
- id
|
|
420
|
+
- action
|
|
421
|
+
properties:
|
|
422
|
+
id:
|
|
423
|
+
type: integer
|
|
424
|
+
description: The auto-resolution record id (from `listAutoResolutions`).
|
|
425
|
+
action:
|
|
426
|
+
type: string
|
|
427
|
+
enum: [dismiss, intervene]
|
|
428
|
+
description: "`dismiss` = reviewed, no action; `intervene` = operator stepped in."
|
|
429
|
+
operator:
|
|
430
|
+
type: string
|
|
431
|
+
description: The reviewing operator's handle (for the audit trail). Optional — defaults to `operator`.
|
|
432
|
+
note:
|
|
433
|
+
type: string
|
|
434
|
+
description: An optional review note (e.g. the intervention guidance).
|
|
435
|
+
AutoResolutionReviewResult:
|
|
436
|
+
type: object
|
|
437
|
+
additionalProperties: false
|
|
438
|
+
required:
|
|
439
|
+
- ok
|
|
440
|
+
properties:
|
|
441
|
+
ok:
|
|
442
|
+
type: boolean
|
|
443
|
+
id:
|
|
444
|
+
type: integer
|
|
445
|
+
reviewState:
|
|
446
|
+
type: string
|
|
242
447
|
LineagePrView:
|
|
243
448
|
type: object
|
|
244
449
|
description: A member PR of a lineage thread (issue #245).
|
|
@@ -1686,6 +1891,9 @@ components:
|
|
|
1686
1891
|
confirmDefaultBase:
|
|
1687
1892
|
type: boolean
|
|
1688
1893
|
description: Acknowledge that `baseBranch` names the repository default branch. See `PlanStartByIssue.confirmDefaultBase`.
|
|
1894
|
+
autoResolveEscalations:
|
|
1895
|
+
type: boolean
|
|
1896
|
+
description: Opt this epic (and every child convergence/merge loop it spawns) into escalation-steward auto-resolution. See `PlanStartByIssue.autoResolveEscalations`.
|
|
1689
1897
|
EpicSetMemberByUrl:
|
|
1690
1898
|
type: object
|
|
1691
1899
|
additionalProperties: false
|
|
@@ -1708,6 +1916,9 @@ components:
|
|
|
1708
1916
|
confirmDefaultBase:
|
|
1709
1917
|
type: boolean
|
|
1710
1918
|
description: Acknowledge landing on the default branch. See `PlanStartByIssue.confirmDefaultBase`.
|
|
1919
|
+
autoResolveEscalations:
|
|
1920
|
+
type: boolean
|
|
1921
|
+
description: Opt this epic (and every child convergence/merge loop it spawns) into escalation-steward auto-resolution. See `PlanStartByIssue.autoResolveEscalations`.
|
|
1711
1922
|
EpicSetDep:
|
|
1712
1923
|
description: >-
|
|
1713
1924
|
One inter-epic dependency edge: the `consumer` epic waits for the `producer` epic to publish
|
|
@@ -1826,6 +2037,9 @@ components:
|
|
|
1826
2037
|
autoMerge:
|
|
1827
2038
|
type: boolean
|
|
1828
2039
|
description: Preferred per-request setting. When false, stop at `converged`; when true, run the merge-loop after convergence only if `NANO_PR_AUTO_MERGE` is enabled. When omitted, the global default applies.
|
|
2040
|
+
autoResolveEscalations:
|
|
2041
|
+
type: boolean
|
|
2042
|
+
description: "Opt this loop's escalations into the escalation steward (issue #889) — it reviews each open escalation every 30 min and submits a recommended choice, recorded reviewably in Auto-resolutions. Default off."
|
|
1829
2043
|
convergeOnly:
|
|
1830
2044
|
type: boolean
|
|
1831
2045
|
deprecated: true
|
|
@@ -1850,6 +2064,9 @@ components:
|
|
|
1850
2064
|
autoMerge:
|
|
1851
2065
|
type: boolean
|
|
1852
2066
|
description: Preferred per-request setting. When false, stop at `converged`; when true, run the merge-loop after convergence only if `NANO_PR_AUTO_MERGE` is enabled. When omitted, the global default applies.
|
|
2067
|
+
autoResolveEscalations:
|
|
2068
|
+
type: boolean
|
|
2069
|
+
description: "Opt this loop's escalations into the escalation steward (issue #889) — it reviews each open escalation every 30 min and submits a recommended choice, recorded reviewably in Auto-resolutions. Default off."
|
|
1853
2070
|
convergeOnly:
|
|
1854
2071
|
type: boolean
|
|
1855
2072
|
deprecated: true
|
|
@@ -1907,6 +2124,9 @@ components:
|
|
|
1907
2124
|
buffer — and fires any merge-to-default side effect per task — so admission will reject
|
|
1908
2125
|
(400) unless confirmed with true (no effect for a non-default base). Accepted by the schema
|
|
1909
2126
|
today but currently has no runtime effect.
|
|
2127
|
+
autoResolveEscalations:
|
|
2128
|
+
type: boolean
|
|
2129
|
+
description: "Opt this epic's (and its child loops') escalations into the escalation steward (issue #889) — it reviews each open escalation every 30 min and submits a recommended choice, recorded reviewably in Auto-resolutions. Default off."
|
|
1910
2130
|
PlanStartByUrl:
|
|
1911
2131
|
type: object
|
|
1912
2132
|
additionalProperties: false
|
|
@@ -1939,6 +2159,9 @@ components:
|
|
|
1939
2159
|
Accepted by the schema today but currently has no runtime effect. When implemented it
|
|
1940
2160
|
will be the required acknowledgement when `baseBranch` names the repository default
|
|
1941
2161
|
branch. See `PlanStartByIssue.confirmDefaultBase`.
|
|
2162
|
+
autoResolveEscalations:
|
|
2163
|
+
type: boolean
|
|
2164
|
+
description: "Opt this epic's (and its child loops') escalations into the escalation steward (issue #889) — it reviews each open escalation every 30 min and submits a recommended choice, recorded reviewably in Auto-resolutions. Default off."
|
|
1942
2165
|
ReadinessProbe:
|
|
1943
2166
|
description: >-
|
|
1944
2167
|
A single durable readiness probe (issue #258, #295) the feature run must satisfy before its
|
|
@@ -2518,6 +2741,9 @@ components:
|
|
|
2518
2741
|
segment starting with `.` or ending in `.lock`, etc.) is rejected at submit. This pattern
|
|
2519
2742
|
mirrors the authoritative server-side gate (`isPlausibleBranchName`, app/baseBranch.ts) so the
|
|
2520
2743
|
documented contract and the door agree.
|
|
2744
|
+
autoResolveEscalations:
|
|
2745
|
+
type: boolean
|
|
2746
|
+
description: "Opt this run's escalations (and any `converge*` connector's enrolled PR) into the escalation steward (issue #889) — it reviews each open escalation every 30 min and submits a recommended choice, recorded reviewably in Auto-resolutions. Default off."
|
|
2521
2747
|
DeliveryGraphDispatchRepoless:
|
|
2522
2748
|
description: >-
|
|
2523
2749
|
The checkout-less dispatch shape (#729): an EXPLICIT `repoless: true` opt-out of repository
|
|
@@ -2571,6 +2797,9 @@ components:
|
|
|
2571
2797
|
fields are not members of this variant, so supplying either alongside `repoless: true` fails
|
|
2572
2798
|
`oneOf` matching and is a 400. To provision a repository, use the repository variant (supply
|
|
2573
2799
|
`repository` + `baseBranch`) and omit `repoless` entirely.
|
|
2800
|
+
autoResolveEscalations:
|
|
2801
|
+
type: boolean
|
|
2802
|
+
description: "Opt this run's escalations (and any `converge*` connector's enrolled PR) into the escalation steward (issue #889) — it reviews each open escalation every 30 min and submits a recommended choice, recorded reviewably in Auto-resolutions. Default off."
|
|
2574
2803
|
DeliveryGraphDispatchNodeProvisioned:
|
|
2575
2804
|
description: >-
|
|
2576
2805
|
The FULLY node-provisioned dispatch shape (#739): supplies NEITHER a run-level `repository`/
|
|
@@ -2617,6 +2846,9 @@ components:
|
|
|
2617
2846
|
description: >-
|
|
2618
2847
|
OPTIONAL run-level ISO-8601 SLA for `human` nodes (#505) before they record an `escalated`
|
|
2619
2848
|
outcome. Absent → the `P1D` default. An invalid duration is rejected at submit.
|
|
2849
|
+
autoResolveEscalations:
|
|
2850
|
+
type: boolean
|
|
2851
|
+
description: "Opt this run's escalations (and any `converge*` connector's enrolled PR) into the escalation steward (issue #889) — it reviews each open escalation every 30 min and submits a recommended choice, recorded reviewably in Auto-resolutions. Default off."
|
|
2620
2852
|
DeliveryGraphDismissRequest:
|
|
2621
2853
|
description: >-
|
|
2622
2854
|
The OPERATOR dismiss request (#520). The cockpit's staged-proposals grid posts the content
|
|
@@ -3496,6 +3728,9 @@ components:
|
|
|
3496
3728
|
its handles against — the consumer dependency whose published provenance must carry each
|
|
3497
3729
|
awaited upstream. When present, `blockedOn` desugars to `capability` probes and the
|
|
3498
3730
|
resolved `pkg@version` is late-bound into the implementation agent's brief.
|
|
3731
|
+
autoResolveEscalations:
|
|
3732
|
+
type: boolean
|
|
3733
|
+
description: "Opt this run's escalations into the escalation steward (issue #889) — it reviews each open escalation every 30 min and submits a recommended choice, recorded reviewably in Auto-resolutions. Default off."
|
|
3499
3734
|
FeatureStartByUrl:
|
|
3500
3735
|
type: object
|
|
3501
3736
|
additionalProperties: false
|
|
@@ -3552,6 +3787,9 @@ components:
|
|
|
3552
3787
|
type: string
|
|
3553
3788
|
minLength: 1
|
|
3554
3789
|
description: OPTIONAL package the `blockedOn` handles resolve against. See `FeatureStartByIssue.consumerPackage`.
|
|
3790
|
+
autoResolveEscalations:
|
|
3791
|
+
type: boolean
|
|
3792
|
+
description: "Opt this run's escalations into the escalation steward (issue #889). See `FeatureStartByIssue.autoResolveEscalations`."
|
|
3555
3793
|
MessageResult:
|
|
3556
3794
|
type: object
|
|
3557
3795
|
description: The result of publishing a message / answering an escalation. Shape varies by message
|
|
@@ -3889,6 +4127,109 @@ paths:
|
|
|
3889
4127
|
application/json:
|
|
3890
4128
|
schema:
|
|
3891
4129
|
$ref: "#/components/schemas/ErrorBody"
|
|
4130
|
+
/auto-resolutions:
|
|
4131
|
+
get:
|
|
4132
|
+
operationId: listAutoResolutions
|
|
4133
|
+
summary: List the escalation steward's auto-resolution records (reviewable), newest first.
|
|
4134
|
+
description: >-
|
|
4135
|
+
The durable, reviewable record of every escalation the opt-in steward (issue #889) resolved
|
|
4136
|
+
or abstained from. Filter by `reviewState` (`pending` | `dismissed` | `intervened`),
|
|
4137
|
+
`subject` (a PR/feature/plan/delivery key) or `kind` (an escalation element id or label).
|
|
4138
|
+
Each record carries the question, the options offered, the submitted variables, the
|
|
4139
|
+
rationale and evidence, the steward provenance, and its operator review state.
|
|
4140
|
+
security:
|
|
4141
|
+
- hookSecret: []
|
|
4142
|
+
- {}
|
|
4143
|
+
parameters:
|
|
4144
|
+
- name: reviewState
|
|
4145
|
+
in: query
|
|
4146
|
+
required: false
|
|
4147
|
+
schema:
|
|
4148
|
+
type: string
|
|
4149
|
+
enum: [pending, dismissed, intervened]
|
|
4150
|
+
description: Filter to one review state (the Auto-resolutions view defaults to `pending`).
|
|
4151
|
+
- name: subject
|
|
4152
|
+
in: query
|
|
4153
|
+
required: false
|
|
4154
|
+
schema:
|
|
4155
|
+
type: string
|
|
4156
|
+
description: Filter to one subject key (e.g. `owner/repo#123` or a delivery run key).
|
|
4157
|
+
- name: kind
|
|
4158
|
+
in: query
|
|
4159
|
+
required: false
|
|
4160
|
+
schema:
|
|
4161
|
+
type: string
|
|
4162
|
+
description: Filter to one escalation kind (an element id or kind label).
|
|
4163
|
+
responses:
|
|
4164
|
+
"200":
|
|
4165
|
+
description: The auto-resolution records.
|
|
4166
|
+
content:
|
|
4167
|
+
application/json:
|
|
4168
|
+
schema:
|
|
4169
|
+
$ref: "#/components/schemas/AutoResolutionList"
|
|
4170
|
+
"401":
|
|
4171
|
+
description: Missing/invalid shared secret (only when NANO_PR_WEBHOOK_SECRET is set).
|
|
4172
|
+
content:
|
|
4173
|
+
application/json:
|
|
4174
|
+
schema:
|
|
4175
|
+
$ref: "#/components/schemas/ErrorBody"
|
|
4176
|
+
/auto-resolutions/review:
|
|
4177
|
+
post:
|
|
4178
|
+
operationId: reviewAutoResolution
|
|
4179
|
+
summary: Mark an auto-resolution record dismissed (reviewed, no action) or intervened (operator stepped in).
|
|
4180
|
+
description: >-
|
|
4181
|
+
The Auto-resolutions view's review affordance (issue #889). `dismiss` marks the record
|
|
4182
|
+
reviewed with no further action; `intervene` marks it and records the operator's guidance for
|
|
4183
|
+
stepping in after the fact. Both fold `review_state` off `pending` (idempotently — an
|
|
4184
|
+
already-reviewed record is left unchanged) and record who reviewed it, when, and an optional
|
|
4185
|
+
note. Reopening the completed user task is NOT required.
|
|
4186
|
+
requestBody:
|
|
4187
|
+
required: true
|
|
4188
|
+
content:
|
|
4189
|
+
application/json:
|
|
4190
|
+
schema:
|
|
4191
|
+
# BEGIN generated:mcp-body source=#/components/schemas/AutoResolutionReview (scripts/inline-mcp-bodies.ts — do not hand-edit)
|
|
4192
|
+
type: object
|
|
4193
|
+
additionalProperties: false
|
|
4194
|
+
required:
|
|
4195
|
+
- id
|
|
4196
|
+
- action
|
|
4197
|
+
properties:
|
|
4198
|
+
id:
|
|
4199
|
+
type: integer
|
|
4200
|
+
description: The auto-resolution record id (from `listAutoResolutions`).
|
|
4201
|
+
action:
|
|
4202
|
+
type: string
|
|
4203
|
+
enum:
|
|
4204
|
+
- dismiss
|
|
4205
|
+
- intervene
|
|
4206
|
+
description: '`dismiss` = reviewed, no action; `intervene` = operator stepped in.'
|
|
4207
|
+
operator:
|
|
4208
|
+
type: string
|
|
4209
|
+
description: The reviewing operator's handle (for the audit trail). Optional — defaults to `operator`.
|
|
4210
|
+
note:
|
|
4211
|
+
type: string
|
|
4212
|
+
description: An optional review note (e.g. the intervention guidance).
|
|
4213
|
+
# END generated:mcp-body
|
|
4214
|
+
responses:
|
|
4215
|
+
"200":
|
|
4216
|
+
description: The record was reviewed.
|
|
4217
|
+
content:
|
|
4218
|
+
application/json:
|
|
4219
|
+
schema:
|
|
4220
|
+
$ref: "#/components/schemas/AutoResolutionReviewResult"
|
|
4221
|
+
"400":
|
|
4222
|
+
description: The request was malformed (missing id/action, or an unknown action).
|
|
4223
|
+
content:
|
|
4224
|
+
application/json:
|
|
4225
|
+
schema:
|
|
4226
|
+
$ref: "#/components/schemas/ErrorBody"
|
|
4227
|
+
"404":
|
|
4228
|
+
description: No pending record with that id (already reviewed, or unknown).
|
|
4229
|
+
content:
|
|
4230
|
+
application/json:
|
|
4231
|
+
schema:
|
|
4232
|
+
$ref: "#/components/schemas/ErrorBody"
|
|
3892
4233
|
/escalations:
|
|
3893
4234
|
get:
|
|
3894
4235
|
operationId: listEscalations
|
|
@@ -4590,6 +4931,9 @@ paths:
|
|
|
4590
4931
|
autoMerge:
|
|
4591
4932
|
type: boolean
|
|
4592
4933
|
description: Preferred per-request setting. When false, stop at `converged`; when true, run the merge-loop after convergence only if `NANO_PR_AUTO_MERGE` is enabled. When omitted, the global default applies.
|
|
4934
|
+
autoResolveEscalations:
|
|
4935
|
+
type: boolean
|
|
4936
|
+
description: "Opt this loop's escalations into the escalation steward (issue #889) — it reviews each open escalation every 30 min and submits a recommended choice, recorded reviewably in Auto-resolutions. Default off."
|
|
4593
4937
|
convergeOnly:
|
|
4594
4938
|
type: boolean
|
|
4595
4939
|
deprecated: true
|
|
@@ -4613,6 +4957,9 @@ paths:
|
|
|
4613
4957
|
autoMerge:
|
|
4614
4958
|
type: boolean
|
|
4615
4959
|
description: Preferred per-request setting. When false, stop at `converged`; when true, run the merge-loop after convergence only if `NANO_PR_AUTO_MERGE` is enabled. When omitted, the global default applies.
|
|
4960
|
+
autoResolveEscalations:
|
|
4961
|
+
type: boolean
|
|
4962
|
+
description: "Opt this loop's escalations into the escalation steward (issue #889) — it reviews each open escalation every 30 min and submits a recommended choice, recorded reviewably in Auto-resolutions. Default off."
|
|
4616
4963
|
convergeOnly:
|
|
4617
4964
|
type: boolean
|
|
4618
4965
|
deprecated: true
|
|
@@ -4665,6 +5012,9 @@ paths:
|
|
|
4665
5012
|
confirmDefaultBase:
|
|
4666
5013
|
type: boolean
|
|
4667
5014
|
description: 'Reserved for a later ADR 0003 admission slice — NOT yet enforced in this slice (B0). When implemented it will be the required acknowledgement when `baseBranch` names the repository default branch: targeting the default lands every task directly on it with no integration buffer — and fires any merge-to-default side effect per task — so admission will reject (400) unless confirmed with true (no effect for a non-default base). Accepted by the schema today but currently has no runtime effect.'
|
|
5015
|
+
autoResolveEscalations:
|
|
5016
|
+
type: boolean
|
|
5017
|
+
description: "Opt this epic's (and its child loops') escalations into the escalation steward (issue #889) — it reviews each open escalation every 30 min and submits a recommended choice, recorded reviewably in Auto-resolutions. Default off."
|
|
4668
5018
|
- type: object
|
|
4669
5019
|
additionalProperties: false
|
|
4670
5020
|
required:
|
|
@@ -4686,6 +5036,9 @@ paths:
|
|
|
4686
5036
|
confirmDefaultBase:
|
|
4687
5037
|
type: boolean
|
|
4688
5038
|
description: Reserved for a later ADR 0003 admission slice — NOT yet enforced in this slice (B0). Accepted by the schema today but currently has no runtime effect. When implemented it will be the required acknowledgement when `baseBranch` names the repository default branch. See `PlanStartByIssue.confirmDefaultBase`.
|
|
5039
|
+
autoResolveEscalations:
|
|
5040
|
+
type: boolean
|
|
5041
|
+
description: "Opt this epic's (and its child loops') escalations into the escalation steward (issue #889) — it reviews each open escalation every 30 min and submits a recommended choice, recorded reviewably in Auto-resolutions. Default off."
|
|
4689
5042
|
type: object
|
|
4690
5043
|
# END generated:mcp-body
|
|
4691
5044
|
responses:
|
|
@@ -4755,6 +5108,9 @@ paths:
|
|
|
4755
5108
|
confirmDefaultBase:
|
|
4756
5109
|
type: boolean
|
|
4757
5110
|
description: Acknowledge that `baseBranch` names the repository default branch. See `PlanStartByIssue.confirmDefaultBase`.
|
|
5111
|
+
autoResolveEscalations:
|
|
5112
|
+
type: boolean
|
|
5113
|
+
description: Opt this epic (and every child convergence/merge loop it spawns) into escalation-steward auto-resolution. See `PlanStartByIssue.autoResolveEscalations`.
|
|
4758
5114
|
- type: object
|
|
4759
5115
|
additionalProperties: false
|
|
4760
5116
|
required:
|
|
@@ -4776,6 +5132,9 @@ paths:
|
|
|
4776
5132
|
confirmDefaultBase:
|
|
4777
5133
|
type: boolean
|
|
4778
5134
|
description: Acknowledge landing on the default branch. See `PlanStartByIssue.confirmDefaultBase`.
|
|
5135
|
+
autoResolveEscalations:
|
|
5136
|
+
type: boolean
|
|
5137
|
+
description: Opt this epic (and every child convergence/merge loop it spawns) into escalation-steward auto-resolution. See `PlanStartByIssue.autoResolveEscalations`.
|
|
4779
5138
|
deps:
|
|
4780
5139
|
type: array
|
|
4781
5140
|
description: The inter-epic dependency edges. Each declares `consumer` waits for `producer` to publish the `{ package, capabilityRef }` capability. Both endpoints must name epics in `epics`. Omit or pass `[]` for a set of independent (root) epics.
|
|
@@ -6319,6 +6678,9 @@ paths:
|
|
|
6319
6678
|
type: string
|
|
6320
6679
|
minLength: 1
|
|
6321
6680
|
description: OPTIONAL npm package name (e.g. `@nanobpm/engine-wasm`) the `blockedOn` shorthand resolves its handles against — the consumer dependency whose published provenance must carry each awaited upstream. When present, `blockedOn` desugars to `capability` probes and the resolved `pkg@version` is late-bound into the implementation agent's brief.
|
|
6681
|
+
autoResolveEscalations:
|
|
6682
|
+
type: boolean
|
|
6683
|
+
description: "Opt this run's escalations into the escalation steward (issue #889) — it reviews each open escalation every 30 min and submits a recommended choice, recorded reviewably in Auto-resolutions. Default off."
|
|
6322
6684
|
- type: object
|
|
6323
6685
|
additionalProperties: false
|
|
6324
6686
|
required:
|
|
@@ -6467,6 +6829,9 @@ paths:
|
|
|
6467
6829
|
type: string
|
|
6468
6830
|
minLength: 1
|
|
6469
6831
|
description: OPTIONAL package the `blockedOn` handles resolve against. See `FeatureStartByIssue.consumerPackage`.
|
|
6832
|
+
autoResolveEscalations:
|
|
6833
|
+
type: boolean
|
|
6834
|
+
description: "Opt this run's escalations into the escalation steward (issue #889). See `FeatureStartByIssue.autoResolveEscalations`."
|
|
6470
6835
|
type: object
|
|
6471
6836
|
# END generated:mcp-body
|
|
6472
6837
|
responses:
|
|
@@ -120,6 +120,11 @@ export default defineOperation("dispatchDeliveryGraph", async ({ body }, app) =>
|
|
|
120
120
|
if (parsed.value !== undefined) timeouts[field] = parsed.value;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
// Opt-in escalation steward (issue #889) — when the dispatch body sets `autoResolveEscalations`,
|
|
124
|
+
// the run's escalations (and any `converge*` connector's enrolled PR) become eligible for steward
|
|
125
|
+
// auto-resolution. Default off.
|
|
126
|
+
const autoResolveEscalations = body && typeof body === "object" && "autoResolveEscalations" in body && body.autoResolveEscalations === true;
|
|
127
|
+
|
|
123
128
|
// Host-git provisioning override (#684/#686) — the OPTIONAL `owner/repo` + base branch the run's
|
|
124
129
|
// `agent` nodes implement against. When both are present the runner seeds the canonical
|
|
125
130
|
// `io.nanobpm.agentTask.repository` isolation envelope (`repoEnvelopeVars`) onto every agent cell's
|
|
@@ -267,7 +272,7 @@ export default defineOperation("dispatchDeliveryGraph", async ({ body }, app) =>
|
|
|
267
272
|
}
|
|
268
273
|
}
|
|
269
274
|
|
|
270
|
-
const dispatched = await dispatchDeliveryGraphRun(app, graph, { runKey: dispatchRunKey, title: proposal.title, repository, baseBranch, repoless, expectedDigest: digest, ...timeouts });
|
|
275
|
+
const dispatched = await dispatchDeliveryGraphRun(app, graph, { runKey: dispatchRunKey, title: proposal.title, repository, baseBranch, repoless, expectedDigest: digest, autoResolveEscalations, ...timeouts });
|
|
271
276
|
if (!dispatched.ok) {
|
|
272
277
|
app.log.warn("dispatch-delivery-graph refused: compile", { digest, errors: dispatched.errors.length });
|
|
273
278
|
const outBody: DeliveryGraphTextResult = {
|
|
@@ -8,7 +8,7 @@ import { noopLog } from "../test/log.ts";
|
|
|
8
8
|
import { withTrackingViews } from "../test/trackingViews.ts";
|
|
9
9
|
import handler from "./listActivePrs.ts";
|
|
10
10
|
|
|
11
|
-
function memApp(rows: any[], userTaskRows: any[] = []): AppApi {
|
|
11
|
+
function memApp(rows: any[], userTaskRows: any[] = [], deliveryRows: any[] = []): AppApi {
|
|
12
12
|
const table = (name: string) => {
|
|
13
13
|
if (name === "user_tasks") {
|
|
14
14
|
return {
|
|
@@ -20,6 +20,13 @@ function memApp(rows: any[], userTaskRows: any[] = []): AppApi {
|
|
|
20
20
|
},
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
+
if (name === "delivery_graph_runs") {
|
|
24
|
+
return {
|
|
25
|
+
async all() {
|
|
26
|
+
return deliveryRows;
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
23
30
|
return {
|
|
24
31
|
async all() {
|
|
25
32
|
return rows;
|
|
@@ -115,3 +122,55 @@ test("shared-secret guard rejects a missing secret when configured", async () =>
|
|
|
115
122
|
else process.env["NANO_PR_WEBHOOK_SECRET"] = prev;
|
|
116
123
|
}
|
|
117
124
|
});
|
|
125
|
+
|
|
126
|
+
test("surfaces each in-flight delivery run with its durable escalation-steward opt-in (issue #889 §1)", async () => {
|
|
127
|
+
// `/status` must show the opt-in for each PR AND each delivery. A terminal (`done`) run drops out;
|
|
128
|
+
// a live run carries its durable `auto_resolve_escalations` flag (never a clobbered process var) and
|
|
129
|
+
// its open escalation pointer (derived from the SAME `user_tasks` read model, subject_type
|
|
130
|
+
// "delivery", keyed by run_key).
|
|
131
|
+
const app = memApp(
|
|
132
|
+
[],
|
|
133
|
+
[
|
|
134
|
+
{
|
|
135
|
+
user_task_key: "ut-d1",
|
|
136
|
+
element_id: "delivery-human-taskish",
|
|
137
|
+
kind_label: "Delivery approval",
|
|
138
|
+
subject_type: "delivery",
|
|
139
|
+
subject_key: "run-live",
|
|
140
|
+
subject_title: "Ship it",
|
|
141
|
+
subject_url: null,
|
|
142
|
+
question: "approve the release?",
|
|
143
|
+
process_key: "pk-live",
|
|
144
|
+
form_key: null,
|
|
145
|
+
created_at: "2026-03-02",
|
|
146
|
+
updated_at: "2026-03-02",
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
[
|
|
150
|
+
{ run_key: "run-live", process_key: "pk-live", status: "running", phase: "Running", title: "Ship it", auto_resolve_escalations: 1, updated_at: "2026-03-02" },
|
|
151
|
+
{ run_key: "run-done", process_key: "pk-done", status: "done", phase: "Done", title: "Old", auto_resolve_escalations: 0, updated_at: "2026-03-01" },
|
|
152
|
+
],
|
|
153
|
+
);
|
|
154
|
+
const res = (await handler(input(), app)) as any;
|
|
155
|
+
assertEquals(res.status, 200);
|
|
156
|
+
assertEquals(res.body.deliveries.length, 1, "a terminal (done) run drops out of the active list");
|
|
157
|
+
const d = res.body.deliveries[0];
|
|
158
|
+
assertEquals(d.runKey, "run-live");
|
|
159
|
+
assertEquals(d.autoResolveEscalations, true, "the durable opt-in flag is surfaced for the delivery");
|
|
160
|
+
assertEquals(d.processKey, "pk-live");
|
|
161
|
+
assertEquals(d.openEscalation, { userTaskKey: "ut-d1", kind: "delivery-human-taskish", summary: "approve the release?" });
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("a delivery run with no opt-in row flag surfaces autoResolveEscalations false and a null escalation", async () => {
|
|
165
|
+
const app = memApp(
|
|
166
|
+
[],
|
|
167
|
+
[],
|
|
168
|
+
[{ run_key: "run-plain", process_key: null, status: "running", phase: null, title: null, auto_resolve_escalations: 0, updated_at: "2026-03-03" }],
|
|
169
|
+
);
|
|
170
|
+
const res = (await handler(input(), app)) as any;
|
|
171
|
+
assertEquals(res.status, 200);
|
|
172
|
+
assertEquals(res.body.deliveries.length, 1);
|
|
173
|
+
assertEquals(res.body.deliveries[0].autoResolveEscalations, false);
|
|
174
|
+
assertEquals(res.body.deliveries[0].openEscalation, null);
|
|
175
|
+
assertEquals(res.body.deliveries[0].processKey, null, "a launch-in-flight run has no stamped process_key yet");
|
|
176
|
+
});
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// stays HERE (the runtime does not enforce OpenAPI `security`): when NANO_PR_WEBHOOK_SECRET is set,
|
|
8
8
|
// callers must present it via the x-hook-secret header. Unset → open (unchanged default).
|
|
9
9
|
|
|
10
|
-
import { activePrs } from "../app/service.ts";
|
|
10
|
+
import { activeDeliveries, activePrs } from "../app/service.ts";
|
|
11
11
|
import { envVar } from "../app/version.ts";
|
|
12
12
|
import { defineOperation } from "../nano-generated/operations.ts";
|
|
13
13
|
|
|
@@ -20,6 +20,6 @@ export default defineOperation("listActivePrs", async ({ req }, app) => {
|
|
|
20
20
|
app.log.warn("listActivePrs rejected: missing/invalid shared secret");
|
|
21
21
|
return { status: 401, body: { error: "unauthorized" } };
|
|
22
22
|
}
|
|
23
|
-
const prs = await activePrs(app.data);
|
|
24
|
-
return { status: 200, body: { count: prs.length, prs } };
|
|
23
|
+
const [prs, deliveries] = await Promise.all([activePrs(app.data), activeDeliveries(app.data)]);
|
|
24
|
+
return { status: 200, body: { count: prs.length, prs, deliveries } };
|
|
25
25
|
});
|