@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/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`. Modeled as `oneOf` named variants (Camunda REST v2
1161
- pattern) so an ambiguous or empty target is a 400 at the edge.
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
- const result = await startFeature(app.data, app.engine, parsed, normalizedBase, converge, autoMerge, customInstructions);
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.102.1",
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",