Outbound HTTP design spec#275
Conversation
Adds the EdgeZero outbound HTTP design spec under docs/superpowers/specs/2026-05-21-outbound-http-design.md. Targets the PR #269 (feature/extensible-cli) baseline. The spec covers six requirements: - portable OutboundHttpClient trait with single send + concurrent send_all on every adapter (Axum, Cloudflare, Fastly, Spin) - per-request and shared deadline / timeout primitives with a documented dispatch budget and bounded-cooperative semantics on Fastly - bounded buffering with explicit persistent vs transient memory accounting and pre-append cap checks - manifest-driven [capabilities] declaration (nine capabilities total) with pre-dispatch enforcement gates at five CLI entry points - adapter contract test plan in three tiers (core mock, per-adapter translation, runtime) - four canonical URI accessors (backend_target, host_authority, sni_hostname, cert_host) so adapters share one canonical host/port/SNI/cert split Revised through 49 review rounds; non-normative resolution journal lives in Appendices A through AX.
Removes references to the originally-named driving consumer and the specific external protocol used as motivation: - midbid → "the driving pattern" / generic - Prebid-style → "fan-out-style" - OpenRTB → "the external batch protocol" - bidder → "target" - auction → "fan-out batch" - tmax → "batch deadline" The technical motivation (N concurrent outbound requests under a shared wall-clock deadline, results harvested in input order, small response bodies, homogeneous-budget common case) is preserved; only the named consumer and its protocol are scrubbed so the spec reads as a portable substrate rather than a single-consumer design. Section §3.3.2 retitled "Mapping an external batch deadline to EdgeZero deadlines"; status header gains a "Driving pattern" line in place of the old "Driving consumer" pointer.
Spec previously claimed Fastly behaviour that did not match the actual SDK / public API. This commit corrects the four normative claims, adds an app-facing consuming body accessor, and records the corrections in Appendix AY. Findings addressed: - lazy-streamed-response-passthrough downgraded Native -> BestEffort on Fastly. `Response::with_streaming_body` does not exist; `Response::stream_to_client()` is the actual API and is documented as incompatible with `#[fastly::main]`. Default scaffold falls back to buffered passthrough; lazy passthrough requires the non-main entry-point template tracked in new section 8 risk 12. - NameInUse semantics rewritten. Fastly's session-uniqueness rule is unconditional; the previous "identical name + identical properties is a re-registration that returns Ok" carve-out was false. SDK's `Backend::from_str(name)` returns a handle only and exposes no registered properties, so a NameInUse on a name not in this adapter's collision map is now an explicit fail-closed internal error rather than a silent property-trust fallback. - between_bytes_timeout is receive-side only per Fastly's Backend API docs. The previous claim that it bounded guest-to-origin writes is removed; the streamed-upload host-write phase is downgraded to BestEffort with the cooperative inter-chunk check as the only adapter-side bound. - Streamed-upload response overshoot tightened from per-chunk accumulator to closed-form bound: first_byte_ms (headers wait) plus one between_bytes_timeout (worst-case first-body-chunk read), one-shot. Footnote 1 single-send section + section 5.4 test row updated. - OutboundResponse::into_body() added as the app-facing consuming accessor for streamed-response orchestration. The send_all rustdoc recommends single send + futures::join_all + into_body() on Axum/CF/Spin as the canonical path; into_parts(..) stays adapter-facing. Appendix AY records the five resolutions. Status header bumped to "rounds 1-50, Date: 2026-06-08"; superseded-AR pointer extended to include AY.
Round 50 — Fastly SDK correctness pass (commit
|
Five round-50 carry-over findings: - Early section 4.3 dynamic-backend prose still preserved the stale identical-properties-re-register carve-out, contradicting the corrected step-5 algorithm. Rewritten in place to match the unconditional session-uniqueness contract. Two historical appendix entries (round-37 in Appendix AK) marked superseded by Appendix AY. - Fastly buffered-fallback for lazy passthrough named max_response_bytes as the cap, but that per-request cap is unavailable at response-converter time. Added FASTLY_RESPONSE_STREAM_BUFFER_BYTES adapter-level constant (mirrors AXUM_RESPONSE_STREAM_BUFFER_BYTES). Three section 5.4 rows rebucketed so Fastly is no longer in the CF/Spin lazy group; new Axum-and-Fastly buffered-fallback row carries both adapter constants. - Residual between_bytes_timeout write-side claim removed from the remaining section 5.4 stalled-upload mechanics row and from section 8 risk 7. Fastly write phase is BestEffort uniformly now; the public Backend API docs are cited as the source. - Spin host-write race rewritten against actual WASI output-stream semantics. Old wording said each write() is raced against a timer; WASI write() is nonblocking and readiness-polled, so the implementable pattern is subscribe-pollable + futures::select! vs timer + nonblocking check_write() + write() within the permitted byte count. - Typo: "docsare migrated" -> "docs are migrated" in section 1.3 non-goals. Status header bumped to rounds 1-51; AR-superseded pointer extended to include AZ.
Round 51 — round-50 carry-overs + Spin WASI write mechanics (commit
|
Summary
Adds the EdgeZero outbound HTTP design spec at
docs/superpowers/specs/2026-05-21-outbound-http-design.md. Targets the PR #269 (feature/extensible-cli) baseline — the spec assumes the multi-store manifest, theedgezero_cli::adapter::execute(..)dispatcher, the expandedAdapterActionset,Adapter::provision/ config-validation hooks, Spin SDK 6 / wasip2, and thedemocommand. Earlier appendices that quote the pre-#269 surface are explicitly flagged as historical.Driving pattern
The spec is written against fan-out HTTP workloads — N concurrent outbound requests under a shared wall-clock deadline, results harvested in input order. The driving pattern is treated as a portable substrate; the spec deliberately does not name a single consumer.
Scope
Six driver requirements:
OutboundHttpClienttrait with singlesendand concurrentsend_allon every adapter (Axum, Cloudflare, Fastly, Spin). One handler source compiles unchanged across all four.dispatch_budget(req, now)with explicitnowsnapshot,DEFAULT_NO_DEADLINE_BUDGET = 30s,DEADLINE_FAR_FUTURE = 7 days,BATCH_DISPATCH_SLACK_MAX = 25ms. Fastly's bounded-cooperative semantics are documented with precise overshoot bounds.max + sizeof(current_chunk)worst case, in-flight chunk size source-controlled), pre-append cap checks across inbound + outbound bounded drains. Batch model isΣᵢ request_bodyᵢ.len() + Σᵢ max_response_bytesᵢ.outbound-http,outbound-deadlines,outbound-flexible-phase-budget,send-all-slot-isolation,streamed-upload-deadlines,lazy-streamed-response-passthrough,config-store,kv-store,secret-store). Enforcement runs as five pre-dispatch gates (one insideexecute(..), siblings onrun_provision/run_config_push/run_config_validate/run_demo).MockOutboundClient), Tier 2 (per-adapter translation), Tier 3 (runtime against a local mock origin). Adapter-specific mechanics (Fastly host timers, harvest behaviour, dynamic backend identity) are restricted to Tier 2/3 since Tier 1's mock has no analogue.backend_target() / host_authority() / sni_hostname() / cert_host()are the single source of truth for the host/port/SNI/cert split. Adapters MUST consume these, not re-derive fromreq.uri(). IP-literal HTTPS (RFC 6066 §3) is handled bysni_hostname() == None && cert_host() == Some(ip).Out of scope (explicit non-goals)
tokio,reqwest,fastly,worker, orspin-sdkin core or app/library crates.Process
The spec was revised through 49 review rounds. The non-normative resolution journal lives in Appendices A through AX. Appendix AR is the round-44 rebase snapshot, superseded by Appendices AS / AT / AU / AV / AW / AX (rounds 44–49).
Test plan
This is a docs-only PR.
cargo fmt --all -- --checkcargo clippy --workspace --all-targets --all-features -- -D warningscargo test --workspace --all-targetsdocs/)