Skip to content

feat(flows): a replay that stops early says so - #866

Open
divshekhar wants to merge 4 commits into
mainfrom
fix/replay-says-it-halted
Open

feat(flows): a replay that stops early says so#866
divshekhar wants to merge 4 commits into
mainfrom
fix/replay-says-it-halted

Conversation

@divshekhar

Copy link
Copy Markdown
Contributor

Part of #697.

What the report actually was

Filed as "flow replay silently skips a second destructive step and reports it as testid drift". All three observations were correct — step 1 never dispatched, no request fired, and the identical click driven by hand with confirmDangerous: true worked. The conclusion was wrong.

The destructive-action gate was never involved. replayFlow ends its loop with if (result.drift !== undefined || !result.ok) break; — step 0's expectation failed and the run stopped, which is documented behaviour. Step 1 not dispatching is the correct outcome.

Why it read as a silent skip

Nothing in the envelope says the run stopped. A two-step flow that halted returns steps: [oneResult], and a reader who does not know the halt rule sees a step that is simply absent.

The cost was a filed defect, an investigation, and very nearly a fix to the destructive-action path that nothing needed.

The change

halted: { atStep, notAttempted }, omitted entirely when every step ran so a clean pass stays token-flat.

Two decisions worth flagging:

  • notAttempted counts from the FLOW's step total, not from the results array. The results array is the short one, so deriving the total from it would report zero skipped on every halt — the defect restated as its own fix. There is a test for exactly that.
  • A failure on the LAST step is not a halt and reports nothing. notAttempted: 0 would send a reader looking for steps that do not exist.

Declared in both the core type and flow-tools.ts's outputSchema. That file's own comment above unverifiable explains why — "a field the handler sets and the schema omits arrives as nothing, silently" — and a test pins the pair.

Not in this PR

The other half of #697, drift naming the expectation's target instead of the step's anchor, was already fixed by expectElementDrift / EXPECT_ELEMENT_NOT_FOUND. I have not changed the destructive-action path, because nothing in the report shows it misbehaving.

Gates

  • pnpm format:check, pnpm lint, pnpm typecheck, pnpm test:unit (670 files)
  • pnpm test:e2e — 36/36 + soak (tool surface change)

Replay breaks on the first failing step. That is right: once a consequence did not hold, every
later step would run against a state the flow never described, and continuing turns one wrong
result into several. What was missing was any way for the caller to SEE it — a two-step flow that
halted came back as `steps: [oneResult]`, and a reader who does not already know the halt rule
sees a step that is simply not there.

Reported as "flow replay silently skips a second destructive step". All three observations in
that report were correct — step 1 never dispatched, no request fired, and the identical click
driven by hand worked — and the conclusion was wrong. The destructive-action gate was never
involved. Step 0's expectation failed and the run stopped, which is documented behaviour that the
output gave no way to read.

The cost of that invisibility was a filed defect, an investigation, and very nearly a change to
the destructive-action path that nothing needed.

`halted: { atStep, notAttempted }`, omitted entirely when every step ran so a clean pass stays
token-flat. `notAttempted` counts from the FLOW's step total, not from the results array: the
results array is the short one, so deriving the total from it would report zero skipped on every
halt — the defect restated as its own fix. A failure on the LAST step is not a halt and reports
nothing, because `notAttempted: 0` would send a reader looking for steps that do not exist.

Declared in BOTH places or it arrives as nothing: the core type and `flow-tools.ts`'s
outputSchema. That file's own comment above `unverifiable` says why, and a test now pins the
pair, because a stripped field fails silently.

Part of #697. The other half of that issue — drift naming the expectation's target instead of the
step's anchor — was already fixed by `expectElementDrift` / EXPECT_ELEMENT_NOT_FOUND.

Signed-off-by: Divyanshu Shekhar <imdshekhar@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant