@nanobpm/nano-workforce 0.37.0 → 0.39.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 +14 -0
- package/README.md +5 -6
- package/SPEC.md +15 -13
- package/app/abandon.test.ts +4 -4
- package/app/abandon.ts +2 -2
- package/app/blackboard.test.ts +3 -3
- package/app/blackboard.ts +2 -2
- package/app/persist-escalation.test.ts +1 -1
- package/app/persist-round.test.ts +1 -1
- package/app/service.ts +1 -1
- package/biome.json +0 -2
- package/main.ts +3 -3
- package/nano.app.json +12 -25
- package/openapi.yaml +653 -0
- package/operations/answerFeatureEscalation.test.ts +100 -0
- package/operations/answerFeatureEscalation.ts +40 -0
- package/operations/appendBlackboard.ts +58 -0
- package/{actions → operations}/blackboard.test.ts +16 -10
- package/{actions/abandon.test.ts → operations/checkAbandon.test.ts} +5 -11
- package/{actions/abandon.ts → operations/checkAbandon.ts} +6 -7
- package/operations/getVersion.ts +4 -6
- package/operations/listActivePrs.ts +5 -9
- package/operations/postMessage.ts +4 -12
- package/operations/readBlackboard.ts +26 -0
- package/operations/startAndMessage.test.ts +5 -3
- package/operations/startConvergenceLoop.ts +16 -19
- package/operations/startPlanFanout.ts +14 -19
- package/package.json +5 -4
- package/pages/epic.page.json +1 -1
- package/pages/home.page.json +1 -1
- package/tsconfig.json +1 -0
- package/workers/persist-task-escalation/worker.ts +1 -1
- package/actions/blackboard.ts +0 -77
- package/actions/feature-answer-hook.ts +0 -45
- package/actions/plan-hook.ts +0 -20
- package/actions/webhook-submit.ts +0 -22
- package/openapi.json +0 -248
package/actions/blackboard.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
// GET/POST /hooks/blackboard?token=<capabilityToken> — the epic coordination blackboard endpoint
|
|
2
|
-
// (Tier 1, issues #51 / #49 D4).
|
|
3
|
-
//
|
|
4
|
-
// This is a DIRECT side-channel for agents, distinct from the c8ctl-nano activation/completion
|
|
5
|
-
// channel. The per-plan capability token (query string) IS the credential: it scopes every read
|
|
6
|
-
// and write to exactly one plan, so no shared secret is needed — the agent curls the exact URL it
|
|
7
|
-
// was handed in its prompt. An unknown token is a 404 (never leaks which plans exist).
|
|
8
|
-
//
|
|
9
|
-
// GET → { planKey, entries: [ { id, author_task, kind, files, body, wave, created_at } ], cursor }
|
|
10
|
-
// optional ?since=<id> returns only entries with id > since (incremental poll). `cursor` is
|
|
11
|
-
// the plan's current head id; pass it back as `since` on the next poll (Tier 2).
|
|
12
|
-
// POST → append one entry: { author_task?, kind?, files?, body, wave?, dedupe_key? }. Idempotent
|
|
13
|
-
// on (plan, dedupe_key). Returns { id, inserted, conflicts } — `conflicts` lists prior
|
|
14
|
-
// sibling `file-claim`s on the same file(s) (advisory first-writer-wins; never a lock).
|
|
15
|
-
import type { ActionHandler } from "@nanobpm/urban";
|
|
16
|
-
import {
|
|
17
|
-
appendEntry,
|
|
18
|
-
detectFileClaimConflicts,
|
|
19
|
-
normalizeKind,
|
|
20
|
-
planKeyForToken,
|
|
21
|
-
readBlackboardPage,
|
|
22
|
-
} from "../app/blackboard.ts";
|
|
23
|
-
|
|
24
|
-
const handler: ActionHandler = async ({ req, body }, app) => {
|
|
25
|
-
const token = (req.query.get("token") ?? req.headers.get("x-blackboard-token") ?? "").trim();
|
|
26
|
-
if (!token) return { status: 400, body: { error: "missing blackboard token" } };
|
|
27
|
-
const planKey = await planKeyForToken(app.data, token);
|
|
28
|
-
if (!planKey) return { status: 404, body: { error: "unknown blackboard token" } };
|
|
29
|
-
|
|
30
|
-
if (req.method === "GET") {
|
|
31
|
-
const rawSince = req.query.get("since");
|
|
32
|
-
const since = rawSince != null && /^\d+$/.test(rawSince) ? Number(rawSince) : undefined;
|
|
33
|
-
const { entries, cursor } = await readBlackboardPage(app.data, planKey, { since });
|
|
34
|
-
return { status: 200, body: { planKey, entries, cursor } };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (req.method === "POST") {
|
|
38
|
-
// biome-ignore lint/plugin: runtime/framework contract boundary for external data shape
|
|
39
|
-
const b = (body ?? {}) as Record<string, unknown>;
|
|
40
|
-
const text = typeof b.body === "string" ? b.body.trim() : "";
|
|
41
|
-
if (!text) return { status: 400, body: { error: "'body' (the note text) is required" } };
|
|
42
|
-
const kind = normalizeKind(b.kind);
|
|
43
|
-
const files = Array.isArray(b.files) ? b.files.map(String) : [];
|
|
44
|
-
// Normalize once (trim + default to "system") so the value we send to appendEntry matches the
|
|
45
|
-
// value we send to detectFileClaimConflicts. Otherwise an omitted/blank author_task is stored as
|
|
46
|
-
// "system" but conflict detection sees "", and the caller's own prior "system" claims are wrongly
|
|
47
|
-
// reported as sibling conflicts.
|
|
48
|
-
const author_task = (typeof b.author_task === "string" ? b.author_task.trim() : "") || "system";
|
|
49
|
-
const res = await appendEntry(app.data, planKey, {
|
|
50
|
-
author_task,
|
|
51
|
-
kind,
|
|
52
|
-
files,
|
|
53
|
-
body: text,
|
|
54
|
-
wave: typeof b.wave === "number" ? b.wave : null,
|
|
55
|
-
dedupe_key: typeof b.dedupe_key === "string" ? b.dedupe_key : undefined,
|
|
56
|
-
});
|
|
57
|
-
// Advisory conflict-of-intent: surface prior sibling claims on the same file(s). Computed AFTER
|
|
58
|
-
// the append and filtered to claims strictly before ours (id < res.id), so first-writer-wins is
|
|
59
|
-
// decided by insertion order — a sibling that raced a claim in between is still caught, and our
|
|
60
|
-
// own just-written row is never reported. Never blocks the append — the agent decides how to react.
|
|
61
|
-
const conflicts = kind === "file-claim"
|
|
62
|
-
? await detectFileClaimConflicts(app.data, planKey, {
|
|
63
|
-
author_task,
|
|
64
|
-
files,
|
|
65
|
-
beforeId: Number(res.id),
|
|
66
|
-
})
|
|
67
|
-
: [];
|
|
68
|
-
return {
|
|
69
|
-
status: res.inserted ? 201 : 200,
|
|
70
|
-
body: { id: Number(res.id), inserted: res.inserted, conflicts },
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return { status: 405, body: { error: "method not allowed (use GET or POST)" } };
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
export default handler;
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
// POST /hooks/feature-answer — answer an implementation-phase task escalation out
|
|
2
|
-
// of band (optional shared-secret guard via X-Hook-Secret, enforced only when
|
|
3
|
-
// NANO_PR_WEBHOOK_SECRET is set — mirrors /hooks/submit and /hooks/plan), issue #25.
|
|
4
|
-
// Lets an external
|
|
5
|
-
// system (a chat relay, a CI job, a human via curl) resume a parked implementation
|
|
6
|
-
// agent without the page. Same idempotent `answerTaskEscalation` path the page's
|
|
7
|
-
// answer form uses.
|
|
8
|
-
//
|
|
9
|
-
// Body accepts either the raw correlation key or a plan+task pair:
|
|
10
|
-
// { "corrKey": "owner/repo#12:task-3", "answer": "…" }
|
|
11
|
-
// { "plan": "owner/repo#12", "task": "task-3", "answer": "…" }
|
|
12
|
-
import type { ActionHandler } from "@nanobpm/urban";
|
|
13
|
-
import { answerTaskEscalation, featureCorrKey } from "../app/plan.ts";
|
|
14
|
-
|
|
15
|
-
const WEBHOOK_SECRET = process.env.NANO_PR_WEBHOOK_SECRET ?? "";
|
|
16
|
-
|
|
17
|
-
const str = (v: unknown): string => (typeof v === "string" ? v.trim() : "");
|
|
18
|
-
|
|
19
|
-
const handler: ActionHandler = async ({ req, body }, app) => {
|
|
20
|
-
if (WEBHOOK_SECRET && req.headers.get("x-hook-secret") !== WEBHOOK_SECRET) {
|
|
21
|
-
return { status: 401, body: { error: "unauthorized" } };
|
|
22
|
-
}
|
|
23
|
-
// biome-ignore lint/plugin: runtime/framework contract boundary for external data shape
|
|
24
|
-
const b = (body ?? {}) as {
|
|
25
|
-
corrKey?: unknown;
|
|
26
|
-
plan?: unknown;
|
|
27
|
-
task?: unknown;
|
|
28
|
-
answer?: unknown;
|
|
29
|
-
};
|
|
30
|
-
const answer = str(b.answer);
|
|
31
|
-
if (!answer) return { status: 400, body: { error: "answer is required" } };
|
|
32
|
-
|
|
33
|
-
const corrKey = str(b.corrKey) || (str(b.plan) && str(b.task) ? featureCorrKey(str(b.plan), str(b.task)) : "");
|
|
34
|
-
if (!corrKey) {
|
|
35
|
-
return {
|
|
36
|
-
status: 400,
|
|
37
|
-
body: { error: "provide corrKey, or both plan (owner/repo#N) and task" },
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const r = await answerTaskEscalation(app.data, app.engine, corrKey, answer);
|
|
42
|
-
return { status: r.ok ? 200 : 404, body: r };
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export default handler;
|
package/actions/plan-hook.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
// POST /hooks/plan — kick off a planning fan-out out of band (shared-secret auth via
|
|
2
|
-
// X-Hook-Secret). Lets an external system (a GitHub webhook relay on issue open/label, a CI job)
|
|
3
|
-
// hand an issue to the fleet. Same idempotent startPlan path as the page's "Plan issue" action.
|
|
4
|
-
import type { ActionHandler } from "@nanobpm/urban";
|
|
5
|
-
import { parseIssue, startPlan } from "../app/plan.ts";
|
|
6
|
-
|
|
7
|
-
const WEBHOOK_SECRET = process.env.NANO_PR_WEBHOOK_SECRET ?? "";
|
|
8
|
-
|
|
9
|
-
const handler: ActionHandler = async ({ req, body }, app) => {
|
|
10
|
-
if (WEBHOOK_SECRET && req.headers.get("x-hook-secret") !== WEBHOOK_SECRET) {
|
|
11
|
-
return { status: 401, body: { error: "unauthorized" } };
|
|
12
|
-
}
|
|
13
|
-
// biome-ignore lint/plugin: runtime/framework contract boundary for external data shape
|
|
14
|
-
const b = (body ?? {}) as { url?: unknown; issue?: unknown };
|
|
15
|
-
const parsed = parseIssue(String(b.issue ?? b.url ?? ""));
|
|
16
|
-
if (!parsed) return { status: 400, body: { error: "could not parse issue (use owner/repo#123 or an issue URL)" } };
|
|
17
|
-
return { status: 202, body: await startPlan(app.data, app.engine, parsed) };
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export default handler;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
// POST /hooks/submit — submit a PR out-of-band (shared-secret auth via X-Hook-Secret). Not
|
|
2
|
-
// part of the page UI; lets an external system (a GitHub webhook relay, a CI job) kick off a
|
|
3
|
-
// convergence run. Same idempotent submit path as the page's "Start review" action.
|
|
4
|
-
import type { ActionHandler } from "@nanobpm/urban";
|
|
5
|
-
import { clampRounds, MAX_ROUNDS, parsePr, submitPr } from "../app/service.ts";
|
|
6
|
-
|
|
7
|
-
const WEBHOOK_SECRET = process.env.NANO_PR_WEBHOOK_SECRET ?? "";
|
|
8
|
-
|
|
9
|
-
const handler: ActionHandler = async ({ req, body }, app) => {
|
|
10
|
-
if (WEBHOOK_SECRET && req.headers.get("x-hook-secret") !== WEBHOOK_SECRET) {
|
|
11
|
-
return { status: 401, body: { error: "unauthorized" } };
|
|
12
|
-
}
|
|
13
|
-
// biome-ignore lint/plugin: runtime/framework contract boundary for external data shape
|
|
14
|
-
const b = (body ?? {}) as { url?: unknown; pr?: unknown; dependsOn?: unknown; maxRounds?: unknown };
|
|
15
|
-
const parsed = parsePr(String(b.url ?? b.pr ?? ""));
|
|
16
|
-
if (!parsed) return { status: 400, body: { error: "could not parse PR url" } };
|
|
17
|
-
const dependsOn = Array.isArray(b.dependsOn) ? b.dependsOn.map((d) => String(d)) : [];
|
|
18
|
-
const maxRounds = clampRounds(b.maxRounds, MAX_ROUNDS);
|
|
19
|
-
return { status: 202, body: await submitPr(app.data, app.engine, parsed, dependsOn, maxRounds) };
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export default handler;
|
package/openapi.json
DELETED
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"openapi": "3.0.3",
|
|
3
|
-
"info": {
|
|
4
|
-
"title": "Nano Workforce control API",
|
|
5
|
-
"version": "1.0.0",
|
|
6
|
-
"description": "The externally-facing control surface an operator, an automation harness, or an LLM uses to observe and steer PR-convergence and planning runs. Contract-first (ADR 0058): the toolkit derives typed request/response contracts + runtime validators from this document and each `operationId` is implemented by a delegate module in `operations/`. Mounted under base `/app/api`, kept off the framework-reserved `/app` page-runtime namespace (which owns `/app/runtime.js`, `/app/pages/*`, `/app/data/*`). The `/hooks/*` webhook endpoints stay on `actions[]` because they live outside the `/app` namespace."
|
|
7
|
-
},
|
|
8
|
-
"components": {
|
|
9
|
-
"securitySchemes": {
|
|
10
|
-
"hookSecret": {
|
|
11
|
-
"type": "apiKey",
|
|
12
|
-
"in": "header",
|
|
13
|
-
"name": "x-hook-secret",
|
|
14
|
-
"description": "Optional shared secret. Enforced by the delegate (NOT the runtime) only when NANO_PR_WEBHOOK_SECRET is set; unset means the endpoint is open. Declared here for documentation."
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
"schemas": {
|
|
18
|
-
"ErrorBody": {
|
|
19
|
-
"type": "object",
|
|
20
|
-
"required": ["error"],
|
|
21
|
-
"properties": { "error": { "type": "string" } }
|
|
22
|
-
},
|
|
23
|
-
"ActivePr": {
|
|
24
|
-
"type": "object",
|
|
25
|
-
"description": "A tracked PR that is not in a terminal (converged/abandoned) state.",
|
|
26
|
-
"required": [
|
|
27
|
-
"prKey", "repo", "number", "url", "title", "status", "round",
|
|
28
|
-
"processKey", "waitingSince", "openEscalation", "updatedAt",
|
|
29
|
-
"activeWorker", "leaseUntil"
|
|
30
|
-
],
|
|
31
|
-
"properties": {
|
|
32
|
-
"prKey": { "type": "string" },
|
|
33
|
-
"repo": { "type": "string" },
|
|
34
|
-
"number": { "type": "integer" },
|
|
35
|
-
"url": { "type": "string" },
|
|
36
|
-
"title": { "type": ["string", "null"] },
|
|
37
|
-
"status": { "type": "string" },
|
|
38
|
-
"round": { "type": "integer" },
|
|
39
|
-
"processKey": { "type": ["string", "null"], "description": "The engine process instance key; also the keyField the pages processExplorer link uses." },
|
|
40
|
-
"waitingSince": { "type": ["string", "null"] },
|
|
41
|
-
"openEscalation": { "type": ["string", "null"] },
|
|
42
|
-
"updatedAt": { "type": "string" },
|
|
43
|
-
"activeWorker": { "type": ["string", "null"] },
|
|
44
|
-
"leaseUntil": { "type": ["string", "null"] }
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
"ActivePrList": {
|
|
48
|
-
"type": "object",
|
|
49
|
-
"required": ["count", "prs"],
|
|
50
|
-
"properties": {
|
|
51
|
-
"count": { "type": "integer" },
|
|
52
|
-
"prs": { "type": "array", "items": { "$ref": "#/components/schemas/ActivePr" } }
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
"VersionInfo": {
|
|
56
|
-
"type": "object",
|
|
57
|
-
"description": "The running app's identity (which code is actually live).",
|
|
58
|
-
"required": [
|
|
59
|
-
"name", "version", "urbanVersion", "gitSha", "gitBranch",
|
|
60
|
-
"runtime", "pid", "startedAt", "uptimeSeconds"
|
|
61
|
-
],
|
|
62
|
-
"properties": {
|
|
63
|
-
"name": { "type": "string" },
|
|
64
|
-
"version": { "type": ["string", "null"] },
|
|
65
|
-
"urbanVersion": { "type": ["string", "null"] },
|
|
66
|
-
"gitSha": { "type": ["string", "null"] },
|
|
67
|
-
"gitBranch": { "type": ["string", "null"] },
|
|
68
|
-
"runtime": { "type": "string" },
|
|
69
|
-
"pid": { "type": ["integer", "null"] },
|
|
70
|
-
"startedAt": { "type": "string" },
|
|
71
|
-
"uptimeSeconds": { "type": "integer" }
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
"SubmitResult": {
|
|
75
|
-
"type": "object",
|
|
76
|
-
"required": ["prKey"],
|
|
77
|
-
"properties": {
|
|
78
|
-
"prKey": { "type": "string" },
|
|
79
|
-
"processKey": { "type": ["string", "null"], "description": "The started convergence-loop instance key (null if the engine did not return one)." },
|
|
80
|
-
"alreadyRunning": { "type": "boolean", "description": "True when a non-terminal convergence loop for this PR already exists; the aggregate was refreshed and no new instance was started." }
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
"StartPlanResult": {
|
|
84
|
-
"type": "object",
|
|
85
|
-
"required": ["planKey"],
|
|
86
|
-
"properties": {
|
|
87
|
-
"planKey": { "type": "string" },
|
|
88
|
-
"processKey": { "type": ["string", "null"] },
|
|
89
|
-
"alreadyRunning": { "type": "boolean", "description": "True when a non-terminal plan for this issue already exists; no new instance was started." }
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
"StartVariables": {
|
|
93
|
-
"type": "object",
|
|
94
|
-
"description": "Process-start variables. `pr`/`url` (convergence) or `issue`/`url` (planning) name the target; extra keys are forwarded to the engine.",
|
|
95
|
-
"additionalProperties": true,
|
|
96
|
-
"properties": {
|
|
97
|
-
"pr": { "type": "string", "description": "PR reference: owner/repo#123 or a PR URL." },
|
|
98
|
-
"issue": { "type": "string", "description": "Issue reference: owner/repo#123 or an issue URL." },
|
|
99
|
-
"url": { "type": "string", "description": "Alias for pr/issue when a bare URL is supplied." },
|
|
100
|
-
"dependsOn": { "type": "array", "items": { "type": "string" } },
|
|
101
|
-
"maxRounds": { "type": "integer", "minimum": 1 }
|
|
102
|
-
}
|
|
103
|
-
},
|
|
104
|
-
"MessageResult": {
|
|
105
|
-
"type": "object",
|
|
106
|
-
"description": "The result of publishing a message / answering an escalation. Shape varies by message name; `ok` is always present.",
|
|
107
|
-
"additionalProperties": true,
|
|
108
|
-
"required": ["ok"],
|
|
109
|
-
"properties": { "ok": { "type": "boolean" } }
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
"paths": {
|
|
114
|
-
"/status": {
|
|
115
|
-
"get": {
|
|
116
|
-
"operationId": "listActivePrs",
|
|
117
|
-
"summary": "List every tracked PR currently in flight (not converged/abandoned), newest-updated first.",
|
|
118
|
-
"security": [{ "hookSecret": [] }, {}],
|
|
119
|
-
"responses": {
|
|
120
|
-
"200": {
|
|
121
|
-
"description": "The active PRs.",
|
|
122
|
-
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ActivePrList" } } }
|
|
123
|
-
},
|
|
124
|
-
"401": {
|
|
125
|
-
"description": "Missing/invalid shared secret (only when NANO_PR_WEBHOOK_SECRET is set).",
|
|
126
|
-
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorBody" } } }
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
},
|
|
131
|
-
"/version": {
|
|
132
|
-
"get": {
|
|
133
|
-
"operationId": "getVersion",
|
|
134
|
-
"summary": "The running app's identity (app/urban versions, git sha/branch, runtime, pid, uptime).",
|
|
135
|
-
"security": [{ "hookSecret": [] }, {}],
|
|
136
|
-
"responses": {
|
|
137
|
-
"200": {
|
|
138
|
-
"description": "The version/identity payload.",
|
|
139
|
-
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/VersionInfo" } } }
|
|
140
|
-
},
|
|
141
|
-
"401": {
|
|
142
|
-
"description": "Missing/invalid shared secret (only when NANO_PR_WEBHOOK_SECRET is set).",
|
|
143
|
-
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorBody" } } }
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
},
|
|
148
|
-
"/actions/start/convergence-loop": {
|
|
149
|
-
"post": {
|
|
150
|
-
"operationId": "startConvergenceLoop",
|
|
151
|
-
"summary": "Register/refresh a PR aggregate (idempotent on prKey) and start its convergence loop.",
|
|
152
|
-
"requestBody": {
|
|
153
|
-
"required": true,
|
|
154
|
-
"content": {
|
|
155
|
-
"application/json": {
|
|
156
|
-
"schema": {
|
|
157
|
-
"type": "object",
|
|
158
|
-
"additionalProperties": true,
|
|
159
|
-
"required": ["variables"],
|
|
160
|
-
"properties": { "variables": { "$ref": "#/components/schemas/StartVariables" } }
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
},
|
|
165
|
-
"responses": {
|
|
166
|
-
"202": {
|
|
167
|
-
"description": "The loop was started (or refreshed).",
|
|
168
|
-
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/SubmitResult" } } }
|
|
169
|
-
},
|
|
170
|
-
"400": {
|
|
171
|
-
"description": "The PR reference could not be parsed.",
|
|
172
|
-
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorBody" } } }
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
},
|
|
177
|
-
"/actions/start/plan-fanout": {
|
|
178
|
-
"post": {
|
|
179
|
-
"operationId": "startPlanFanout",
|
|
180
|
-
"summary": "Register/refresh a plan aggregate (idempotent on planKey) and start the planning fan-out.",
|
|
181
|
-
"requestBody": {
|
|
182
|
-
"required": true,
|
|
183
|
-
"content": {
|
|
184
|
-
"application/json": {
|
|
185
|
-
"schema": {
|
|
186
|
-
"type": "object",
|
|
187
|
-
"additionalProperties": true,
|
|
188
|
-
"required": ["variables"],
|
|
189
|
-
"properties": { "variables": { "$ref": "#/components/schemas/StartVariables" } }
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
},
|
|
194
|
-
"responses": {
|
|
195
|
-
"202": {
|
|
196
|
-
"description": "The plan fan-out was started (or was already running).",
|
|
197
|
-
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/StartPlanResult" } } }
|
|
198
|
-
},
|
|
199
|
-
"400": {
|
|
200
|
-
"description": "The issue reference could not be parsed.",
|
|
201
|
-
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorBody" } } }
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
},
|
|
206
|
-
"/actions/message": {
|
|
207
|
-
"post": {
|
|
208
|
-
"operationId": "postMessage",
|
|
209
|
-
"summary": "Publish a message / answer an escalation. For escalation-answered and feature-escalation-answered names, runs the corresponding answer flow; otherwise a plain publishMessage.",
|
|
210
|
-
"requestBody": {
|
|
211
|
-
"required": true,
|
|
212
|
-
"content": {
|
|
213
|
-
"application/json": {
|
|
214
|
-
"schema": {
|
|
215
|
-
"type": "object",
|
|
216
|
-
"additionalProperties": true,
|
|
217
|
-
"required": ["name"],
|
|
218
|
-
"properties": {
|
|
219
|
-
"name": { "type": "string", "description": "The message name (correlates a waiting event)." },
|
|
220
|
-
"correlationKey": { "type": "string" },
|
|
221
|
-
"variables": {
|
|
222
|
-
"type": "object",
|
|
223
|
-
"additionalProperties": true,
|
|
224
|
-
"properties": { "answer": { "type": "string" } }
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
},
|
|
231
|
-
"responses": {
|
|
232
|
-
"200": {
|
|
233
|
-
"description": "The message was published (or the escalation answered).",
|
|
234
|
-
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/MessageResult" } } }
|
|
235
|
-
},
|
|
236
|
-
"400": {
|
|
237
|
-
"description": "A required field was missing/blank.",
|
|
238
|
-
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorBody" } } }
|
|
239
|
-
},
|
|
240
|
-
"404": {
|
|
241
|
-
"description": "No matching open escalation / parked token to answer.",
|
|
242
|
-
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/MessageResult" } } }
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|