Hand-written. ../harvested/pagination.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.
- The
Enumerator/ensureasymmetry holds on the whole supported range, not only on 3.4.10, and the hard rule it forces binds phase 3 before it binds phase 7. Supersedespagination/d626cf17, whose "On Ruby 3.4.10, anEnumeratorbuilt withEnumerator.new { |y| ... ensure ... }runs its ensure block when a consumer calls#eachwith a block and breaks out" was verified on 3.4.10 alone, and widenspagination/731d9f17's companion claim, which carries no version qualifier at all. Re-verified on 2026-09-08 against three installed interpreters, one enumerator per case: on 3.2.11, 3.4.10 and 4.0.6 alike,e.each { |v| break if v == 2 }runs the block'sensure; driving the same generator externally with two#nextcalls and then dropping the reference leaves theensureunrun after twoGC.startcalls; and#rewindafter an external#nextdoes not run it either. Why the widening matters rather than being a tidier citation:docs/sdk-design-ruby/07-pagination-sse-and-serialization.md§7.1 turns the pair into a repository-wide hard rule — resource acquisition and release never live inside anEnumeratorblock; the engine owns the resource in its own scope with its ownensureand exposes#close— and the internal-iteration half is the half the rule leans on, becauseDexpace::Page.each { }'s block form is what makesPAGE-11/PAGE-12free. A version-scoped verification of exactly that half leaves open the possibility that the block form behaves differently onrequired_ruby_version's floor, which would have meant a different shape on 3.2 from the one on the developer's Ruby — the same failure mode as theData#withfinding in../notes/data-modeling.mdand design §3.5'sURI::DEFAULT_PARSERpin, both of which pass where you look and fail where you do not. It does not; the shape is uniform across the range. The rule reaches phase 3 first.docs/work/mvp/phase3/2026-09-08-phase3-segmentation-design.mdrecords it as a phase-3 cross-cutting constraint rather than a pagination one:Dexpace::IO::BufferedSource.over(body)pulls chunks from a body's#eachon demand (design §3.1, §10.2), severalBODYvariants areEnumerator-shaped, andIO-41/IO-42/BODY-15/BODY-27all put a real transport resource behind a close that must actually run. So the owning object with its ownensureand its own#closeis a phase-3 obligation, discharged through phase 2'sDexpace::Closeablelatch, and phase 7 inherits a rule already paid for rather than discovering it. review ·docs/work/mvp/phase3/2026-09-08-phase3-segmentation-design.md· high · sha:manual-phase3-enumerator-range - The rule reaches an ordinary
#eachmethod, not only anEnumerator.newblock, and no guard inside the method can catch it. Supersedespagination/f57c50f6("Resource acquisition and release must never live inside an Enumerator block in the Ruby SDK, because Ruby's cleanup guarantee on break only holds for internal iteration and not for external iteration via #next"), which states the right rule over one class of object too few. Verified on 2026-09-08 against 3.2.11, 3.4.10 and 4.0.6. The fact: a plaindef each; @log << :open; begin; yield "a"; yield "b"; yield "c"; ensure; @log << :close; end; end— noEnumerator.newanywhere — behaves exactly like the generator block does. Driven withobj.to_enum(:each), two#nextcalls and then dropping the reference, theensureis unrun after twoGC.startcalls;#rewinddoes not run it either;obj.each { |c| break if c == "b" }does run it; and a full external drive toStopIterationdoes run it, which is whyDexpace::IO::BufferedSource.over(body)draining a body to exhaustion is safe and only early abandonment is not. Uniform across the range. The half that closes the obvious escape hatch:block_given?is true inside#eachwhen the method is reached throughto_enum(:each)and#next, because the enumerator supplies the yielder as the block — so araise ArgumentError unless block_given?guard, which looks like it would forbid external iteration, does nothing at all. There is no in-method defence; the only defence is where the resource lives. Why the widening matters rather than being a tidier citation:docs/sdk-design-ruby/07-pagination-sse-and-serialization.md§7.1's rule is read by every phase as "do not writeEnumerator.new { ... ensure ... }", and phase 3b's bodies do not write one —Dexpace::Body#eachis an ordinary method derived from#write_to. Under the narrow reading aDexpace::FileBodythat opens its handle inside#eachlooks compliant and leaks a file descriptor on abandonment. What the SDK does, perdocs/work/mvp/phase3/phase3b/2026-09-08-phase3b-body-lifecycle-design.md: bodies that hold a resource hold it on the object with#close—Dexpace::ResponseBodyand an ownership-transferredDexpace::StreamBody— so abandoning their enumerator leaks nothing#closewould not still release; andDexpace::FileBody, whichBODY-11obliges to open a fresh handle per write so the handle cannot live on the object, states the residue in its YARD instead of pretending to close it, on the same principle asdocs/sdk-design-ruby/10-deliberate-deviations-from-the-reference-contract.mditem 10 andBufferedSource.over's own documented residue.resource-management/1676974dis why a finalizer is not the answer. review ·docs/work/mvp/phase3/phase3b/2026-09-08-phase3b-body-lifecycle-design.md· high · sha:manual-phase3b-each-method-abandonment
- The SSE facade's single-use rule is filed under
PAGE-14and under noSSEID, and the defect is a PAIR, not a singleton.sse-streaming/5f4803a0(Rules: "An SSE Enumerable view must not be taken twice over the same source … the facade enforces this by latching a@viewedflag") and its Conclusions companionsse-streaming/b94ce49e("The single-use guard on the SSEEnumerableview is a SHOULD requirement that the port implements outright") both carry onlyPAGE-14, so--prefix PAGE --section ruleshands a pagination author the SSE facade's latch and--prefix SSEreturns neither. Both rules are right about SSE; only the attribution is wrong, and a correction naming5f4803a0alone leaves half of it standing. Phase 7c's ownPAGE-14latch isDexpace::Page::Pages'@viewedflag raisingDexpace::Page::PageStateError— the same shape asSSE-26's, on an unrelated object (the phase-7 segmentation design's Convergence points), and the same error family (the roadmap's phase-10 inbound bullet of 2026-09-13 onPAGE-14/SSE-26; 7c's ledger row P7-103). Found by 7c's design on 2026-09-10 while running the pagination audit group, confirmed at implementation on 2026-09-20; a harvest attribution, routed here becauseharvested/is never hand-edited. review ·docs/work/mvp/phase7/phase7c/2026-09-10-phase7c-pagination-design.md· high · sha:manual-phase7c-sse-under-page14 - The
#each-method entry above (the note keyed pagination/b2a85752) carriesBODY-11and not "no requirement ID at all". The phase-7 segmentation design's corpus-attribution finding characterises it as an entry with no ID; measured,--req BODY-11 --origin noteand--prefix BODYreturn it while--prefix PAGEand--prefix PAGE --origin notedo not, although its text is the whole ofPAGE-11/PAGE-12's lifetime rule. The consequence the charter names is exactly right — a pagination author's audit group misses it — and the characterisation is not, which matters because a reader chasing "an entry with no IDs" will not find it and may conclude the defect was fixed. Recorded beside the entry it describes so the next harvest, or a re-keying of the note, is checked against it. review ·docs/work/mvp/phase7/phase7c/2026-09-10-phase7c-pagination-design.md· high · sha:manual-phase7c-b2a85752-carries-body11 - §12's transport-agnosticism and serde-agnosticism are harvested nowhere.
pagination/cb5f1b9eharvestsdocs/product-spec/12-pagination.md:3as its second sentence only — "A port MUST preserve the two-view model, the page-lazy fetch discipline, deterministic response-lifecycle management, and the strategy contract" — and the same line's first sentence, "It is transport-agnostic and serde-agnostic: a single stateless strategy parses each response into the page's items plus the fully-formed request for the next page (or an end-of-stream signal)", appears in no entry:--grep 'serde-agnostic|transport-agnostic'returns exactly one hit,http-domain-model/f4bd2330, which isXCUT-18's validation layer and unrelated. The property carries no requirement ID, is the reason the phase-7 segmentation design's spec-forced boundary 5 exists, and is what phase 7c builds to: the built-in strategies take a caller-supplied#call(response)extractor and never a serializer (7c's P7-6), nothing underlib/dexpace/page/names one,gems/dexpace-core/test/dexpace/page_test.rbscans the fifteen files for the two bare tokens, and phase 7b'stools/serde_boundary.rb(its Task 11) is the gate that carries the directory's row. A harvest-COVERAGE gap rather than an attribution one — a species neither the CLI's[cited by …]/[overridden by …]conflation nor the--prefixrules gap covers — recorded so the next harvest can be checked against it. review ·docs/product-spec/12-pagination.md:3· high · sha:manual-phase7c-serde-agnosticism-unharvested R8's "read$!at the top of theensure" is unsafe for the reason the pipeline note keyed pipeline/7ce4431d (notes/pipeline.md:8) already measured, and the pagination layer is built without it. Adds to that note, whose finding —$!is dynamically scoped to the thread and non-nil inside anything called from a CALLER'srescue— reaches the close discipline as well as the re-raise: anensurethat read$!to decide between surfacing a close failure (PAGE-15) and attaching it (PAGE-13,PAGE-32) would, inside a consumer's own rescue block, attach the walk's close failure to an unrelated in-flight error and swallow what the requirement says to surface. Measured on 3.2.11, 3.3.12, 3.4.10 and 4.0.6 on 2026-09-20 (gems/dexpace-core/test/dexpace/page/matrix_facts_test.rb, fact 2) and driven as a test (lifetime_test.rb, the twoCALLER'S RESCUEcases): the frame that owns the walk records the primary in arescue ::Exception => errorarm that re-raises unchanged and hands the local toDexpace::Page::Closing.close_walk;$!appears nowhere underlib/dexpace/page/(7c's ledger row P7-105). The measurement underR8— a bareensurewhose close raises REPLACES the in-flight error as the primary — stands and is what the shape exists to answer. review ·docs/work/mvp/phase7/phase7c/2026-09-10-phase7c-pagination-design.md· high · sha:manual-phase7c-r8-without-dollar-bang