@nanobpm/nano-workforce 0.102.1 → 0.103.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 +7 -0
- package/app/feature.test.ts +71 -0
- package/app/feature.ts +42 -0
- package/app/featureReadiness.test.ts +165 -0
- package/app/featureReadiness.ts +151 -0
- package/e2e/feature-preflight.e2e.ts +191 -0
- package/openapi.yaml +102 -2
- package/operations/startFeature.ts +29 -1
- package/package.json +1 -1
- package/resources/processes/feature.bpmn +300 -77
package/openapi.yaml
CHANGED
|
@@ -1153,12 +1153,65 @@ components:
|
|
|
1153
1153
|
Accepted by the schema today but currently has no runtime effect. When implemented it
|
|
1154
1154
|
will be the required acknowledgement when `baseBranch` names the repository default
|
|
1155
1155
|
branch. See `PlanStartByIssue.confirmDefaultBase`.
|
|
1156
|
+
ReadinessProbe:
|
|
1157
|
+
description: >-
|
|
1158
|
+
A single durable readiness probe (issue #258, #295) the feature run must satisfy before its
|
|
1159
|
+
implementation agent is dispatched. `kind` selects the source; `target` + `match` are the
|
|
1160
|
+
per-kind predicate. The `capability` kind resolves "which published `pkg@version` first
|
|
1161
|
+
carries capability C?" from publish provenance and late-binds it into the run. See
|
|
1162
|
+
`app/readiness.ts` for the full per-kind semantics.
|
|
1163
|
+
type: object
|
|
1164
|
+
additionalProperties: false
|
|
1165
|
+
required:
|
|
1166
|
+
- kind
|
|
1167
|
+
- target
|
|
1168
|
+
properties:
|
|
1169
|
+
kind:
|
|
1170
|
+
type: string
|
|
1171
|
+
enum: [http, command, npm, github-check, capability]
|
|
1172
|
+
description: The readiness source. `command` is the escape hatch; `capability` resolves a cross-repo published-artifact edge.
|
|
1173
|
+
target:
|
|
1174
|
+
type: string
|
|
1175
|
+
minLength: 1
|
|
1176
|
+
description: The kind-specific target (a URL, a shell command, a `pkg@version`, an `owner/repo@ref`, or `github-releases:owner/repo`).
|
|
1177
|
+
onTimeout:
|
|
1178
|
+
type: string
|
|
1179
|
+
enum: [escalate, fail, continue]
|
|
1180
|
+
description: What the gate does when the bounded wait elapses (default `escalate`).
|
|
1181
|
+
credentialEnv:
|
|
1182
|
+
type: string
|
|
1183
|
+
description: A declared env-contract key supplying a credential (http kind only). Names a key, never a secret value.
|
|
1184
|
+
match:
|
|
1185
|
+
type: object
|
|
1186
|
+
additionalProperties: false
|
|
1187
|
+
description: The per-kind readiness predicate; every field is optional and read only by the kinds that understand it.
|
|
1188
|
+
properties:
|
|
1189
|
+
status: { type: integer, description: "http: the exact status that means ready (default any 2xx)." }
|
|
1190
|
+
bodyIncludes: { type: string, description: "http: a substring the response body must contain." }
|
|
1191
|
+
exitCode: { type: integer, description: "command: the exit code that means ready (default 0)." }
|
|
1192
|
+
stdoutIncludes: { type: string, description: "command/npm: a substring stdout must contain." }
|
|
1193
|
+
version: { type: string, description: "npm: the version that must be published." }
|
|
1194
|
+
conclusion: { type: string, description: "github-check: the conclusion that means ready (default success)." }
|
|
1195
|
+
checkName: { type: string, description: "github-check: restrict to the named check run." }
|
|
1196
|
+
capabilityRef: { type: string, description: "capability: the upstream issue/PR handle the resolved version must carry." }
|
|
1197
|
+
package: { type: string, description: "capability: the package whose releases are scanned for provenance." }
|
|
1198
|
+
verifyCommand: { type: string, description: "capability: optional empirical verifier run once at the gate boundary." }
|
|
1199
|
+
poll:
|
|
1200
|
+
type: object
|
|
1201
|
+
additionalProperties: false
|
|
1202
|
+
description: The poll cadence (how often to re-probe, how long to keep trying, and the backoff shape).
|
|
1203
|
+
properties:
|
|
1204
|
+
everyMs: { type: integer, description: Interval between poll attempts (ms). }
|
|
1205
|
+
timeoutMs: { type: integer, description: Bounded budget (ms) before the gate escalates. }
|
|
1206
|
+
backoff: { type: string, enum: [fixed, exponential], description: Backoff shape between attempts. }
|
|
1156
1207
|
FeatureStart:
|
|
1157
1208
|
description: The start-feature request body — a SINGLE-issue feature run. Names the target issue
|
|
1158
1209
|
by EXACTLY ONE of `issue` (an `owner/repo#123` reference) or `url` (a bare issue URL), plus a
|
|
1159
1210
|
REQUIRED `baseBranch` (ADR 0003, same admission as the epic path), and the two optional
|
|
1160
|
-
follow-on knobs `converge` / `autoMerge`.
|
|
1161
|
-
|
|
1211
|
+
follow-on knobs `converge` / `autoMerge`. May also carry an intake-time readiness gate
|
|
1212
|
+
(`readiness` and/or `blockedOn` + `consumerPackage`, per issue 295) that parks the run until
|
|
1213
|
+
the declared upstreams land. Modeled as `oneOf` named variants (Camunda REST v2 pattern) so an
|
|
1214
|
+
ambiguous or empty target is a 400 at the edge.
|
|
1162
1215
|
oneOf:
|
|
1163
1216
|
- $ref: "#/components/schemas/FeatureStartByIssue"
|
|
1164
1217
|
- $ref: "#/components/schemas/FeatureStartByUrl"
|
|
@@ -1210,6 +1263,36 @@ components:
|
|
|
1210
1263
|
OPTIONAL free-text steering appended to the implementation agent's prompt for this run
|
|
1211
1264
|
(via the implement task's `appendPrompt`). Blank/whitespace is treated as absent. Persists
|
|
1212
1265
|
on the instance, so it also applies to the agent's answer-loop redispatch.
|
|
1266
|
+
readiness:
|
|
1267
|
+
type: array
|
|
1268
|
+
maxItems: 32
|
|
1269
|
+
items:
|
|
1270
|
+
$ref: "#/components/schemas/ReadinessProbe"
|
|
1271
|
+
description: >-
|
|
1272
|
+
OPTIONAL intake-time readiness gate (issue #295): one or more durable probes the run must
|
|
1273
|
+
ALL satisfy before its implementation agent is dispatched. The run parks (durably, bounded
|
|
1274
|
+
by the gate's escalating timer) at the leading readiness preflight until every probe goes
|
|
1275
|
+
green. Absent/empty ⇒ the run implements immediately, unchanged.
|
|
1276
|
+
blockedOn:
|
|
1277
|
+
type: array
|
|
1278
|
+
maxItems: 32
|
|
1279
|
+
items:
|
|
1280
|
+
type: string
|
|
1281
|
+
minLength: 1
|
|
1282
|
+
description: >-
|
|
1283
|
+
OPTIONAL ergonomic shorthand for `readiness` (issue #295): a list of upstream
|
|
1284
|
+
`owner/repo#123` issue/PR handles the run waits to land. With `consumerPackage` each
|
|
1285
|
+
desugars to a `capability` probe (resolve which published `pkg@version` first carries the
|
|
1286
|
+
handle, and late-bind it into the run); without it, to a `command` probe that goes green
|
|
1287
|
+
once the referenced issue/PR is closed/merged.
|
|
1288
|
+
consumerPackage:
|
|
1289
|
+
type: string
|
|
1290
|
+
minLength: 1
|
|
1291
|
+
description: >-
|
|
1292
|
+
OPTIONAL npm package name (e.g. `@nanobpm/engine-wasm`) the `blockedOn` shorthand resolves
|
|
1293
|
+
its handles against — the consumer dependency whose published provenance must carry each
|
|
1294
|
+
awaited upstream. When present, `blockedOn` desugars to `capability` probes and the
|
|
1295
|
+
resolved `pkg@version` is late-bound into the implementation agent's brief.
|
|
1213
1296
|
FeatureStartByUrl:
|
|
1214
1297
|
type: object
|
|
1215
1298
|
additionalProperties: false
|
|
@@ -1246,6 +1329,23 @@ components:
|
|
|
1246
1329
|
description: >-
|
|
1247
1330
|
OPTIONAL free-text steering appended to the implementation agent's prompt for this run.
|
|
1248
1331
|
See `FeatureStartByIssue.customInstructions`.
|
|
1332
|
+
readiness:
|
|
1333
|
+
type: array
|
|
1334
|
+
maxItems: 32
|
|
1335
|
+
items:
|
|
1336
|
+
$ref: "#/components/schemas/ReadinessProbe"
|
|
1337
|
+
description: OPTIONAL intake-time readiness gate. See `FeatureStartByIssue.readiness`.
|
|
1338
|
+
blockedOn:
|
|
1339
|
+
type: array
|
|
1340
|
+
maxItems: 32
|
|
1341
|
+
items:
|
|
1342
|
+
type: string
|
|
1343
|
+
minLength: 1
|
|
1344
|
+
description: OPTIONAL readiness shorthand — upstream `owner/repo#123` handles to wait on. See `FeatureStartByIssue.blockedOn`.
|
|
1345
|
+
consumerPackage:
|
|
1346
|
+
type: string
|
|
1347
|
+
minLength: 1
|
|
1348
|
+
description: OPTIONAL package the `blockedOn` handles resolve against. See `FeatureStartByIssue.consumerPackage`.
|
|
1249
1349
|
MessageResult:
|
|
1250
1350
|
type: object
|
|
1251
1351
|
description: The result of publishing a message / answering an escalation. Shape varies by message
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// confirm-default / shared-base rules, with the same typed-error → HTTP mapping.
|
|
14
14
|
|
|
15
15
|
import { startFeature } from "../app/feature.ts";
|
|
16
|
+
import { parseFeatureReadiness } from "../app/featureReadiness.ts";
|
|
16
17
|
import { BaseBranchMustExistError } from "../app/github.ts";
|
|
17
18
|
import {
|
|
18
19
|
admitPlan,
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
parseIssue,
|
|
23
24
|
SharedBaseError,
|
|
24
25
|
} from "../app/plan.ts";
|
|
26
|
+
import type { ReadinessProbe } from "../app/readiness.ts";
|
|
25
27
|
import { defineOperation } from "../nano-generated/operations.ts";
|
|
26
28
|
|
|
27
29
|
export default defineOperation("startFeature", async ({ body }, app) => {
|
|
@@ -120,13 +122,39 @@ export default defineOperation("startFeature", async ({ body }, app) => {
|
|
|
120
122
|
const customInstructions = "customInstructions" in body && typeof body.customInstructions === "string"
|
|
121
123
|
? body.customInstructions
|
|
122
124
|
: null;
|
|
123
|
-
|
|
125
|
+
// Intake-time readiness gate (issue #295): desugar the optional `readiness` descriptors and/or the
|
|
126
|
+
// `blockedOn` shorthand (resolved against `consumerPackage`) into the probes + bound the run parks
|
|
127
|
+
// on before implementing. A malformed gate (bad descriptor, unparseable handle, blank package) is a
|
|
128
|
+
// 400 at the edge — it must never wait forever at runtime.
|
|
129
|
+
let readiness: { probes: ReadinessProbe[]; probeTimeout: string | null };
|
|
130
|
+
try {
|
|
131
|
+
readiness = parseFeatureReadiness({
|
|
132
|
+
readiness: "readiness" in body ? body.readiness : undefined,
|
|
133
|
+
blockedOn: "blockedOn" in body ? body.blockedOn : undefined,
|
|
134
|
+
consumerPackage: "consumerPackage" in body ? body.consumerPackage : undefined,
|
|
135
|
+
});
|
|
136
|
+
} catch (err) {
|
|
137
|
+
const message = err instanceof Error ? err.message : "invalid readiness gate";
|
|
138
|
+
app.log.warn("start-feature rejected: invalid readiness gate", { message });
|
|
139
|
+
return { status: 400, body: { error: message } };
|
|
140
|
+
}
|
|
141
|
+
const result = await startFeature(
|
|
142
|
+
app.data,
|
|
143
|
+
app.engine,
|
|
144
|
+
parsed,
|
|
145
|
+
normalizedBase,
|
|
146
|
+
converge,
|
|
147
|
+
autoMerge,
|
|
148
|
+
customInstructions,
|
|
149
|
+
{ probes: readiness.probes, probeTimeout: readiness.probeTimeout },
|
|
150
|
+
);
|
|
124
151
|
app.log.info("feature run started", {
|
|
125
152
|
featureKey: parsed.planKey,
|
|
126
153
|
requestedBaseBranch: normalizedBase,
|
|
127
154
|
converge,
|
|
128
155
|
autoMerge,
|
|
129
156
|
hasCustomInstructions: typeof customInstructions === "string" && customInstructions.trim() !== "",
|
|
157
|
+
readinessProbes: readiness.probes.length,
|
|
130
158
|
alreadyRunning: "alreadyRunning" in result && result.alreadyRunning === true,
|
|
131
159
|
});
|
|
132
160
|
return { status: 202, body: result };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/nano-workforce",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.103.0",
|
|
4
4
|
"description": "Nano Workforce — an Agent Graph Orchestration application for Agentic SDLC: durable BPMN processes that coordinate a graph of AI agents across the software delivery lifecycle.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "main.ts",
|