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.
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