Skip to contents

Composes a single URL fold map from two distinct web signals – 3xx **redirects** and declared **rel=canonical** links – and reports, per folded URL, which signal caused the fold. This is the source of truth that [pagerank()] uses to fold edge endpoints and TIPR prior URLs, and that the downstream `semantic` bridge consumes to build its `graph_fold` table without duplicating the composition logic.

The two signals are kept separate internally for auditability and resolved with their own duplicate/loop policies, then composed with explicit precedence (see Details). Self-referential pairs (self-redirects, self-canonicals) are dropped as no-ops.

Usage

build_fold_map(
  redirects_df = NULL,
  canonicals_df = NULL,
  redirect_from_col = "from",
  redirect_to_col = "to",
  canonical_from_col = "from",
  canonical_to_col = "to",
  duplicate_from_policy = c("strict", "first_wins", "last_wins", "most_frequent",
    "prune_source", "resolve_if_consistent"),
  loop_handling = c("error", "prune_loop", "break_arrow"),
  canonical_duplicate_from_policy = c("strict", "first_wins", "last_wins",
    "most_frequent", "prune_source", "resolve_if_consistent"),
  canonical_loop_handling = c("error", "prune_loop", "break_arrow"),
  canonical_conflict_policy = c("redirect_wins", "error", "canonical_wins")
)

Arguments

redirects_df

Optional data frame of 3xx redirect rules, or `NULL`.

canonicals_df

Optional data frame of declared rel=canonical links, or `NULL`. Each row pairs a source URL with the canonical it declares.

redirect_from_col, redirect_to_col

From/to columns in `redirects_df`. Default `"from"` / `"to"`.

canonical_from_col, canonical_to_col

From/to columns in `canonicals_df`. Default `"from"` / `"to"`.

duplicate_from_policy

How to handle a redirect source with multiple distinct targets. See [resolve_redirects()]. Default `"strict"`.

loop_handling

How to handle redirect cycles. See [resolve_redirects()]. Default `"error"`. Also governs cross-signal cycles in the composed graph.

canonical_duplicate_from_policy

How to handle a canonical source with multiple distinct declared canonicals. Reuses the `duplicate_from_policy` enum. Default `"strict"`.

canonical_loop_handling

How to handle cycles among declared canonicals. Reuses the `loop_handling` enum. Default `"error"`.

canonical_conflict_policy

How to resolve a redirect-vs-canonical disagreement on the **same source** URL. One of:

`"redirect_wins"`

(Default) The 3xx redirect wins; a canonical declared on a URL that itself redirects is ignored and flagged, never transferred onto the redirect target.

`"error"`

Error when a redirect and a canonical disagree on the same source (after audit context is computed). Sources where the two signals agree do not error.

`"canonical_wins"`

The declared canonical wins for that source; still flagged in the audit. The explicit exception to the default ignored-canonical-on-redirecting-source rule.

Value

A data frame with one row per folded source URL (rows where the URL actually changes), with columns:

from

The source URL.

to

Its final composed representative.

signal

Which signal folded this source: `"redirect"` or `"canonical"`.

The data frame additionally carries the cross-signal conflict tables as attributes `"conflicts"` and `"ignored_canonicals"` (see [audit_canonicals()] / `audit_fold()`).

Details

## Composition semantics

1. The redirect rules are resolved to terminal destinations using `duplicate_from_policy` / `loop_handling`; the canonical rules are resolved **independently** using `canonical_duplicate_from_policy` / `canonical_loop_handling`. The two terminal maps are kept separate. 2. They are then composed into one graph and resolved to terminals, so that: a **canonical target is itself redirect-resolved** before folding (a canonical may point at a URL that 3xx's), and chains spanning both signals collapse to a single representative. 3. For the **same source**, `canonical_conflict_policy` decides the winner. Under the default `"redirect_wins"`, the canonical declared on a redirecting source is dropped and recorded in the audit.

Inputs are expected to be **already canonicalized** to the node namespace (e.g. via the same `rurl` profile used for edges). [pagerank()] cleans redirects and canonicals before composing; call this directly only when your URLs already share that namespace.

See also

[resolve_redirects()], [audit_canonicals()], [pagerank()]

Examples

redirects <- data.frame(from = "http://a", to = "http://b")
canonicals <- data.frame(from = "http://c", to = "http://a")
# c declares canonical a, a redirects to b => c folds to b via both signals
build_fold_map(redirects, canonicals)
#>       from       to    signal
#> 1 http://a http://b  redirect
#> 2 http://c http://b canonical