Skip to content

Section refs are not unique (path-derived names collide), so a canonical page identity can resolve to the wrong page #654

Description

@yumike

Problem

rw presents (sectionRef, subpath) as a page's canonical identity — it's what listPages() returns, what PageMeta exposes, what comments key on, and (via rwdocs/backstage-plugins) what a Backstage search hit now carries so a consumer can read the page back. pagePathFor(sectionRef, subpath) is the resolver for exactly that.

But a section ref is not unique. Section::name is the last path segment of the section root (crates/rw-sections/src/lib.rs:109), so:

docs/a/billing/meta.yaml   kind: domain   ->  domain:default/billing
docs/z/billing/meta.yaml   kind: domain   ->  domain:default/billing

Two different sections, one ref. rw only warns, and find_by_ref resolves to whichever sorts first (there's even a test pinning that: find_by_ref_deterministic_on_duplicate_identifier).

Why that's worse than a lint

An identity that can name two pages isn't an identity. Concretely, in the Backstage plugins (rwdocs/backstage-plugins#102):

  • The search collator enumerates a/billing/overview, which listPages() reports as (domain:default/billing, "overview"), and indexes its text.
  • A user clicks the hit. The backend resolves the identity with pagePathFor("domain:default/billing", "overview") → find_by_ref → z/billing/overview — and serves a different page's content than the one that was indexed and matched.

Same for a comment: a thread anchored to (domain:default/billing, "overview") can be resolved back onto the wrong page after an unrelated section elsewhere in the tree is renamed to collide.

Nothing in the host can detect this — the ref looks perfectly well-formed. And the collision is easy to create by accident: two teams each add a billing/ folder with kind: domain in different parts of the tree.

Proposal

Now (cheap, stops the silent wrong-page reads): make a duplicate section ref a hard error at load rather than a warning. A site with an ambiguous ref refuses to build, with both paths named. This is a breaking change for any site that currently has a collision — but such a site is already serving wrong pages through pagePathFor, so failing loudly is strictly better than resolving arbitrarily.

Properly (the real fix): let a section declare its own id, instead of deriving one from the folder name:

# docs/a/billing/meta.yaml
kind: domain
name: retail-billing      # explicit; unique within the site

falling back to the path segment when omitted. Then a ref is stable across folder moves and unique by construction, and the collision above just becomes two distinct refs.

Worth noting the ref namespace is doubly loaded: rw section refs are formatted as Backstage entity refs (kind:namespace/name) and the Backstage plugins feed them straight into catalogApi.getEntitiesByRefs. So a section ref is expected to name a real catalog entity — which makes uniqueness a cross-system contract, not just an rw-internal detail.

At minimum: the ambiguity should be impossible to hit silently.

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions