docs: record Phase 21 (windowed streaming) as landed; note Direction A to B pivot

Move Phase 21 from PLAN to COMPLETED with the as-built record, and annotate
the spec that Direction B shipped after WASM fetch buffering defeated A.
This commit is contained in:
daniel-c-harvey
2026-06-24 16:05:30 -04:00
parent c1e6930c70
commit 036ee1f78e
3 changed files with 47 additions and 113 deletions
@@ -1,9 +1,24 @@
# Phase 21 — Windowed Streaming Buffer (bounded client memory for long streams)
Product spec. Status: **design / framing — reconciled to as-built Phase 18 (two decode paths);
implementation-ready pending Daniel's open-question calls (OQ1OQ4 product; OQ6OQ7 staff-engineer
architecture).** Author: product-designer. Date: 2026-06-23 (reconciliation pass after Phase 18 landed).
**No code has been written by this doc.**
Product spec. Status: **LANDED 2026-06-24 on `streaming-overhaul`.** See `COMPLETED.md` for the full
as-built record. Author: product-designer. Date: 2026-06-23 (reconciliation pass after Phase 18 landed).
> **AS-BUILT NOTE — Direction A→B pivot (2026-06-24).** This spec recommended **Direction A** (sliding
> window on one open-ended forward stream, pausing `ReadAsync`/the segment loop to backpressure the
> socket) and held **Direction B** (discrete bounded `Range: bytes=start-end` segments, §3.2) as the
> documented fallback. **21.4 browser validation proved Direction A insufficient for Blazor WASM:** the
> browser `fetch` API buffers the entire HTTP response body regardless of read pace — pausing reads
> bounded the *decode* but not the *network download*, so the whole ~970 MB body accumulated in browser
> memory even with the application decoding only a window of it. **We shipped Direction B.** The forward
> stream now issues sequential 4 MB bounded Range requests (`SegmentSizeBytes = 4 MB`), fetched via
> `RunSegmentedStreamAsync` in `StreamingAudioPlayerService`, each issued only after
> `PlaybackScheduler.evaluateProductionPause()` clears below low-water. Browser holds ~one 4 MB segment
> of raw bytes; 21.4 confirmed network-memory bounding in Daniel's browser run. The decode-side windowing
> (21.1/21.2) is unchanged and pairs with Direction B; seek/refill converge on the same segmented loop
> via `RecoverFromFailedRefill`. Direction A is recorded as tried-in-validation and found insufficient
> for the WASM `fetch` runtime. Sections §3.2–§3.3 below retain the original A vs. B vs. C analysis as
> the decision record; **Direction B is what shipped.**
Surface: **public listener site only** (`DeepDrftPublic.Client` player stack + `DeepDrftPublic`
TypeScript audio interop). No CMS (`DeepDrftManager`) change. No data-model or schema change. The one
server touch is **reuse, not new surface**: the existing `DeepDrftAPI` HTTP `Range: bytes=X-`