Hand-written. ../harvested/pipeline.md is what the documents say; this file is what the
implementation found, and it wins. Each entry names the harvested entry it answers by that entry's
stable key.
RECOV-10's "rethrow the contained throwable UNCHANGED" is broken by the obvious Ruby spelling: a bareraise errorsilently assigns whatever exception is in flight as that error's#cause, and the in-flight exception can be the caller's. Supersedespipeline/b3f74458("Dispatch unwraps a Failure by re-raising the contained error unchanged, with no wrapping or substitution"), whose rule is exactly right and whose Ruby realisation is a trap the entry cannot warn about because it names no code. Verified on 2026-09-08 against 3.2.11, 3.4.10 and 4.0.6. The facts, uniform across the range.raise storedwhen$!is non-nilandstored.causeisnilsetsstored.causeto$!— Ruby's implicit cause assignment — so the object the caller receives is not the object theFailurecarried: it has acquired a parent it never had.$!is dynamically scoped to the thread and is therefore non-nilinside a method called from a caller'srescueblock, which is ordinary consumer code (begin; …; rescue; client.send(request); end) and needs norescueanywhere in core to reach; measured, the surfaced error came back carrying "the CALLER's unrelated in-flight error" as its cause.raise stored, cause: nilsuppresses the assignment; it does not clear a cause the error legitimately already has (measured: a pre-existing cause survives, same object); and it never tripsArgumentError: circular causes, including against the caller-defined#causeoverrides../notes/error-handling.mdrecords as the only route to a cycle.raise stored, cause: stored.causebehaves identically in every case measured but calls a caller-overridable method to do it. What the SDK does, perdocs/work/mvp/phase4/phase4b/2026-09-08-phase4b-recovery-primitives-design.md: everyRECOV-10unwrap and every place core re-raises an error it is carrying rather than one it just rescued is writtenraise error, cause: nil, with the reason at the call site. Outside the recovery chain there is exactly one such place and it is phase 2's:Dexpace::Hooks.notifycollects the first handler failure, runs the rest of the list, and re-raises the stored object afterwards, and$!at that line is measurably the caller's in-flight exception whenever a hook list is drained from inside arescue; phase 4b's Task 2, which attaches the later handler failuresHooks.notifydrops, rewrites it asraise failure, cause: nilin the same edit that adds the suppressed trail. How farcause: nilreaches is worth stating exactly, because it is less than the rule sounds. It suppresses an assignment the re-raise would itself make; it cannot undo one already made. Measured on all three: an error the caller's code raised while an outerrescuewas in flight acquired that exception as its cause at its ownraise, andcause: nilat a later re-raise leaves it in place — so the spelling changes the surfaced object only for an error that reaches the re-raise withcause == nil, which is exactlyRECOV-10's own named case, "a recovery step constructing the error and returning a Failure". The consequence for tests is concrete: aRECOV-10fixture whoseFailurecarries a raised error makesassert_nil error.causefail against a correct implementation, so the fixture must construct the error and never raise it. TheRECOV-10test asserts#causeis unchanged across the unwrap while an unrelated exception is in flight — an assertion a test written without this fact would not think to make, and which the bare spelling passes whenever the suite happens not to be inside arescue.pipeline/c76ca402(theRECOV-10/RECOV-11spec rule) andpipeline/77de2b12are unaffected and adopted verbatim. review ·docs/work/mvp/phase4/phase4b/2026-09-08-phase4b-recovery-primitives-design.md· high · sha:manual-phase4b-reraise-causePIPE-15's "reusing the same cursor handle must be treated as a defect" is enforceable in Ruby only for a sequential second call, and the race that hides that is visible on the 3.2 floor and invisible above it. Adds topipeline/591329b5("A step that drives the downstream chain more than once must fork a fresh cursor per re-drive, and reusing the same cursor handle for a second downstream invocation must be treated as a defect") andpipeline/debd2eb6; the rule is right, is implemented, and is not weakened here — what neither entry can say, because neither names code, is how far the obvious Ruby spelling reaches. Verified on 2026-09-08 against 3.2.11, 3.4.10 and 4.0.6. The measurement. A per-invocation cursor guards its single use with a plain instance variable —unless @spent; @spent = true; …; end— because it is created per step invocation, handed to exactly one step and never published, so aThread::Mutexwould cost an allocation on every step of every call and would put a per-fiber-owned, non-reentrant lock in the hot path (concurrency-and-async/c0fab747, the smallest-critical-section rule, and theMutex-is-per-fiber-owned entry in../notes/concurrency-and-async.md). Eight threads released from oneThread::Queuebarrier onto that body, 2000 runs: 29 of 2000 runs let more than one caller through on 3.2.11, and 0 of 2000 on 3.4.10 and on 4.0.6; at 32 threads, 9 of 2000 on 3.2.11 and 0 of 2000 on the other two. AThread::Mutexaround the same body gave 0 of 50 on all three. What that licenses, and what it does not. It licenses the design decision — no lock, and a YARD sentence saying detection is sequential-only, since handing one cursor to two threads is already the defectPIPE-15names. It does not license "3.4 and 4.0 are safe": zero in 2000 is an absence of observation and the interleaving is not excluded, so the port's statement is written over the whole supported range. The consequence for tests is the part a reader will otherwise get wrong: a test that asserts the race — that two concurrent callers both get through — passes on the matrix's floor and fails to reproduce on every other column, which is a flake reporting the opposite of what it means.docs/work/mvp/phase4/phase4c/2026-09-08-phase4c-stage-pipeline-design.mdtherefore ships no such test and records the limit as deviationP4-33instead. A phase-6 pillar-step author reading that rule alone would reasonably conclude the runtime catches every reuse; it catches every sequential one. A second thing neither entry can say, and it is about the fork rather than the latch. Both are written as "fork a fresh cursor per re-drive", which describes the reference's shape: drive 1 runs on the handle and drives 2..n on copies. This port's rule is the other one — a pillar step that may drive more than once forks for every drive, the first included, never calls its own#call, and#forkon a cursor whose#callis already spent raisesDexpace::PipelineError— because the fork is also where cursor-scoped state is written (docs/sdk-design-ruby/05-pipeline-architecture.md§5.1, and §6.2's cross-origin marker), so a first drive that ran on the un-forked handle would have no stage slot to write and hop 1 could publish nothing. Forking earlier than the requirement demands is inside it: whatPIPE-15forbids is reusing a spent handle. The consequence for a phase-6 redirect or retry step is concrete and is the reason this is recorded here rather than only in the phase document: writing the mixed shape the harvested sentence describes compiles, passes every ordering test, and silently drops whatever the pillar meant to publish on its first drive. review ·docs/work/mvp/phase4/phase4c/2026-09-08-phase4c-stage-pipeline-design.md· high · sha:manual-phase4c-cursor-latch