Skip to content

feat(urns): forward URNs retired by publication to the published record - #863

Draft
davereinhart wants to merge 1 commit into
release-2026.3.0from
feature/davereinhart/tmp-urn-redirect
Draft

feat(urns): forward URNs retired by publication to the published record#863
davereinhart wants to merge 1 commit into
release-2026.3.0from
feature/davereinhart/tmp-urn-redirect

Conversation

@davereinhart

Copy link
Copy Markdown
Collaborator

This pull request introduces a robust mechanism for forwarding requests that use "retired" URNs (Uniform Resource Names) to the current, published URNs for datasets, experiments, and score sets. This ensures that links shared before publication continue to work after a record is published and its URN changes. The implementation covers database schema changes, backend logic, API integration, and tests.

URN Redirects: Database and Model Support

  • Added a new urn_redirects table (with migration) and corresponding SQLAlchemy model UrnRedirect to track mappings from old (retired) URNs to new (published) URNs. This enables the system to look up and forward requests for outdated URNs. [1] [2]
  • Registered the urn_redirect model in the models package for application-wide access.

URN Redirect Logic and Integration

  • Implemented record_urn_redirect and related logic in urn_redirects.py to record URN changes and determine when a request should be forwarded. Only public, published records are eligible as redirect targets, and only read requests (GET/HEAD) are forwarded.
  • Integrated URN redirect recording into the publication flow for experiment sets, experiments, and score sets, so that every time a URN is changed during publication, the redirect is saved. [1] [2]

API and Application Integration

  • Applied the forward_retired_urns dependency globally to all FastAPI routes, ensuring that any request using a retired URN is automatically redirected to the current URN if appropriate. [1] [2] [3]

Testing

  • Added comprehensive tests for the URN redirect functionality, covering correct recording, ignoring of no-op or invalid cases, and the forwarding logic for various scenarios (including sub-resources and variants).URN of an experiment set, an experiment and a score set in place, and refresh_variant_urns rebuilds every variant URN from the score set's. Nothing recorded the old value, so a link already shared to the unpublished record began returning 404 under a name the caller had no way to guess. Reloading a score set page after publishing it was enough to hit this.

Refs: VariantEffect/mavedb-ui#617

publish_score_set overwrites the tmp:<uuid> URN of an experiment set, an experiment and a score set
in place, and refresh_variant_urns rebuilds every variant URN from the score set's. Nothing recorded
the old value, so a link already shared to the unpublished record began returning 404 under a name
the caller had no way to guess. Reloading a score set page after publishing it was enough to hit
this.

Record what each URN became in a new urn_redirects table, and resolve it in forward_retired_urns, an
application-wide dependency: a read naming a retired URN is answered 308 to the same path under the
record's current URN. One implementation covers every route that takes a URN, sub-resources included,
and since substitution operates on the URN substring, a variant follows its score set without a row
of its own.

A dependency rather than ASGI middleware, because it needs the request's session; middleware runs
outside dependency resolution, so it would open a session of its own that no dependency_overrides
could redirect. Reads only: an owner is permitted to publish a published score set, so forwarding a
stale POST .../publish would rename a live public record. And only onto a target confirmed public,
since a Location header names its target to an anonymous caller before any route checks a
permission. That check also keeps a deleted record's surviving row from answering a permanent
redirect with a 404.

The dependency reads the path from the ASGI scope. request.url.path truncates at the '#' in a variant
URN, because Starlette rebuilds that URL by re-parsing it, which turns everything after the '#' into
a fragment and drops the variant number, the sub-resource and the query string.
lib/logging/context.py has the same pattern and is left for a separate change.

Forwarding is one hop, which is all that can arise while nothing renames a published record. URNs
retired before this are unrecoverable, since publication overwrote them and kept no history, so the
table is not backfilled and links to records published earlier stay broken.
@davereinhart
davereinhart marked this pull request as draft September 2, 2026 21:48
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 33687149340

Warning

No base build found for commit 5aefb34 on release-2026.3.0.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 88.958%

Details

  • Patch coverage: 2 uncovered changes across 2 files (74 of 76 lines covered, 97.37%).

Uncovered Changes

File Changed Covered %
src/mavedb/lib/urn_redirects.py 54 53 98.15%
src/mavedb/models/urn_redirect.py 12 11 91.67%
Total (4 files) 76 74 97.37%

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 15685
Covered Lines: 13953
Line Coverage: 88.96%
Coverage Strength: 0.89 hits per line

💛 - Coveralls

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants