Build the byte-streaming layer every body, every logging snapshot, every codec and the whole of SSE and
pagination later stand on: one FIFO buffer, one buffered source/sink pair with typed reads and non-consuming
views, and one tee sink that mirrors a bounded tap without ever altering the wire body. The behavioural
contract is the whole deliverable — design §10.1 retired the seam that made it pluggable, so there is no
factory, no registry and no installation call.
Scope
Dexpace::IO's FIFO buffer (IO-7–IO-10, with MAX_MATERIALIZED_BYTES as §10.18's substituted
constant), BufferedSource/BufferedSink with typed reads, exact-count reads (IO-12), explicit-charset
reads (IO-13), peek and slice views (IO-19/IO-20), and the close latch (IO-41/IO-42).
IO-1's read primitive is #read_into, not #read — IO.copy_stream hands #readpartial one buffer
it reuses across calls, so a tail-append under Ruby's name would corrupt every streamed upload; #read,
#readpartial, #getbyte and #each keep Ruby's semantics, which is what makes IO-16 true by
construction.
Dexpace::EndOfStreamError < ::EOFError, load-bearing: IO.copy_stream terminates only on an EOFError
subclass from a duck-typed #readpartial.
- The
TeeSink (IO-25–IO-29) that 3b's request-logging tee restates one layer up.
IO-6's ownership-on-wrap rule stated once, without SEAM-3 (R3); the Dexpace::IO shadowing gate (R1) —
inside module Dexpace, x.is_a?(IO) is silently false for a real ::IO.
- Every chunk is
Encoding::BINARY; resource acquisition and release never live inside an Enumerator block
(design §7.1).
Spec refs
Product spec §5 — IO-1–IO-42 (42 IDs); IO-6 read out of appendix C, IO-32–IO-35 out of ch.05 as the
retired provider apparatus. sdk-design: §3.1, §10.1, §10.12, §10.18.
Dependencies
Phases 0–2. Leads 3b, which consumes the FIFO buffer, the tee sink, the peek/slice views and the close latch
by name — nothing in 3a needs anything from 3b.
Docs
- Segmentation:
docs/work/mvp/phase3/2026-09-08-phase3-segmentation-design.md
- Design:
docs/work/mvp/phase3/phase3a/2026-09-08-phase3a-io-contracts-design.md
- Plan:
docs/work/mvp/phase3/phase3a/2026-09-08-phase3a-io-contracts.md (15 tasks)
- Checklist:
docs/work/mvp/phase3/phase3a/<date>-phase3a-io-contracts-checklist.md — written at execution
time, one row per ID in scope
Notes
3a adds nothing to the require allowlist and requires nothing at all. The #read_line_utf8 line-cap finding
this sub-phase opens is resolved by phase 7b (its Tasks 6 and 12), not here.
Build the byte-streaming layer every body, every logging snapshot, every codec and the whole of SSE and
pagination later stand on: one FIFO buffer, one buffered source/sink pair with typed reads and non-consuming
views, and one tee sink that mirrors a bounded tap without ever altering the wire body. The behavioural
contract is the whole deliverable — design §10.1 retired the seam that made it pluggable, so there is no
factory, no registry and no installation call.
Scope
Dexpace::IO's FIFO buffer (IO-7–IO-10, withMAX_MATERIALIZED_BYTESas §10.18's substitutedconstant),
BufferedSource/BufferedSinkwith typed reads, exact-count reads (IO-12), explicit-charsetreads (
IO-13), peek and slice views (IO-19/IO-20), and the close latch (IO-41/IO-42).IO-1's read primitive is#read_into, not#read—IO.copy_streamhands#readpartialone bufferit reuses across calls, so a tail-append under Ruby's name would corrupt every streamed upload;
#read,#readpartial,#getbyteand#eachkeep Ruby's semantics, which is what makesIO-16true byconstruction.
Dexpace::EndOfStreamError < ::EOFError, load-bearing:IO.copy_streamterminates only on anEOFErrorsubclass from a duck-typed
#readpartial.TeeSink(IO-25–IO-29) that3b's request-logging tee restates one layer up.IO-6's ownership-on-wrap rule stated once, withoutSEAM-3(R3); theDexpace::IOshadowing gate (R1) —inside
module Dexpace,x.is_a?(IO)is silentlyfalsefor a real::IO.Encoding::BINARY; resource acquisition and release never live inside anEnumeratorblock(design §7.1).
Spec refs
Product spec §5 —
IO-1–IO-42(42 IDs);IO-6read out of appendix C,IO-32–IO-35out of ch.05 as theretired provider apparatus. sdk-design: §3.1, §10.1, §10.12, §10.18.
Dependencies
Phases 0–2. Leads
3b, which consumes the FIFO buffer, the tee sink, the peek/slice views and the close latchby name — nothing in
3aneeds anything from3b.Docs
docs/work/mvp/phase3/2026-09-08-phase3-segmentation-design.mddocs/work/mvp/phase3/phase3a/2026-09-08-phase3a-io-contracts-design.mddocs/work/mvp/phase3/phase3a/2026-09-08-phase3a-io-contracts.md(15 tasks)docs/work/mvp/phase3/phase3a/<date>-phase3a-io-contracts-checklist.md— written at executiontime, one row per ID in scope
Notes
3aadds nothing to the require allowlist and requires nothing at all. The#read_line_utf8line-cap findingthis sub-phase opens is resolved by phase 7b (its Tasks 6 and 12), not here.