@smithers-orchestrator/scheduler 0.25.0 → 0.25.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smithers-orchestrator/scheduler",
3
- "version": "0.25.0",
3
+ "version": "0.25.1",
4
4
  "description": "Pure decision engine: session, scheduler, and task state management for Smithers workflows",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -176,8 +176,8 @@
176
176
  ],
177
177
  "dependencies": {
178
178
  "effect": "^3.21.1",
179
- "@smithers-orchestrator/errors": "0.25.0",
180
- "@smithers-orchestrator/graph": "0.25.0"
179
+ "@smithers-orchestrator/errors": "0.25.1",
180
+ "@smithers-orchestrator/graph": "0.25.1"
181
181
  },
182
182
  "devDependencies": {
183
183
  "@types/bun": "latest",
package/src/RunResult.ts CHANGED
@@ -13,4 +13,20 @@ export type RunResult = {
13
13
  readonly output?: unknown;
14
14
  readonly error?: unknown;
15
15
  readonly nextRunId?: string;
16
+ /**
17
+ * Number of tasks that ended in a `failed` state yet did not fail the run —
18
+ * "masked" child failures the run-level status cannot express. Present (and
19
+ * `> 0`) only on a `finished` result that tolerated at least one failure
20
+ * (a {@link https://smithers.sh/components/task `continueOnFail`} task, or an
21
+ * agent task that failed transiently: rate limit, timeout, abort). A binary
22
+ * `finished` status would otherwise read as a clean success. See
23
+ * `docs/runtime/run-state.mdx`.
24
+ */
25
+ readonly failedChildren?: number;
26
+ /**
27
+ * Task state keys (`nodeId::iteration`) of the tasks counted by
28
+ * {@link failedChildren}. The iteration disambiguates the same `nodeId` failing
29
+ * across loop/Ralph iterations.
30
+ */
31
+ readonly failedChildKeys?: readonly string[];
16
32
  };
package/src/index.d.ts CHANGED
@@ -138,6 +138,22 @@ type RunResult$1 = {
138
138
  readonly output?: unknown;
139
139
  readonly error?: unknown;
140
140
  readonly nextRunId?: string;
141
+ /**
142
+ * Number of tasks that ended in a `failed` state yet did not fail the run —
143
+ * "masked" child failures the run-level status cannot express. Present (and
144
+ * `> 0`) only on a `finished` result that tolerated at least one failure
145
+ * (a {@link https://smithers.sh/components/task `continueOnFail`} task, or an
146
+ * agent task that failed transiently: rate limit, timeout, abort). A binary
147
+ * `finished` status would otherwise read as a clean success. See
148
+ * `docs/runtime/run-state.mdx`.
149
+ */
150
+ readonly failedChildren?: number;
151
+ /**
152
+ * Task state keys (`nodeId::iteration`) of the tasks counted by
153
+ * {@link failedChildren}. The iteration disambiguates the same `nodeId` failing
154
+ * across loop/Ralph iterations.
155
+ */
156
+ readonly failedChildKeys?: readonly string[];
141
157
  };
142
158
 
143
159
  type WaitReason$1 = {
@@ -353,14 +353,37 @@ export function makeWorkflowSession(options = {}) {
353
353
  * @returns {EngineDecision}
354
354
  */
355
355
  function finishedResult(status = "finished") {
356
- return {
357
- _tag: "Finished",
358
- result: {
359
- runId: state.runId,
360
- status,
361
- output: [...state.outputs.values()].at(-1)?.output,
362
- },
356
+ /** @type {RunResult} */
357
+ const result = {
358
+ runId: state.runId,
359
+ status,
360
+ output: [...state.outputs.values()].at(-1)?.output,
363
361
  };
362
+ if (status === "finished") {
363
+ // At a `finished` terminal, any task still in `failed` state is a
364
+ // *tolerated* failure — an unhandled one would have produced a `Failed`
365
+ // decision via unhandledFailureDecision() and never reached here. Those
366
+ // are exactly the masked children (continueOnFail tasks, transient agent
367
+ // failures) the binary run status cannot express. Surface them so callers
368
+ // can detect a run that "succeeded" while children failed. See issue #295
369
+ // and docs/runtime/run-state.mdx.
370
+ //
371
+ // Keys are the canonical task state keys (`nodeId::iteration`), not bare
372
+ // node ids: a looped/Ralph workflow can fail the same nodeId across
373
+ // iterations, and the iteration is what disambiguates which child to
374
+ // inspect.
375
+ const failedChildKeys = [];
376
+ for (const [key, taskState] of state.states) {
377
+ if (taskState === "failed") {
378
+ failedChildKeys.push(key);
379
+ }
380
+ }
381
+ if (failedChildKeys.length > 0) {
382
+ result.failedChildren = failedChildKeys.length;
383
+ result.failedChildKeys = failedChildKeys;
384
+ }
385
+ }
386
+ return { _tag: "Finished", result };
364
387
  }
365
388
  /**
366
389
  * @returns {ScheduleResult}