Hand-written. ../harvested/url-and-query-encoding.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.
SEAM-27's base-URL composition is not RFC 3986 reference resolution, andURI.joinis banned by a phase-0 cop; where resolution genuinely is wanted the spelling isURI::RFC3986_PARSER.join. Supersedesurl-and-query-encoding/ef5ecf25("Base-URL composition uses URI.join/URI#merge for RFC 3986 reference resolution"), and correctsredirect-handling/d4885fbcandurl-and-query-encoding/2e8b4bb5on the name to write rather than on the semantics. Two separate findings, verified on 2026-09-06 against Ruby 3.2.11 and 4.0.6. First, the semantics are wrong forSEAM-27.URI::RFC3986_PARSER.join("https://host/c?sig=1", "/pets")yields"https://host/pets"on both interpreters, andURI::RFC3986_PARSER.parse("https://host/c?sig=1").merge("/pets")yields the same: an absolute-path reference replaces the whole path under RFC 3986, so both the/cbase segment and thesig=1base query are discarded.SEAM-27's own conformance step requires the opposite —host/c?sig=..+/pets→host/c/pets?sig=..&<opquery>— and its four stated rules (a trailing slash normalises to exactly one separator, an empty operation path leaves the base untouched, an existing base query is preserved with the operation query appended after it with its dangling separator dropped, a base carrying a fragment is rejected) describe a concatenation, not a resolution. Following the harvested sentence would therefore silently drop a signed-URL base's path prefix and its SAS query, which is the exact scenario the requirement's rationale names ("these invariants ... support signed/SAS bases"). What the SDK does instead:Dexpace::Operation#build_requestimplements the four rules directly against adupof the frozen base URI, assigning#pathand#queryand never re-parsing a re-rendered string — verified that a frozenURI::Genericdups to an unfrozen copy whose#path=/#query=work while the original still raisesFrozenError, so the "never round-trip throughto_s" half of the harvested entry is preserved and is the reason already-encoded octets survive. Second, the spelling is unusable as written. Phase 0'sDexpace/NoUriDefaultParsercop bansURI.joinalong with the rest of theURI.parse/URI.splitfamily, because those route throughURI::DEFAULT_PARSER, which isURI::RFC2396_Parseron 3.2 and 3.3 andURI::RFC3986_Parserfrom 3.4 — both inside the supported range. The sanctioned forms, both verified present and equivalent on 3.2.11 and 4.0.6, areURI::RFC3986_PARSER.join(base, ref)and#mergeon a URI that was itself parsed withURI::RFC3986_PARSER; verified thatURI::RFC3986_PARSER.parse(...).parserisURI::RFC3986_Parseron both, so#mergeinherits the pinned parser rather than reaching for the default. Where this matters next is phase 6: the redirect-handling entry corrected above tells the redirect step to resolve throughURI.join/URI#mergeforREDIR-13's wire-exact preservation, and the semantics there are right — a redirect target genuinely is a reference resolved against the previous URL — but the literalURI.joinwill fail the build, so that step writesURI::RFC3986_PARSER.join. One further caveat for any test written near this code:URI::InvalidURIError's message differs by a space between 3.2.11 (bad URI(is not URI?)) and 4.0.6 (bad URI (is not URI?)), so no assertion may match it — phase 1'sDexpace::URL.parse!converts it to aDexpace::InvalidArgumentErrorcarrying the offending input, and that is what to assert on. review ·docs/work/mvp/phase2/2026-09-06-phase2-seam-foundations-design.md· high · sha:manual-phase2-seam27-composition