Skip to content

Type the redirect target at the transport boundary instead of passing a raw Location #705

Description

@leynos

Context

Follow-up from the Domain Architecture warning raised during review of PR #667,
which implements #647 and ADR-023 (revalidate every fetch redirect against
NetworkPolicy). The warning is about the shape of the transport/domain
boundary, not about the policy check the PR adds, so it was deliberately
deferred rather than actioned in #647.

Observation

RedirectChain::advance accepts the raw Location header value as a string and
then reports the transport's parsing failures in the chain's own vocabulary:

// src/stdlib/network/redirect_chain.rs:116
pub(super) fn advance(
    &mut self,
    location: Option<&str>,
) -> Result<RedirectTransition, RedirectRejection> {
    let Some(raw_location) = location else {
        return Err(RedirectRejection::LocationMissing {
            current_url: self.current_url.clone(),
        });
    };
    let mut next_url = self.current_url.join(raw_location).map_err(|_err| {
        RedirectRejection::LocationInvalid {
            current_url: self.current_url.clone(),
        }
    })?;
    ...

Two of the six RedirectRejection variants — LocationMissing and
LocationInvalid — therefore describe the HTTP response rather than a redirect
decision. The domain resolves and parses the header, then has to localize a
failure that belongs to the adapter.

Decision required

Pick one and record it:

  1. Move the parse to the adapter. Resolve the Location header at the
    transport boundary and hand the chain a typed target — either an
    already-resolved Url, or an enum distinguishing absent from present but
    unparsable
    — with both cases localized as adapter diagnostics instead of
    RedirectRejection variants.
  2. Keep the current shape and state explicitly why a header-level fact
    belongs in the domain's rejection vocabulary.

Blast radius if option 1 is taken

  • src/stdlib/network/redirect.rs:240-241 (the redirect_failure metric
    mapping) and the two localized error constructors at :255 and :261.
  • Two Fluent keys across all 35 locales under locales/.
  • Two entries of the closed redirect_failure vocabulary
    (location_missing, location_invalid) in src/stdlib/network/telemetry.rs,
    and any snapshot fixtures that pin them.
  • src/stdlib/network/redirect_chain_tests.rs and the end-to-end coverage in
    tests/std_filter_tests/network_redirect_chain_tests.rs and
    tests/std_filter_tests/network_redirect_tests.rs.
  • ADR-023 and the redirect section of docs/developers-guide.md, which both
    document the current resolve-then-check ordering.

Constraints to preserve

  • ADR-023's four-field bound: emitted fields stay operation, outcome,
    reason, and hop. Both variants currently carry only a redacted
    current_url, so the redesign must not reintroduce a location, URL, host, or
    userinfo into any emitted or logged field.
  • The ordering guarantees ADR-023 relies on: the hop limit, loop detection,
    cross-origin credential removal, and the policy check must still run in that
    order, and the policy check must still precede every redirected connection.
  • Metric cardinality stays fixed by the closed vocabularies, never by input.

Acceptance

  • A decision record (ADR-023 amendment or a new ADR) selecting option 1 or 2
    with its rationale.
  • If the boundary changes: the closed redirect_failure vocabulary, the Fluent
    keys in every locale, the snapshots, and the documentation are updated in the
    same change, with the redirect tests still green.

Refs #647, PR #667.

Activity

  1. added
    enhancementNew feature or request
    mediumRoadmap items to schedule within the current quarter. Clear scope, normal review cycles.
    on Sep 13, 2026
  2. added
    refactorBehaviour-preserving restructuring that improves code health.
    on Sep 13, 2026
  3. leynos commented on Sep 16, 2026

    @leynos
    OwnerAuthor
  4. coderabbitai commented on Sep 16, 2026

    @coderabbitai
    Contributor

    Coding Plan

    Summary

    Select option 1 and record it as a dated addendum to ADR-023.

    Move Location resolution and its absent and unparsable diagnostics into redirect.rs. Reduce RedirectRejection to the four redirect-decision variants. Keep the check order and the four bounded telemetry fields.

    Keep location_missing and location_invalid in the closed telemetry vocabulary. Reuse the existing Fluent keys, so metric cardinality and user-visible text stay fixed.

    Update the unit tests, snapshots, and documentation in the same change. Keep the redirect tests green.


    File-level change summary

    • docs/adr-023-revalidate-fetch-redirects.md: add a dated addendum; correct the implementation-reference attribution.
    • src/stdlib/network/redirect_chain.rs: change advance to take a Url; remove LocationMissing and LocationInvalid; update doc comments.
    • src/stdlib/network/redirect.rs: add the LocationFailure enum, the resolver, and the reporter; wire them into dispatch_request; trim failure_category and rejection_error to four variants.
    • src/stdlib/network/telemetry.rs: no vocabulary change; verify FETCH_REDIRECT_FAILURE_VALUES still lists both location reasons.
    • src/stdlib/network/redirect_chain_tests.rs: pass resolved Url values; remove unusable_locations_are_refused.
    • src/stdlib/network/redirect_adapter_tests.rs: trim the rejection enumeration to four variants; add resolver and reporter tests.
    • src/snapshots/network_redirect/: relocate or regenerate the two location snapshots.
    • src/localization/keys.rs and locales/*/messages.ftl: verify the two keys and messages remain unchanged.
    • docs/developers-guide.md: update the "Fetch redirect architecture" section.

    Follow-up agent prompt

    Implement the ADR-023 addendum and the redirect boundary change described in the plan.
    
    Constraints you must not break:
    - Redirect telemetry and trace events emit only operation, outcome, reason, and hop. Never pass a URL, host, location, or userinfo into any emitted or logged field.
    - Keep the check order in RedirectChain::advance: hop limit, cross-origin credential removal, loop detection, policy check. The policy check must still precede every redirected connection.
    - Keep "location_missing" and "location_invalid" in telemetry::FETCH_REDIRECT_FAILURE_VALUES. The adapter still records them.
    - Reuse the existing Fluent keys STDLIB_FETCH_REDIRECT_LOCATION_MISSING and STDLIB_FETCH_REDIRECT_LOCATION_INVALID with redacted current_url. Do not change their message text.
    
    Steps:
    1. Change RedirectChain::advance to take a resolved Url and remove the LocationMissing and LocationInvalid variants.
    2. Add a LocationFailure enum, a header resolver, and a failure reporter in redirect.rs, and call chain.advance only with a resolved Url.
    3. Trim failure_category and rejection_error to the four remaining variants.
    4. Update redirect_chain_tests.rs and redirect_adapter_tests.rs, add adapter tests for the two location failures, and regenerate the two snapshots.
    5. Update the developers-guide "Fetch redirect architecture" section.
    
    Run the redirect unit tests and the tests/std_filter_tests redirect suites, and confirm they are green.
    
    Design Choices
    Design Choice 1: Which option does the ticket select?

    Options Considered:

    1. Move Location resolution to the adapter and hand the chain a typed, already-resolved target. Remove LocationMissing and LocationInvalid from RedirectRejection.
    2. Keep advance(Option<&str>) and document why a header-level fact stays in the domain's rejection vocabulary.

    Chosen Option: 1

    Rationale: It aligns with the documented domain/adapter boundary (ADR-005, ADR-021, developers-guide) that keeps parsing and transport facts in the adapter and keeps only pure decisions in the domain.

    Design Choice 2: What typed target does the adapter hand to the chain?

    Options Considered:

    1. advance(&mut self, target: Url). The adapter resolves the header and handles absence or parse failure with an adapter-local enum before calling advance.
    2. advance(&mut self, target: Result<Url, LocationFailure>). The chain receives the failure and forwards it.

    Chosen Option: 1

    Rationale: It keeps advance free of transport failure vocabulary and keeps the domain input a fully valid Url, so the domain describes only redirect decisions.

    Design Choice 3: Do we amend ADR-023 or write a new ADR?

    Options Considered:

    1. Append a dated ## Addendum to ADR-023.
    2. Create ADR-024 that cross-links back to ADR-023.

    Chosen Option: 1

    Rationale: The repository convention amends an existing decision with a dated addendum in the same file, and this change refines ADR-023 rather than introducing an independent decision.

    💡 User Tips

    Regenerate the plan with different choices with @coderabbitai <feedback>.

    Implementation Steps

    Phase 1: Record the decision

    Record the architectural choice before you change any code. This makes the boundary change auditable, and it restates the ordering and telemetry guarantees in one place.

    Add an ADR-023 addendum selecting option 1

    Amend the existing decision record to move Location resolution to the adapter.

    • Append a ## Addendum — <YYYY-MM-DD>: Resolve the Location header at the transport boundary section to docs/adr-023-revalidate-fetch-redirects.md.
    • State the amendment: the adapter now resolves Location and owns the "absent" and "present but unparsable" diagnostics, and the chain now receives an already-resolved Url.
    • Restate the unchanged invariants: the hop limit, cross-origin credential removal, loop detection, and the policy check still run in that order; the policy check still precedes every redirected connection; redirect telemetry still emits only operation, outcome, reason, and hop.
    • Correct the "Implementation references" attribution so Location parsing is credited to src/stdlib/network/redirect.rs and the chain module is credited only with decisions on a resolved target.
    🤖 Prompt for AI agents
    Implement Phase 1: record the architectural decision before touching any code.
    
    - Open `docs/adr-023-revalidate-fetch-redirects.md` and append a new section
    titled `## Addendum — <YYYY-MM-DD>: Resolve the Location header at the transport
    boundary`. Use the actual date.
    - In the addendum, state clearly that the adapter now resolves the `Location`
    header and owns the "absent" and "present but unparsable" diagnostics, and that
    the chain now receives an already-resolved `Url` instead of a raw string.
    - Restate the invariants that do not change: the check order (hop limit,
    cross-origin credential removal, loop detection, policy check) stays the same;
    the policy check still runs before every redirected connection; redirect
    telemetry still emits exactly four fields: `operation`, `outcome`, `reason`, and
    `hop`.
    - Correct the "Implementation references" section of the ADR so that `Location`
    parsing is attributed to `src/stdlib/network/redirect.rs`, and the chain module
    (`redirect_chain.rs`) is attributed only with decisions made on an
    already-resolved target.
    

    Phase 2: Relocate Location resolution to the adapter

    Move the header resolution and its two failure diagnostics from the pure chain into the adapter. Reduce RedirectRejection to redirect decisions only. Preserve the check ordering and the four bounded telemetry fields.

    Task 1: Narrow the domain to decisions on a resolved target

    Make RedirectChain operate on a valid Url and drop the transport-shaped variants.

    • Update src/stdlib/network/redirect_chain.rs: change advance to accept a resolved Url instead of Option<&str>, and remove the early LocationMissing and join/LocationInvalid branches.
    • Remove the LocationMissing and LocationInvalid variants from RedirectRejection. Keep CredentialsNotRemovable, LimitExceeded, Loop, and Policy.
    • Keep the remaining check order inside advance: reject_excessive_redirects, redact_cross_origin_userinfo, reject_redirect_loop, evaluate_target.
    • Update the module and advance doc comments to state that the input is an already-resolved target and that transport parsing lives in the adapter.
    Task 2: Resolve the Location header in the adapter with typed failures

    Add the header resolution step and an adapter-local failure type at the transport boundary.

    • Add a small adapter-local enum in src/stdlib/network/redirect.rs that distinguishes an absent header from a present but unparsable value (for example LocationFailure::Missing and LocationFailure::Unparsable).
    • Add a resolver function in redirect.rs that reads the raw header, resolves it against chain.current_url() with join, and returns either the resolved Url or the typed LocationFailure.
    • Update dispatch_request so it resolves the header first, returns the adapter diagnostic on failure, and calls chain.advance(target) only with a resolved Url.
    • Compute the reported hop for a resolution failure as chain.hops().saturating_add(1), matching the existing refusal hop computation.
    Task 3: Emit adapter diagnostics and telemetry for resolution failures

    Reproduce the localised error and the four-field telemetry for the two relocated cases without going through RedirectRejection.

    • Add a reporter function in redirect.rs for LocationFailure that mirrors report_refused_redirect: call telemetry::record_redirect_refused with "location_missing" or "location_invalid", emit the tracing::warn! event with only operation, redirect_outcome, redirect_failure, and hop, and return the localised error.
    • Build the localised error for the two cases with the existing Fluent keys STDLIB_FETCH_REDIRECT_LOCATION_MISSING and STDLIB_FETCH_REDIRECT_LOCATION_INVALID, using redacted_url(chain.current_url()) for the message argument. Do not pass any URL, host, location, or userinfo into the telemetry or trace fields.
    • Update failure_category and rejection_error in redirect.rs to remove the LocationMissing and LocationInvalid arms, so both functions match only the four remaining RedirectRejection variants.
    • Keep "location_missing" and "location_invalid" in telemetry::FETCH_REDIRECT_FAILURE_VALUES. The adapter still records these reasons, so the closed vocabulary and metric cardinality stay fixed.
    🤖 Prompt for AI agents
    Implement Phase 2: move `Location` header resolution from the domain module into
    the transport adapter, and shrink the domain's rejection vocabulary accordingly.
    
    In `src/stdlib/network/redirect_chain.rs`:
    - Change `advance` to accept a resolved `Url` instead of `Option<&str>`. Remove
    the early `LocationMissing` and `join`/`LocationInvalid` branches.
    - Remove `LocationMissing` and `LocationInvalid` from `RedirectRejection`. Keep
    only `CredentialsNotRemovable`, `LimitExceeded`, `Loop`, and `Policy`.
    - Keep the check order inside `advance` unchanged: `reject_excessive_redirects`,
    `redact_cross_origin_userinfo`, `reject_redirect_loop`, `evaluate_target`.
    - Update the module and `advance` doc comments to state that the input is an
    already-resolved target, and that transport parsing now lives in the adapter.
    
    In `src/stdlib/network/redirect.rs`:
    - Add a small adapter-local enum, for example `LocationFailure::Missing` and
    `LocationFailure::Unparsable`, to distinguish an absent header from a present
    but unparsable value.
    - Add a resolver function that reads the raw `Location` header, resolves it
    against `chain.current_url()` using `join`, and returns either the resolved
    `Url` or the `LocationFailure`.
    - Update `dispatch_request` to call the resolver first. On failure, return the
    adapter diagnostic. On success, call `chain.advance(target)` with the resolved
    `Url` only.
    - Compute the reported hop for a resolution failure as
    `chain.hops().saturating_add(1)`, matching the existing refusal hop computation.
    - Add a reporter function for `LocationFailure` that mirrors
    `report_refused_redirect`: call `telemetry::record_redirect_refused` with
    `"location_missing"` or `"location_invalid"`, emit a `tracing::warn!` event with
    exactly `operation`, `redirect_outcome`, `redirect_failure`, and `hop`, and
    return the localised error.
    - Build the localised error using the existing Fluent keys
    `STDLIB_FETCH_REDIRECT_LOCATION_MISSING` and
    `STDLIB_FETCH_REDIRECT_LOCATION_INVALID`, with
    `redacted_url(chain.current_url())` as the message argument. Never pass a URL,
    host, location, or userinfo into any telemetry or trace field.
    - Trim `failure_category` and `rejection_error` in `redirect.rs` to remove the
    `LocationMissing` and `LocationInvalid` arms, so both functions handle only the
    four remaining `RedirectRejection` variants.
    - Keep `"location_missing"` and `"location_invalid"` in
    `telemetry::FETCH_REDIRECT_FAILURE_VALUES` in `telemetry.rs`. Do not remove
    them; the adapter still records these reasons, and the closed telemetry
    vocabulary must stay fixed.
    

    Phase 3: Update tests, snapshots, localisation, and documentation

    Bring every guard and every document that pins the old boundary into line with the new one. Keep the redirect tests green.

    Task 1: Update the domain and adapter unit tests

    Realign the tests that drive advance and enumerate the rejections.

    • Update src/stdlib/network/redirect_chain_tests.rs: change every advance call to pass a resolved Url, and remove unusable_locations_are_refused, because the LocationMissing and LocationInvalid variants no longer exist in the domain.
    • Update src/stdlib/network/redirect_adapter_tests.rs: reduce every_rejection and the exhaustiveness and redaction tests to the four remaining variants.
    • Add adapter tests for the resolver and the new reporter that assert: an absent header yields the "location_missing" reason and the STDLIB_FETCH_REDIRECT_LOCATION_MISSING message; an unparsable header yields the "location_invalid" reason and the STDLIB_FETCH_REDIRECT_LOCATION_INVALID message; neither path leaks credentials.
    Task 2: Relocate the location snapshots

    Keep the pinned Fluent output for the two relocated cases.

    • Regenerate or move the two snapshot fixtures under src/snapshots/network_redirect/ for location_missing and location_invalid, sourced from the new adapter test path in redirect_adapter_tests.rs.
    • Confirm the rendered text is unchanged: the same redacted current_url, the same "did not include a Location header" and "Invalid redirect location '' … Location could not be resolved" wording.
    Task 3: Verify localisation and integration coverage

    Confirm the Fluent keys stay consistent across all locales and the integration suite still passes.

    • Verify that STDLIB_FETCH_REDIRECT_LOCATION_MISSING and STDLIB_FETCH_REDIRECT_LOCATION_INVALID in src/localization/keys.rs and their messages in every locales/*/messages.ftl file remain correct. Keep the message text and arguments unchanged, because the adapter reuses these keys.
    • Run and, if needed, update tests/std_filter_tests/network_redirect_chain_tests.rs and tests/std_filter_tests/network_redirect_tests.rs. These suites do not exercise the absent or unparsable paths, so expect no behavioural change; adjust only if a compilation reference to the removed variants exists.
    Task 4: Update the developers guide

    Bring the architecture prose into line with the new boundary.

    • Update the "Fetch redirect architecture" section of docs/developers-guide.md: state that the adapter resolves the Location header and owns the absent and unparsable diagnostics, and that the chain receives an already-resolved target.
    • Update the rejection vocabulary description so location_missing and location_invalid are documented as adapter-level redirect_failure reasons, not RedirectRejection variants.
    • Restate the maintenance rule and the four-field bound so they remain consistent with the ADR-023 addendum.
    🤖 Prompt for AI agents
    Implement Phase 3: align every test, snapshot, localisation source, and
    documentation page with the new domain/adapter boundary from Phase 2, and keep
    the redirect test suites green.
    
    Tests:
    - In `src/stdlib/network/redirect_chain_tests.rs`, change every `advance` call
    to pass a resolved `Url`. Remove the `unusable_locations_are_refused` test,
    because `LocationMissing` and `LocationInvalid` no longer exist in the domain.
    - In `src/stdlib/network/redirect_adapter_tests.rs`, reduce `every_rejection`
    and the exhaustiveness and redaction tests to the four remaining
    `RedirectRejection` variants.
    - Add new adapter tests for the resolver and the reporter. Assert that an absent
    header produces the `"location_missing"` reason and the
    `STDLIB_FETCH_REDIRECT_LOCATION_MISSING` message. Assert that an unparsable
    header produces the `"location_invalid"` reason and the
    `STDLIB_FETCH_REDIRECT_LOCATION_INVALID` message. Assert that neither path leaks
    credentials.
    
    Snapshots:
    - Regenerate or move the two snapshot fixtures under
    `src/snapshots/network_redirect/` for `location_missing` and `location_invalid`,
    sourced from the new adapter test path.
    - Confirm the rendered text stays exactly the same: the same redacted
    `current_url`, the same "did not include a Location header" wording, and the
    same "Invalid redirect location '<redacted>' … Location could not be resolved"
    wording.
    
    Localisation and integration:
    - Verify `STDLIB_FETCH_REDIRECT_LOCATION_MISSING` and
    `STDLIB_FETCH_REDIRECT_LOCATION_INVALID` in `src/localization/keys.rs`, and
    their messages in every `locales/*/messages.ftl` file, stay correct and
    unchanged, because the adapter reuses these keys as-is.
    - Run `tests/std_filter_tests/network_redirect_chain_tests.rs` and
    `tests/std_filter_tests/network_redirect_tests.rs`. Expect no behavioural
    change, since neither suite exercises the absent or unparsable paths. Fix only
    compilation references to the removed variants, if any exist.
    
    Documentation:
    - Update the "Fetch redirect architecture" section of
    `docs/developers-guide.md`. State that the adapter resolves the `Location`
    header and owns the absent and unparsable diagnostics, and that the chain
    receives an already-resolved target.
    - Update the rejection vocabulary description so `location_missing` and
    `location_invalid` are documented as adapter-level `redirect_failure` reasons,
    not as `RedirectRejection` variants.
    - Restate the maintenance rule and the four-field telemetry bound so the guide
    stays consistent with the ADR-023 addendum.
    
    Research

    The fetch redirect logic splits across two modules under src/stdlib/network/. redirect_chain.rs is a pure state machine. It holds RedirectChain, the six-variant RedirectRejection enum, and advance, which today accepts the raw Location string, resolves it with current_url.join, and then applies the hop limit, cross-origin credential removal, loop detection, and policy check in that order. redirect.rs is the transport adapter. It drives the HTTP loop with ureq, reads response.header("Location"), hands the raw value to advance, maps rejections to the closed redirect_failure telemetry vocabulary through failure_category, and builds localised Fluent diagnostics through rejection_error. telemetry.rs declares the closed label vocabularies, including FETCH_REDIRECT_FAILURE_VALUES. ADR-023 and the "Fetch redirect architecture" section of docs/developers-guide.md document the current resolve-then-check ownership boundary. The repository convention (ADR-005, ADR-021) keeps pure decisions in the domain module and keeps parsing, telemetry, and localised text in the adapter.


    🚀 Next Steps

    🤖 All AI agent prompts combined
    Task: 1
    
    Implement Phase 1: record the architectural decision before touching any code.
    
    - Open `docs/adr-023-revalidate-fetch-redirects.md` and append a new section
    titled `## Addendum — <YYYY-MM-DD>: Resolve the Location header at the transport
    boundary`. Use the actual date.
    - In the addendum, state clearly that the adapter now resolves the `Location`
    header and owns the "absent" and "present but unparsable" diagnostics, and that
    the chain now receives an already-resolved `Url` instead of a raw string.
    - Restate the invariants that do not change: the check order (hop limit,
    cross-origin credential removal, loop detection, policy check) stays the same;
    the policy check still runs before every redirected connection; redirect
    telemetry still emits exactly four fields: `operation`, `outcome`, `reason`, and
    `hop`.
    - Correct the "Implementation references" section of the ADR so that `Location`
    parsing is attributed to `src/stdlib/network/redirect.rs`, and the chain module
    (`redirect_chain.rs`) is attributed only with decisions made on an
    already-resolved target.
    ===============================================================================
    
    Task: 2
    
    Implement Phase 2: move `Location` header resolution from the domain module into
    the transport adapter, and shrink the domain's rejection vocabulary accordingly.
    
    In `src/stdlib/network/redirect_chain.rs`:
    - Change `advance` to accept a resolved `Url` instead of `Option<&str>`. Remove
    the early `LocationMissing` and `join`/`LocationInvalid` branches.
    - Remove `LocationMissing` and `LocationInvalid` from `RedirectRejection`. Keep
    only `CredentialsNotRemovable`, `LimitExceeded`, `Loop`, and `Policy`.
    - Keep the check order inside `advance` unchanged: `reject_excessive_redirects`,
    `redact_cross_origin_userinfo`, `reject_redirect_loop`, `evaluate_target`.
    - Update the module and `advance` doc comments to state that the input is an
    already-resolved target, and that transport parsing now lives in the adapter.
    
    In `src/stdlib/network/redirect.rs`:
    - Add a small adapter-local enum, for example `LocationFailure::Missing` and
    `LocationFailure::Unparsable`, to distinguish an absent header from a present
    but unparsable value.
    - Add a resolver function that reads the raw `Location` header, resolves it
    against `chain.current_url()` using `join`, and returns either the resolved
    `Url` or the `LocationFailure`.
    - Update `dispatch_request` to call the resolver first. On failure, return the
    adapter diagnostic. On success, call `chain.advance(target)` with the resolved
    `Url` only.
    - Compute the reported hop for a resolution failure as
    `chain.hops().saturating_add(1)`, matching the existing refusal hop computation.
    - Add a reporter function for `LocationFailure` that mirrors
    `report_refused_redirect`: call `telemetry::record_redirect_refused` with
    `"location_missing"` or `"location_invalid"`, emit a `tracing::warn!` event with
    exactly `operation`, `redirect_outcome`, `redirect_failure`, and `hop`, and
    return the localised error.
    - Build the localised error using the existing Fluent keys
    `STDLIB_FETCH_REDIRECT_LOCATION_MISSING` and
    `STDLIB_FETCH_REDIRECT_LOCATION_INVALID`, with
    `redacted_url(chain.current_url())` as the message argument. Never pass a URL,
    host, location, or userinfo into any telemetry or trace field.
    - Trim `failure_category` and `rejection_error` in `redirect.rs` to remove the
    `LocationMissing` and `LocationInvalid` arms, so both functions handle only the
    four remaining `RedirectRejection` variants.
    - Keep `"location_missing"` and `"location_invalid"` in
    `telemetry::FETCH_REDIRECT_FAILURE_VALUES` in `telemetry.rs`. Do not remove
    them; the adapter still records these reasons, and the closed telemetry
    vocabulary must stay fixed.
    ===============================================================================
    
    Task: 3
    
    Implement Phase 3: align every test, snapshot, localisation source, and
    documentation page with the new domain/adapter boundary from Phase 2, and keep
    the redirect test suites green.
    
    Tests:
    - In `src/stdlib/network/redirect_chain_tests.rs`, change every `advance` call
    to pass a resolved `Url`. Remove the `unusable_locations_are_refused` test,
    because `LocationMissing` and `LocationInvalid` no longer exist in the domain.
    - In `src/stdlib/network/redirect_adapter_tests.rs`, reduce `every_rejection`
    and the exhaustiveness and redaction tests to the four remaining
    `RedirectRejection` variants.
    - Add new adapter tests for the resolver and the reporter. Assert that an absent
    header produces the `"location_missing"` reason and the
    `STDLIB_FETCH_REDIRECT_LOCATION_MISSING` message. Assert that an unparsable
    header produces the `"location_invalid"` reason and the
    `STDLIB_FETCH_REDIRECT_LOCATION_INVALID` message. Assert that neither path leaks
    credentials.
    
    Snapshots:
    - Regenerate or move the two snapshot fixtures under
    `src/snapshots/network_redirect/` for `location_missing` and `location_invalid`,
    sourced from the new adapter test path.
    - Confirm the rendered text stays exactly the same: the same redacted
    `current_url`, the same "did not include a Location header" wording, and the
    same "Invalid redirect location '<redacted>' … Location could not be resolved"
    wording.
    
    Localisation and integration:
    - Verify `STDLIB_FETCH_REDIRECT_LOCATION_MISSING` and
    `STDLIB_FETCH_REDIRECT_LOCATION_INVALID` in `src/localization/keys.rs`, and
    their messages in every `locales/*/messages.ftl` file, stay correct and
    unchanged, because the adapter reuses these keys as-is.
    - Run `tests/std_filter_tests/network_redirect_chain_tests.rs` and
    `tests/std_filter_tests/network_redirect_tests.rs`. Expect no behavioural
    change, since neither suite exercises the absent or unparsable paths. Fix only
    compilation references to the removed variants, if any exist.
    
    Documentation:
    - Update the "Fetch redirect architecture" section of
    `docs/developers-guide.md`. State that the adapter resolves the `Location`
    header and owns the absent and unparsable diagnostics, and that the chain
    receives an already-resolved target.
    - Update the rejection vocabulary description so `location_missing` and
    `location_invalid` are documented as adapter-level `redirect_failure` reasons,
    not as `RedirectRejection` variants.
    - Restate the maintenance rule and the four-field telemetry bound so the guide
    stays consistent with the ADR-023 addendum.
    
    💡 Iterate on the plan with: `@coderabbitai` <feedback>
    Example Feedback
    - `@coderabbitai` You can skip phase 3. Add a simple unit test case for phase 2.
    - `@coderabbitai` For design choice 1 go ahead with option 3 and replan.
    

    Start Coding

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestmediumRoadmap items to schedule within the current quarter. Clear scope, normal review cycles.refactorBehaviour-preserving restructuring that improves code health.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions