Skip to content

Latest commit

 

History

History
9 lines (7 loc) · 3.88 KB

url-and-query-encoding.md

File metadata and controls

9 lines (7 loc) · 3.88 KB

url-and-query-encoding — notes

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.

Superseded

  • SEAM-27's base-URL composition is not RFC 3986 reference resolution, and URI.join is banned by a phase-0 cop; where resolution genuinely is wanted the spelling is URI::RFC3986_PARSER.join. Supersedes url-and-query-encoding/ef5ecf25 ("Base-URL composition uses URI.join/URI#merge for RFC 3986 reference resolution"), and corrects redirect-handling/d4885fbc and url-and-query-encoding/2e8b4bb5 on 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 for SEAM-27. URI::RFC3986_PARSER.join("https://host/c?sig=1", "/pets") yields "https://host/pets" on both interpreters, and URI::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 /c base segment and the sig=1 base 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_request implements the four rules directly against a dup of the frozen base URI, assigning #path and #query and never re-parsing a re-rendered string — verified that a frozen URI::Generic dups to an unfrozen copy whose #path=/#query= work while the original still raises FrozenError, so the "never round-trip through to_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's Dexpace/NoUriDefaultParser cop bans URI.join along with the rest of the URI.parse/URI.split family, because those route through URI::DEFAULT_PARSER, which is URI::RFC2396_Parser on 3.2 and 3.3 and URI::RFC3986_Parser from 3.4 — both inside the supported range. The sanctioned forms, both verified present and equivalent on 3.2.11 and 4.0.6, are URI::RFC3986_PARSER.join(base, ref) and #merge on a URI that was itself parsed with URI::RFC3986_PARSER; verified that URI::RFC3986_PARSER.parse(...).parser is URI::RFC3986_Parser on both, so #merge inherits 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 through URI.join/URI#merge for REDIR-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 literal URI.join will fail the build, so that step writes URI::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's Dexpace::URL.parse! converts it to a Dexpace::InvalidArgumentError carrying 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