Skip to content

service-automation: populate the contained-failure visibility contract — FlowRunSummary.failed fold, loop iteration through try_catchrunRegion, $error.iteration / $error.item, failed= on the summary line (engine half of #13681) #14456

Description

@claude

Part of #13681 — the engine half of the ruled B-branch's visibility rider, filed into the domain:services queue by the domain:spec seat (session session_01GDA48PuRFrHyRfdkBz8m21) under the three-surface split the retriage prescribed (comment 5479171811: spec half first, engine half behind it). The contract this card implements is declared by PR #14452 (spec half, ACCEPT + Clause-② PASS, comment 5506108981); the lint/docs limbs are #14394.

Blocked-by: #14452

Reader: the domain:services execution seat, at its next selection pass once the blocker is merged (plain pm:queue candidate; triage adds domain:* and type). Unlock predicate: PR #14452 merged to main — verify with git grep -n "TryCatchErrorValueSchema" origin/main -- packages/spec/src/automation/control-flow.zod.ts (1+ hits) before dispatch.

Ruling of record (do not re-decide)

Maintainer 2026-08-31 (director batch #18, verbatim 「其他同意」) on the conditional ruling (issue comment 5478768627); branch B selected by measurement (comment 5478879587): ⛔ no loop.config.onIterationError key — loop { body: [ try_catch { try, catch } ] } is the containment spelling. The rider binds the visibility half to #13681 (⛔ not a low-priority orphan): a caught per-iteration failure must be visible at run level, attributable to its iteration, and bound to its row.

Exact contract to populate (from PR #14452's "Handoff to the engine card", verbatim in substance)

  1. FlowRunSummary.failedsummarizeRun sets failed = Σ node.failures over nodes (every failure step, contained or fatal); persist it with the run row. Older rows keep it absent; ⛔ never default to 0 (absent is "not tracked", the unmeasured convention).
  2. Per node — no new key; keep incrementing FlowRunNodeSummary.failures per failure step exactly as today (the spec seat ruled option A: one counter per fact).
  3. Iteration propagationtry-catch-node.ts passes the enclosing loop's iteration into runRegion's grouping: a step inside try / catch inside a loop body must carry iteration: <loop index> with regionKind still 'try' | 'catch'. Today runRegion only fills fields the innermost tagger left undefined, so either the try/catch call site forwards the loop's iteration or the tagger fills iteration on already-tagged steps that have none — pick the one that keeps parallel branches untouched (see A parallel branch inside a loop body overloads the step record's iteration with the branch index — the enclosing loop iteration is lost, so a branch step cannot be attributed to its row #14414 for the adjacent parallel-in-loop overload; ⛔ do not fold it in).
  4. $error bindingtry-catch-node.ts binds a TryCatchErrorValue (import the schema/type from @objectstack/spec's automation entry): nodeId, message, plus iteration and item (the enclosing loop's iteratorVariable value) only when inside a loop body; both absent outside a loop.
  5. formatRunSummaryLine prints failed=N when summary.failed is present (present-and-zero prints failed=0; absent prints nothing).

Acceptance (executable)

  • The 5-row / third-fails measurement from comment 5478851960 (loop { body: [ try_catch { try: [notify], catch: [assignment] } ] } on the real AutomationEngine, 5 elements, element 3 fails) reproduced as a test in packages/services/service-automation: run status completed, 5/5 iterations, summary.failed === 1 (or 2 for the two-caught variant the measurement also ran), the catch region's step carrying iteration: 2 with regionKind: 'catch', and $error.iteration === 2 / $error.item equal to the failing row.
  • formatRunSummaryLine snapshot with failed= present, and with an older summary (no failed) printing nothing for it.
  • Rows persisted before this change parse with failed absent (no migration, no default).

Out of scope


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions