Approval is part of a consequential state transition. If it lives in a confirmation modal beside the application instead of inside the service-owned pipeline, the UI can ask a question without controlling the candidate that question is meant to protect. Chapter 15 puts an explicit approval boundary inside the FeatureCell™ pipeline, where a decision can continue or block a candidate before it reaches committed FeatureCell State.

The Stepwise behaviors let you inspect a candidate after Resolve, after Filter, or after Reducer processing. The Stepwise Controller coordinates those pauses, while the service owns the pending request and the one-use decision contract. The component can render a prompt, but it does not become a second state manager.

Key takeaway: Choose the smallest boundary that exposes enough information to answer the approval question, then guarantee that every pending request reaches a terminal decision.

Why Approval Belongs at a Pipeline Boundary

A modal is a presentation surface. It can ask a person whether a change should proceed, but it does not know which candidate is paused, what work has already run, or whether the answer is still relevant to the pipeline attempt that opened it. When approval is bolted onto the side of the application, those responsibilities tend to leak into click handlers and component state.

Stepwise behaviors move the pause to a documented processing boundary. The callback receives the current value, the candidate at that point, and a decision object. It may inspect the candidate and present the question through an application-owned prompt. It does not transform the candidate. A Continue decision lets the same attempt proceed; a Block decision discards that attempt and preserves the previously committed State.

That separation matters because a candidate is not committed State. The pipeline can pause while the application gathers an explicit answer. If the answer is Block, the old committed value remains authoritative. If the answer is Continue, the candidate keeps moving through the normal pipeline toward the commitment boundary.

Boundary Candidate inspected Policy question
Stepwise Resolve Resolved candidate before normal filtering Should this resolved input enter downstream processing?
Stepwise Filter Filtered candidate before reducers Should this surviving candidate be transformed?
Stepwise Reducer Fully reduced candidate with derived fields Should this display-ready result proceed toward commitment?

Warning: Do not install every gate by default. Chapter 15 uses all three as a comparison lab. A production feature should use the smallest number of approval boundaries its policy requires.

Choosing Between Resolve, Filter, and Reducer Gates

The canonical processing order places Resolve and Merge before Stepwise Resolve, then Operators and Filters before Stepwise Filter, and Before Tap, Reducers, and Stepwise Reducer before After Tap. That order gives each gate a different meaning. “After Resolve” is useful shorthand for the chapter, but Merge still occurs between Resolve and Stepwise Resolve in the full pipeline.

Use Stepwise Resolve when the policy concerns the incoming or hydrated candidate before ordinary filtering. This is the earliest point at which the feature can ask whether an input should enter its normal processing path. Use Stepwise Filter when the policy depends on what remains after filters have removed candidates. Use Stepwise Reducer when the reviewer needs to see derived display fields or the complete reduced result before it can move toward commitment.

The final approval boundary is not the final pipeline stage. A continued Stepwise Reducer candidate still has normal downstream processing before State is committed. Name the boundary according to the question it answers, not according to the visual location of a prompt.

Register Stepwise approval boundaries — Core

employeeCell .withStepwiseResolve({ stepwiseCallback: (_current, _candidate, decisions) => { const shouldContinue = window.confirm('Continue this candidate?'); if (shouldContinue) { decisions.continue(); return; } decisions.block(); } }) .withStepwiseFilter({ stepwiseCallback: (_current, _candidate, decisions) => { const shouldContinue = window.confirm('Continue this candidate?'); if (shouldContinue) { decisions.continue(); return; } decisions.block(); } }) .withStepwiseReducer({ stepwiseCallback: (_current, _candidate, decisions) => { const shouldContinue = window.confirm('Continue this candidate?'); if (shouldContinue) { decisions.continue(); return; } decisions.block(); } }); 

Register Stepwise approval boundaries — Angular

The callback bodies are identical. Angular registers the three fluent calls independently on the service-owned vault.

this.#vault.withStepwiseResolve!({ stepwiseCallback: (_current, _candidate, decisions) => { const shouldContinue = window.confirm('Continue this candidate?'); if (shouldContinue) { decisions.continue(); return; } decisions.block(); } }); this.#vault.withStepwiseFilter!({ stepwiseCallback: (_current, _candidate, decisions) => { const shouldContinue = window.confirm('Continue this candidate?'); if (shouldContinue) { decisions.continue(); return; } decisions.block(); } }); this.#vault.withStepwiseReducer!({ stepwiseCallback: (_current, _candidate, decisions) => { const shouldContinue = window.confirm('Continue this candidate?'); if (shouldContinue) { decisions.continue(); return; } decisions.block(); } }); 

Warning: window.confirm('Continue this candidate?') keeps this example easy to run, but it is not a production approval experience. Production code should use an application-appropriate review flow with accessible UI, clear authorization, cancellation, and a reliable settlement policy for every pending decision.

When Approval Must Happen Later

Example 1 makes the decision immediately inside the callback. That is useful for a small, synchronous policy, but many approvals need a dialog, a reviewer, or another application service to respond later. In that case, the callback must hand the decision contract to the application and return without calling either terminal method.

The Stepwise Controller coordinates this pause with the pipeline. The application owns the pending decision reference, while the pipeline remains the authority that determines whether the candidate proceeds. The example below uses one Filter boundary so the handoff is easy to see. The same pattern applies to Resolve or Reducer when the policy belongs at one of those boundaries.

import { type StepwiseBehaviorDecisionShape } from '@sdux-vault/addons'; interface Employee { id: number; name: string; } type PendingApproval = { candidate: Employee[]; decisions: StepwiseBehaviorDecisionShape; }; let pendingApproval: PendingApproval | undefined; const captureFilterApproval = ( _current: Employee[] | undefined, candidate: Employee[], decisions: StepwiseBehaviorDecisionShape ): void => { pendingApproval = { candidate, decisions }; }; employeeCell.withStepwiseFilter({ stepwiseCallback: captureFilterApproval }); export const approvePending = (): void => { const pending = pendingApproval; if (!pending) { return; } pendingApproval = undefined; pending.decisions.continue(); }; export const blockPending = (): void => { const pending = pendingApproval; if (!pending) { return; } pendingApproval = undefined; pending.decisions.block(); }; 

The Angular shape is the same inside a service: store PendingApproval, register this.#captureFilterApproval with withStepwiseFilter!, then expose approvePending() and blockPending() methods that clear the reference before calling the selected decision.

The callback does not call a reducer, replace the candidate, or write committed State. It stores the candidate only so the application can show what is awaiting approval, and it stores the one-use decision so a later action can settle the paused pipeline. Clearing the reference before calling Continue or Block prevents the same decision from being reused.

Designing Reliable Settlement and Timeout Policy

The most important operational fact in this chapter is also the easiest to miss: an unresolved decision can pause execution indefinitely. Waiting for the pipeline to settle does not answer a pending approval question. A test, prompt, navigation guard, or policy service must eventually call Continue or Block, or deliberately clear the decision according to the documented contract.

The tutorial uses native prompts and asks the reader to settle each prompt before starting another request. A production application needs an explicit settlement policy that survives the ordinary ways a UI can disappear. Decide what happens when the user navigates away, a dialog is closed, an approver loses access, a request exceeds its response window, or the application is shutting down. The teaching example does not provide timeout, navigation cancellation, durable approvals, authorization, or audit history; those are application policy choices.

Keep the policy small and testable. For each gate, define who can settle it, what the default outcome is when the deadline expires, how the pending UI is dismissed, and how the application proves that the decision was consumed once. Tests should exercise both Continue and Block at the boundary under review and verify that a blocked candidate does not replace committed State.

Warning: Settlement is part of correctness. A feature that can open an approval boundary must also have a reliable path to settle it. “The user will probably answer” is not an execution policy.

Deeper Dive

Chapter 15’s Stepwise tutorial is the runnable comparison. Pair it with the complete pipeline specification and the execution guarantees to place each approval boundary accurately. The central design test is simple: can the policy see the right candidate, can the service settle the decision exactly once, and does Block leave committed State alone?