Per-pipeline middleware, composed once at Bind - #97
Merged
Merged
Conversation
dangra
force-pushed
the
pipeline-middleware
branch
from
September 17, 2026 19:19
b2350cd to
7bbf075
Compare
A pipeline declares its own middleware chain: pipelinedef.Config.Middleware, exposed by the generated constructor as NewXxx(h, mw...). It wraps only that pipeline's operations, forward and unwind alike, inside the engine chain — engine middleware outermost, then the pipeline's own in order, then the handler. A rule that belongs to one pipeline, such as a store's not-found ending any forward operation of that pipeline permanently, no longer reaches every pipeline bound to the engine. Both chains are composed once at Bind: boundStep carries the composed forward and unwind operations, so the per-attempt wrap and the unwind closure are gone. Bind rejects a nil middleware entry. Invariant 119.
The generated constructor takes opts ...pipelinedef.Option, the knobs a proto cannot declare; WithMiddleware is the first. Wiring code imports pipelinedef for it; handler code still never does.
Each generated package carries Option (= pipelinedef.Option) and WithMiddleware, so wiring code configures a pipeline as machinespb.NewProvisionMachine(h, machinespb.WithMiddleware(mw)) and never imports pipelinedef. Emitted once per Go package.
dangra
force-pushed
the
pipeline-middleware
branch
from
September 17, 2026 20:08
7bbf075 to
bdd9062
Compare
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A pipeline can now declare its own middleware chain.
pipelinedef.Config.Middlewarecarries it, and the generated constructor takes it as an option —NewXxx(h, opts ...Option), with the generated package'sWithMiddleware(an alias ofpipelinedef.WithMiddleware, so wiring code never imports pipelinedef) the first option, since the middleware is optional:Invocation.Phasedistinguishes). A rule that belongs to one pipeline — the flyd stop port's "a store not-found ends any forward operation as a permanent failure" — no longer reaches every pipeline bound to the engine, and no longer needs aPipelineIDstring compare on every attempt.engine[0](…engine[n](pipeline[0](…pipeline[m](h)))). A tracing span installed on the engine wraps a pipeline's rules.boundStepcarries each step's composed forward and unwind operations; the per-attemptwrapand the unwind closureinvokeUnwindallocated are gone. Wrapper factories run once per step and phase, the inner handler once per attempt, so invariant 71 holds anddurableotel.Middlewareis unaffected. The engine chain is final by Bind: options apply only inengine.New.inv.StepID()covers it.Spec: 04-engine Middleware section gains the pipeline level and the composition line; invariant 119; http-analogy gets the "wrapping at mux registration" row (and its stale
XFuncrow fixed); 05-codegen and 02-authoring show the constructor option, and 02-authoring's stale positional-constructor sections are brought to the one-implementor form. README and tour observability sentences mention the per-pipeline form.Tests: scope and order across two pipelines on one engine (engine/p1/p2 onion on one, engine alone on the other, forward attempts and unwind); the flyd case (one pipeline's not-found is permanent, the other's retries); factories run once at Bind regardless of attempts; Bind rejects a nil entry;
Newcopies the slice; a machines example test passes middleware through the constructor option.Perf gate (3 interleaved slices vs master): zero regressions; allocs and bytes within ±1%.
Additive for hand-rolled definitions; generated packages regenerate (the constructor gains an options variadic, existing call sites compile unchanged).